[13] Godot Wrap-up
- wilkopolis
- Oct 9
- 10 min read
In my previous post I described the process of developing my game YardSweeper. In this post I will describe:
Developing the game for Android in Godot
Optimizing the game for Android in Godot
Porting the Android game to iOS in Godot
Adding in an IAP for a full game unlock in Godot
Solo marketing your mobile game
Android:
I started this project with 0 minutes in Godot and mobile development. Initially, I created a prototype in MonoGame, then tried to build it for Android. After a few evenings of attempted builds in MonoGame, I started to get the feeling that task would become a painful slog. I love MonoGame but the Android scene looked sparse, and I decided to look for alternatives.
In early 2025 Godot was getting a lot of buzz, so I tried it out. The element positioning and anchoring and sizing (and signals) were a bit confusing but I was able to get some early prototypes working.
Performance:
After the early days of bug fixing and polishing, I had a stable game on most devices. For some friends however, large boards would run at several frames per second. My experience was always smooth, so I never noticed. I assumed it was some weird Android feature or config on his phone, but I decided to profile the performance before releasing it.

I was shocked to find that a simple 2D image like this was barely getting 60fps myself.

In my MonoGame engine, I used a simple shader to color the different tiles and round the corners. Once the texture was created, I saved the result out and didn't render it again. In Godot however, the shaders were running every frame. On each frame, the game was taking the original green texture, rending out the border radius, then coloring the result. While not ideal, this was apparently taxing on some large-resolution android phones in Godot.

To avoid the runtime-rendering I used the time-tested trick of rendering all the tiles out into a spritesheet. Using a TextureAtlas in Godot terms, I was able to easily remake the board with a performance boost.

In the code, I set the texture of a tile to a rectangle in the AtlasTexture (spritesheet).

The results speak for themselves:

Using the Visual Profiler in Godot, I am able to measure the time it takes to render out each of my Canvas's. This is a large 11x11 board, running at ~300 FPS where before I was getting around 80 FPS. Pretty neat.
File I/O:
Due to the nature of the game, generating boards is not trivial. I discuss some of the approaches I used to reduce the calculations in the previous post [here], but just know that it can take about 20-60 seconds to generate a large, complex 11x11 board.
However, I don't want players to have to wait up to a minute after pressing the "New Board" button. The solution to this is to maintain a supply of boards in the background. Since boards are easy to store, I have a multi-threaded background task running to make sure you have at least 3 of each board in-stock, at all times.

Because of this, each user has a boards.txt file saved on their phone that the game pulls from when a new board is needed-almost like a save file. The code for which is pretty straight forward.

On Android, files are stored in the "user://" path. I don't know much about Android but I believe this is like a virtual disk purely for your app. The file paths are simple like "user://boards.txt" and the actual file is stored outside of the regular filesystem. In fact, I believe the permission pop-up "Do you want this app to be able to access files"-is for when apps want to access files outside of their virtual "user://" drive. They are still unable, however, to access the private files used in another app's space-I think. Not an expert. With that being said, the Android development went pretty well.
A few other miscellaneous notes on the Android experience:



