Introduction to Scripting
In Lua, comments start with a --
-- This is a comment in Lua
There are eight basic types in Lua.
- 1.nil
- 2.boolean
- 3.number
- 4.string
- 5.userdata
- 6.function
- 7.thread
- 8.table
Nil is a type with a single value, nil, whose main property is to be different from any other value. As we have seen, a global variable has a nil value by default, before a first assignment, and you can assign nil to a global variable to delete it. Lua uses nil as a kind of non-value, to represent the absence of a useful value.
The boolean type has two values, false and true, which represent the traditional boolean values. However, they do not hold a monopoly of condition values: In Lua, any value may represent a condition. Conditionals (such as the ones in control structures) consider false and nil as false and anything else as true. Beware that, unlike some other scripting languages, Lua considers both zero and the empty string as true in conditional tests.
In Lua, true and false are in lowercase.
The number type represents real (double-precision floating-point) numbers. Lua has no integer type, as it does not need it.
Strings have the usual meaning: a sequence of characters. Lua is eight-bit clean and so strings may contain characters with any numeric value, including embedded zeros. That means that you can store any binary data into a string. Strings in Lua are immutable values. You cannot change a character inside a string, as you may in C; instead, you create a new string with the desired modifications.
The table type implements associative arrays. An associative array is an array that can be indexed not only with numbers, but also with strings or any other value of the language, except nil. Moreover, tables have no fixed size; you can add as many elements as you want to a table dynamically. Tables are the main (in fact, the only) data structuring mechanism in Lua, and a powerful one. We use tables to represent ordinary arrays, symbol tables, sets, records, queues, and other data structures, in a simple, uniform, and efficient way. Lua uses tables to represent packages as well. When we write
io.read
, we mean "the read
entry from the io
package". For Lua, that means "index the table io
using the string "read"
as the key".Tables in Lua are neither values nor variables; they are objects. If you are familiar with arrays in Java or Scheme, then you have a fair idea of what we mean. However, if your idea of an array comes from C or Pascal, you have to open your mind a bit. You may think of a table as a dynamically allocated object; your program only manipulates references (or pointers) to them. There are no hidden copies or creation of new tables behind the scenes. Moreover, you do not have to declare a table in Lua; in fact, there is no way to declare one. You create tables by means of a constructor expression, which in its simplest form is written as
{}
Functions are first-class values in Lua. That means that functions can be stored in variables, passed as arguments to other functions, and returned as results. Such facilities give great flexibility to the language: A program may redefine a function to add new functionality, or simply erase a function to create a secure environment when running a piece of untrusted code (such as code received through a network). Moreover, Lua offers good support for functional programming, including nested functions with proper lexical scoping.
The userdata type allows arbitrary C data to be stored in Lua variables. It has no predefined operations in Lua, except assignment and equality test. Userdata are used to represent new types created by an application program or a library written in C; for instance, the standard I/O library uses them to represent files.
A variable is essentially a name that can hold a value. Variable values can be numbers, strings, booleans, data types, and more.
In Lua, variable names can be any non-reserved string of letters, digits, and underscores, but they cannot start with a digit:
LETTERS -- valid
a1 -- valid
var_name -- valid
_test -- valid
if -- NOT valid
25th -- NOT valid
Note that Lua is a case-sensitive language, meaning that TestVar and TESTVAR are two unique names. As a convention, names which begin with an underscore followed by uppercase letters (such as _VERSION) should be avoided, as they may be reserved for internal global Lua variables.
The following keywords are reserved by Lua and cannot be used as variable or function names:
- and
- break
- do
- else
- elseif
- end
- false
- for
- function
- if
- in
- local
- nil
- not
- or
- repeat
- return
- then
- true
- until
- while
Assigning a value to a variable is done with the = operator.
Lua
Output
x = 10
word = "Hello"
reference = workspace.Part
print(x)
print(word)
print(reference)
10
Hello
Part
Remember that the variable is always on the left side of the
=
while the value is on the right side.Once declared, a variable’s value can be changed by simply assigning another value to it:
Lua
Output
x = 10
print(x)
x = 1000
print(x)
10
1000
In Lua, variables exist in one of two possible scopes, global or local. All variables will default to global unless they are declared as local.
A global variable is visible to all scopes of a script. In the following code, the global variable
x
starts at 0, increments by 1 in each iteration of the for
loop, and is printed again afterward with a final value of 5.Lua
Output
x = 0 -- Global variable "x"
for i = 1, 5 do
x = x + 1
print("Global 'x' = " .. x)
end
print("Global 'x' = " .. x)
Global 'x' = 1
Global 'x' = 2
Global 'x' = 3
Global 'x' = 4
Global 'x' = 5
Global 'x' = 5
A local variable is accessible only in the block where it’s declared. Local variables are declared using the
local
statement:local x = 0 -- Local variable "x"
In the following example, the initial local variable
x
is set to 0, and another local variable x
is declared inside the for
loop. As the loop iterates, its locally-scoped x
is printed with a constant value of 1, then the initial variable x
is printed with an unchanged value of 0.Lua
Output
local x = 0 -- Local variable "x"
for i = 1, 5 do
local x = 1 -- Different variable "x", local to this "for" loop
print("Loop 'x' = " .. x)
end
print("Initial 'x' = " .. x)
Loop 'x' = 1
Loop 'x' = 1
Loop 'x' = 1
Loop 'x' = 1
Loop 'x' = 1
Initial 'x' = 0
Local variables are obtained faster than global variables because they're integrated into the environment in which they were created. If possible, you should always use local variables over global variables, unless there's a specific reason otherwise.
Lua provides a small and conventional set of control structures, with if for conditional and while, repeat, and for for iteration. All control structures have an explicit terminator: end terminates the if, for and while structures; and until terminates the repeat structure.
An if statement tests its condition and executes its then-part or its else-part accordingly. The else-part is optional.
Lua
Output
robux = 1000
if robux > 999 then
print("awesome!")
else
print("not enough!")
end
awesome!
When you write nested ifs, you can use elseif. It is similar to an else followed by an if, but it avoids the need for multiple ends.
Lua
Output
robux = 500
if robux > 999 then
print("awesome!")
elseif robux > 499 then
print("not enough!")
end
not enough!
As usual, Lua first tests the while condition; if the condition is false, then the loop ends; otherwise, Lua executes the body of the loop and repeats the process.
Lua
Output
local i = 1
while i < 10 do
print(i)
i = i + 1
end
1
2
3
4
5
6
7
8
9
Functions are sets of instructions that can be used multiple times in a script. Once defined, a function can be executed through a command or triggered through an event.
A basic function declaration includes the
function
keyword followed by the function name and a pair of parentheses (()
). Since the function’s body will be a block of code, it must be closed with the end
keyword.local function moreMoney()
end
Between the
()
and end
is where commands and other code make up the function body. These commands will be executed when the function is called:local function moreMoney()
-- Function body
print("moreMoney Function called!")
end
Once a function is defined, it can be executed by calling it (functions do not self-execute). To call a function, simply type its name followed by parentheses (
()
):local function moreMoney()
-- Function body
print("moreMoney Function called!")
end
moreMoney()
Functions can utilize parameters for data that will be passed in. When you declare a function, you may include one or more parameter names inside the parentheses:
-- "num1" and "num2" are parameters
local function moreMoney(num1, num2)
end
When calling a function with parameters, specify the values that should be passed to the function. For instance, the following function takes the two numbers passed in during each call, adds them together, and outputs the result:
local function moreMoney(num1, num2)
local result = num1 + num2
print(num1 .. " + " .. num2 .. " = " .. result)
end
moreMoney(2, 3)
moreMoney(4, 0.5)
moreMoney(1000, 500)
Function parameters are always local to the function and are only used in the function's scope and its descending scopes.
If you call a Lua function with more parameters than it's expecting, the excess ones will be ignored. Conversely, if the function expects more parameters than you provide, the value
nil
will be used for all missing parameters.In addition to accepting parameters, functions can also return (send back) data to the calling command. This is done through
return
values. Working off the example above, the following function returns the sum instead of printing it:local function moreMoney(num1, num2)
local myMoney = num1 + num2
return myMoney
end
Do not put any commands following a
return
command as doing so will generate an error.When a function returns a value, it can be assigned to a variable or used wherever a variable can be used. The following code illustrates this concept:
local function moreMoney(num1, num2)
local myMoney = num1 + num2
return myMoney
end
-- Call the function and store the returned value in variables
local result1 = moreMoney(2, 3)
print(result1)
local result2 = moreMoney(1000, 1000)
print(result2)
Lua also lets you return multiple values from a function:
local function moreMoney(num1, num2)
local sum = num1 + num2
local difference = num1 - num2
return sum, difference
end
local sum, difference = moreMoney(2, 3)
print(sum)
print(difference)
Functions don’t always need to be called with a command — they can also be called through an event. We'll look at this later.
Functions can be created anonymously, that is without assigning them a name. This is useful when you need to call a function from the result of another function or event, for instance a
delay()
call or a PlayerAdded
event connection:delay(2, function(exactTimeElapsed)
print(exactTimeElapsed)
end)
local Players = game:GetService("Players")
Players.PlayerAdded:Connect(function(player)
print(player.Name .. " joined the game!")
end)
Note that anonymous functions still require an
end
closure before the ending )
of a containing function or event like delay()
or :Connect()
.Since functions are a Lua data type, they can be stored in tables. This technique is commonly used in
ModuleScripts
where the module’s table contains various functions:local MathModule = {}
function MathModule.addNumbers(num1, num2)
local sum = num1 + num2
return sum
end
function MathModule.subtractNumbers(num1, num2)
local difference = num1 - num2
return difference
end
return MathModule
Once contained in the module’s table, these functions can be called by any script which accesses the
ModuleScript
via require()
:local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Require module
local MathModule = require(ReplicatedStorage:WaitForChild("MathModule"))
local sum = MathModule.addNumbers(2, 3)
print(sum)
local difference = MathModule.subtractNumbers(2, 3)
print(difference)
In addition to properties and functions, every object also has events which can be used to set up cause-and-effect systems. Events send out signals when specific things happen in a game, such as a player touching an object or a player connecting to the game. To fire an event is to have it send out such a signal.
The
Wait()
function will cause the script to pause until the event occurs once. When it does, the function returns the data associated with the event’s firing.local myPart = game.Workspace.Part
-- Wait until another part collides with "myPart"
local otherPart = myPart.Touched:Wait()
print("Part was touched by: " .. otherPart.Name)
The
Connect()
function can be used when a given function should run every time an event fires. This function immediately returns a connection object. In the example below, we connect a function, onTouched()
, to a Part
in the Workspace
. If another part collides with myPart
, the script prints the name of the other part involved in the collision.local myPart = game.Workspace.Part
local function onTouched(otherPart)
print("Part was touched by: " .. otherPart.Name)
end
myPart.Touched:Connect(onTouched)
Eventually, you may no longer need a connected function to run when an event fires. To disconnect it, use the
Disconnect()
method of the connection object returned by Connect()
.local points = Instance.new("NumberValue")
points.Name = "Points"
points.Value = 0
local connection
local function onPointsChanged(newPoints)
print("Points: " .. newPoints)
if newPoints >= 50 then
-- Stop listening for changes if we have at least 50 points
connection:Disconnect()
end
end
connection = points.Changed:Connect(onPointsChanged)
-- Trigger some changes
points.Value = 25
points.Value = 100
points.Value = 0 -- Prints nothing because we called Disconnect()
Always call
Disconnect()
when a connection is no longer needed. Forgetting to do so can cause your game to use more resources than necessary. Note, however, that this may not always be necessary; when an object is destroyed, all connections to that object’s events are disconnected automatically.Almost every event in Roblox will send data relevant to the event’s occurrence. For example, when a
Player
joins the game, the Players.PlayerAdded
event fires with a reference to the new player.local Players = game:GetService("Players")
local function onPlayerAdded(player)
print(player.Name .. " joined the game")
end
Players.PlayerAdded:Connect(onPlayerAdded)
Sometimes you will need to connect a function to an event on an object provided by another event. To do so, define a second local function within the function connected to the first event.
A common example is detecting when a player’s character is spawned into the game. For this, you’ll need to access the
CharacterAdded
event of the Player
involved in the Players.PlayerAdded
event.local Players = game:GetService("Players")
local function onPlayerAdded(player)
local function onCharacterAdded(character)
print(player.Name .. " spawned in: " .. character:GetFullName())
end
player.CharacterAdded:Connect(onCharacterAdded)
end
Players.PlayerAdded:Connect(onPlayerAdded)
Note that when a player leaves the game, their
Player
object is destroyed. This causes the connection to CharacterAdded
made inside onPlayerAdded()
to be disconnected, so you don't need to call Disconnect()
in this case. In other circumstances where the object in question is not necessarily destroyed, you may need to use a table to keep track of connections to be disconnected later via another function.Often times, you will need to define your own events for gameplay, such as when a team scores a goal. You might have one script award that team a point and another script launch some fireworks. For this, you’ll need to use a
BindableEvent
which features a single user-fireable event named Event
.You can either use a
BindableEvent
that already exists in the game hierarchy or create one using Instance.new()
. Then, call Fire
with any relevant data to trigger the Event
. For example:-- Create or get reference to a BindableEvent
local beGoal = Instance.new("BindableEvent")
-- Connect a function to the custom event
local function onGoalScored(team)
team.Score = team.Score + 1
end
beGoal.Event:Connect(onGoalScored)
-- Somewhere else in your code, fire the event
local Teams = game:GetService("Teams")
local redTeam = Teams["Red Team"]
beGoal:Fire(redTeam)
Simple data like strings, numbers, booleans, simple tables, and Roblox object references can be sent as event data through
BindableEvent
, but more complicated data such as functions or tables with metatables will require a more custom solution. See the documentation for BindableEvent:Fire()
for details on the kinds of data that can be sent when firing bindable events.Roblox runs on a client-server model. As such, some events that fire on the server will replicate and also fire on the client. This depends on the event, but when a game needs to define a custom event to be replicated from server-to-client or vice-versa, a
RemoteEvent
can be used. This works similarly to BindableEvent
but is network-ready. Last modified 2yr ago