Free Movement
Today things start to get more exciting — instead of seeing just a static screen with no stimulating action, now we’re going to add movement! (I imagine you are all gasping in awe.)
So because I’m a visual guy and like to get something good looking that I can see out there quickly, I’m going to start this part by creating a new scene object in Godot and adding a cute little character sprite to it. I’m also going to instance the TileMap
scene we created last time within this new scene, which I’m calling MovementDemo
.

crop.py
script, “frames” will be generated from the spritesheet in the 0x72_DungeonTileset directory.Because I know that I want to control multiple things about a unit from a central place, I’m also going to create an empty Node2D
node and use it as the parent for the sprite I just dragged into the scene. I named the new parent node Character
for reference.

Now let’s write some code! Let’s create a new script called CharacterController
and assign it to the Character
node, then dive into the code editor.
My first order of business is to figure out how to move the character! (I’m still fairly new at this, remember?) Fortunately when you create a script through the Godot editor it will be generated with a template that helps newbies like me. The _process()
function seems like the thing I’m looking for, since it runs each frame. For now I’m just going to test out basic movement through the arrow keys on my keyboard.
func _process(delta: float):
var movement := Vector2.ZERO
if Input.is_key_pressed(KEY_RIGHT):
movement += Vector2.RIGHT
if Input.is_key_pressed(KEY_LEFT):
movement += Vector2.LEFT
if Input.is_key_pressed(KEY_UP):
movement += Vector2.UP
if Input.is_key_pressed(KEY_DOWN):
movement += Vector2.DOWN
# The reason to multiply movement by delta is so that we
# can get a frame-independent movement speed. This is common
# practice for probably 99.9% of all games.
position += movement * delta
Now if we go back to the editor and run the scene we’ll notice that our little fella is indeed moving when the arrow keys are pressed, but veeeeeery slowly. (Must be all that armor.) Now I could go back into the script and modify the final line of code there to this:
position += movement * delta * 100
but that would mean I would need to open up the script every time I want to tweak the character’s speed, which could potentially be quite often. And what if I want to have multiple characters use this same script for movement, but have different speeds? Nope, this calls for a different approach.
In Unity this could be achieved through declaring a public member variable (or a serialized private one, for all you secretive people out there), which would allow you to edit the variable in the editor and on a per-instance basis. Godot provides a similar functionality through their export
keyword. Let’s add an exported variable in the CharacterController
called character_speed
, and set the initial value to 100
.
export(float) var character_speed = 100
Now when we check in the editor we’ll see the variable show up in the inspector panel and we’re able to modify it from there!

Finally, we need to use it in the script for the value to actually mean anything. Let’s modify that final line of movement code again, but replace the magic number with our new variable.
position += movement * delta * character_speed
Now when we run the scene the character moves much more quickly! Hooray for progress! However, we didn’t set up a grid layout last time for nothing. Ideally we want the units only to move in orthogonal and 45° diagonal directions. That leads us to the next section, pathfinding.
Pathfinding
I started by researching the A* (pronounced A-star) algorithm for pathfinding. I remembered reading an article a while ago when I was going about writing my first pathfinding algorithm. It helped a lot for me to wrap my head around the concept initially, and I still find it’s a helpful reference to have when I inevitably have questions about it. As a matter of fact, that blog was one of the inspirations for creating this blog! But I digress…
While researching I discovered that Godot actually already has an A* implementation, which is awesome! Now, I’m a guy that likes to do things from scratch… but I’ve already implemented a pathfinding algorithm in one of my past projects, and I’m also a fan of not reinventing the wheel. So for this project we’re going to utilize the tools available!
In order to use Godot’s AStar
class I needed to figure out how it works. Eventually after watching a few videos and reading some code and documentation, I was able to boil it down to the basic steps you need to follow in order to use it correctly.
- Populate the
AStar
object with all of the points you want to be able to traverse. - Tell the
AStar
object, for each point registered in the previous step, which other points the current point is connected to.- This also brings a fun use case where you could theoretically have two points that are visually pretty far apart. If you tell
AStar
that they’re adjacent, then you have effectively created a grid-wormhole! It’s an idea that sounds pretty fun to me, so I might try to see how to work it into the game later down the line.
- This also brings a fun use case where you could theoretically have two points that are visually pretty far apart. If you tell
- Ask the
AStar
object for a path from point A to point B!
When you break it down to those three steps it becomes much easier to work with.
So with this knowledge I was able to write a Pathfinder
script that takes a TileMap
object and can give you an optimal path from one tile to another!