Gotta shout-out this perfect tutorial for mobile camera controls. It covers pinch to zoom, panning, rotating. Worked perfectly.
That concludes the pleasant part of this development story. Porting this project over to iOS was much more work (and much more expensive).
Apple Ecosystem
Initially, I expected to copy over the project to some Mac hardware, build an archive, and ship it off to the App Store. What could go wrong! That's how uploading to Google Play works after all.
If you have any iOS development experience, you may have laughed or cringed at the above statement. It turns out this is very, very false. Whats even more embarrassing is that I rented a remote Mac for a month, assuming I could get it all done by just renting some hardware. Also those cheapest "rent a mac" sites nickle and dime you with misleading pricing and hidden fees, very scummy.
To build for iOS you need Apple hardware, and a valid Apple Dev Certificate (that'll be 100$). And I wasn't going to be able to use a rented Mac for this. Not owning any Apple hardware, I went out to Microcenter and bought some.

While playing with my new hardware, I encountered some unexpected platform specific behaviors. These behaviors would end up being a month of unanticipated work.
The large, showstopping, unexpected, multi-week detour is something called Native-AOT. From this reddit post:

From what I understand, "Publishing your app as Native AOT produces an app that's self-contained and that has been ahead-of-time (AOT) compiled to native code". This means Godot uses this feature to build a C# project to iOS which is running native iOS code. The implementation of this is recent and experimental. Because of this, some .net features did not work.

The consequence of this is that my .net project could not run using reflection (some .net scholars may know more details about this). What did that break in my project? Well everything that was called var did not build. I had to change every variable from var to the explicit type I was using. This was tedious, and replacing them was not trivial. In hindsight I wonder if Jetbrains Rider could have done this automatically (that IDE is really nifty).
That's not all, little tools like Enum.TryParse and other types of casting had to be replaced. The second big hurdle was my current serialization no longer worked on this platform. I had to adjust data types, change json formatting, even little things like x:0.0 => x:0 in the big json txt files. It was a big undertaking.

Also found that signals were no longer working. Straight up got rid of those.
Also learned that if a file didn't exist on iOS (say user://boards.txt) the app would crash, even in a try/catch. Unlike Android which creates the file potentially or just throws an exception, the iOS app will straight up crash.

On a side note, when debugging in XCode my app said some resources
were still in use when the app closed. I believe this was because I wasn't calling savefile.close() and savefile.dispose() on my FileAccess objects in Godot. According to the docs, those are closed and disposed automatically when leaving the function. So maybe this was a red herring, never did figure that out.
Also mysteriously when debugging in XCode, the built game would always hit an interrupt in XCode-like a debugger breakpoint. To avoid this, there is a magic code to disable session interrupts in XCode:
process handle SIGUSR1 -n true -p true -s falseAnother difference was the display mode behavior. The Android build relied on game starting in Portrait so that things were sized and aligned correctly. This was not a defect of Godot but probably related to my implementation of the game. However from what I could tell iOS only supported "Sensor" orientation to run at all. So I had to change how I made some scenes.
This is the end of the iOS tribulations. After this I was able to build and upload to the App Store. I had bought a Mac Mini, an iPhone 12, and an Apple Dev certificate, it was time to start raking in the cash.
IAP
As is this wasn't enough, I wanted to also get my feet wet with app store monetization. In theory I could make a cool, new, fun game, charge 1$, and maybe recoup my expenses. I certainly believed in the product. So I started down the path of releasing the app for free on Google Play, and charging 1$ on iOS (with a free demo of course).
At first, I wanted to create a lite version of YardSweeper on iOS, to accompany a paid version. This seemed easy, since I wouldn't have to deal with in-app purchase crap. I could just set the price on the app listing and so what if traffic is split between two apps? The quality of the game will speak for itself.
Well it turns out that's against the Apple TOS. They don't allow free/lite versions of apps, they rejected my other app as 'spam'. My knowledge of mobile app stores was clearly set in 2012. Apparently apps don't do this anymore.
The alternative to the lite version was adding an In-App Purchase (IAP). The advantage being all users went through the same app, same store listing, and the demo/full version was one install. It seems a lot cleaner. I, however, did not know how to implement an IAP. And this would prove to be exactly as difficult as I anticipated.
The worst part of software development is when you are using a fringe, niche, experimental github branch with no-users and no documentation. You are on your own and will have to be very patient trying to source support. That's not a criticism of developers or a community, these things change all the time. Especially stuff like plugins and app store integration. Be grateful people have shared their code with you at all.
And I wouldn't characterize https://github.com/hrk4649/godot_ios_plugin_iap as experimental. This thing works. Matter of fact-I got it to work. I was able to get this plugin to work in C# too, all the examples are in gdscript exclusively. Here is my implementation in C#:
I was very excited because this repo was less than a week old when I started looking for plugins. There is another godot ios plugin repo that all the reddit posts funnel towards. But I was struggling to get through it.
Once I got this plugin downloaded and installed, the battle was not over. During my darkest hours, I came 🤏 close to submitting an issue to his repo, asking for help. I had it all written up and ready to post. In the end however I would triumph over this software. I found the right magic words to call and things to setup to get it working. I am very proud of this. Aside from the code I wrote, you also have to setup valid payment in the Apple developer portal before you can start testing with In-App Purchases. And now that I conquered the final frontier of my game's development it was time to reap the rewards.
Marketing:
If you thought I was joking about rewards and cash in the above paragraphs-I was. While I am very proud of the game I made, I learned some things about mobile game development in 2025. One thing I learned: people are not looking for new games to play. Rather, they are not searching out for new releases, you have to market your product. Also, marketing is not free. You can try to post your game honestly in certain reddits or make dishonest posts phrased as questions.
"Is this post-game animation any good?" "This unexpected bug in my new free sudoku-like puzzle game" "This board was CRAZY 🤪" - have some shame, people.
But I didn't care to shill my game on reddit. Nor was I going to try to make fake-viral posts. So I decided to also get my feet wet on various marketing platforms.

Now I'm a little ashamed of the slightly clickbaity ads I made. "Can you find the treasure?" etc. But I did my best to make them as inoffensive as I could.
I looked at Facebook/Instagram, Google Ads (including Youtube), TikTok and Apple Ads. I have my short reviews of the experience (and performance) below.
One thing I must say however, is that you cannot advertise to modern iPhones without implementing ad-tracking libraries in your app. TikTok and Facebook will not let you run ads on iOS 14+ unless you implement their tracking APIs so they can gather all that metric data.
This game was 100% offline and collected no data, and I wasn't gonna change that. So my iOS campaigns were doomed from the start.
Facebook/Instagram:

Not bad. Had the best ad-setup website. Was able to make a campaign and link to my app. Got poor performance compared to google. Extremely lot volume of ads served.
Apple Ads:
Comically ineffective. I understand that I did not have a niche app to promote and I was not effective at setting up a campaign. But Apple ads are all about search presence. To this day, if you search YardSweeper you have about 3 pages of sudoku results before you get to my app named YardSweeper. I realize I should remove mention of "sudoku" from the list of tags and description. And my cost per install on this platform was about $16.50. Which is a poor return for a potential $1 lifetime IAP. Not worth it and I regret the $80 I spent on it.
TikTok Ads:
Most broken website I've ever tried to use in my life. Got to the point where I had to install their iOS tracking API and gave up.
Google Ads:

Really good results here. Now this is a free puzzle game, which demographically is well situated for cheap google ads. I ran this one campaign and for $60 I got 906 direct installs. Which peaked the game at like 1100 or 1300 lifetime devices. That's like $0.0648 per install which is a fantastic result I think.
In conclusion:
I am not a gifted salesman. I wasn't going to 'go-viral' or try to generate interest through organic interaction. If you want to make money as a mobile game developer, serving in-app ads on a "f2p" game or creating some predatory micro transactions are more effective. You aren't making money with a $1 lifetime upgrade on an unknown game. I took the IAP out-it's free on iOS now. Ads, while effective in some places, aren't going to be worth the money spent. Could I get 10k installs with a $500 google ad campaign? Probably. But I'm proud of the game I made, I don't need those numbers to prove it.
Comments