Both Roblox and Lua provide pre-defined global functions and properties as a part of their implementation. These are reserved keywords that can be called or referenced from within any script. The ‘print’ function is one such example of a global function.
pcall
Once a game has been deployed, you will no longer have the ability to stop it and handle any errors that show up.
Even minor errors that were insignificant during development, now become game breaking. Or at the very least, script breaking.
We need a way to run potentially problematic functions without breaking the entire script. We need an ‘exception handler.’
The “protected call” invokes a function on your behalf in a protected environment. The status of its execution is then returned along with either the result, if the function ran successfully, or the error if it did not.
local nonexistentPart = game.Workspace:FindFirstChild("nonexistentPart")
nonexistentPart.Position = Vector3.new(1, 2, 3) --Error: attempt to index nil with 'Position'
Because this part does not exist, it will err and halt the entire script.
So instead, we can wrap this in a pcall function and have that set the part’s position without the risk of halting.
local nonexistentPart = game.Workspace:FindFirstChild("nonexistentPart")
local function setPartPos(part, pos)
part.Position = pos
end
local success, result = pcall(setPartPos, nonexistentPart, Vector3.new(1, 2, 3))
print(success) --Output: false
print(result) --Error: attempt to index nil with 'Position'
For completeness sake, here’s one that runs successfully and returns the results from the function.
local function getPartDimensions(part)
return part.Size.X, part.Size.Y, part.Size.Z
end
local part = Instance.new("Part")
part.Size = Vector3.new(123, 234, 345)
local _, rX, rY, rZ = pcall(getPartDimensions, part)
print(rX, rY, rZ) --123 234 345
xpcall
A variation to the pcall is the ‘xpcall.’
This version takes a second function argument that will be invoked if the first function does not run successfully and then returns that result.
local nonexistentPart = game.Workspace:FindFirstChild("nonexistentPart")
local function getPartDimensions(part)
return part.Size.X, part.Size.Y, part.Size.Z
end
local function handleError(errorMessage)
print(errorMessage)
return "Error Handler Response!"
end
local success, rX, rY, rZ = xpcall(getPartDimensions, handleError, nonexistentPart, Vector3.new(1, 2, 3))
print(success) --false
print(rX, rY, rZ) --Error Handler Response! nil nil
pcalls incur a minor performance penalty, meaning it is impractical and unlikely feasible to run them an appreciable number of times each frame.
Most sources of error can be addressed using sanity checks, which are simpler and more practical.
Use pcalls for critical operations that are difficult or impossible to predict, such as writing to data stores.
We will get to DataStores in a later chapter, but the gist is, DataStores allow you to store data that can be loaded in a later session or different server. An error while writing to a datastore will lead to loss of important data and so running such a function in a pcall allows to you detect and fix such critical errors.
tostring & tonumber
While Lua’s automatic type conversion is sufficient for most use cases, there are occasions where you will have to manually perform a type conversion.
For instance, input from a TextBox are strings. And this includes “numbers.”
If you were to take this as user input and compare it with a number, you would not be comparing the same data types and the result would be incorrect.
print(3 == "3") --false
Use the ‘tonumber’ function to manually perform a type conversion.
print(3 == "3") --false
print(3 == tonumber("3")) --true
print(tonumber("three")) --nil
The ‘tostring’ function does a similar operation for converting values to strings.
Nil and Booleans especially ubiquitous for this. Despite resembling strings, nil, true, and false are not strings.
local success = true
print(success == "true") --false
print(tostring(success) == "true") --true
print("Result: " .. nil) --error: attempt to concatenate string with nil
print("Result: " .. true) --error: attempt to concatenate string with boolean
print("Result: " .. false) --error: attempt to concatenate string with boolean
print("Result: " .. tostring(nil)) --Result: nil
print("Result: " .. tostring(true)) --Result: true
print("Result: " .. tostring(false)) --Result: false
typeof
We met the ‘typeof’ function way early on in the beginning. But at the time, we had not encountered any of the Roblox specific data types.
Unlike the ‘type’ function, typeof can return the specific data types, making it often more useful than the generic ‘type’ function.
print(typeof(3.14)) --number
print(typeof("foo")) --string
print(typeof({})) --table
print(type(Vector3.new())) --vector
print(type(Vector2.new())) --userdata
print(type(CFrame.new())) --userdata
print(type(BrickColor.new())) --userdata
print(type(UDim2.new())) --userdata
print(typeof(Vector3.new())) --Vector3
print(typeof(Vector2.new())) --Vector2
print(typeof(CFrame.new())) --CFrame
print(typeof(BrickColor.new())) --BrickColor
print(typeof(UDim2.new())) --UDim2
tick & time
Spend enough time in computer science circles and chances are you will hear the term ”Unix time” or “Unix epoch time” tossed around.
Time is relative and to computers, it is only useful if that time is relative to a previously agreed upon point in time. That point is 00:00:00 on 1 January 1970.
In computer speak, Unix is the number of seconds that has elapsed since 00:00:00 on 1 January 1970 and provides a standard way for computers to tell time.
The ‘tick’ function we’ve been using returns the number of seconds since Unix time, synced to the user’s locale.
On the other hand, the ‘time’ function returns the number of seconds since the game environment started meaning it will differ between clients and servers.
print(tick()) --1715241785.8105142
print(time()) --8.779167126864195