In this module, we’ll learn how to:
Get information from a tile
Check if a tile is plantable
Add or remove a plant
Display useful information on the screen
By the end of this module, you’ll have a simple gameplay loop that lets you select a tile, plant, remove, and restart.
The first thing we need is to get the tile that is currently under our cell cursor.
We’ll create a function for that and use the tilemap.get_tile method.
This method needs a few pieces of information to work:
url_of_map – the URL of the tilemap we want to check.
layer – the name of the layer in which the tile is located.
x_position – the x-coordinate of the tile in the layer.
y_position – the y-coordinate of the tile in the layer.
Open the player.script and add the following code at the top:
[8] – We declare a function that takes two parameters:
layer – the name of the layer we want to check (for example, "plantable_ground").
cell_position – the position of the cell we want to get the tile from.
[9] – We use tilemap.get_tile to get the ID of the tile at that position.
[10] – Finally, we return the tile ID.
So this function should return a number but if the cell is empty it will return 0, remember zero means there is no tile !
Now that we have a function to get the tile under our cursor, we can move on and create a function to check if the player can plant on that cell.
Remember: in our farming_tilesource, we have a layer called plantable_ground. The player will only be allowed to plant on this layer.
Now let’s write a function to check if the player is on a plantable cell. We’ll also check if there’s no vegetable already planted. If both conditions are true, the player will be allowed to plant.
Write the following code just after the get_tile_id function:
[15] - We declare a function named isPlantable which takes as parameter the cell coordinate we want to check.
[16] - We call get_tile_id on the "plantable_ground" layer. If the id is not 0, it means the ground can be planted. We store this result in isGroundID.
[17-18] - If isGroundID is true, we then check the "vegetables" layer at the same cell. If the id is 0, it means nothing is planted there, so the cell is free.
[19-20] - If both conditions are satisfied (the ground is plantable and the cell is free), the function returns true.
[23] - Otherwise, if any condition fails, the function returns false.
Now that we have a function to check if the selected cell is plantable, it’s time to actually plant something!
But first, we need to define the possible plants the player can use. For that, we’ll create another module to store all the plant data.
Go to the environment folder, create a new module, and name it plants_data.lua. As usual, a new file will open with some default code, you can delete everything inside.
Then write the code below:
[1] - We declare a local table M
[3] - We store a table named listPlants in M, this table we listed the future possible plant for us.
[5] - As always in a module we return our data
Next, let’s create a local function to add plants for us.
For now, when we create a plant, we’ll simply save its name and a list of indexes representing its growth stages.
Write the following code just after the listPlants table:
[7] - We declare a function called create_plant that takes two parameters: the name of the plant and its index (the list of tile IDs).
[9] - We create a new empty table called newPlant, which will store all the information about this plant.
[11] - We assign the name of the plant to the field newPlant.name.
[12] - We assign the index (the tile IDs list) to the field newPlant.index.
[14] - We save this new plant inside the global list M.listPlants, using the plant's name as the key.
[15] - Finally, the function returns the new plant we just created.
Now that we have our function to create plants, we can use it to add some new plants and store them in the listPlants table.
To do this, we’ll create an init function in our module that calls the create_plant function for each plant we want to add.
Write the code below just after the create_plant function inside the module:
[20] - We declare a function named M.init. Its purpose is to initialize the plant data when the game starts.
[21] - We call print to log a message in the console, just to know that the initialization is happening.
[22-24] - We create different type of plant by calling create_plant and passing a list of tile IDs for each plant to represent each step of grow (check the tilesource).
Each of these calls automatically saves the plant inside the global list M.listPlants (thanks to the create_plant function).
Here are the tile IDs we assigned for each plant.
Our module is almost complete, but there’s one last thing we need: a function to get a plant from the list using the tile ID we find on our tilemap. Let’s write this function just before the init function.
[20] - We declare a function called M.get_plant_at which takes one parameter: the tile ID we want to check.
[21] - We initialize a variable name with the value "nothing". This will be the default result if no plant is found.
[23] - We loop through every plant stored in M.listPlants using pairs.
[25] - For each plant, we loop through its list of tile IDs (plant.index) using ipairs.
[27] - If the given p_id matches one of the IDs in the list, we assign the plant’s name to the variable name.
[31] - After checking all plants, the function returns the variable name (either the found plant’s name or "nothing" if no match exists).
Our module is ready! Let’s go back to the player script and declare a new variable so we can access the functionality we just created.
At the top of the script, create a new local variable called plants_data and use require to load our new module.
Next, go to the init function in the player script and call the initialization function from the plants_data module. This will create and set up all the plants for our game.
Launch the game and open the console. You should see some print statements showing the creation of your plants. This means your code is working correctly!
Let's continue with the player logic. Before we handle planting, we can start with something simple: updating the name of the plant that our cursor is currently selecting in the garden.
We'll write a function to update the label and use the get_plant_at function we created in the plant data module.
Add the following code just after update_label_coordinate:
[35] - We declare a function named update_label_id_name which takes a cell coordinate as parameter.
[36] - We call get_tile_id on the "vegetables" layer at this cell coordinate, and store the result in the variable id.
[37] - We call plants_data.get_plant_at(id) to find the plant name corresponding to this ID, and store it in the variable text.
[38] - We update the label component /player#label_id with this text, using label.set_text.
Next, go to the move_cursor_cell function and call your new function right after the call to update_label_coordinate.
Now, launch the game. The label should update automatically to show the name of the plant.
Yes, we created a module just for that. First, it’s to show you a good practice: separating the data from the logic. Later, we can add more functionality to our plants, like their growth state, whether they are harvestable, and much more.
Now let’s add two final features: planting and removing. Let’s start with the easiest one: removing a plant.
Removing a plant basically just means saying there’s no tile here. Add the following code just before update_label_coordinate:
[28] - We declare a function called removePlant which takes the world position as parameter.
[29] - We convert this world position into a tile coordinate by calling utils.world_to_tile(position).
[30] - We check if the tile at this coordinate in the "vegetables" layer is not empty (its ID is different from 0). The result is stored in isNotFree.
[31-32] - If isNotFree is true, we call tilemap.set_tile to set the tile ID to 0 on the "vegetables" layer, which removes the plant.
The behavior we want is simple: when the player left-clicks, we call our function to remove the plant at the position where the player wants to remove it.
Check the input bindings, I’ve already set up the key bindings for the cursor in the input folder.
Everything is ready. Now, go to the on_input function and add the following code:
[78] - We check if the action_id corresponds to "remove_plant" (converted into a hash). We also check if the action was pressed (the player has just triggered the input).
[79] - If both conditions are true, we call the function removePlant(pos) to remove the plant at the given position.
Now, let’s write the final feature: planting!
Go back to the on_input function and add the following code just before the action to remove a plant:
Now, let’s implement the final feature: planting!
Go back to the on_input function and add the following code just before the call to remove a plant:
[79] - We check if the action_id corresponds to "plant" (converted into a hash). We also check if the action is pressed (the player has just triggered the input).
[80] - If both conditions are true, we call the function plant(pos) to place a new plant at the given position.
Yes, I know we’re calling a function named plant that we haven’t created yet. I love coding like that! What I mean is, I prefer to write what I want first, even if the function doesn’t exist yet, and then go back and implement it.
So, let’s write the plant function just after the remove_plant function.
[37] - We declare a function called plant which takes a world position as parameter.
[38] - We convert the world position into a tile coordinate using utils.world_to_tile(position).
[39] - We check if this tile coordinate is plantable by calling isPlantable(cell_coordinate).
[40] - If the cell is plantable, we call add_plant(cell_coordinate) to actually place the plant on the tile.
I hope you understand what this function should do!
This function needs to try planting a vegetable on the selected cell. To do that, it first checks if the ground is plantable, and then it adds a plant, again, using a function that doesn’t exist yet.
So, just before the plant function, let’s write our add_plant function:
[37] - We declare a function called add_plant which takes a tile coordinate as parameter.
[38] - We generate a random number between 1 and 3 using math.random(1,3) and store it in rand_id.
[39] - We define a list of tile IDs for the plants: {57, 5, 43}.
[40] - We select a plant ID from this list using the random index rand_id.
[41] - We set the tile at the given coordinate on the "vegetables" layer to this plant ID using tilemap.set_tile.
Now we have almost everything we need! There’s just one last thing to do.
Since we’re using a random function, we want to make sure it works correctly.
Go to the `init` function and add the following:
Now, launch the game to finally see and enjoy what we’ve created together!
And with this final part, we conclude the lesson. I hope you’ve learned a lot! With this game, you now have the basics needed to create a small farming game. I encourage you to try adding more vegetables and modifying the code accordingly.
In a future module, we’ll return to this garden to implement additional features, such as plant growth, choosing which plant to plant, and checking if plants are ready to be harvested.
As always, you can download the code here: Download project: Part4.