Annoyed with Unity? My experience making a Game in React Native
Last year, I got that itch to make a game again, and this time I was determined to ship! Like clockwork, once a year I re-download both Unity and Unreal Engine. These tools are solid but by the time I onboard I lose focus. This time, I knew what I was going to make, and I wasn't going to let the technology distract me damnit!
Every time I re-onboard to Unity or Unreal my web searches wound up looking something like:
- "How do I make a 2d game with X?"
- "How do I make X engine render a certain resolution/dimension?"
- "How to write text on screen for X engine? How to make it look good?"
- "How do I make a menu in X engine?"
- "How do I write tests for my game rules in X engine?"
After spending a few days trying to plow forward my take away was - making a simple game in Unity or Unreal Engine seems harder than using an app framework. I will admit my mental model is more geared towards writing code first as opposed to building maps, and since these tools prioritize map/scene building it feels harder to get started especially without many art assets. So much of what I do ends up being authoring code and the visual editors seem to get in the way of building the actual core game mechanics.
Making a simple game in Unity or Unreal Engine seems harder than using an app framework
The other question on the back of my mind when I choose a stack is "what's the automated testing and dev story here?" Looking at the rules I'd need to build for this strategy game it seemed obvious that I'd quickly start forgetting about various mechanics - Unity/Unreal's strategy there seems weaker overall. All of these reasons drove me to choose React Native to build "Formidable, Inc."
In this post, I'll talk about the experience of making the game and the technology choice. The two are so interconnected, and it should help others see what sorts of problems they'll face when building similar games.
How does it work?
My setup was pretty straight forward.
- I set up React Native via Expo. Expo gave me great over-the-air updates, hot reloading, great support for simulator/real devices (while testing), and a reliable beta testing mechanism. All-in-all this made it easy to get a good build/deploy loop going.
- I pulled in Redux for state management, and Redux Persist made it easy to just save that state straight to device storage, which meant saving and loading the game was free and I didn't need to think it too often.
This setup made building my game easy. Often I'd be able to iterate on building new mechanics completely from the command line or in VS code's debugger without needing constantly get the game into the appropriate state. I loved that all this tech was fairly boring, got out of my way for the most part™, and most importantly I knew I could work quickly using the stack.
What didn't go so well?
Fewer redux reducers would have simplified things - there's 5 reducers for this game: agency, heroes, missions, the store, app. I think I would have squashed agency, heroes, missions and store into 1 big reducer called "active game." The amount of work my actions had to do to keep the stores in sync slowed me down. In retrospect, I would have preferred 1 big file, broken down into a bunch of like 150 line functions, as opposed to an action creator that orchestrated data between 4 reducers with smaller functions.
Achieving fun took a while, and it's still only 80% there - it took me quite a while to get to a sufficiently fun, core game loop. There were 3 significant steps that made the game more fun:
- Since this is a simulation/strategy game I figured more variation in hero abilities vs mission types was going to be important. I also thought learning how the variations interacted with each other would be fun. It turns out the variation made it not fun and too complicated. I simplified it to the point of a matching game with extra flair and used iconography to highlight how missions and heroes relate to one another.
- Less randomization and more curation - Originally, heroes, missions, etc were all randomly generated. Random generation is "easy". Good random generation is hard. I'd frequently have heroes not match to the missions that appeared, the game could also become too hard or too easy causing the whoel and the whole game wouldn't work any more. I ended up opt'ing for "some randomization."
- In about 2 dev days I went from a "ListView" for the available missions to a map based view. All of a sudden the missions felt more engaging. Fundamentally though, it was just a list of 20 manually specified points on top of an image that I whipped up in Figma. The individual assets in the map are a mix of assets from game asset libraries and flaticon.com
Animations - Reanimated + hermes (and maybe turbomodules and maybe expo) - The state of these libraries all working together well and the information they provide when library versions are misaligned is rough. Expo tries to fix this - and it honestly gets you 90% there. That last 10%, especially around animations is a bit rough. I recall trying to get animations to trigger only to realize they don't in dev under certain circumstances.
React Native Modal - it still doesn't trigger occasionally - a hard app reboot fixes it. I think it has to do with battery level. But I was a bit at a loss around how to fix this one.
What went really well?
I regained context quickly after stepping away for 2-4 weeks - Often I'd be halfway through some feature, and I put the game dev on pause for a bit. The thing that helped me find my place again was a mix of the TODO+ VSCode extension and running the tests. I start building to a point where I knew I'd remember where I was due to remaining todos and active fires in my tests.
Battery and perf seem pretty solid - I recall hitting play in Unity for a nearly empty scene, and hearing the fan rev on my previous Macbook Pro. I've had a few performance issues, but all of them have been fairly tractable. I'm glad I had the tools I needed at that moment to fix these issues, doing this in Unity is probably a "learn from scratch" moment again. What does everyone even do there?
Churning out revisions while in Beta (and post-launch) was so incredibly fast - My testers would say "hey check this out, I just had a play through where I made $20M by end of game lololol" or "I don't understand this mechanic at all". I'd have a fix created in a few hours and sent out an "over the air" update then ask them what they think.
State migrations are built in - For client apps like these that *save* local state, people often forget that they actually need to migrate their client data on app start as they make changes. There's nothing worse than an app crashing on app start due to corrupted save data. I was able to build a system around redux-persist for this, and test it out in dev before deploying.
Would I do it again? *Yes* for an app like this (mobile, management sim) I would use RN again. For something like platformer or FPS, it's a terrible fit. That being said, I'm still cautious about using Unity or Unreal (especially after the recent news with Unity). I really wish we could initialize their game engines from code, and have more ownership over the lifecycle of an app. For me, I'm unsure why scene editors need to be so tightly coupled to the rest of those game engines.
Ultimately - learning to make fun games takes practice. I've learned a lot from the experience and I'm glad I just started from the technologies I knew and scoped it in a way that I could actually complete. Too often in the past I'd say "I want to make a 3D MMO", and I'd fail at the installing Blender phase. Hey - I finally launched a game and it's not so bad - some people even said they played for more than 10 hours. Not bad at all for a first launch, I give Formidable, Inc 3.5/5.