Crash, Replace, Repeat — Porting Resolutiion to Godot 3
Resolutiion is an indie action-adventure game in the making, with a modern pixel style and plenty of fat, yellow worms. My brother and I have been working on this passion project for the better part over the last three years.
Initially “The Game” needed an engine for the heavy lifting. We are both big open source enthusiasts and I really wanted to do all the development on my Linux machine. Unity and Unreal both support development and exporting projects to Linux platforms and allow access to parts of their source code. Still, they don’t feel truly open-source in the sense of community engagement and direction.
Searching for alternatives I stumbled upon Godot. The young game engine looked very promising, following a holistic open source approach and all the features I needed for our little 2D game. It felt similar to Blender and I didn’t expect to outgrow its capabilities in the future, being my first game development project — ever.
Port Royal
July 9th, 2015.
I committed the first lines of code to our repository, based on Godot 1.0.
Since the API never changed dramatically during the following months, I jumped from version to version like nothing could stop me, taking “The Game” along.
When Godot 3.0 was announced in 2017, mayor API breaks appeared on the horizon. By then we had called the project “Resolutiion” and it had grown to a beast, collecting over 1,700 files and resources. Porting all our scripts and scenes to a new system would require tons of work. But the new features like audio effects, sprite tessellation and plenty of editor improvements were just too tempting to pass up.
I waited until early August when Godot 2.1.5 had reached the “stable” mark. It included an exporter to help transfer any project from the 2.x to the 3.x version. The rest of the team was on vacation, so no one could distract me.
This is how porting our game to Godot 3.1 went down.
Step 1 — The Magic Button
I created a new Git-branch, called it “PortTo3.0”, and fired up Godot 2.1.5. This version included a promising button, labeled “Export to Godot 3.0 (WIP)”.
Click!
A nice loading screen appeared and started to count percentages up. Be patient I told myself, this was going to take a while... Eventually the counter hit 99%, and then disaster:
Couldn't load resource: res://scenes/tileset.xscn::40
Method/Function failed, returning: ERR_BUG
Ok, that’s easy. Godot used to support three internal file-types: scn (binary), xscn (xml) and tscn (readable text). The xscn-format is now deprecated, in favor of scn and tscn. No big deal. I changed the corresponding files and restarted the routine. This time no errors occurred and the 100% export was completed safe and sound.
Importing the converted project into Godot 3.0.6 resulted in another progress bar. Plenty of warnings were thrown on the console, but I ignored them for the time being. Since the whole game is made up of individual scenes and sub-scenes, I had to take a look at the important ones anyway. Let’s see what works and what breaks.
Lessons learned
- Don’t forget to branch (Git)
- Convert xscn files to tscn format
Step 2 — Global Scene
Like most games, we run a global scene. Ours is called global.scn. It acts as a singleton and handles all the boring stuff like saving and loading, switching levels, input controls, the audio system and a couple of globally used shaders. It's the foundation for every other scene that runs on top of it.
Opening global.scn, I noticed that all our shaders were missing. The shaders had to be rewritten.
Here’s a simple example:
Old
uniform float size_pixel=0.008;
uv-=mod(uv,vec2(size_pixel,size_pixel));
COLOR.rgb = texscreen(uv);
New
shader_type canvas_item;
uniform float size_pixel=0.008;
void fragment() {
vec2 uv = SCREEN_UV;
uv-=mod(uv,vec2(size_pixel,size_pixel));
COLOR.rgb= textureLoad(SCREEN_TEXTURE,uv,0.0).rgb;
}
Resolutiion’s pixel aesthetic does not require many shaders, so the workload was manageable, but this might have been a different story for any other game.
Let’s start the scene.
Crash!
The debugger tells me Identifier not found: Globals.
That right. The global variables Globals
are now called ProjectSettings
.
We use the Globals system everywhere in the game. It would be a pain to change all respective scripts by hand, but there are great tools available to help with bulk processing. I use Atom due to its powerful and extensively used “find-all-occurrences-of-a-string-in-the-whole-project-folder-and-rename-them-accordingly” feature. Nothing to do with Atom being open source software ;-)
Let’s start the scene again.
Crash!
get_data_dir
for loading save-files is now called get_user_data_dir
.
A quick fix.
Restart.
Crash!
Something was wrong with the audio system.
Finally, we’re onto the fun stuff.
Since Godot’s audio system had been completely reworked, we needed to do the same to our internal pipeline. I set up some busses for music and effects streams, and adapted the configuration of those according to the new bus API. The volume scale in Godot 2.x was defined from 0 to 1. Godot 3’s new bus system makes use of the decibel scale instead, ranging from -80 to 0.
Ok, restart scene.
Crash!
Dammit, it’s the input-event-system-types.
All right, that one was also redesigned and event-types don’t exist anymore. Instead, input-events are now resources and checking them looks something like:
func _input(ev):
if ev is InputEventMouseButton or ev is InputEventKey: #mouse/keyboard
cur_input_type = 0
elif ev is InputEventJoypadButton or ev is InputEventJoypadMotion: #joypad
cur_input_type = 1
Restart scene.
Wait …
By Marty’s Boot: it’s working!
Committing all those audio related changes to the global scene, I realized that my trusty, sound-handling buddy SamplePlayer2D was no more. Instead there was a new AudioStream2D node, connecting to the audio busses. This fella can hold no more than a single sample, instead of a variable array, like my old friend.
What a wuss.
Also, looping and loop times are now decoupled from the audio player and set directly on the audio resource (wav or ogg). So we have to do some more coding.
Play music in a loop:
func music_play(newsong, offset):
#load audio players, set loop & offset
#newsong is an audio file like ogg or wav
if newsong != null:
newsong.loop = true
newsong.loop_offset = offset
get_node("MusicPlayer").stream = newsong
At this point it became obvious to me that Godot 3’s audio system was not mature enough for our project. Most of the player and NPC characters trigger their many audio effects by performing their respective animations. Due to the new AudioStreamPlayer’s limitations, juggling the streams at the time as the animations would be very cumbersome.
All was not lost. After some frantic Googling, I discovered that Godot 3.1 will have an improved audio workflow. At that point, I realised I had to leave safe developer space and dive into the unstable and deep waters of Godot 3.1-some-weird-development-build-from-a-shady-website. I downloaded the latest nightly build and went on with my business, slightly more anxious.
Lessons learned
- Audio system has to be set up from scratch
AudioServer.get_stream_global_volume_scale()
becomes AudioServer.get_bus_volume_db(0)- Shaders need to be rewritten
Global
becomesProjectSettings
OS.get_data_dir()
becomesOS.get_user_data_dir()
- Input Event types become Input Event resources
Step 3 — Start Menu
Enter Resolutiion. An atmospheric tune fades in. The logo appears. Buttons emerge and the background graphics slide up — parallaxing (naturally). Some particles float...
Wait, there are no particles!
Probably because the particle system was also completely refactored in Godot 3.x.
Does that mean I have to dive into all scenes where particles are used, and set them up properly again?
Yes. Yes it does.
And there are plenty.
Another problem: buttons and other UI elements no longer reacted to my clicks. This was down to some control nodes in the top layer that stopped the mouse event. The solution was to set all control nodes that could intercept the mouse click events to mouse → ignore.
Some asset positions in the timelines are messed up, too. So is the UI theme in the settings menu.
More renaming work.
Fixing asset positions.
Starting the scene.
Success!
Lessons learned
- All particle systems have to be refactored
- Set control nodes to mouse → ignore
- Animation player signal
finished
becomesanimation_finished
- Tween signal
tween_complete
becomestween_completed
Step 4 — Characters
With all the main control scenes running again, continuing the port got more colorful: levels, our player character Valor, NPCs and enemies. The latter didn’t get very far: trying to start the first level caused the NPC class to crash.
Let’s fix those buggers first, shall we?
collision_layer
and collision_mask
are key words now. Variables with the same name have to be renamed.
Also get_layer_mask
becomes get_collision_layer
, which makes it more consistent with the old and new get_collision_mask
.
And for helper systems within the editor, we have to change get_tree().is_editor_hint()
to Engine.editor_hint == true
.
Again, Atom did that work for me in bulk.
Another major change for Godot 3.x is movement and collision handling of kinematic characters. There are two new methods for moving characters: move_and_slide
or move_and_collide
, depending on what you want to accomplish. The first will do for most applications and clearly reduces the amount of code needed:
Old
func _fixed_process(delta):
if state in move_states:
move( vec.normalized() * speed * delta )
if (is_colliding()):
var n = get_collision_normal()
move( n.slide(vec).normalized() * speed * delta )
New
move_and_slide( vec.normalized() * speed )
Don’t forget to remove the delta
when calculating the motion, or your character will just crawl very slowly across the screen.
Developer-humor:
In Godot 2.1 line 19 said trailpath.remove(0)
, where trailpath
is an array. Somehow the exporter thought re-move
should do some collisions and renamed that line to trailpath.remove_and_collide()
.
The auto-exporter clearly showed some character here.
With the enemies fixed, I moved along to our big, bad, black and deadly player character.
Valor is a toolbox of his own, spawning guns, bombs, and various animals to wreak ruin to the world of Resolutiion. When these spawned objects “die”, I usually removed their collision with the clear_shapes()
-function.
Well, this function doesn’t exist anymore in Godot 3.x, instead we have the very cool disable-variable for all shapes. It lets us toggle the collision of a single shape at runtime.
$CollisionShape2D.disabled = true
With enemies and our player character back in place, they may resume beating the crap out of each other.
Lessons learned
- Character movement needs to be refactored to use the new
move_and_slide
andmove_and_collide
functions - Variable names that are keywords now need to be changed, for example
collision_layer
get_layer_mask()
becomesget_collision_layer()
get_tree().is_editor_hint()
becomesEngine.editor_hint == true
clear_shapes()
becomes$CollisionShape2D.disabled = true
- Rotations are now calculated clockwise instead of counterclockwise
Step 5 — Levels
In Resolutiion every level is its own scene and embeds some logic for the save and audio systems, as well as the scripts for all cutscenes. There’s a new helper to yield a timer timeout, reducing six lines of code to one:
Old
var t = Timer.new()
t.set_wait_time(3)
t.set_one_shot(true)
add_child(t)
t.start()
yield(t, "timeout")
New
yield(get_tree().create_timer(3), "timeout")
After hitting Atom’s “Replace all” button for what felt like days, I got a very unsettling message when opening one of the levels:
Alert! Error while parsing 'Stadium02.tscn'.
Another one gave me:
Alert! Unexpected end of file 'Grass.scn'.
Probably a bug in the exporter?
Usually the terminal gives a hint on what went wrong and in which line the problem occurred. In this case it was mostly audio related stuff: SampleLibrary’s sub-resources were used, which Godot 3.x has forgotten all about.
Parse Error: Can't create sub-resource of type: SampleLibrary.
Here, the problem with easily editable scene files became obvious. Occasionally I had to remove a whole sub-resource block and its corresponding links — the sub-resource’s ID comes in handy. Saving the tscn-file solves the error.
Then, files that were saved as scn can not be opened with a text editor. So I had to fire up Godot 2.1, open the particular scene, save it as a tscn file, move it to the new project folder, and reopen it in Godot 3.1.
Tedious.
Lessons learned
- Open all scenes and check if they were converted properly. If not, save them as tscn files in Godot 2.x and fix them manually.
Final thoughts
As I write these lines, Resolutiion might easily be twelve months away from being released. But over the last few weeks, we got much, much closer. Taking our not-so-little project from Godot 2.1.5 to 3.1 gave us plenty of exciting new options and set us up nicely for more to come.
It took me about twenty hours to refactor all the changes in logic and scripts, and twenty more to grind through the boring tasks. The former was fine, because I used the opportunity to fix some bugs that had been around for a while. The latter one, though, was just repetitive and annoying: opening every single scene and object, updating each animation in respect to the new audio system and then rebuilding all particle systems we had distributed in our heavily connected world.
An agony.
But it was worth it.
Investing a week of pain to gain access to amazing features like the new audio pipeline and others can easily be justified. Being able to quickly toggle collision shapes, yielding a Timer, and the improved helper systems $
and get_node
will be used extensively in my future development.
Godot 3 is a wonderful game engine. Using and seeing it mature month by month is certainly a great inspiration. It helps us to stay motivated as we progress with Resolutiion.
Richi and Günther Beyer from Germany are developing their first video game. Inspired by early Zelda and fueled by the do-it-yourself spirit of the open-source movement, Resolutiion will ship in 2019, with plenty of pixels, cats and the mystery of the Cradles. Subscribe to our newsletter or tag along at Twitter.
Make real ether while destroying your opponents.
Chibi Fighters is a brutally fun and addicting game.
Come check out Chibi Fighters
https://chibifighters.io/
That is something that will be developing quickly creating of programs for computer games. That is a great market and have more and more users, especially considering that young generation know computer games already from their almost first steps. Everyone who play games have his own thought what is good and what needs to be better and that is the best stimulus to create it better and having such big market it brings a competition and again that is engine to improve yourselves.
It is nice to see how much time and efforts you put in your work and I like the way you mentioned what you have learnt from it. This is sometimes we learn on our mistakes and improve ourselves. I wish you a lot of success!
Hey thanks so much for that, I'll make a port for my game, Moon Cheeser as well since 3.1 supports GLES2 again. This will for sure help a lot
Hi cloudif,
Visit curiesteem.com or join the Curie Discord community to learn more.
Thank you for sharing you game development post with us! You have received an upvote! Please visit our page @steemgg to learn more about how you can use our platform Steemgg, the first html5 gaming platform built on the Steem Blockchain to get more out of game development.
Congratulations @cloudif! You have completed the following achievement on the Steem blockchain and have been rewarded with new badge(s) :
Award for the total payout received
Click on the badge to view your Board of Honor.
If you no longer want to receive notifications, reply to this comment with the word
STOP
Do not miss the last post from @steemitboard:
Congratulations @cloudif! You received a personal award!
You can view your badges on your Steem Board and compare to others on the Steem Ranking
Do not miss the last post from @steemitboard:
Vote for @Steemitboard as a witness to get one more award and increased upvotes!