Plinth! My first arcade-style game.

Plinth! My first arcade-style game.

I just shipped Plinth, a small one-tap arcade game for Android. You stand on a column, a new column rises next to you, and you tap to stop it, ideally at your height. If you stop it too high, the extra gets sliced off, so every column after that is shorter. Stop it too low and you fall. 👉 Get Plinth on Google Play · plinth-mobile.web.app It's built with Flutter and Flame. Here's the story! Faking 3D to promote performance Plinth looks 3D, but it's plain 2D canvas drawing. Every column is a box in isometric projection: three filled polygons (top, left side, right side) computed from grid coordinates. The only real trick is draw order. Boxes further "back" have to be painted first, so each frame I sort everything by gx + gy and paint back to front (the painter's algorithm). The player gets drawn last, except when falling into a gap, where it slots into the sort so the columns in front correctly cover it. Game feel is a pile of tiny things The first playable version worked but felt dead. What fixed it was a slew of small touches, none of which were too hard to implement... Manipulating the character on the character: a little crouch before a hop, a stretch in the air, a squash on landing. A rising chime for perfect stops that climbs a scale with your streak, so a streak of perfects sounds like a melody. Screen shake and debris when a column gets sliced. A "NEW BEST!" moment mid-run whenever you beat your previous best score, complete with confetti. All of the sound effects were generated with a short Python + numpy script (sine waves, envelopes, some filtered noise). Syncing coins across devices without a backend I wanted coins and unlocks to follow players between phones, without running a server. Google Play Games' Saved Games stores a blob per player for free, so I only needed a merge strategy for when a local copy and the cloud copy disagree. A plain "take the max coins" merge is wrong: spend coins on one phone, sync an older copy, and you get the coins back. Instead, coins are stored as per-device running totals of earned and spent: Each device only ever increases its own counters, so merging is just "take the max per device". Owned items merge as a union. The rejection that taught me to test release builds My first production submission was rejected: "Plinth keeps stopping." It had never crashed for me once. Here is where I used Claude Code to find a quick fix. The cause: release builds run R8 (code shrinking), debug builds don't. The ads SDK pulls in an old version of WorkManager, which uses Room, which finds its generated database class by name at startup. R8 renamed it, so the app crashed before drawing a single frame: Failed to create an instance of androidx.work.impl.WorkDatabase Enter fullscreen mode Exit fullscreen mode The fix was a keep rule plus a newer WorkManager: # android/app/proguard-rules.pro -keep class * extends androidx.room.RoomDatabase { (); } Enter fullscreen mode Exit fullscreen mode // android/app/build.gradle.kts dependencies { implementation("androidx.work:work-runtime:2.11.2") } Enter fullscreen mode Exit fullscreen mode The real lesson: before every upload, install a release build fresh on an emulator and actually play it. flutter run in debug mode tells you nothing about R8. While testing that, I found a second problem: on a phone with no Google account signed in (like many review devices), my startup code triggered the interactive Play Games sign-in, which opened Google's full account login screen. Now the app only silently checks sign-in status at launch, and the interactive flow lives behind a "Sign in" button on the leaderboard. Two small gotchas worth knowing AdMob's app-ads.txt check vs Firebase Hosting. firebase init offers a single-page-app rewrite that sends every unknown URL to index.html with a 200 status. That means /app-ads.txt "exists" but returns HTML, and AdMob reports a mismatch. Use real files with cleanUrls and no catch-all rewrite. The consent form is not optional in the EU and UK. Google's UMP SDK ships inside google_mobile_ads. Request consent before initializing ads, and add a "Privacy options" entry so players can change their choice later. Monetization: rewarded only Every ad in Plinth is opt-in: continue a run once, double your coins, or earn coins in the shop. No banners and no interstitials. It earns less per player than forced ads would, but I'd rather people keep playing than bounce off an ad between every run. Try it Plinth is free on Android: Google Play. If you give it a go, I'd love to hear your best score, and any feedback on how it feels. And if you're building something similar in Flutter and Flame, ask away in the comments.

Original Source

Read the full article at Dev →

KhanList aggregates and links to publicly available news content. We do not host full articles from third-party sources. Always verify important information with original sources.