If you are new to Defold, this guide will help you getting your bearings right in the editor. It also explains the basic ideas and the most common building blocks in Defold: Game objects, Collections, Scripts and Sprites.
We’re going to start from an empty project and work step by step to a very small, playable application. At the end you will hopefully have a feel for how Defold works and you will be ready to tackle a more extensive tutorial or dive right into the manuals.
Throughout the tutorial, detailed descriptions on concepts and how to do certain moments are marked like this paragraph. If you feel that these section go into too much detail, please skip them.
Start by creating a new project and opening it in the editor. If you double-click the file main/main.collection the file will open up:
The editor consists of the following main areas:
print()
and pprint()
debug messages from your scripts. If your app or game won’t start the console is the first thing to check. Behind the console are a set of tabs displaying error information as well as a curve editor that is used when building particle effects.The “Empty” project template actually is completely empty. Nonetheless, select Project ▸ Build to build the project and launch the game.
A black screen is perhaps not very exciting, but it’s a running Defold game application and we can easily modify it into something more interesting. So let’s do that.
The Defold editor works on files. By double-clicking a file in the Assets pane you open it in a suitable editor. You can then work with the contents of the file.
When you are done editing a file you have to save it. Select File ▸ Save in the main menu. The editor gives a hint by adding an asterisk ‘*’ to the filename in the tab for any file that contain unsaved changes.
The first thing we’re going to do is to create a new collection. A collection is a container of game objects that you have placed and put into position. Collections are most commonly used to build game levels but they are very useful whenever you need to reuse groups and/or hierarchies of game objects that belong together. It might be helpful to think about collections as a kind of prefab.
Click on the main folder in the Assets pane, then right-click and select New ▸ Collection File. You can also select File ▸ New ▸ Collection File from the main menu.
Name the new collection file car.collection and open it. We’re going to use this new, empty collection to build a small car out of a couple of game objects. A game object is a container of components (like sprites, sounds, logic scripts etc) that you use to build your game. Each game object is uniquely identified in the game by its id. Game objects can communicate with each other through message passing, but more on that later.
Also, it’s possible to create a game object in place in a collection, as we did here. That results in a one-of-a-kind object. You can copy that object but each copy is separate—changing one does not affect the others. This means that if you create 10 copies of a game object and realize that you want to change them all, you will need to edit all 10 instances of the object. Therefore, in place created game objects should be used for objects that you do not intend to make a lot of copies of.
However, a game object that is stored in a file works as a prototype (also known as “prefabs” or “blueprints” in other engines). When you place instances of a file stored game object in a collection each object is placed by reference—it is a clone based on the prototype. If you decide you need to change the prototype, every single placed game object based on that prototype is instantly updated.
Select the root “Collection” node in the Outline view, right-click and select Add Game Object. A new game object with the id “go” will appear in the collection. Mark it and set its id to “car” in the Properties view. So far, “car” is very uninteresting. It is empty, has neither visual representation nor any logic. To add a visual representation, we need to add a sprite component.
Components are used to extend game objects with presence (graphics, sound) and functionality (spawn factories, collisions, scripted behaviors). A component can’t exist by itself but has to reside inside a game object. Components are usually defined in place in the same file as the game object. However, if you want to reuse a component you can store it in a separate file (like you can with game objects) and include it as a reference in any game object file. Some component types (Lua scripts, for instance) has to be placed in a separate component file and then included as reference in your objects.
Note that you do not manipulate components directly—you can move, rotate, scale and animate properties of game objects that in turn contain components.
Select the “car” game object, right-click and select Add Component, then select Sprite and click Ok. If you mark the sprite in the Outline view you will see that it needs a few properties set:
Images for our game:
Add these images to the atlas:
Continue by adding two more game objects in the collection. Call them “left_wheel” and “right_wheel” and put a sprite component in each, showing the tire image that we added to sprites.atlas. Then grab the wheel game objects and drop them onto “car” to make them children under “car”. Game objects that are children under other game objects will be attached to their parent when the parent moves. They can be moved individually as well, but all motion happens relative to the parent object. For the tires this is perfect since we want them to stick to the car and we can just rotate them slightly left and right as we steer the car. A collection can contain any number of game objects, side by side or arranged into complex parent-child trees, or a mix.
Move the tire game objects into place by selecting them and then choosing Scene ▸ Move Tool. Grab the arrow handlebars, or the center green square to move the object to a good spot. The final thing we need to do is to make sure that the tires are drawn below the car. We do that by setting the Z component of the position to -0.5. Every visual item in a game is drawn from the back to front, sorted on their Z value. An object with a Z-value of 0 will be drawn on top of an object with a Z-value of -0.5. Since the default Z-value of the car game object is 0, the new value on the tire objects will put them under the car image.
The last piece of the puzzle is a script to control the car. A script is a component that contains a program that defines game object behaviors. With scripts you can specify the rules of your game, how objects should respond to various interactions (with the player as well as other objects). All scripts are written in the Lua programming language. To be able to work with Defold, you or someone on your team needs to learn how to program in Lua.
Mark “main” in the Assets pane, right-click and select New ▸ Script File. Name the new file car.script, then add it to the “car” game object by marking “car” in the Outline view, right click and select Add Component File. Select car.script and click OK. Save the collection file.
Double click car.script to open it.
Defold provides several lifecycle functions to code game logic. Read more about them in the Script Manual.
Start by removing the final
, on_message
and on_reload
functions as we won’t be needing them
for this tutorial.
Next, add the following lines of code before the init function start starts.
-- Constants
local turn_speed = 0.1 -- Slerp factor
local max_steer_angle_left = vmath.quat_rotation_z(math.pi / 6) -- 30 degrees
local max_steer_angle_right = vmath.quat_rotation_z(-math.pi / 6) -- -30 degrees
local steer_angle_zero = vmath.quat_rotation_z(0) -- Zero degrees
local wheels_vector = vmath.vector3(0, 72, 0) -- Vector from center of back and front wheel pairs
local acceleration = 100 -- The acceleration of the car
-- prehash the inputs
local left = hash("left")
local right = hash("right")
local accelerate = hash("accelerate")
local brake = hash("brake")
The changes made here are fairly simple, we just added a bunch of constants
to our script that we’ll be using later on to code our car.
Pay notice to how we store the hashes beforehand in variables. It is actually a good practice to do so as it makes your code more readable and performant.
Next, edit the init
function so that it contains the following:
function init(self)
-- Send a message to the render script (see builtins/render/default.render_script) to set the clear color.
-- This changes the background color of the game. The vector4 contains color information
-- by channel from 0-1: Red = 0.2. Green = 0.2, Blue = 0.2 and Alpha = 1.0
msg.post("@render:", "clear_color", { color = vmath.vector4(0.2, 0.2, 0.2, 1.0) } ) --<1>
-- Acquire input focus so we can react to input
msg.post(".", "acquire_input_focus") -- <2>
-- Some variables
self.steer_angle = vmath.quat() -- <3>
self.direction = vmath.quat()
-- Velocity and acceleration are car relative (not rotated)
self.velocity = vmath.vector3()
self.acceleration = vmath.vector3()
-- Input vector. This is modified later in the on_input function
-- to store the input.
self.input = vmath.vector3()
end
Wondering what we just changed? Here is an explanation.
acquire_input_focus
needs to be sent to the game object holding the component. In our case we send this message to the gameobject that holds the car script.That was easy now, wasn’t it? We’ll continue now by editing the update
function so that it now contains the following:
function update(self, dt)
-- Set acceleration to the y input
self.acceleration.y = self.input.y * acceleration -- <1>
-- Calculate the new positions of front and back wheels
local front_vel = vmath.rotate(self.steer_angle, self.velocity)
local new_front_pos = vmath.rotate(self.direction, wheels_vector + front_vel)
local new_back_pos = vmath.rotate(self.direction, self.velocity) -- <2>
-- Calculate the car's new direction
local new_dir = vmath.normalize(new_front_pos - new_back_pos)
self.direction = vmath.quat_rotation_z(math.atan2(new_dir.y, new_dir.x) - math.pi / 2) -- <3>
-- Calculate new velocity based on current acceleration
self.velocity = self.velocity + self.acceleration * dt -- <4>
-- Update position based on current velocity and direction
local pos = go.get_position()
pos = pos + vmath.rotate(self.direction, self.velocity)
go.set_position(pos) -- <5>
-- Interpolate the wheels using vmath.slerp
if self.input.x > 0 then -- <6>
self.steer_angle = vmath.slerp(turn_speed, self.steer_angle, max_steer_angle_right)
elseif self.input.x < 0 then
self.steer_angle = vmath.slerp(turn_speed, self.steer_angle, max_steer_angle_left)
else
self.steer_angle = vmath.slerp(turn_speed, self.steer_angle, steer_angle_zero)
end
-- Update the wheel rotation
go.set_rotation(self.steer_angle, "left_wheel") -- <7>
go.set_rotation(self.steer_angle, "right_wheel")
-- Set the game object's rotation to the direction
go.set_rotation(self.direction)
-- reset acceleration and input
self.acceleration = vmath.vector3() -- <8>
self.input = vmath.vector3()
end
That was a huge function! But don’t worry, this is how it all works:
Finally, it is time to make our car react to input. Update the on_input
function so it looks like this:
function on_input(self, action_id, action)
-- set the input vector to correspond to the key press
if action_id == left then
self.input.x = -1
elseif action_id == right then
self.input.x = 1
elseif action_id == accelerate then
self.input.y = 1
elseif action_id == brake then
self.input.y = -1
end
end
This function is actually fairly simple, we just accept the input and set our input vector.
Don’t forget to save your edits.
There is no input actions set up yet, so let’s fix that. Open the file /input/game.input_bindings and add key_trigger bindings for “accelerate”, “brake”, “left” and “right”. We set them to the arrow keys (KEY_LEFT, KEY_RIGHT, KEY_UP and KEY_DOWN):
Now the car is ready to roll. We have created it inside “car.collection” but it does not yet exist in the game. That is because the engine currently loads “main.collection” on startup. To fix that we simply have to add car.collection to main.collection. Open main.collection, mark the “Collection” root node in the Outline view, right-click and select Add Collection From File, select car.collection and click OK. Now the contents of the car.collection will be placed in main.collection as new instances. If you change the content of car.collection, each instance of the collection will be updated automatically when the game is built.
Now, select Project ▸ Build and take your new car for a spin! You’ll notice that you’re now able to move to make the car move to your will. But something isn’t right yet. When you leave the controls, the car does not stop, as it should have. It’s time to add that in!
Whenever an object moves in the real world, the force of drag acts against the object causing it to slow down. This force almost falls proportional to the square velocity of the moving object, and hence can be described as D = k * |V| * V
where k
is a constant, V
is the velocity and |V|
its magnitude (speed). Let’s add that.
In the constants section at the top of the script, add the following constant
local drag = 1.1 --the drag constant <1>
Then in the update function, just above this line add the following lines and save the file.
function update(self, dt)
...
-- Calculate new velocity based on current acceleration
self.velocity = self.velocity + self.acceleration * dt
...
end
function update(self, dt)
...
-- Speed is the magnitude of the velocity
local speed = vmath.length_sqr(self.velocity)
-- Apply drag
self.acceleration = self.acceleration - speed * self.velocity * drag
-- Stop if we are already slow enough
if speed < 0.5 then self.velocity = vmath.vector3(0) end
...
end
After completing the above steps, your car.script should look like :
local turn_speed = 0.1 -- Slerp factor
local max_steer_angle_left = vmath.quat_rotation_z(math.pi / 6) -- 30 degrees
local max_steer_angle_right = vmath.quat_rotation_z(-math.pi / 6) -- -30 degrees
local steer_angle_zero = vmath.quat_rotation_z(0) -- Zero degrees
local wheels_vector = vmath.vector3(0, 72, 0) -- Vector from center of back and front wheel pairs
local acceleration = 100 -- The acceleration of the car
local drag = 1.1 -- the drag constant
function init(self)
-- Send a message to the render script (see builtins/render/default.render_script) to set the clear color.
-- This changes the background color of the game. The vector4 contains color information
-- by channel from 0-1: Red = 0.2. Green = 0.2, Blue = 0.2 and Alpha = 1.0
msg.post("@render:", "clear_color", { color = vmath.vector4(0.2, 0.2, 0.2, 1.0) } )
-- Acquire input focus so we can react to input
msg.post(".", "acquire_input_focus")
-- Some variables
self.steer_angle = vmath.quat()
self.direction = vmath.quat()
-- Velocity and acceleration are car relative (not rotated)
self.velocity = vmath.vector3()
self.acceleration = vmath.vector3()
-- Input vector. This is modified later in the on_input function
-- to store the input.
self.input = vmath.vector3()
end
function update(self, dt)
-- Set acceleration to the y input
self.acceleration.y = self.input.y * acceleration
-- Calculate the new positions of front and back wheels
local front_vel = vmath.rotate(self.steer_angle, self.velocity)
local new_front_pos = vmath.rotate(self.direction, wheels_vector + front_vel)
local new_back_pos = vmath.rotate(self.direction, self.velocity)
-- Calculate the car's new direction
local new_dir = vmath.normalize(new_front_pos - new_back_pos)
self.direction = vmath.quat_rotation_z(math.atan2(new_dir.y, new_dir.x) - math.pi / 2)
-- Speed is the magnitude of the velocity
local speed = vmath.length(self.velocity)
-- Apply drag
self.acceleration = self.acceleration - speed * self.velocity * drag
-- Stop if we are already slow enough
if speed < 0.5 then self.velocity = vmath.vector3() end
-- Calculate new velocity based on current acceleration
self.velocity = self.velocity + self.acceleration * dt
-- Update position based on current velocity and direction
local pos = go.get_position()
pos = pos + vmath.rotate(self.direction, self.velocity)
go.set_position(pos)
-- Interpolate the wheels using vmath.slerp
if self.input.x > 0 then
self.steer_angle = vmath.slerp(turn_speed, self.steer_angle, max_steer_angle_right)
elseif self.input.x < 0 then
self.steer_angle = vmath.slerp(turn_speed, self.steer_angle, max_steer_angle_left)
else
self.steer_angle = vmath.slerp(turn_speed, self.steer_angle, steer_angle_zero)
end
-- Update the wheel rotation
go.set_rotation(self.steer_angle, "left_wheel")
go.set_rotation(self.steer_angle, "right_wheel")
-- Set the game object's rotation to the direction
go.set_rotation(self.direction)
-- reset acceleration and input
self.acceleration = vmath.vector3()
self.input = vmath.vector3()
end
function on_input(self, action_id, action)
-- set the input vector to correspond to the key press
if action_id == hash("left") then
self.input.x = -1
elseif action_id == hash("right") then
self.input.x = 1
elseif action_id == hash("accelerate") then
self.input.y = 1
elseif action_id == hash("brake") then
self.input.y = -1
end
end
Now, select Project ▸ Build from the main menu and take your new car for a spin!
That concludes this introductory tutorial. Here is a set of challenges that you may wish to tackle on your own:
properties
so that they can be changed for different instances of the car.Now go ahead and dive into Defold. We have lots of manuals and tutorials prepared to guide you, and if you get stuck, you are very welcome to the forum.
Happy Defolding!
Did you spot an error or do you have a suggestion? Please let us know on GitHub!
GITHUB