In this part, we want to move around our environment with a cursor that shifts cell by cell on the grid, while also displaying the coordinates of the current cell in the game.
So, in this part you will learn:
How to use a tilemap resource to create an animation
How to configure your own font in Defold
What a module is and how to use it
How to convert pixel positions to tile coordinates
The first thing we need is a game object to represent our player.
Open the main.collection file.
Create a new Game Object and name it "Player".
Add two components to it:
A Sprite component → rename it to "cell_cursor".
A Label component → rename it to "label_id".
Go to the Asset Tree, inside the game folder:
Create a new folder and name it player.
Inside this folder, create a new Script and name it player.script (or simply player).
This script will later be attached to our Player game object to handle its logic.
Before going back to our Player game object, let’s create a TileSource to represent our cell cursor.
👉 Yes, you can use a TileSource directly in a Sprite component!
This is very handy, especially if you’re working with a spritesheet that contains multiple frames for animations.
For this project, I already included a spritesheet with different UI cursors from Kenney’s Cursor Pixel Pack.
So no need to download anything, it’s ready to use!
Go to the simple_ui folder and create a new TileSource inside. Name it player_ui.
The TileSource editor should automatically open.
In the Properties panel, set the Image property to simple_ui.png.
Let’s take a look at the Outline panel.
You’ll notice a component named default. This one is related to collision, but since we won’t use it in this course, you can go ahead and delete it.
There’s also a component named anim. Rename it to cursor. This is where we’ll choose some tiles to create an animation.
If you want to create more animations later, you can simply right-click in the Outline panel and select:
Add → Animation.
As you can see, there are several properties in the animation panel.
The ones we’re interested in for now are Start Tile and End Tile.
We simply need to put the tile IDs that represent the beginning and the end of our animation.
Set Start Tile to 65 and End Tile to 66.
This will make your cursor animation play between these two tiles.
☝️tile 65 and 66.
Below the End Tile property, you’ll see a property called Playback. This defines how the animation will play.
We want our cursor animation to loop continuously from the beginning to the end, so choose Loop Forward.
Tip: Feel free to experiment with the other options, you’ll quickly see how they affect the animation.
There’s one last thing to do: change the FPS property and set its value to 1. And that’s it!
Click in the View area and press Spacebar to preview the animation. When you’re done, press Ctrl+T to stop the preview.
Go back to the main collection. In the Outline panel, hide the map game object by clicking the eye icon next to it.
Now, select the player game object and press F to center the view on it.
Since we want it to always render on top of the environment, set its Z position to 1.0.
Next, add the script component we created earlier.
In the Sprite component, change the Image to the cursor tilesource, and set the Default Animation to cursor, the animation we created earlier.
Finally, switch the Blend Mode to Add. This removes the black values from the sprite and makes it blend nicely with the background.
Great! Let’s create another game object that will always display the tile coordinates.
In the Outline panel, add a new Game Object and name it "label". Inside this object, add a Label component and rename it to "label_coordinate".
In the Label’s Text property, type: "X : 0, Y : 0"
Now place this new game object above the garden. For example, I set mine to X: 625 and Y: 520.
Finally, set its Z value to 1.0 so it will always render on top of the tilemap.
Okay, now let’s create a Font resource.
In the Font folder (I already created it for you), you’ll find a file named Kenney Pixel.ttf.
Right-click inside the folder, select New → Font, and name the new resource kenney_pixel.
Tip: Fonts in Defold are just resources. You can reuse them across multiple labels, so it’s a good idea to organize them in a dedicated folder like we did here.
A new window will open, showing the font resource settings.
In the Properties panel, here are the settings that matter to us:
Font: The font file to use (.ttf, .otf, or .fnt).
Material: The material that defines how the font is rendered. For most cases, you can leave it as is. (You’ll only need to change this for advanced setups like distance field fonts or BMFonts.)
Output format : Defines how the font is processed:
TYPE_BITMAP: Converts the TTF/OTF into a bitmap texture. This is the default and works for most use cases.
TYPE_DISTANCE_FIELD: Generates a texture where each pixel stores the distance to the font edge. This makes the font scalable without losing quality.
Size: The font size in pixels.
Outline Alpha: Controls the transparency of the outline (0.0 to 1.0).
Outline Width: The thickness of the outline, in pixels. Set it to 0 if you don’t want an outline.
In the Font property, select the file Kenney Pixel.ttf.
Next, change the Material property to font-df.material.
This is important because we want to use Distance Field rendering instead of Bitmap. Distance Fields make the font look sharp even when scaled.
Now increase the Size to 50 so the text is clearly visible in-game.
Finally, set the Outline Width to 4.0 to give the text a nice, clean border.
Go back to the main.collection and select the label_coordinate component.
In the Font property, assign the new font we just created (kenney_pixel).
Then, update the Outline Color property. I used the color #472504ff, but feel free to pick any color you like that fits your game’s style.
Finally, update the Font property of the label_id component as well, assigning it the new kenney_pixel font.
You should now see something like this in your game:
Alright! Let’s get the player cursor working step by step.
First, we want the cursor to follow the mouse, but not pixel-perfect. Instead, it should snap to the tile the mouse is over.
Here’s the plan:
Get the mouse position.
Convert that position to a tile coordinate using the tilemap.
Make sure the tile coordinate is inside the bounds of the tilemap — we don’t want the cursor floating outside.
Open the player.script, delete all the existing code, and let’s start fresh.
Add an init function to get input focus so our script can receive mouse events:
[2] – The call to msg.post(".", "acquire_input_focus") gives this game object input focus, which means the script can now detect keyboard and mouse events.
Now, just after the init function
create an on_input function.
In this function, you’ll get the mouse position from the action parameter. This is usually action.x and action.y in screen coordinates.
[6] – The variable pos stores action.x and action.y, which is the current position of the mouse.
[7] – A condition to check if the mouse is moving, since action.x and action.y only exist when the mouse is present and moving.
This is what the documentation says. Always check it when you want to understand something you’re unsure about:
The action parameter is a table containing data about the input mapped to the action_id. For mapped actions, it specifies the value of the input and whether it was just pressed or released.
Actions are linked to input in an input_binding file.
Mouse movement is handled specially: it uses nil as its action_id. In this case, the action table only contains positional information, like the x and y coordinates of the pointer.
Let’s take a little break from coding. Yeah, I know we haven’t done much yet, but I want to introduce you to something really cool in Defold: modules.
In Defold, a module is simply a Lua script file that you can create to organize and reuse code. Instead of writing the same code in multiple places, you can put functions and variables inside a module, and then use that module anywhere in your project.
Think of a module like a toolbox:
Each function in the module is like a tool. Whenever you need that tool, you just open the toolbox and use it, instead of rebuilding the tool from scratch every time.
Using modules has several advantages when making games with Defold:
Reusability – You can write a function once (for example, to calculate player health, save data, or manage inventory) and then use it across multiple scripts in your game.
Organization – As your game grows, having all your logic inside a single script can get messy. Modules allow you to split your code into separate, focused files (for example: player.lua, enemy.lua, math_utils.lua).
Teamwork – If you’re working with others, modules make it easier to divide tasks. One teammate can work on the player system while another works on the enemy system, each inside their own module.
Maintainability – If you need to fix or improve something, you only update it once in the module, and the change is applied everywhere it is used.
Okay, now that we know what a module is, we’re going to create one.
This module will hold some useful things, like a variable for the tile size, and functions to convert from pixel position to tile coordinates and from tile coordinates back to pixel position.
In the main folder,
Right-click and create a new Lua module file,
Name it "utils", and press Create Lua Module.
A new file will automatically open, and you can delete everything inside.
The first thing to do is declare a local table. This table will store functions and data, and we will return it at the end.
Write the code below:
[1] - We declare our module table.
[5] - We return the table so we can access its data, values, and functions elsewhere.
Let’s add a variable to store our tile size.
For now, this variable will stay local to the module.
Write the code below:
[4] - Remember, the original size of our tile is 18 pixels, but we doubled it, that’s why we multiply it by two.
Since the variable is declared local, only this module can access it.
Now let’s write a function to convert pixel world coordinates into tile coordinates.
The logic is simple:
We divide each axis (x and y) by the tile size.
The result tells us which tile the object is located in.
Since we can’t have a fraction of a tile, we use math.ceil to round up to the nearest whole tile.
At its core, converting a world position (in pixels or units) into a tile coordinate is just an application of fractions and division.
Imagine your game world is split into squares (tiles) of equal size.
To figure out which square an object is in, you simply divide its pixel position by the size of one tile.
The result is basically "how many tiles fit into this distance" , just like dividing a length by a unit of measure.
For example:
If each tile is 32 pixels wide, and the object’s X position is 65 pixels,
65 ÷ 32 = 2.03 → this means the object has moved a little over 2 full tiles.
Since you can’t stand “inside” a fraction of a tile, we round up to get tile 3.
This is exactly how fractions work in math:
Numerator = the pixel position (the distance traveled).
Denominator = the tile size (the unit we measure with).
Result = the tile index (which tile we are in).
So now that we know how to convert a pixel position into a tile coordinate, let’s go ahead and write our function:
[7] - We create a function need a parameter, pos which is the position in pixel.
[8] - Store the position to a new variable called tile
[9-10] - calculate to find the tile coordinate for each axis
[12] - Lastly we return the tile coordinate
Okay let's write a function to convert now tile coordinate to pixel position, the logic is the reverse of world to tile coordinate, we need to multiply the tile index by the tile size, this will gives the top-left corner position of the tile, and then we have to get the pixel center position of the tile so we just have to substract half a tile size.
Explanation
Suppose each tile is 32x32 pixels, and we are looking at tile (3, 4).
On the X axis → 3 * 32 = 96, then subtract 16 → 80.
On the Y axis → 4 * 32 = 128, then subtract 16 → 112.
So the tile (3,4) corresponds to the world position (80, 112), which is the center of that tile.
As you can see the math behind it's just multiplaction.
Okay now let's write the code logic just after the function world to tile:
[7] - We create a function that takes one parameter, pos, which represents the position in pixels.
[8] - Store this position in a new variable called tile.
[9–10] - Calculate the tile coordinate for each axis.
[12] - Finally, return the tile coordinate.
Okay, our module is now ready to use! Let’s go back to the player.script.
We’ll need to require the module in our script to be able to use its functions.
Open the player.script, then in the Assets panel, right-click on our utils.lua module and choose Copy Require Path.
At the top of the player.script, declare a local variable named utils.
Use the require function and paste the require path we copied earlier.
This way, the module is stored in our utils variable, and we can access all of its functions and data through it.
Now let’s write a function to move our cursor.
This function will simply update the position of the player game object based on the tile coordinate.
Write this function just above the init function in the player.script.
[5] – The function takes a parameter named pos, which represents the position of our cursor.
[6] – We declare a variable cursor_cell and store the conversion of the pixel position to tile coordinates.
[7] – We declare a variable cursor_pos to store the conversion from tile coordinates back to pixel position.
[8] – I chose to adjust the z position to ensure the cursor appears above all other game objects.
[9] – We update the position of the cursor game object to the new position.
[19] – Finally, we call our new function and pass the current position to move the cursor.
You can now launch the game by pressing F5. You’ll see the cursor follow the mouse, but notice it doesn’t move smoothly like the mouse pointer. Instead, it snaps cell by cell because we calculate its position based on the tile coordinates.
Now we want to check if the cursor is within the boundaries of our tilemap! This step isn’t strictly necessary, but it’s a good opportunity to introduce a useful Tilemap method.
The method is called get_bounds(). It returns the top-left coordinate of the tilemap as well as the number of rows and columns. With this information, we can easily check if the cursor’s current cell is inside the tilemap limits.
Let’s write this code at the top of the script, just below the utils variable.
[4] – We store the URL of the tilemap component in a constant variable named MAP.
[7] – We declare a new function called within_bounds that takes one parameter: pos.
[8] – In a variable named tile, we store the tile coordinate obtained by converting the pixel position using our utils module.
[9] – We use four variables to store the values returned by get_bounds(): bx and by are the top-left coordinates of the tilemap, while bw and bh store the number of columns and rows.
[10] – We return a boolean that checks if the tile coordinates are greater than or equal to the top-left corner and less than the bottom-right corner of the tilemap. This tells us whether the cursor is inside the map boundaries.
Now, let’s go back to our on_input function. Before moving the cursor, we’ll check if the mouse position is within the bounds of the tilemap. This ensures the cursor doesn’t move outside the map.
[28] – Before moving the cursor, we use an if condition to check the result of our within_bounds function. This ensures the cursor only moves when the mouse is inside the tilemap.
Launch the game to test if everything works! Normally, it should.
Now, let’s finish with the last part: updating our label components.
We’ll start with the label_coordinate component. The text property of a label only accepts strings, so we’ll need to use tostring() to convert any number into a string before assigning it to the label.
Take a look at the code below. Try to read and understand it first, then write it in your script. After that, read my explanation to see how it works.
[5] – Our new function takes the cell coordinate as a parameter.
[6] – We declare a variable called text and assign it a string that shows the X and Y values. To combine strings and numbers, we use .. and tostring() to convert numbers to strings.
[7] – We use the label.set_text method to update the label_coordinate component. The first parameter is the URL of the label, and the second is the string we want to display.
[17] – Finally, we call our update_label_coordinate function to refresh the label with the current cursor position.
Perfect! Press F5 to launch the game, and you should now see the coordinates updating in real time as you move the cursor across the tilemap.
Perfect! Everything is working like a charm. 🎉
That wraps up this part of the course. In the next and final module, we’ll dive deeper into using modules: you’ll learn how to change tiles in code, add or remove plants in the garden with your cursor, and display the plant’s name dynamically in the label. It’s going to be a fun one!
you can download the full project here: Download project: Part3.