Home Posts Porting a Game from Unity to Godot as a Solo Dev: An...

Porting a Game from Unity to Godot as a Solo Dev: An Honest Retrospective

10
0

I shipped my first commercial game, a small puzzle platformer called Latch, in early 2024 after building it in Unity over two years. It sold modestly, earned decent reviews, and I assumed I would keep updating it in Unity forever. Then Unity changed its pricing model in a way that made me physically angry. Like many solo developers, I started looking for alternatives, and Godot kept coming up. It was free, open source, and had just released version 4 with a shiny new renderer. I decided to port Latch entirely from Unity to Godot, alone, while keeping the game live for existing players. This is an honest account of what that process actually felt like, how long it took, what broke, and whether it was worth it.

Why I Left Unity and Why Godot

I did not leave Unity because of a single policy. I left because the trust was gone. When a platform holder can retroactively change the deal, my business felt fragile. I wanted my tools to be boring and predictable, and Godot promised exactly that. No runtime fees, no account requirements, no tracking. The engine is a single executable you download and run. That simplicity was the entire pitch.

I chose Godot 4.2, the latest stable release at the time. The engine uses its own scripting language called GDScript, which looks like Python but is purpose-built for the engine. It also supports C-sharp, but I decided to go all-in on GDScript for the port to truly learn the engine, even though I had years of C-sharp muscle memory from Unity. That decision turned out to be both freeing and punishing.

Week One: The Translation Shock

I opened my Unity project folder, which contained 143 C-sharp scripts, dozens of prefabs, and a complicated scene hierarchy, and felt the first wave of dread. There is no automated converter that takes a Unity project and makes it work in Godot. Every script, every scene, every component had to be manually recreated. I started by drawing a map on paper: all the core systems listed out with their Unity equivalents and what they might become in Godot. This took a full day and prevented a lot of aimless flailing later.

The first real task was the player movement controller. In Unity, I used a Rigidbody2D with forces applied in FixedUpdate. In Godot, the closest equivalent is a CharacterBody2D with a custom script that uses move_and_slide(). The syntax was different but the concepts mapped cleanly. I had basic movement and jumping working in about four hours. The relief was so strong I almost cried. Then I tried to port the ledge grab system and the pain began.

Latch has a core mechanic where the character can grab onto ledges and pull themselves up. This relied on Unity’s raycasting, collision layers, and a state machine that I had built over months. Recreating the exact same behavior in Godot meant learning the engine’s raycasting API, understanding its collision layers (which are bitmask-based and slightly different from Unity’s layer system), and rewriting a state machine from scratch. The ledge grab alone took three full evenings. It worked eventually, but I had to abandon my Unity logic and rethink the problem in Godot’s terms. Trying to replicate Unity’s patterns one-for-one led to ugly code. When I finally leaned into Godot’s scene tree and signal system, the code got cleaner and the bugs reduced.

The Scene System: A Better Way to Structure Everything

In Unity, I organized the game with prefabs and a single large scene. In Godot, the engine encourages composing scenes out of smaller, reusable pieces. A player is a scene. An enemy is a scene. A level is a scene composed of those scenes. This was initially confusing because I was used to Unity’s hierarchy, but once it clicked, I realized this was a genuine improvement. I broke the game into dozens of small scenes: the player, each enemy type, collectibles, UI elements, and each level. Managing them was easier, and changes to one scene propagated everywhere it was used. This modularity alone saved me hours of duplicated work later in the port.

There was a catch. Godot’s scene instancing creates a deep nesting tree that can make debugging node paths painful. I spent a whole evening chasing a bug where a signal from a UI button was not reaching the main game controller because the node path was wrong after I rearranged a scene. The solution was to use autoload singletons for global systems and avoid hardcoded paths for scene-specific references. This is a best practice in Godot that I learned the hard way.

The Tilemap and Level Porting Nightmare

Latch uses tile-based levels, built in Unity’s Tilemap system. Godot also has a Tilemap node, but the two are incompatible. I could not export my levels as JSON and reimport them. I had to rebuild every level by hand, tile by tile, inside Godot’s editor. The game had 28 levels. Each one took about 45 minutes to recreate if I focused. That is roughly 21 hours of mind-numbing work, spread over multiple weeks. I listened to entire audiobooks during this phase. I never want to look at a tile palette again.

There was a silver lining. Rebuilding the levels forced me to re-examine every design choice. Some levels had been in the game since the early prototype days and were, frankly, not good. I tweaked layouts, removed unfair enemy placements, and added small secrets that made the levels better than the originals. The port became an opportunity to remaster the game, and player reception later confirmed that the new levels felt tighter and more intentional. Still, if a tool existed to convert Unity tilemaps to Godot tilemaps, I would have paid real money for it.

The Audio and Shader Rebuild

Audio in Unity used the built-in AudioSource component and an AudioMixer for volume control. Godot uses AudioStreamPlayer nodes and an AudioBus system that is conceptually similar but different in implementation. Importing the sound effects and music was straightforward: Godot supports WAV, OGG, and MP3 out of the box. Setting up the bus structure for master, music, and SFX took about an hour. The integration with code was similar, just different method names. I replaced AudioSource.PlayOneShot() with creating an AudioStreamPlayer and calling play(). It was verbose but functional.

