Jackalope - Dev Log 01
Topics: GridMap (Godot), MeshLibrary (Godot), Tween (Godot)
I began writing this blog post and immediately fell into the cooking recipe trap - you know how when you find a recipe on some website you always have to read through some chef’s/blogger’s life story before they finally list the recipe (the one reason you went there in the first place)? Yeah, that bullshit.
So I started writing out my reasons for learning to code and working on a solo game project and I was about 5 paragraphs in before I realized what I was doing. You’re (probably) not here for that shit! And you know what, my dudes? I definitely don’t blame you. You’re (probably) here to hear about the process of making the game - the issues I ran into and the things that may or may not have worked - so let’s just cut to that shit.
Let me tell you about Jackalope.
Jackalope (I’ve decided to use cryptid names as the theme for my project codenames) is my first solo project, which I’m building in Godot 4.
At the moment, it is a game about a mole navigating his way through a mine and trying not to get hurt. I say “at the moment” because, as a solo developer and novice game designer, I have some ideas I think might be fun but are, as of yet, unproven. Even working alone on a project, ideas have a way of shifting as you “search for the fun.”
Despite diving into development the project still has a lot of gaps, both in terms of the design of the game as well as my knowledge of Godot.
For the former, as I alluded to before, my hope is to avoid getting too attached to what I think may be fun to allow myself to find what actually is fun. I have fallen into that trap before; I’ve spent dozens of hours on a game idea writing up design docs, creating concept art, naming characters and writing their back stories - all things, as an artist, I had the tools to do - only to realize later that the core gameplay experience is simply not compelling. The more developed these early ideas are, the harder (I’ve found) it is to recognize when something better presents itself and let go of the original ideas.
Regarding the gaps in my Godot knowledge, by diving into development now I’m hoping to avoid the same issue Mark Brown of GMTK acknowledged in his YouTube series, Developing:
”As I said, there’s lots of tutorials for Unity out there and that is exactly what I tried to use when I first started learning it [. . .] Every time they clicked a box, I clicked that box on my screen. Every time they typed out a line of code, I typed out the same line of code. And before long I had a working game on my computer, which was like… super exciting. But then, many weeks later, I decided to open up a blank Unity project to kind of see what I had learned and the answer was… nothing. Like, literally nothing. Like, everything they had said had gone in one ear and out the other.”
Mark realized, at least for him, this was not a method that helped him retain knowledge, which rang very true for me as well. I don’t necessarily need to know everything yet. I just need to know enough to get started and the more time I spend in the program the more comfortable I will feel with exploring and trying new things. And those new things will be easier to learn, because I’m not trying to learn and retain everything, all up front, all at once.
So with my half-baked plan, I’ve started to get my hands dirty in Godot and create the building blocks for the prototype.
My first major goal is to implement the core gameplay loop. To test this I need enough of the basic elements to be present and functional, but they do not need to look good. As an artist I obviously understand the power and importance of good art in elevating the player’s experience, but polished visuals can sometimes make it easier to overlook glaring issues in other areas, like the gameplay.
Even just to get the bare minimum, “basics of the basics” gameplay loop in place, I’ve identified at least the following items will be needed:
A level that has a floor, walls, and a start point and end point
A character the player can control
Obstacles within the level that harm the player
Health tracked in UI
Game over when health is depleted
A second level the player is taken to (when they reach the end point in the first level)
One of the initial, loose ideas I have is wanting the player’s movement locked to a grid - similar to how the movement works in Frogger, which is one of the influences for this project - and based on my current, limited Godot knowledge, I thought this might call for a GridMap. The GridMap allows me to place out tiles that are each assigned a set of coordinates (relative to the tile grid) along with their actual position in space (relative to the parent). These tiles give me positions I can snap to at regular intervals to create the kind of locked movement I was looking for.
The tile choices are populated through the use of a MeshLibrary - a collection of meshes which are each given a number, called an “index,” to identify them. This might look like the following:
0 - Tree
1 - Rock
2 - Bench
The hypothetical MeshLibrary above contains three different meshes, and each one is given an index to identify it. When building out a space using the GridMap, these meshes act as items in a palette you can select and place on the grid.
The indexes also came in handy for a couple reasons. First, you can search for all the items on your grid that have a certain index. So, using our example from before, if you wanted to find all the rocks in your scene you could simply ask the GridMap to search for all instances of index “1” being used, and in response it would return a list of coordinates for every space that uses the rock tile. For the MeshLibrary in Jackalope, one of my meshes is a "start tile,” which I can then use to dynamically place the player on at the start of the game by searching for where its index is being used on the grid and setting the player’s position to that tile’s position.
Second, the indexes let me identify where the grid is and isn’t. I wanted to prevent the player from moving the character off the grid (into walls, or simply into nothingness), so to do so I simply perform a check to see if the space they are trying to move to is on the grid. If the tile does not contain any of the meshes from the mesh library the check will return a value of “-1”, which lets me know it is not a space on the grid and therefore not a move I should allow.
With the MeshLibrary created, I can now use it in tandem with the GridMap to check the first item off my list (a level) while also putting me in a good position to begin working on the second item (a controllable character).
Player movement gave me a bit of trouble. From past tutorials I was pretty familiar with coding player movement that was more freeform - scenarios in which the player can move any direction they wish, for as long as they continue to press and hold the movement button. The movement I was hoping to achieve for this game was a bit different, though. I wanted to restrict the player movement to the four main cardinal directions (forward, backward, left, and right), and when they move I wanted them to move in grid space increments - in other words, even if they only press the movement button for a split second, it would still move them an entire grid space length to the adjacent grid tile (Problem “A”). I also want the player to stay aligned with the grid spaces, so at rest they are always centered on a tile (Problem “B”). This also means I would need to prevent the player from inputting other movement commands while the character is in the process of moving from one grid space to the next, which might misalign them from the grid, put them in a weird state, or otherwise look odd (Problem “C”).
Problems A & B
As I mentioned before, my (albeit limited) experience with coding player movement before usually went something along the lines of the following:
Create a “speed” variable (X) and set it equal to a numeric value.
Detect when the player is pressing a movement direction button.
For as long as they are doing so, move the character X units per frame in the specified direction.
But, as I mentioned before, this movement system is a bit different. I quickly identified that moving the character by a number of units per frame was not the way to go, and instead I might want to try a different system: Tweens.
With a tween, you specify a start point, an endpoint, and a duration of time, and the engine handles the movement for you. Having a specific end point for the movement was the main draw here, as it allowed me to define “movements” as units having a distinct distance to them (in my case, the length of a grid space). By setting my start point to the center of the current tile the player is standing on, and the end point as the center of the tile they are moving to, I was able to successfully limit the player’s movement to the cardinal directions as well as keep them aligned to the grid tiles. The variable for the duration of the tween then becomes my “walking speed” value, which I exposed in the editor and can continue to tweak until I find something that feels good.
Problem C
This is where things got tricky. After implementing the tweens, movement technically worked but it was very fragile: in the middle of moving from one tile to an adjacent tile you could input a different movement command, which would then abruptly take you to a different tile (allowing you to do “half-moves”); you could also input the same movement command twice in quick succession (“forward” followed by another “forward”), which would rapidly slow the character down.
To prevent these issues from occurring I needed a way to check if the player was still in the middle of a move and, if so, prevent any additional movement inputs. I first tried seeing if I could treat the tweens like animation clips and detect if one was currently “playing,” but if this is possible I wasn’t able to figure it out. When that didn’t work, I took a different approach: checking the character’s current position and comparing it to the position of their destination. When performing a move, the destination value is set to the position of the adjacent tile they are trying to move to. As long as the current position and destination position are different, the character is considered “moving.” When the current position and destination position match, the character is “not moving.” Each time the player attempts to move the character I check to see if they are already moving, and if they are I ignore any additional movement inputs until they reach their destination. Problem solved!
Next up was to start creating some obstacles for the player, the first of which I decided would be “bats”:
I put “bats” in quotes because, as you can see, at this point they are really the merest suggestion of a bat. As I mentioned earlier, though, the focus here is on functionality, not on visual polish.
I gave the bats a simple flying animation and coded some basic movement behavior, once again involving tweens. The bats are intended to be one of the more simple obstacles you face, so really all they do is move back and forth on set paths. I exposed a few variables for their flight paths - start point, end point, speed, and turning speed - which I can set per instance to vary the behavior between bats as well as accommodate different level layouts and flexible placements within those levels.
There isn’t any damage behavior coded in yet, but at least their movement behavior is functional and they give off the impression of "bats”:
Still so much to do, but I feel like I’m off to a good start!
Next up will be adding hit detection on the bats to allow them to damage the player, and tracking the player’s health in the UI.