This tutorial goes through the steps needed to create a small playable game embryo in Defold. You do not need to have any prior experience with Defold, but if you have done some programming in Lua, Javascript, Python or similar, that will help.
You start with an empty project but we have added the required assets for you. You can verify that the project is empty by building and running it (or selecting Project ▸ Build from the menu). This will launch the game and you should see nothing but a black window.
Your game needs a setting, a map. The map that you are going to draw will be made out of tiles, small images that are put together like a mosaic into a larger image. In Defold, such an image is called a Tile map. In order to create a tile map, you need an image file that contains the various tiles. You then need to specify the size of the tiles, margins and padding and what image file to use in a file of a type called Tile source.
-
Right click the folder "main" and select New ▸ Tile source. This will create a new tile source file. Name the file "map" (full name "map.tilesource").
-
The new tilesource file opens automatically in the editor. Set the Image property of the tile source to the image file "/assets/map.png". The easiest way to do that is to click the resource selector by the Image property to bring up the resource selector. Then select the file "/assets/map.png":
The tiles are 16⨉16 pixels in the source image with no margins or padding so there is no need to alter the default properties of the tile source.
-
Right click the folder "main" and select New ▸ Tile map. Name the file "map" (full name "map.tilemap"). The tile map is automatically opened in the editor view.
-
Set the Tile source property of the new tile map to "/main/map.tilesource".
-
Select "layer1" in the Outline.
-
Select Edit ▸ Select Tile.... This brings up the tile palette.
-
Click on a grass tile. This selects the clicked tile as the current brush. Then paint the tile map layer as you see fit with the grass tile. Select other tiles from the tile palette to paint different graphics.
-
You can hold Shift, then click and drag to make a selection on the current tile map layer. The selection then becomes your new brush. This is a useful way to paint with a brush consisting of multiple tiles.
When you are happy with the map, it is time to add it to the game.
Defold stores everything you build in collections. A collection is a file used to build hierarchies of game objects and other collections. In the file "game.project" you specify a particular collection that is loaded when the game starts up. This is initially set to the file "/main/main.collection".
-
Open the file "main.collection".
-
Right click the root node of the collection in the Outline and select Add game object.
-
Change the Id property of the game object to "map". The id does not really matter for this game object but it is a good habit to set identifiers that are descriptive---it makes it easier to find your way around when you have many game objects.
-
Right click the new game object and select Add Component File.
-
In the resource selector, pick the file "/main/map.tilemap". This creates a new component in the game object based on the tilemap file. The tile map should now appear in the editor view.
-
Run the game by selecting Project ▸ Build and check that everything looks good. If you feel that the window is a bit large you can open "game.project" in the project root and alter the display width and height:
NOTE: If you get an "Out of tiles to render" error when running the game it means that the tilemap you created was larger than the maximum configured number of tiles. You can solve this by increasing the Tilemap->Max Tile Count value in the game.project file.
-
Right click the folder "main" in the Assets view and select New ▸ Atlas. Name the new atlas file "sprites" (full name "sprites.atlas"). An atlas is a collection of images (PNG or JPEG) that are baked into a larger texture. Defold uses atlases instead of single image files for performance and memory reasons. The new atlas should open in the editor.
-
Right click the root node of the atlas in the Outline and select Add Animation Group.
-
Select the new animation group and change its Id property to "player-down".
-
Right click the "player-down" animation group and select Add Images.... In the resource selector, pick the images "/assets/infantry/down/1.png" to "/assets/infantry/down/4.png". You can type "down" in the text box to filter the selection of images.
-
With the animation group marked, select View ▸ Play from the menu to preview the animation. Press F to frame the animation in the editor if necessary. The animation will play back at full 60 FPS which is way too fast. Set the playback speed (Fps property) to 8.
Now you have an atlas with a single flipbook animation for the player. This is enough for initial testing---you can add more animations later. Now, let's create the player game object.
A Defold game object is an object with an id, a position, a rotation and a scale that holds components. They are used to create things like a player character, a bullet, a game's rule system or a level loader/unloader. A component, in turn, is an entity that gives a game object visual, audible and/or logic representation in the game.
-
Open "main.collection".
-
Right click the root node of the collection in the Outline and select Add Game Object. Set the Id property of the new game object to "player".
-
Change the Z Position property of the game object named "player" to 1.0. Since the "map" game object is at the default Z position 0 the "player" game object must be at a higher value (between -1.0 and 1.0) for it to be on top of the level.
-
Right click the game object "player" and select Add Component ▸ Sprite. This creates a new sprite component, that can show graphics, in the "player" game object.
-
Make sure that the Z Position of the Sprite is 0 so it will be rendered at the same depth as the game object "player". Setting the Z to a different value will offset the sprite depth from 1.0, which is the Z position of the game object.
-
Set the Image property of the sprite to "/main/sprites.atlas".
-
Set the Default Animation property of the sprite to "player-down".
-
Run the game and check that the player character is animating.
The player game object now has visual representation in the game world. The next step is to add a script component to the player game object. This will allow you to create player behavior, such as movement. But that depends on user input, so first you need to set that up.
There are no input mapped by default so you need to add input actions for your player character:
-
Open the file "/input/game.input_binding". This file contains mappings from input sources (keyboard, touch screen, game pads etc) to input actions. Actions are just names that we want to associate with certain input.
-
Add Key triggers for the four arrow keys. Name the actions "up", "down", "left" and "right".
Unlike the sprite component, which you added directly into the "player" game object, a script component requires that you create a separate file. This script file is then used a template for the script component:
-
Right click the folder "main" in the Assets view and select New ▸ Script. Name the new script file "player" (full name "player.script"). The script file, pre-filled with template functions, opens up in the editor.
-
Open "main.collection", Right click the game object "player" and select Add Component File. Pick the new file "/main/player.script" as the file to use for the component.
You now have a script that runs in the "player" game object. It does not do anything yet though. Start by creating the logic for player movement.
The Lua code needed to create character movement in 8 directions is not long, but may require some time to understand completely. Replace the code for each of the functions in "player.script" with the code below, run the game, then take your time to carefully read through the code notes below.
function init(self) -- [1]
msg.post(".", "acquire_input_focus") -- [2]
self.moving = false -- [3]
self.input = vmath.vector3() -- [4]
self.dir = vmath.vector3(0, 1, 0) -- [5]
self.speed = 50 -- [6]
end
function final(self) -- [7]
msg.post(".", "release_input_focus") -- [8]
end
function update(self, dt) -- [9]
if self.moving then
local pos = go.get_position() -- [10]
pos = pos + self.dir * self.speed * dt -- [11]
go.set_position(pos) -- [12]
end
self.input.x = 0 -- [13]
self.input.y = 0
self.moving = false
end
function on_input(self, action_id, action) -- [14]
if action_id == hash("up") then
self.input.y = 1 -- [15]
elseif action_id == hash("down") then
self.input.y = -1
elseif action_id == hash("left") then
self.input.x = -1
elseif action_id == hash("right") then
self.input.x = 1
end
if vmath.length(self.input) > 0 then
self.moving = true -- [16]
self.dir = vmath.normalize(self.input) -- [17]
end
end
- The
init()
function is called when the script component is brought to life in the game engine. This function is useful for initial setup of the game object state. - This line posts a message named "acquire_input_focus" to the current game object ("." is shorthand for the current game object). This is a system message that tells the engine to send input actions to this game object. The actions will arrive in this script component's
on_input()
function. self
is a reference to the current component instance. You can store state data that is local to the component instance inself
. You use it like a Lua table by indexing the table field variables with the dot notation. The flag variablemoving
is used to track if the player is moving or not.input
is a vector3, a vector with 3 components:x
,y
andz
, that will point in any of the current 8 input directions. It will change as the player presses the arrow keys. The Z component of this vector is unused so it is kept at value 0.dir
is another vector3 that contains the direction the player faces. The direction vector is separate from the input vector because if there is no input and the player character does not move, it should still face a direction.speed
is the movement speed expressed in pixels per second.- The
final()
function is called when the script component is deleted from the game. This happens either when the container game object ("player") is deleted or when the game shuts down. - The script explicitly releases input focus, telling the engine that it wants no more input. Input focus is automatically released when the game object is deleted so this line is not necessary but is included here for clarity.
- The
update()
function is called once each frame. The game is running at 60 frames per second so the function is called at an interval of 1/60 seconds. The argument variabledt
contains the current frame interval---the number of seconds elapsed since the last call to the function. - If the
moving
flag is true, get the current game object position. The functiongo.get_position()
takes an optional argument which is the id of the game object to get the position of. If no argument is given, the current game object's position is returned. - Add the current direction vector (scaled to speed and frame interval) to the position.
- Set the position of the game object to the new position.
- After the calculations have been made, set the input vector to 0 and unset the
moving
flag. - The
on_input()
function is called every frame for all mapped input that is active. The argumentaction_id
contain the action as set up in the input bindings file. The argumentaction
is a Lua table with details on the input. - For each input direction, set the X or the Y component of the
input
vector inself
. If the user presses the up arrow and left arrow keys simultaneously, the engine will call this function twice and the input vector will end up being set to(-1, 1, 0)
. - If the user presses any of the arrow keys, the input vector length will be non zero. If so, set the
moving
flag so the player will be moved inupdate()
. The reason the script does not move the player in theon_input()
function is that it is simpler to collect all input each frame and then act upon it inupdate()
. - The
dir
direction vector is set to the normalized value of the input. If the input vector is(1, 1, 0)
, for instance, the vector length is greater than 1 (the square root of 2). Normalizing the vector brings it to a length of exactly 1. Without normalization diagonal movement would be faster than horizontal and vertical. When the engine runs theupdate()
function, any user input will have an effect on thedir
vector which will cause the player to move.
With the code above, your game now has a player character that can move around on the screen. Next, let's add the possibility to fire rockets.
Rockets should work like this: whenever the user presses a key, a rocket should fire. It should be possible to fire any number of rockets. To solve that you cannot just add a rocket game object to "main.collection"---that would be only one single rocket object. Instead, what you need to is a blueprint for a rocket game object and then a factory that creates new game objects on the fly based on that blueprint.
Start by creating the game object blueprint file:
-
Right click the folder "main" in the Assets view and select New ▸ Game Object. Name this file "rocket" (full name "rocket.go").
-
Open "sprites.atlas" and create a new animation group (right click the root node and select Add Animation Group). Name the animation "rocket".
-
Add the three rocket images (in "/assets/buildings/turret-rocket") to the animation group and set the Fps property to a value that makes the animation look good when you preview.
-
Open "rocket.go" and Right click the root in the Outline and select Add Component ▸ Sprite.
-
Set the Image property of the sprite to "/main/sprites.atlas" and the Default Animation to "rocket".
Now you have a basic rocket game object blueprint, on file. The next step is to add functionality to spawn game objects based on this blueprint file. For that, you will use a Factory component. You also need to add a new input action for the firing mechanic.
-
Open "main.collection" and Right click on the "player" game object. Select Add Component ▸ Factory.
-
Select the new factory component and set its Id property to "rocketfactory" and its Prototype to the file "/main/rocket.go" (the one you created above). Now the player game object is all set.
-
Open the file "/input/game.input_binding".
-
Add a Key trigger for the firing action. Call this action "fire".
-
Open "main/player.script" and add a flag to track if the player is firing to the
init()
function:function init(self) msg.post(".", "acquire_input_focus") self.moving = false self.firing = false -- [1] self.input = vmath.vector3() self.dir = vmath.vector3(0, 1, 0) self.speed = 50 end
- Whenever the player is firing this value will be set to
true
.
- Whenever the player is firing this value will be set to
-
In
update()
, add what should happen when the flag is set: the factory component should create a new game object instance:function update(self, dt) if self.moving then local pos = go.get_position() pos = pos + self.dir * self.speed * dt go.set_position(pos) end if self.firing then factory.create("#rocketfactory") -- [1] end self.input.x = 0 self.input.y = 0 self.moving = false self.firing = false -- [2] end
- If the
firing
flag is true, tell the factory component called "rocketfactory" that you just created to spawn a new game object. Note the character '#' that indicates that what follows is the id of a component. - Set the firing flag to false. This flag will be set in
on_input()
each frame the player presses the fire key.
- If the
-
Scroll down to the
on_input()
function. Add a fifthelseif
for the case where the function is called with the "fire" action and only the one frame when the key is pressed down:... elseif action_id == hash("right") then self.input.x = 1 elseif action_id == hash("fire") and action.pressed then self.firing = true end ...
If you run the game now you should be able to move around and drop rockets all over the map by hammering the fire key. This is a good start.
When a rocket is spawned, it is currently not oriented in the player's direction. That needs to be fixed. It should also fly straight ahead and explode after a short interval:
-
Open "player.script" and scroll down to the
update()
function and update its code:function update(self, dt) if self.moving then local pos = go.get_position() pos = pos + self.dir * self.speed * dt go.set_position(pos) end if self.firing then local angle = math.atan2(self.dir.y, self.dir.x) -- [1] local rot = vmath.quat_rotation_z(angle) -- [2] local props = { dir = self.dir } -- [3] factory.create("#rocketfactory", nil, rot, props) -- [4] end ...
- Compute the angle (in radians) of the player.
- Create a quaternion for that angular rotation around Z.
- Create a table containing property values to pass to the rocket. The player's direction is the only data the rocket needs.
- Add explicit position (
nil
, the rocket will spawn at the player's position), rotation (the calculated quaternion) and spawn property values.
Note that the rocket needs a movement direction in addition to the game object rotation (
rot
). It would be possible to make the rocket calculate its movement vector based on its rotation, but it is easier and more flexible to separate the two values. For instance, with a separate rotation it is possible to add rotation wobble to the rocket without it affecting the movement direction. -
Right click the folder "main" in the Assets view and select New ▸ Script. Name the new script file "rocket" (full name "rocket.script"). Replace the template code in the file with the following:
go.property("dir", vmath.vector3()) -- [1] function init(self) self.speed = 200 -- [2] end function update(self, dt) local pos = go.get_position() -- [3] pos = pos + self.dir * self.speed * dt -- [4] go.set_position(pos) -- [5] end
- Define a new script property named
dir
and initialize the property with a default empty vector (vmath.vector3()
). The default value can be overrided by passing values to thefactory.create()
function. The current property value is accessed asself.dir
. This is expected to be a unit vector (of length 1). - A rocket speed value, expressed in pixels per second.
- Get the current rocket position.
- Calculate a new position based on the old position, the movement direction and the speed.
- Set the new position.
- Define a new script property named
-
Open "rocket.go" and Right click the root in the Outline and select Add Component File. Select the file "rocket.script" as basis for the component.
-
Run the game and try the new mechanic. Notice that the rockets fly in the right direction but they are oriented 180 degrees wrong. That's an easy fix.
-
Open "sprites.atlas", select the "rocket" animation and click the Flip horizontal property.
-
Run the game again to verify that everything looks ok.
The rockets should explode a short while after they are fired:
-
Open "sprites.atlas" and create a new animation group (right click the root node and select Add Animation Group). Call the animation "explosion".
-
Add the nine explosion images in "/assets/fx/explosion" to the animation group and set the Fps property to a value that makes the animation look good when you preview. Also make sure that this animation has the Playback property set to
Once Forward
. -
Open "rocket.script" and scroll down to the
init()
function and change it to:function init(self) self.speed = 200 self.life = 1 -- [1] end
- This value will act as a timer to track the lifetime of the rocket.
-
Scroll down to the
update()
function and change it to:function update(self, dt) local pos = go.get_position() pos = pos + self.dir * self.speed * dt go.set_position(pos) self.life = self.life - dt -- [1] if self.life < 0 then -- [2] self.life = 1000 -- [3] go.set_rotation(vmath.quat()) -- [4] self.speed = 0 -- [5] msg.post("#sprite", "play_animation", { id = hash("explosion") }) -- [6] end end
- Decrease the life timer with delta time. It will decrease with 1.0 per second.
- When the life timer has reached zero.
- Set the life timer to a large value so this code won't run every subsequent update.
- Set the game object rotation to 0, otherwise the explosion graphics will be rotated.
- Set the movement speed to 0, otherwise the explosion graphics will move.
- Play the "explosion" animation on the game object's "sprite" component.
-
Below the
update()
function, add a newon_message()
function:function on_message(self, message_id, message, sender) -- [1] if message_id == hash("animation_done") then -- [2] go.delete() -- [3] end end
- The function
on_message()
gets called whenever a message is posted to this script component. - Check if the message posted has the hashed name (or id) "animation_done". The engine runtime sends this message whenever a sprite animation initiated with "play_animation" from this script has completed.
- When the animation is done, delete the current game object.
- The function
Run the game.
This is definitely getting somewhere! Now you just need something to fire the rockets at!
-
Right click the folder "main" in the Assets view and select New ▸ Game Object. Name this file "tank" (full name "tank.go"). Like the rocket game object, this is a file that can be used as a blueprint when creating actual tank game objects.
-
Open "sprites.atlas" and create a new animation group (right click the root node and select Add Animation Group). Name the animation "tank-down".
-
Add the two downwards facing images in "/assets/units/tank/down" to the animation and set it's Fps value to something that looks good.
-
Open "tank.go" and Right click the root in the Outline and select Add Component ▸ Sprite.
-
Set the Image property of the sprite to "/main/sprites.atlas" and the Default animation to "tank-down".
-
Open "main.collection".
-
Right click the root node of the collection in the Outline and select Add Game Object File. Select "tank.go" as blueprint for the new game object.
-
Create a few more tanks from the blueprint. Position them on the map with the Move Tool. Make sure to set the Z position to 1.0 so they are all rendered on top of the map.
Run the game and check that the tanks look okay.
When you fire at the tanks, the rockets currently fly straight through them. The next step is to add collision between the tanks and the rockets:
-
Open "tank.go" and Right click the root in the Outline and select Add Component ▸ Collision Object.
-
Set the Type property to "Kinematic". This means that the physics engine will not simulate any gravity or collision on this object. Instead it will only detect and signal collisions and leave it to you to code the response.
-
Set the Group property to "tanks" and Mask to "rockets". This causes this game object to detect collisions against object in the group "rockets" that has the mask set to "tanks".
-
Right click the "collisionobject" component in the Outline and select Add Shape ▸ Box. Set the size of the box shape to match the tank graphics.
-
Open "rocket.go" and Right click the root in the Outline and select Add Component ▸ Collision Object.
-
Set the Type property to "Kinematic".
-
Set the Group property to "rockets" and Mask to "tanks". This causes this game object to detect collisions against object in the group "tanks" that has the mask set to "rockets".
Now the group and mask between rockets and tanks match each other so the physics engine will detect when they interact.
-
Right click the "collisionobject" component in the Outline and select Add Shape ▸ Box. Set the size of the box shape to match the rocket graphics.
The physics engine sends messages to game objects that collide. The last piece of the puzzle is to add code that reacts to those messages.
-
Open "rocket.script" and scroll down to the
update()
function. There are a couple of things to do here:local function explode(self) -- [1] self.life = 1000 go.set_rotation(vmath.quat()) self.speed = 0 msg.post("#sprite", "play_animation", { id = hash("explosion") }) end function update(self, dt) local pos = go.get_position() pos = pos + self.dir * self.speed * dt go.set_position(pos) self.life = self.life - dt if self.life < 0 then explode(self) -- [2] end end function on_message(self, message_id, message, sender) if message_id == hash("animation_done") then go.delete() elseif message_id == hash("collision_response") then -- [3] explode(self) -- [4] go.delete(message.other_id) -- [5] end end
- Since you want the rocket to explode either when the timer runs out (in
update()
) or when the rocket hits a tank (inon_message()
) you should break out that piece of code to avoid duplication. In this case that is done with a local function. The function is declaredlocal
, meaning that it only exist within the scope of the rocket script. Lua's scoping rules says that local functions need to be declared before they are used. Therefore the function is placed aboveupdate()
. Also make sure to passself
as a parameter to the function so you can accessself.life
etc. - The code that used to live here has been moved to the
explode()
function. - The engine sends a message called "collision_response" when the shapes collide, if the group and mask pairing is correct.
- Call the
explode()
function if there is a collision. - Finally delete the tank. You get the id of the game object the rocket collided with through the
message.other_id
variable.
- Since you want the rocket to explode either when the timer runs out (in
Run the game and destroy some tanks! The tanks aren't very interesting enemies, but they should nevertheless give you some score.
-
Right click the folder "main" in the Assets view and select New ▸ Font. Name this file "text" (full name "text.font").
-
Open "text.font" and set the Font property to the file "/assets/fonts/04font.ttf".
-
Right click the folder "main" in the Assets view and select New ▸ Gui. Name this file "ui" (full name "ui.gui"). It will contain the user interface where you will place the score counter.
-
Open "ui.gui". Right click Fonts in the Outline view and select Add ▸ Fonts. Select the "/main/text.font" file.
-
Right click Nodes in the Outline view and select Add ▸ Text.
-
Select the new text node in the outline and set its Id property to "score", its Text property to "SCORE: 0", its Font property to the font "text" and its Pivot property to "West".
-
Place the text node in the top left corner of the screen.
-
Right click the folder "main" in the Assets view and select New ▸ Gui Script. Name this new file "ui" (full name "ui.gui_script").
-
Go back to "ui.gui" and select the root node in the Outline. Set the Script property to the file "/main/ui.gui_script" that you just created. Now if we add this Gui as a component to a game object the Gui will be displayed and the script will run.
-
Open "main.collection".
-
Right click the root node of the collection in the Outline and select Add Game Object.
-
Set the Id property of the game object to "gui", then Right click it and select Add Component File. Select the file "/main/ui.gui". The new component will automatically get the Id "ui".
Now the score counter is displayed. You only need to add functionality in the Gui script so the score can be updated.
-
Open "ui.gui_script".
-
Replace the template code with the following:
function init(self) self.score = 0 -- [1] end function on_message(self, message_id, message, sender) if message_id == hash("add_score") then -- [2] self.score = self.score + message.score -- [3] local scorenode = gui.get_node("score") -- [4] gui.set_text(scorenode, "SCORE: " .. self.score) -- [5] end end
- Store the current score in
self
. Start from 0. - Reaction to a message named "add_score".
- Increase the current score value in
self
with the value passed in the message. - Get hold of the text node named "score" that you created in the Gui.
- Update the text of the node to the string "SCORE: " and the current score value concatenated to the end of the string.
- Store the current score in
-
Open "rocket.script" and scroll down to the
on_message()
function where you need to add one new line of code:function on_message(self, message_id, message, sender) if message_id == hash("animation_done") then go.delete() elseif message_id == hash("collision_response") then explode(self) go.delete(message.other_id) msg.post("/gui#ui", "add_score", {score = 100}) -- [1] end end
- Post a message named "add_score" to the component "ui" in the game object named "gui" at the root of the main collection. Pass along a table where the field
score
has been set to 100.
- Post a message named "add_score" to the component "ui" in the game object named "gui" at the root of the main collection. Pass along a table where the field
-
Try the game!
There you go! Well done!
We hope you enjoyed this tutorial and that it was helpful. To get to know Defold better, we suggest that you to continue working with this little game. Here are a few suggested exercises:
-
Add directional animations for the player character. Tip, add a function called
update_animation(self)
to theupdate()
function and change the animation depending on the value of theself.dir
vector. It is also worth remembering that if you send a "play_animation" message each frame to a sprite, the animation will restart from the beginning, each frame---so you should only send "play_animation" when the animation should change. -
Add an "idle" state to the player character so it only plays a walking animation when moving.
-
Make the tanks spawn dynamically. Look at how the rockets are spawned and do a similar setup for the tanks. You might want to create a new game object in the main collection with a script that controls the tank spawning.
-
Make the tanks patrol the map. One simple option is to have the tank pick a random point on the map and move towards that point. When it is within a short distance of the point, it picks a new point.
-
Make the tanks chase the player. One option is to add a new collision object to the tank with a spherical shape. If the player collides with the collision object, have the tank drive towards the player.
-
Make the tanks fire at the player.
-
Add sound effects.
Check out the documentation pages for examples, tutorials, manuals and API docs.
If you run into trouble, help is available in our forum.
Happy Defolding!