Lately, I’ve been working on a small browser game called “Rainboids.” This is a modernized, browser-based Asteroids clone designed for Canvas 2d that features pseudo-3d geometry, heavy particle and visual effects, and motion blur.
Try it at: https://rainboids.cat.computer/
View the source: https://github.com/afeique/rainboids
Visual Aesthetics
The game relies on code-based graphic generation using drawn primitives for player and enemy ships. It uses pseudo-3d geometry and matrix calculations to rotate and render the asteroids as smooth-rotating 3d-wireframe icosahedrons that are drawn to a 2d canvas.

Meanwhile, the player ship is a collection of geometry with a soft glowing effect using multiply-blending. The entire screen has some hefty motion blur and added scanline overlays for a retro, CRT-feel. Removing these extraneous visual effects alone would lead to a huge performance gain.

Rendering these effects and performing all these math and geometry calculations per frame, including accurate 2d collisions using 2d projections, is computationally expensive.
Some graphical optimizations that would greatly improve CPU performance: caching rendered player and enemy ship geometry to a 2d sprite texture that is then rotated and reused. This would offload a lot of CPU calculations to the GPU and drastically reduce the number of calculations and operations per frame.
Simply disabling the motion blur and blending effects would also yield a tremendous performance boost.
While it would be much cheaper to use pre-rendered sprites by caching pre-drawn geometry in-memory as a rasterized bitmap, doing this takes away from the very smooth, vectored aesthetic of the game.
Using sprites and optimized raster graphics doesn’t look bad; in fact, it can be made to look very good as a style unto itself, but it isn’t my preference: it’s a very different visual aesthetic.
Fortunately, I can get away with inefficiencies in favor of aesthetics by tuning and simplifying gameplay. Because there are so few overall elements on-screen, the game still plays at a solid 60 FPS. But introducing more elements: more asteroids, enemies, more bullets, more particles, and so on, would cause the game to slow down.
Eventually, I will implement such optimizations – reduced visual effects and pre-rendered sprites – to drastically reduce the number of computations per frame so that I can tune the gameplay to something much more frenetic and fast-paced. But for now, this slower-paced, prettier looking game is what I’m going for.
Further Graphical Acceleration Using WebGL
I have made some rudimentary efforts to try and accelerate the game using WebGL, but these efforts have led me to believe that a different graphical style and approach using 3d geometries, shading, and heavy particles would be much better suited to WebGL. Again, this would lead to a very different overall visual aesthetic.
And thus, I’m left squeezing out as much performance as I can from an overall inefficient approach to rendering on 2d canvas, for stylistic reasons.
Initial Approach: A Monolithic Script
My initial design of this game was to implement a single, pure JavaScript/HTML file encapsulating all the code and assets to run the game in-line. It’s pretty nice when you can share an entire game as a single, self-contained file with people, like an executable.
Implementing Icosahedral Asteroids
Initially, the asteroids were rendered as simple, rotating 2d n-gons with randomized geometries to give them a jaggy, craggy feel. There was no pseudo-3d. This is the simplest geometry and easies to design collisions for.
However, I wanted to have a more 3d-effect to really capture the rotation of the asteroids and give them a greater sense of on-screen volume. I eventually decided to use an icosahedron whose vertices were stored and rotated in 3d, then projected onto the 2d plane for rendering. Initially, these icosahedrons were filled, with some shading calculations to render certain faces darker than others. However, this initial approach with opaque faces made the icosahedrons appear more like jagged blobs with some splotches on them; there was no sense of depth. The rotating 2d n-gons looked much better in comparison.
The next approach was using RGBA to render faces with opacity, the idea being that you could see-through the icosahedron to get a better sense of depth. This… looked weird. There was a sense of optical illusion that messed with the sense of depth and left the asteroid looking like a very distorted clump of geometry.
I added wireframes to see if that would enhance the sense of depth – it did. I realized the slightly opaque faces were hurting more than helping, so I turned to a pure-wireframe rendering using white lines and completely transparent faces. This looked much better, and was much more aligned with my vision of a crisp, pseudo-3d asteroid.
To really get it polished, I had the system render lines with different colors: the lines closer to the camera (higher z-value if the z-axis extends out of the screen) were rendered white. The lines behind them (lower z-value) were rendered as a light gray: slightly darker. This sealed the psuedo-3d effect of the wireframe approach.
Experimenting With Simplex Noise For Generative Nebulae Backgrounds
I experimented a lot with different effects and ideas. One idea was to render randomize nebulae to the background using code. For this, I integrated jwagner/simplex-noise.js into my code.
However, despite my best attempts to randomize the feel of the nebulae, e.g. by layering multiple, different-colored noise layers and tweaking the noise generated, the nebulae always appeared like white noise with uniform, repetitive clumps; not organic clumps. Because of the uniformity of the nebulae appearance, I found it didn’t improve the overall aesthetic of the game, possibly detracted from it by adding graphical complexity. Plus, the simplex-noise library added a lot of logic to the monolithic codebase.
Consequently, I dropped the nebulae and simplex-noise, and opted for a simple parallax field of small, twinkling, randomly-distributed star particles. This gave the game a good feel and was much more code-efficient.
Client-Side Sound Effect Generation
To generate sound-effects in real-time on the client-side, I integrated the jsfxr (github) library to generate semi-random, parameterized sound effects on-the-fly. This means that every re-load of the game is different-sounding, but similar.
This integration was seamless for the most part. The only thing to watch out for is that sound effects generated by jsfxr are not encapsulated in an HTML <audio> element and thus lack object controls for play and pause. Effects generated by jsfxr can only be played, and that’s it. They play once, and then they can be played again. You can’t control playback while the sound is playing.
This isn’t an issue, really. The most difficult part of adding sound effects was designing the thruster noise: it took time to figure out a good sound that could be played constantly by the sound system but still sound good when the same sound was repeatedly played repeatedly. For this, a noise-based sound is your best bet, as noise-based sounds blend together well. Wave-based sounds can be incredibly jarring when they are played repeatedly, because they will naturally create pockets where the waves are in-phase and constructive, and out-of-phase and destructive, leading to a very jarring effect that is difficult to fine-tune overall.
Game Design Centered Around Asteroids, Score, And Pickups
Asteroids, naturally, has a high-score. The player gains score for destroying asteroids. I took time to more carefully tune how the player gains score. Gaining score for a pickup is a visceral reaffirmation, and it is a satisfying element of the gameplay experience.
I always found picking up items in games to be incredibly satisfying. Almost doesn’t matter what the item is! A thought occurred to me: why not make the background stars, with all their neat parallax-ness, be something the user could pick up for points?
I am also a big fan of enemies dropping pickups, so I thought: what if the asteroids also drop green stars, distinct from the purple background stars, that home in towards the player?
The green “burst” stars provide more points than the background stars, but both are collectable.
During testing, I found a strong homing effect for the burst-stars to be best. Initially, I played with weaker attraction of burst-stars towards the player, and made the burst-stars fade out relatively quickly. This required the player to be more conscientious of collecting burst-stars before they faded if they were aiming for a high-score. However, I felt this was a distraction compared to the pure goal of dodging and shooting asteroids. I wanted the player to remain focused on that and not have to worry about anything else.
I also felt that destroying an asteroid should give a constant number of points. As a result, I made the homing effect for the burst-stars much, much stronger. I found this to be very satisfying, especially with pickup-noises and slight graphics affirmation (a small white-pulse on pickup).
I toyed with attraction of the normal, background, purple stars to the player, and settled on slight attraction that made normal stars easier to pickup, but the player had to more conscientiously gun for them.
Elements Of Perceptual Feedback
Perceptual feedback – tactile, visual, and audial – is incredibly powerful in a game. Whether it’s the screen flashing, a certain kind of noise, a different color, screen shake, rumble, or some other kind of haptic feedback, utilizing different parts of a person’s perceptions can add a great deal of depth and immersion to the experience.
For this reason, special attention was given to the feedback from scoring hits on asteroids to destroying asteroids. A screen-shake effect was added so every hit against an asteroid gave a satisfying visual cue, albeit one that could also be potentially distracting if overdone. Hits also resulted in particle debris splashing from the asteroid, and a satisfying collision noise. When an asteroid was destroyed, a much-more-apparent sea of fiery particles erupted, alongside a lot of line-geometries getting scattered, making it seem like the wireframe geometry of the asteroid literally exploded.
This made destroying asteroids quite satisfying.
Working Towards Good Mobile Support
The last step in the monolithic approach was to implement decent mobile support for the game, so it could be played on a cellphone or tablet. My primary target was desktop environments, but there’s no doubt a game is much more appealing when it has strong mobile support.
The first stab at support was to implement an analog joystick for rotation, with thrust and fire buttons. This would make the mobile version have the exact same mechanics as the desktop version, just rendered on a smaller screen with touch-screen controls.
However, the default asteroids-type controls (rotate and thrust using arrow-keys, with a distinct button to shoot) don’t lend themslelves well to the limited real-estate of a cellphone.
In particular, portrait mode doesn’t have enough horizontal real estate for the analog touch-screen joystick AND fire and thrust buttons. Landscape mode makes the controls feel much better, but doesn’t have enough vertical real estate for the player to see obstacles clearly.
At best, the ability to play on a mobile device was janky and a nice-to-have, but it wasn’t implemented well-enough for me to be happy into it. It led me to think a lot about how to modify and tune the game mechanics to be much more mobile-friendly, including toying with the idea of having very distinct controls for desktop versus mobile, to make the mobile experience much more fun and attractive to players.
Refactoring Into ES Modules
Once a working, monolithic version was made with decent physics and asteroid-blasting mechanics, I had a basic engine in place. These classes were then refactored into ES modules implemented without any server-side code, package-manager, or build-process (e.g. nodejs).
Adding Energy And Energy Drain Mechanics
The game mechanics were then enhanced with an energy system intended to make the gameplay more interesting. This system made it so:
- Thrusting and firing bullets depleted the user’s energy
- This stopped the player from zooming around endlessly and firing continuously
- The player’s energy gradually recharged, but at a slower rate than thrusting
- Star pickups (both green and purple stars) conferred energy to recharge the player’s ship
- If the player’s energy hit 0, they didn’t die, but their ship entered a “critical” state with screen-flashing
These mechanics effectively ruled out the running-and-gunning style of gameplay and enforced a much more conservative, calculating approach to gameplay where the player had to think through their movements and space out their shots.
I found this to be a decent “twist” to the game to make it more unique, but somehow… it also felt less fun. It imposed one specific style of gameplay that was slower-paced.
A really close friend of mine forked my code and used it to create a bullet-hell style game. I tried this out and found the fast-based gameplay with much more action to be immediately much more fun and addicting than my simple asteroids clone, which focused more on visual aesthetics than gameplay.
Don’t get me wrong, for an asteroids clone, this game would have been fine in the 1980s and even the 1990s. But for modern games, it’s far too slow, unless you’re going for the retro feels.
Assessing The “Energy Drain” Mechanic
My honest opinion is that faster-paced, action-packed games are more enjoyable to me, personally. I believe they are more appealing to a lot more people in general, though I have no numbers to back this up.
Having a simpler set of gameplay mechanics reduces the complexity of our meta-cognitive, mental “gameplay loop” which leads to less thinking, more acting, and more dopaminergic rewards. That’s my working hypothesis, anyways.
Elements that restrict player freedom tend to add much greater complexity to this mental gameplay loop because it takes a lot of thought-power to develop cognitive strategies around limited or finite resources – especially compared to games that give you the freedom of doing “whatever you want, however much you want.” The latter freedom encourages a lot of different possible gameplay styles.
That isn’t to say there isn’t a place for deep, contemplative thought and strategy: plenty of games do this, and a lot of role-playing games balance this with a combination of action and strategy.
But asteroids is an action-game first, not a strategy game, so I found the energy-drain mechanic to not be as fun as I had hoped, and perhaps even out-of-place.
Going Forward: Frenetic Fun, Mobile-First Mentality
I want to make this game incredibly fun on mobile to ensure it has a wide audience and reach. This means coming up with more refined mobile-friendly controls and mobile-friendly gameplay mechanics. I am so intent on refining the mobile experience that I believe the gameplay has to be re-tuned with a mobile-first mentality.
The experience is fun on desktop, but this limits its overall reach. An overhead shooter is a simple concept and not one that most people would spend much time playing on a console or desktop these days. However, it is the sort of game that plenty of people could enjoy on a mobile device.
As a result, I plan to integrate a lot of changes to the mechanics and gameplay so that the game is more fun and engaging, and plays much better on mobile than it does right now.
Stay tuned for more updates!
Leave a Reply