DEV Community

Cover image for I'm a React developer with zero game-dev experience. I shipped a real-time strategy game to Google Play.

I'm a React developer with zero game-dev experience. I shipped a real-time strategy game to Google Play.

I'm a React/TypeScript developer with zero game-dev experience. In August I decided to find out whether that background is enough to ship a real game. Two and a half months later Weft is live on Google Play — a one-thumb real-time strategy with 1 500 levels, bosses and online PvP.

👉 Play it free: https://play.google.com/store/apps/details?id=com.weft.game

Here's what the road from npm to a published game looked like, and what a web developer gets wrong first.

The game

Planets are joined by one-way links. Swipe from your planet to another and ships start flowing along the link. Swipe across your own lines to cut them and send the flow somewhere else. Paint the whole map your colour and the match is won — usually in about a hundred seconds.

That's all the controls there are. The depth comes from where you send your flow and when you cut it: feed a planet and it grows, overextend and the enemy walks through your back door. Later come boss fights with their own skills, your own skills and perks, and 1v1 PvP with a rating.

Weft gameplay: three levels, links between planets and a boss

Three stacks in two weeks

Unity, attempt one. My first prototype was in Unity, and it humbled me fast. I lost a day chasing a performance problem that turned out to be the editor itself — on the phone a frame cost 0.6 ms. The first build drew bright magenta instead of planets: Unity strips shaders nothing references, and my whole look was built from code. And with no frame-rate cap, the game drew 400 frames a second and cooked the GPU.

Back home: Expo + React Native + Skia. So I did what a React developer does and went back to my stack. In ten days it was a full game — rules, board, menus, levels, economy, two languages. And then I spent most of my time fighting the frame:

  • 60 fps only came once the board bypassed React entirely: the whole scene drawn as one Skia Picture per frame.
  • Hermes ran 62 garbage collections a second. Weeks of cleaning up allocations in the game logic changed nothing — the logic took 7 µs of a 16 ms frame. What actually halved the freezes was switching off an animation: freezes followed the time left in the frame, not the amount of garbage.
  • On iOS every icon vanished from the home screen one day, because display: none on a Skia view doesn't mean "hide", it means "give the Skia layer back".

It worked. It just never felt smooth on a mid-range Android.

Unity again — this time measured. I built the same match in Unity and recorded frame times on the same two phones. The medians were almost identical. The tail wasn't: p90 of 16.9 ms in Unity against 33.5 ms in React Native, with no doubled frames. That one number ended the argument, and Unity became the production version.

What carried over from web development

More than I expected:

  • Keep logic out of the view. The game rules, the save data and the menu rules live in plain C# libraries that are not allowed to reference the engine at all. A screen gets ready data and reports what was pressed — basically props and callbacks. The bonus: those rules are tested with plain dotnet test in seconds, without opening Unity. There are about 2 800 tests now.
  • One source of truth. The mechanics live in a single rulebook document, and the code follows it. When the game and the doc disagree, one of them is a bug.
  • Measure, don't guess. Every performance decision was made on a real phone, a mid-range Redmi. The editor lies in both directions.

And what was new:

  • Floats are not your friend. Porting the level generator from TypeScript, (3.4f − 1.95f) × 10 came out as 14.5 in C# float — a different dice roll than in JavaScript, and a different map. Everything that must match goes through one seeded random generator and an injected clock. No Math.random(), no Date.now() inside the rules.
  • That determinism is what makes PvP cheap. Phones send only their commands; the server runs the same match at 30 ticks a second, and both sides compare a hash of the game state every second. The same trick checks leaderboard records: a record has to actually replay.
  • Balancing 1 500 levels by hand is impossible. Bots play every level, and the ladder is built from what they measure — map shape, enemy pace, how often the bot wins.

The backend, briefly

PvP and leaderboards needed a server, and I had never run one in production. It's a single C# service deployed with Kamal to one VPS behind Cloudflare, with Postgres (backed up off-site and restored every week to prove the backups work), Valkey and monitoring that pings my Telegram. Before launch I load-tested it on production: it holds about 6 000 requests a second of max-level players plus PvP.

Which brings me to the actual problem.

The part nobody warns you about

Shipping turned out to be the easy part. The game is out, it's free, and almost nobody knows it exists. The PvP queue is lonely.

So if you've read this far, I'd love it if you:

  1. Play a few levels — https://play.google.com/store/apps/details?id=com.weft.game (Android for now).
  2. Tell me where it stops being fun — too hard, too slow, unclear. Comments here or contact@playweft.com.
  3. Reach PvP (it opens as you climb the ladder) and come find me there.

And if you're a web developer wondering whether you could make a game — you can. Just measure on the phone.

Top comments (0)