(Adapted from a reddit post: https://www.reddit.com/r/roguelikedev/comments/oag25g/built_a_roguelike_in_javascript_for_mobile/)
This is a record of my exploits in building Overworld, a mobile mini-RPG currently in beta testing. It looks like this.
https://redasteroidgames.com/overworld-press-kit/
It will mainly be about how I got a single javascript code base running on Android, and (theoretically) iOS, as well as server side for validation. Tech stack involves vanilla javascript, closure compiler, pixi.js, ionic+capacitor, node.js, express.js, npm, mongodb.
Intro
I’ve been a back-end engineer for about 20 years, most of that in games and always working on a game side-project. Most of these have been MMORPGs that I built for fun. Two projects I tried to commercialize, Overworld being the second. After wrapping up the last MMO, my artist Santiago said I should try something small for a change. So I spent 6 years building a mobile roguelike! (It felt small at the beginning.)
Foundation
My previous big project, Battle Mines, had been written in Flash. We all know how that went, I finally shut down its servers at the end of last year. It never generated a stable following, but some players hung on to the bitter end, and some spent thousands of dollars on in-game currency. The takeaway is, if you build it they might not come, but SOMEONE will come so that’s kinda cool!
From that experience, my goal was to write something once and make it as portable as possible. Something with established 2D engines that doesn’t require a lot of overhead. Something everyone knows already, including me? Javascript! I have no first-hand Unity experience, but it looks harder than the dev setup I chose with basically VSCode and a web browser.
As for the game, the hook was to be its simplicity. Most RPGs/roguelikes feature a lot of depth, which can be hard to cram elegantly onto a small device. I wanted something highly accessible with low hardware spec, but still deep and engaging. Overworld features:
- 10 minute sessions
- 3×3 visible map
- 6-item inventory
- No numbers larger than 3
- Concise dialogue
This material fits comfortably on pretty much any device from the past 4 years.
Early Dev (~2015)
For several years the game was made of crude static images rendered in an old tag. You’d tap to move your hero, and the page would refresh. No animation, just 3×3 tiles of programmer art and an inventory. In this environment I was happy building game mechanics, but the game wouldn’t have gotten a second look from most players.
Graphics Engine (2016)
An out-of-town client engineer friend came to stay with me. For rent, he added a proper canvas renderer to the game in Pixel.js, and animated the hero moving between tiles. This was arcane magic to me. I have spent months wrestling with the 500 or so lines of code he wrote (now ballooned all the way to 1700) and even now barely understand it. Slight exaggeration but I’m more used to databases and rest clients.
At this point I didn’t even have sprite maps, and the browser started to chug just from the sheer number of 16×16 images it had to load. Fortunately my friend had added the basic capability to capture assets from a sprite map, so my artist friend Santi started creating actual 2D assets. Still going strong today!
Optimize and Obfuscate (2017)
By now gameplay was becoming defined, and there were a handful of characters (heroes) for the user to play. I wanted to feel better about putting my code out in the wild, so I decided to get it running through the closure compiler. Closure basically transpiles blocks of javascript into dense, optimized text blobs that otherwise behave identically.
There were two main challenges. First I had to get rid of my non-standard uses of javscript. Misplaced globals, undeclared vars, stuff like that. Second, to get the game running discretely from the renderer, I had to segregate some of the hot organic mess I’d created so far. This involves being much cleaner with globals and leveraging externs. I made a blog post about it, details in the link.
Server Side Validation (2019)
Good old Javascript, so ubiquitous that even server-side it has a massive community and following in Node.js. Solid tech to build on! With the game engine separated out and closure compilation in place, I was able to set up a node server that accepted the string of commands a user made playing one game of Overworld, and re-run the game on the server. At first this was just to verify the output of the client, later on it became the definitive output for values I would store against the user’s account.
A game of Overworld looks like this:
13121111522222F6699926F64478288882558778444666228W2288222L8887444222251775994231222311212191595559159299669936979975666658889921278772222662886655599865894884LLLL91132328
The numbers are player movements (3×3 map movements, per the number pad). The letters are actions taken with inventory items, and some special actions. Along with a number seed, the whole payload is miniscule and human readable. Since the whole game is rerun on the server from the same code, it’s cheap and reliable to validate user input. I started building out meta, adding challenges to unlock heroes (eg. Kill 10 rats!) and nethack-style conducts for fun. The challenges fill up passively just by playing the game, though can be focused for faster completion.
Javascript isn’t as univeral as you might think though. Depending on the engine you use, some operations like floating point math are non-deterministic. So I spent a long time stripping out that stuff and finding workarounds. For randomness, I built a service that returns a static array of random numbers, which are looped all game long to generate the world. That way the client and server have access to an identical pool of random numbers and produce identical outputs from matching preconditions.
I did another blog post on this subject. I was up to 3 or 4 posts a year by now. 🙂
Mobile Client (2020)
Until now everything was through a web browser. You could play on a mobile browser, but only on my website. It was time to make the game into an app.
PhoneGap (a technology for porting javascript to iOS/Android) is dead it seems. The cordova project it was based on has accumulated tons of libraries and community and lives on. It forms the core of most related tech, including the service I chose, Ionic. Ionic is “a complete open-source SDK for hybrid mobile app development”. I have only built for Android so far, but it’s good to know iOS should be quick to ramp up.
Capacitor is an app container built that makes interfacing with native device features easy. Even if not adopting the “look” of a native app, you’ll probably want access to stuff like haptics, focus detection, keyboard, native browser, clipboard, not to mention the native game platform (Play Services, Game Center), store (Play Store, App Store), and billing.
All the modules are handled through NPM (Javascript package manager). Some time around here I switched from I think a php backend to express.js. A simple RESTful api, it handles auth and game validation, storing user data in MongoDB. It runs under pm2, a “process manager for the JavaScript runtime Node.js”. All tech was chosen to be off-the-shelf, proven, and open source wherever possible.
As a side note, with ionic you have to pick an engine: angular, react, or vue. In a single act of contrarianism I went with vue, and happily have not regretted it yet. There was some trouble injecting my pixel.js canvas renders into the vue world, another friend helped me with that. At the end of the day I’m not doing much with that medium. The graphics are very unambitious, and a pass to improve them would be hot on the heels of any commercial traction.
Testing and Iterating (2021)
A lot of this past year has been spent fleshing out the economy and testing with real users. This has been tricky during the pandemic, fortunately it’s very easy to record video these days. I used playtestcloud.com until the freebies ran out, then I used fiverr to have gamers record their first-play experience. This has informed a lot of the recent design. I’d been gearing towards a free-to-play payment model from the start, and first time user experience (FTUE) is integral to the success of that business model. Retention, retention retention!
Production Servers and Monitoring (2021)
Fortunately there are capacitor libraries for Firebase integration, which gave me analytics and crashlytics for free.
I had an old dev box on Linode which saw me through about a decade of general use. For this app I’ve moved to Digital Ocean, who are getting big and have lots of app rollout support, without being AWS in terms of complexity + price. I set up a simple load-test using Locust, which indicated I should be able to handle around 1000 DAU on a $5/month box. Even with a safety factor of 2 or 3 that should be in my price range. (Remember that tiny 100-byte game payload?)
Still a two-man team (me + artist), right now I’m working to generate some very modest hype and get my beta test filled up. That’ll prove the tech, and hopefully give me a core cadre of followers. I just started this last week, it’s intense but fun. You have to integrate the delays of getting approval from google to publish changes (even just adding a new list of testers) with your release pipeline. That created a few launch-week snafus but good learnings.
Overworld Meets Real World (Present day)
What do you think of my story? Did I make any obvious mistakes? I’ve been pretty happy with the pace of development, and the end result. Overworld doesn’t look as flashy as 90% of games these days with store-bought assets, but I feel it conveys the gameplay experience I’ve always been shooting for. Unlike my old Flash game it’s built on tech that doesn’t look like it will go away soon. It’s cheap to run, cheap to develop, and can be played on just about any machine.
- Website: https://redasteroidgames.com/overworld-press-kit/
- Trailer: https://www.youtube.com/watch?v=nuovHHd0fZA
- Gameplay: https://www.youtube.com/watch?v=DCmjYr-UFnk
- Newsletter signup: https://redasteroidgames.com/subscribe/