Contained Movement
We now have a basic movement script and a working A* pathfinding algorithm, all that’s left is to put them together! Let’s go back into the CharacterController
class and get a reference to the Pathfinder
script that’s sharing a scene with our cute lil’ guy.
var pathfinder: Pathfinder
func _ready():
# The way to get a sibling node is to first call
# get_parent(), then get_node() with the desired node path
pathfinder = get_parent().get_node("TileMap/Pathfinder")
Now we can create a function that will use the pathfinder.find_path()
method and send our character a-runnin’!
func move_to(loc: Vector2):
var path = pathfinder.find_path(position, loc)
Let’s also modify the original GridLayout
class to direct the character whenever a tile is clicked.
var character
func _ready():
character = get_parent().get_node("Character")
func _input(event):
if event.is_class("InputEventMouseButton") and (event as InputEventMouseButton).is_pressed():
_on_grid_click(event)
func _on_grid_click(event: InputEventMouseButton):
if not character:
return
character.move_to(event.position)
Now everything is set up for when we press “play” on our scene! …Everything except the actual following of the path. This presents our next challenge because we don’t want our character to move from their starting position directly to the final position on the path — that completely defeats the purpose of having the path there in the first place! But how can we move the character from each point to the next point, sequentially, until arriving at the target? Enter the next cool thing about Godot:
The “yield” keyword
If you’re familiar with Unity you’ve probably heard about coroutines. It’s the Unity way to execute a function in parallel with the main game loop; essentially a non-blocking function. In Godot, every function can be a coroutine without doing anything more than adding the yield
keyword inside your function! Essentially, you can pause the function execution until a certain condition is met, without blocking the main game loop thread. In our case this is a perfect solution to the problem of moving to certain points sequentially. We simply need to set a target point, yield
execution until we reach that target point, then check if there are any more points left in the path. If there are, we set the next point in the path as our target point and yield
execution until that point is reached. If there are no more points in the path, then we have reached our goal!
In practice, this is how our move_to()
function is going to look:
var current_destination
signal _arrived_at_path_point
func move_to(loc: Vector2):
var path = pathfinder.find_path(position, loc)
_follow_path(path)
func _follow_path(path: PoolVector2Array):
while not path.empty():
current_destination = path[0]
# We want to remove the first item in the path so that
# when the path is empty we can be sure we're at the
# final destination
path.remove(0)
yield(self, "_arrived_at_path_point")
current_destination = null
The way we’re using yield
here is we’re waiting until we get the signal _arrived_at_path_point emitted from ourself (self
). Signals are a beast we can cover another day, but in short they’re events anybody can subscribe to and listen for.
The final code we need to modify in our CharacterController
script is the _process()
function. Currently it holds the logic listening for keyboard input. We’re going to change that so that the character only moves when it has a current_destination
to walk to.
func _process(delta: float):
if current_destination:
position = position.move_toward(current_destination, delta * character_speed)
# The Vector2.is_equal_approx() method is desirable in this situation
# because it's a bad idea to compare floating point values due to the
# nature of how computers store those values. This just checks if they're
# approximately equal, and if so returns true.
if position.is_equal_approx(current_destination):
# This triggers the signal we were waiting for in the yield statement
emit_signal("_arrived_at_path_point")
Pulling it All Together
Whew! That was a lot! But we did it, and if we play the scene now we can see that it’s working!

Originally I had also planned on adding different tile types like water tiles and obstacles, but this post has already gone on for quite a while so I think that will have to wait for a follow-up post.
If you really want to see how the pathfinding works when it’s not just a square grid you can remove some tiles from the TileMap
. When calculating the path, the Pathfinder
script does not take into account missing tile spaces.

All of the code up until this point in the project can be found here. Until next time!
Leave a Reply