Script components allows you to create game logic using the Lua programming language.
There are three types of Lua script in Defold, each has different Defold libraries available.
Defold executes Lua scripts as part of the engine lifecycle and exposes the lifecycle through a set of predefined callback functions. When you add a script component to a game object the script becomes part of the game object’s and its component(s) lifecycle. The script is evaluated in the Lua context when it is loaded, then the engine executes the following functions and passes a reference to the current script component instance as parameter. You can use this self
reference to store state in the component instance.
self
is a userdata object that acts like a Lua table but you can’t iterate over it with pairs()
or ipairs()
and you can’t print it using pprint()
.
init(self)
function init(self)
-- These variables are available through the lifetime of the component instance
self.my_var = "something"
self.age = 0
end
final(self)
function final(self)
if self.my_var == "something" then
-- do some cleanup
end
end
update(self, dt)
dt
contains the delta time since the last frame.
function update(self, dt)
self.age = self.age + dt -- increase age with the timestep
end
fixed_update(self, dt)
dt
contains the delta time since the last update. This function is called when engine.fixed_update_frequency
is enabled (!= 0). Useful when you wish to manipulate physics objects at regular intervals to achieve a stable physics simulation when physics.use_fixed_timestep
is enabled in game.project.
function fixed_update(self, dt)
msg.post("#co", "apply_force", {force = vmath.vector3(1, 0, 0), position = go.get_world_position()})
end
on_message(self, message_id, message, sender)
msg.post()
the engine calls this function of the receiver component. Learn more about message passing.
function on_message(self, message_id, message, sender)
if message_id == hash("increase_score") then
self.total_score = self.total_score + message.score
end
end
on_input(self, action_id, action)
acquire_input_focus
) the engine calls this function when input is registered. Learn more about input handling.
function on_input(self, action_id, action)
if action_id == hash("touch") and action.pressed then
print("Touch", action.x, action.y)
end
end
on_reload(self)
function on_reload(self)
print(self.age) -- print the age of this game object
end
A game object with a script component implements some logic. Often, that logic is dependent on some external factor. An enemy AI might react to the player being within a certain radius from the AI; a door might unlock and open as a result of player interaction, etc, etc.
The update()
function allows you to implement complex behaviors defined as a state machine running each frame—sometimes that is the adequate approach. But there is a cost associated with each call to update()
. Unless you really need the function you should delete it and instead try to build your logic reactively. It is cheaper to passively wait for some message to trigger a response than it is to actively probe the game world for data to respond to. Furthermore, solving a design problem reactively also often leads to cleaner and more stable design and implementation.
Let’s look at a concrete example. Suppose that you want a script component to send a message 2 seconds after it has been initiated. It should then wait for a certain response message and after receiving the response, it should send another message 5 seconds later. The non reactive code for that would look something like this:
function init(self)
-- Counter to keep track of time.
self.counter = 0
-- We need this to keep track of our state.
self.state = "first"
end
function update(self, dt)
self.counter = self.counter + dt
if self.counter >= 2.0 and self.state == "first" then
-- send message after 2 seconds
msg.post("some_object", "some_message")
self.state = "waiting"
end
if self.counter >= 5.0 and self.state == "second" then
-- send message 5 seconds after we received "response"
msg.post("another_object", "another_message")
-- Nil the state so we don’t reach this state block again.
self.state = nil
end
end
function on_message(self, message_id, message, sender)
if message_id == hash("response") then
-- “first” state done. enter next
self.state = "second"
-- zero the counter
self.counter = 0
end
end
Even in this quite simple case we get fairly tangled up logic. It’s possible to make this look better with the help of coroutines in a module (see below), but let’s instead try to make this reactive and use a built-in timing mechanism.
local function send_first()
msg.post("some_object", "some_message")
end
function init(self)
-- Wait 2s then call send_first()
timer.delay(2, false, send_first)
end
local function send_second()
msg.post("another_object", "another_message")
end
function on_message(self, message_id, message, sender)
if message_id == hash("response") then
-- Wait 5s then call send_second()
timer.delay(5, false, send_second)
end
end
This is cleaner and easier to follow. We get rid of internal state variables that are often hard to follow through the logic—and which might lead to subtle bugs. We also dispose of the update()
function completely. That relieves the engine from calling our script 60 times a second, even if it’s just idling.
It is possible to use a Lua preprocessor and special markup to conditionally include code based on the build variant. Example:
-- Use one of the following keywords: RELEASE, DEBUG or HEADLESS
--#IF DEBUG
local lives_num = 999
--#ELSE
local lives_num = 3
--#ENDIF
The preprocessor is available as a build extension. Learn more about how to install and use it on the extension page on GitHub.
The Defold editor supports Lua script editing with syntax coloring and auto-completion. To fill out Defold function names, press Ctrl+Space to bring up a list of the functions matching what you are typing.
Did you spot an error or do you have a suggestion? Please let us know on GitHub!
GITHUB