Shaders were the hardest technical challenge. Latch used a handful of custom shaders written in Unity’s ShaderLab and HLSL: a water ripple effect, a dynamic lighting overlay for the character, and a simple palette swap for enemies. Godot uses its own shading language, which is based on a variant of GLSL. The syntax was completely different. I am not a shader expert; I had written the originals following tutorials. Recreating them required learning Godot’s shading language from scratch. The water shader took three days of trial and error before it looked close to the original. The palette swap shader was simpler but still took an evening. The dynamic lighting overlay I ended up dropping entirely because Godot’s built-in CanvasItemMaterial handled it better with less complexity. That was a happy accident that cleaned up the code.

Performance Surprises and Build Sizes

I expected Godot to be slower than Unity, based on internet chatter. I was wrong. Latch ran at a locked 60 FPS on my target hardware (a mid-range laptop from 2021) with CPU usage under 15 percent. The Unity build had similar frame rates but occasional GC spikes from C-sharp garbage collection that I had spent months taming. GDScript, being reference-counted, did not have those spikes. The game felt smoother, and the load times were noticeably faster because scene loading in Godot is aggressively optimized.

The build size was a pleasant shock. The Unity version of Latch was around 240MB for a Windows build, thanks to the engine’s runtime and included libraries. The Godot build was 82MB. It was smaller, faster to download, and easier to distribute on platforms with size limits. The export process was simple: select the platform, tweak a few settings, and click export. No license management, no build cloud. It felt like building a local project, not an enterprise product.

The Things That Almost Made Me Quit

There were moments when the port felt like a mistake. GDScript’s lack of strong typing (until optional typing was added) meant that simple typos in variable names would not be caught until runtime. I spent two hours hunting a bug where a variable playerHealth was accidentally typed as playerHeath in one place. The editor’s autocomplete helped, but it was not as robust as Visual Studio’s C-sharp IntelliSense. I started using the optional type hints everywhere after that, which caught many errors at parse time, but the language still felt looser than I liked.

The documentation, while enthusiastic and community-driven, had gaps. Several features I needed, like advanced Tilemap collision customization, were documented only in YouTube tutorials or Discord threads. The official docs are improving rapidly, but I found myself scouring the Godot subreddit and various Discord servers more often than I wanted. The community itself is generous and helpful, but relying on scattered information added time and frustration.

I also missed the asset store. Godot has an asset library, but it is smaller and less curated. I needed a set of sound effects and some UI icons that I had bought on the Unity Asset Store. I could not transfer those licenses to Godot. I ended up finding open source alternatives and recreating some assets myself. It was a reminder that an engine is not just a renderer; it is an ecosystem, and Godot’s ecosystem is still growing.

What I Gained Beyond the Code

The port took me four months of evenings and weekends. The game shipped again, this time on Godot, and the existing players received the update as a free patch. The reviews were positive, and several players commented that the game felt smoother, though they could not articulate why. I knew why. It was the garbage collection, the smaller memory footprint, and the cleaner level designs.

I also gained a deep understanding of my own game. Porting forces you to examine every mechanic, every line of code, every design decision. You cannot copy-paste. You have to understand it well enough to rebuild it. That process revealed dead code, unnecessary complexity, and a few hidden bugs that had been in the Unity version for years. The Godot version is a cleaner, leaner game, and I am prouder of it.

Financially, the port did not change sales dramatically. The game continued to sell at the same modest pace. But my costs dropped. No more Unity subscription fees. No more worrying about runtime fees. The engine is free, and my royalty obligations are zero. For a solo developer living on thin margins, that peace of mind is worth more than any sales spike.

What I’d Do Differently

If I faced another Unity-to-Godot port, I would change my approach in three significant ways.

I would start with the hardest system first, not the easiest. I began with the player movement because it felt achievable, and it was. But the ledge grab and shaders, which came later, nearly broke my timeline. Starting with the most complex system would have given me a realistic understanding of the port’s difficulty early, and I could have adjusted my expectations or sought help sooner. Instead, the hard parts hit me after I was already emotionally committed.

I would port only the core and cut the rest. I tried to bring every feature from the Unity version into Godot, including a couple of minor visual effects that nobody noticed. Those effects cost me days of shader work. If I were doing it again, I would strip the game to its essential mechanics, port those perfectly, and only add the extras if there was time and demand. The game would ship faster and lose nothing meaningful.

I would invest time in learning GDScript’s type system earlier. For the first month, I wrote GDScript as if it were JavaScript, with no type annotations. The bugs that this introduced were subtle and frequent. Adding type hints after the fact was tedious, but once I did, the error rate dropped sharply. If I had embraced static typing from day one, I would have saved myself a dozen hours of debugging invisible typos.

Is Godot Ready for Your Game?

Godot 4 is not a toy. It is a real engine that can ship commercial games, as Latch proved to me. But it demands a different mindset from Unity. You will not find an asset store with thousands of ready-made components. You will need to build more things yourself, and you will spend time in community forums piecing together knowledge. The reward is an engine that is genuinely yours, with no fees, no tracking, and a community that wants to see you succeed.

For solo developers who value control and independence over convenience, the port is worth it. The process is long and sometimes frustrating, but the result is a game that runs on an open foundation nobody can take away from you. That feeling, of true ownership, is rare in modern game development, and it is the reason I will keep using Godot for my next project.

LEAVE A REPLY

Please enter your comment!
Please enter your name here