Designing an isometric game with Godot Engine
Created On 01. Sep 2022
Updated: 2022-09-01 12:42:46.165899000 +0000
Created By: acidghost
In this post I will shed some light on the design process of the action based isometric game I've been working on with CriPon3, that we named Faint Frontiers. For convenience, you can follow along with the developer edition of the game. Here https://skydrifter.itch.io/faint-frontiers you can find and play the most up to date version! After this post was published, we released the stable version 0.1.0!
Let me give a brief tour with git, which is helpful in case anyone wants to contribute, but also shows how we've been pushing updates where our editions stay.
To have it working with git, you need to be a contributor and be authenticated from the CLI. For CLI access you need to have your public key pasted in the git settings. On Linux, you can easily generate a key pair with:
and then use the
*.pub one from
~/.ssh/ for your github account. This applies if you've never generated any key before and left the default name to be used. After this is set, you can go the the repo and run a
git clone. Choose the SSH link! This will clone the repo and now every time you want to stay updated with the latest changes just execute
git pull. If you want to be more efficient with git, I advise reading this article, which was also very helpful to me for understanding it better.
The Initial Concept
Before starting to work on assets of the game, first step is always to define the idea. At beginning we wanted to a simple action based game made in 2D where the players would defeat their enemies and get points. However, there are already a lot of such games, so why would we want to make something very similar again? One of the main more special aspects of the game is its isometric perspective, which is also referred as 2.5D. This expands the possibilities, whereas the game has the 3rd dimension and still look very nice with pixel art. Here we've got a more special element that expands the horizons of a simple 2D game and has even more room for unique adjustments. With this we've decided to go with an action-slash game in isometric view, where the player would defeat enemies for points. As the game progressed, we have been integrating and adding more ideas.
To be able to easily maintain and spend less time on getting lost in the own project, a good structure is important. However, it is often not easy to integrate a structure that will stick along the whole development process. This is because, it is often hard to predict at an earlier time how it can evolve. Well, the structure below was added after working over a year and we've came to conclusion that for the moment is the most practical option:
Let's explore next each of the folders within
So far we've been working on the game, the characters have been the initial focus and the most complex part. They are the ones that drive the whole action and highlight most the spirit of the game.
The protagonist of the game was the first asset we focused on during the design. It is also the most complex part that went through most changes. The designs have been changing every now and then to come up with what we've found appropriate.
To have the movement in isometric way, I used the
which shifts from the conventional up down movement by 45 degrees. This is achieved by having on X axis the cartesian X and Y vectors subtracted. Each type of action is mapped inside the
AnimationTree where with
animationTree.set function, the path towards each animation type is set.
Another interesting function is
impact_slowdown that slows down the time of the whole engine when the player receives damage.
- Venom Spider
We've had a few spiders before Venom Spider came in and one of them you can find in assets of the developer version, which will be covered below. One of the more special functions is its shoot function:
func shoot_player(point, delta): spider_web.global_position = get_node("CollisionShape2D").global_position spider_web.direction = global_position.direction_to(point) get_tree().get_root().add_child(spider_web) spider_web.show() $shoot.play()
Further we define in the
SpiderWeb.gd that after the web hits the player, the net has to entrap him, with a new
func _process(delta): position = position + speed * delta * direction func _on_Spider_Web_area_entered(area): var web_net = preload("res://assets/characters/spider/WebNet.tscn") var hit = preload("res://assets/characters/spider/SpiderWeb_Hit.tscn").instance() get_parent().add_child(hit) hit.global_position = get_node("CollisionShape2D").global_position hide() var web_netto = web_net.instance() web_netto.transform = transform web_netto.global_position = get_node("CollisionShape2D").global_position yield(get_tree().create_timer(0.2),"timeout") get_parent().add_child(web_netto)
- Spectre Ghost
This is the second villain of the game. While the spider traps the player from far and nears to inflict damage, Spectre Ghost uses only far ranged attacks and inflicts almost 3 times more damage. This is especially dangerous if the player is trapped in the web. Due to fact that the spectral beam is an object with top and bottom that doesn't leave him, the shooting function had to be adapted:
func shoot_player(point, delta): var spectral_beam = beam.instance() if spectral_beam != null: var player = playerDetectionZone.player var player_enemy = (global_position - player.global_position) spectral_beam.global_position = get_global_position() spectral_beam.global_rotation = player_enemy.angle() + PI print(spectral_beam.global_rotation) get_tree().get_root().add_child(spectral_beam) $shoot.play()
To have the beam working on 360 degree, the angle of player's location + PI was used. However, in game he is allowed to shot only in 4 directions on 90 degree angles, otherwise he would be too overpowered. Well don't forget, that he also gets invincible for few seconds if he's not feeling that well!
The environment consists of tiles and objects that the characters live around. For animated tiles such as water, Godot has a the functionality to have static tiles inside the TileMap that can use looped frames. With the isometric view, the tiles have to follow a specific convention to align with characters movement. This is where the 3D world is created with the tiles on the X Y Z axis. The engine has as well the functionality to use isometric tiles directly, which is very helpful when alignments on all 3 axes have to be made.
The UI includes the aesthetics and functionality of the interface driven components. These cover the Main Menu with all the contained settings, special effects in the game and UX quirks. One of the coolest part of our game that we've integrated with currently most recent stable version is the possibility to map the keyboard keys. Thanks to Cripon3 for adding it!
With quiet ambient at the beginning escalating to louder dynamics through the action, there is a multitude palette of sounds and effects in the game. The audio is randomized with every new start of the game here.
The sound was made with the help of few audio software and hardware synthesizers. Godot can also deal with the sounds quite well. It has its own equalizer and effects, while more adjustments can be made in the code itself. This is especially cool in case we want to have the volume changing, or having sound effects occurring in some specific pattern, when certain actions occur. This is not yet present in the game, but it is for sure on our list!
All the action happens inside the worlds which are created from separate scenes. The scene contains the player, tiles, UI components, but also adds more functionality through scripts. For example, the time is calculated inside the world itself, and not in some global variable (which to note, the worlds do no make any part of). An interesting function is where the enemies get spawned. Too many enemies or to frequent spawns impacts the player experience and we are keeping an eye with all the changes to have this option optimally set.
While Godot is a solid engine for game development, it is likely more common to encounter bugs caused by engine inconsistencies, than in Unity or Unreal Engine. One problematic thing I found, is that developing a project that went through more version updates can leave some mechanics broken, which I assume could be coming from some cached states. As well, when combining scenes that overlap in-between, could cause weird behavior. For example, the scripts bellow takes the spider and the spectre ghost scenes into an array and then it is randomized with the
var Enemy=[preload("res://assets/characters/spider/spider.tscn"), preload("res://assets/characters/spectre/SpectreGhost.tscn")] var arr = randi() % Enemy.size() ... for i in arr: var enemy = Enemy[arr].instance() get_tree().get_root().add_child(enemy) enemy.position = position return position
Spawning the enemies this way proved to be an issue for some scenes. For unknown reasons, every spectre ghost after the first one, had the animations inactive, so I've had the same static idle sprite doing every action. Godot can have more of such subliminal issues and creative workarounds are always beneficial. While the engine is one of the favorites in the scene, there are surely lots of things to be improved. Luckily, it has a very active and dedicated community, which can be trusted to be doing a great job with their updates.
One step at a time
Designing and developing the game through Godot engine proved to be an interesting experience. While there are drawbacks that neither we or the engine can recover for us, there are still a lot of possibilities how we can improve and change our game. In future we will be focusing on adding more action elements to the game including combo moves, enemies, worlds, sounds and at one point an interactive multiplayer mode as well. Thank you for following our progress and feel free to join our discord server to stay up to date with the all the news! We hope you enjoy the game and will like our additions coming next!