Porting from LibGDX to Godot
Early last year I ported this project from LibGDX to Godot and I wanted to write a little blog post to outline the technical challenges it involved, so I can reference this whenever my experience of transitioning from doing mostly Java+LibGDX to C#+Godot comes up. Better late than never I guess.
Java to C#
The old project itself is like only a handfull of Java classes, so I just started by re-writing them all in C#. Which means the first challenge was dealing with a few language constructs used in Java that have no corresponding C# counterpart.
Labeled breaks
Here an example from the Java code. What it does exactly is not important, what's important is that there are 2 (or more) nested loops.
outer: while(tetronimo.getY() - ydiff > -10) { for (Position position : bottomPositions) { Tile tile = map.getTile(tetronimo.getX() + position.x, tetronimo.getY() + position.y - ydiff); if (tile != null || tetronimo.getY() + position.y - ydiff < 0) { ydiff--; break outer; } } ydiff++; }
Java provides a neat syntax called labeled breaks, which allows breaking out from the outer loop and not just the innermost loop. No such thing in C#. Quick googling will reveal this is by design, and not an oversight. There has been lots of discussion on this topic already, and I will not take part in it. I will only say I like my labeled breaks, because they make my code easy to understand to my brain.
There are two ways to work around this limitation of C#. Option A: one extracts the loops into their own function and instead of doing a labeled break with e.g. "break outer;" one can simply return from the function to exit all loops at once. Or Option B: introduce a new variable that one needs to check for in the outer loop to see if we need to break out completely or not. I chose Option B because the changes are kept very local and it needs little refactoring. See the resulting C# code from the above example:
var breakOuter = false; while (tetronimo.y - ydiff > -10) { foreach (var position in bottomPositions) { var tile = map.getTile(tetronimo.x + position.x, tetronimo.y + position.y - ydiff); if (!tile.isNull() || tetronimo.y + position.y - ydiff < 0) { ydiff--; breakOuter = true; break; } } if (breakOuter) { break; } ydiff++; }
There's also other things like anonymous classes that have no direct C# counterpart, but those are easier to work around. And I just saw this project didn't actually use them so that was some other project where I had issues with that, so why am I even talking about this. Moving on.
Benefits of C#
Of course there are benefits that come from switching to C# too. I made liberal use of properties, delegates and structs when porting the code, and am happy about those things. The benefits aren't really that impactful for a codebase this small, but I will take what I can get.
LibGDX to Godot
Now to the more interesting stuff, the issues specifically from porting from LibGDX to Godot. I chose Godot 3.x because I wanted C# support with web export support and that just wasn't there yet for 4.x.
Things like input handling were pretty straightforward, I merely replaced the LibGDX input handling with the Godot input handling. Just needed to define some actions and then the API calls are pretty much the same just with slightly different names. Thankfully this game was light on UI, only a credits scroll in the beginning, which I recreated with a single big label in the editor. I later added a pause menu with more options to the game and had some issues dealing with Godot's UI framework implementing that, but that is nothing specific to porting the game, as that UI was brand-new.
Now the biggest challenge was dealing with the rendering. It's a big shift to go from what I would call "Immediate Mode" rendering, to Godot's way of needing nodes for EVERYTHING.
(Very small background info on my code structure: to support the infinite sizes of the game, the tilemap is separated into chunks of like 16x16 tiles. And only chunks that aren't empty have to be considered for the game logic and rendering.)
Now in LibGDX I could just do something like:
private void renderChunk(SpriteBatch batch, Chunk chunk, int tileSize){ for (int i = 0; i < chunk.getSize(); i++) { for (int j = 0; j < chunk.getSize(); j++) { Tile tile = chunk.getTileRelative(i,j); if(tile != null) { TextureRegion texture = InfinitrisGame.getTextureRegion(tile.getTexture()); batch.draw(texture, chunk.getXoffset() * tileSize + i * tileSize, chunk.getYoffset() * tileSize + j * tileSize, tileSize, tileSize); } } } }
Which would get called every frame for every chunk, and just render it to the screen immediately. Any change to the data is immediately reflected in the visuals. Delightfully simple!
On the other hand, there is no way to do this in Godot. With Godot one has to create a TileMap node and then update it whenever something changes. Luckily for this project, the places in the code where something in the tilemap changes are thankfully very few, only one in fact. When the individual moving and falling tetronimo comes to a halt and is permanently added to the landscape. So it was just a matter of updating the tilemap there and be done with it.
For the falling tetronimo and the ghost tile preview I similarly created nodes for rendering them and added code to update them in the appropriate locations (Which were considerably more places in the code, and I didn't manage to get them all on my first try, as you will see below).
So far so good, that should be all. Let's see the fruits of our labor:

uh oh ... something doesn't seem to be right ...
Okay, ignoring some of the bugs in here like the active tetronimo not showing the correct shape, and the colors for the pieces not being correctly applied. There is one glaring issue here: it's all upside down!
Now this is because in LibGDX the coordinate system has the y-axis positive direction UP, and Godot has y-axis positive direction DOWN. Surely there is some setting in the TileMap that allows me to easily flip the y-axis and all will be neat and dandy, right? Wrong!
So essentially there are like 3 or 4 distinct approaches to dealing with this, each option with its own drawbacks:
A. Flip the camera! This will make the y-axis point up, howeverit will also flip all the graphics! All of the tiles would need to be flipped, either by directly flipping the assets themselves, or doing a post-processing step to flip them before rendering. One would probably set up a viewport or something to only flip the game area, without also flipping all the UI and other elements.
B. Similar to option A, invert the tilemap, by scaling it along the y-axis by -1. And then set all tiles with the flip modifier.
C. Flip the tile coordinates when updating the tilemap, so a tile that would be at position (0,3) in the data would be at (0,-3) in the tilemap instead.
D. Re-write all the logic to assume Y-axis is DOWN instead of UP.
In the end I went with option C, because that seemed like the least effort. I certainly didn't want to refactor all the code, potentially introducing bugs at every corner. And I also didn't want to mess with the assets or asset loading. In hindsight, option B seems to be even simpler and I don't know whether I didn't think of this back then, or whether I encountered some issue I can't think of right now.
For option C, I needed to adapt any code that dealt with the visuals and keep in mind that it had a flipped coordinate system. Those places were mainly the camera code and the function that updated the TileMap. To make it easier for me to reason about it in the future, I marked all places in the code I had to adapt with the comment "// remember #yAxisDown".
Music
I also did research into some legal matters given the contested subject matter and learned a bit about copyright and trademarks in the process. And I came to the conclusion, despite the music being a self-made distorted rendition of the Korobeiniki theme, which was not an asset taken from any other game directly, big T still owns the trademark for this melody when used in any video game. So in terms of copyright I was good, but in terms of trademarks I was on shaky ground. So my solution was to reverse the music track and call it a day. It sadly is no longer recognizable as the iconic music, which hurts the atmosphere of the game, but on the flipside my game is no longer a threat to the integrity of the Tetris brand. Thank god.
That's all folks
All in all it was a pretty smooth porting process as one would expect for such a rather simple game. Obviously I'm not saying the way I did it was the best way, it's just what I ended up doing. I still am unhappy about having to create and babysit godot nodes for everything one wants to render. I really am a fan of the "immediate mode" rendering (this is probably not the correct term, but whatever) and kinda want it back. I have the tendency to write all my logic separated from the rendering and this just makes it very tedious to handle that.
That was pretty much all I wanted to talk about. Writing this article itself over one year after doing the porting certainly makes it harder to remember the details, but makes it easier to only write about the more important things. Because you can't get lost in details when you don't remember them. Just happy to have checked this off my list and hope this will be useful in some way to someone.
Anyway, game is playable in browser and also Android, and maybe, just maybe it will be on iOS too eventually. We'll see, no promises on this one.
Get Infinitris
Infinitris
Stack blocks forever in an infinitely large world, for whatever reason.
Status | Released |
Author | Accidently Awesome |
Genre | Simulation |
Tags | artgame, Endless, infinity, Tetris |
More posts
- Infinitris is now available on AndroidJan 30, 2024
Leave a comment
Log in with itch.io to leave a comment.