In this post I will explain how my game works and the process I went through. Before reading, please take a few moments to play the game if you haven’t already. The source code is also available on GitHub!
Legend has it that while working on “Zelda: Breath of the Wild” a team at Nintendo developed a prototype using art from the original NES Zelda game. This amazing amazing video is all we have to wonder what it would be like to play that game.
That concept applied to Link’s Awakening for Game Boy comprised my main inspiration for the project. I chose a boomerang for the player’s weapon to satisfy the theme and the game just flowed out from there. During development it helped to imagine this game as kind of an alternate universe Zelda.
Another goal of mine was to deliver something that feels like a complete experience regardless of the size limit. While 13k may not seem like much to the average dev, for serious code golfers it’s a mountain of space. Also an entire month is also a fairly long time to polish a game of this size. So I wanted to push the format as much as possible, and hopefully make something that players will keep coming back to.
I always try to use a primary mechanic that supports the jam’s theme. So I gave the main character a boomerang as their only weapon. Games in the Zelda series often feature a boomerang as a second tier weapon but I completely reworked the boomerang mechanics to feels more physical and responsive.
Boomerangs are thrown by clicking the mouse, so players have full analog aim control. When thrown, the player’s velocity is added to the boomerang’s for extra nuance. While in flight, boomerangs are pulled back towards the player, damaging and applying knock back to anything hit along the way. If not caught in time, boomerangs will gradually lose height and fall to the ground where they must be picked back up.
In addition to dealing damage, boomerangs can also pick up items and bring them back to the player. Players can even acquire multiple boomerangs and throw them simultaneously! A super boomerang which does double damage but can’t collect items is also available for purchase. The next boomerang to be thrown is shown by drawing it on the player’s back in game!
To give the player some defensive ability and combo with the boomerang, there is a dash move on space bar which refills every 3 seconds. This move gives the the player a speed boost and protects against damage. While dashing the player can also smash through level objects like bushes and rocks. Dash can be combined with a boomerang throw to increase range or make a diving catch.
There is a special effect applied while dashing to show a trail behind the player. Rather then show every frame, it uses every third frame so each image is more distinct. A white outline is displayed around the player while the dash is charging and a sound plays when it becomes available again.
The visual design of the player was inspired by Zelda, and sprites from “Link’s Awakening” were used as reference. All the sprites use the same 16×16 resolution as that game and stick close to a 4 color palette. The image to the right shows a comparison between the corresponding animation frames from Zelda.
For this game I made the character wider so it is more unique but also to better approximate the square/circle collision detection. The hat got changed to a floppy hood so the race and gender of the player is unknown. I try to do this whenever possible so players can better identify with their character. A few other small changes were made like changing the color scheme, removing the faux shadow, and reworking the death frame.
There is some logic necessary to animate the character because each of these frames is used in multiple ways. For example the throw frames are used when a boomerang is thrown or caught but also while dashing. The direction the player is facing determines which frame is used and if it should be mirrored.
The walk cycle works differently if the player is facing horizontally or vertically. When horizontal, it alternates between 2 frames and mirrors when facing the opposite way. When vertical it selects either the upwards or downwards facing frames and alternates between mirroring to create the appearance of walking. In this way, 4 directional movement with 2 frame walk cycles can be achieved with only 4 frames of animation.
There are in total 11 levels including the hub/start level. When the player dies or restarts they will always return back to the hub level while keeping any money they earned. Once level two is reached, a warp portal is available to skip back to the highest level. The player’s money and warp level are permanently saved in local storage. These features help encourage players to continue and come back later after taking a break. I’ve noticed that this is especially important for game jams where players often need extra incentive.
The start level introduces the story which is purposefully left open to interpretation. A short scene plays out where a mysterious character runs away from the player, steals a heart container and escapes into an exit portal. So the goal of trying to get your heart back becomes a call to adventure for the player’s character. If a player tries to quickly attack, they will notice that boomerangs bounce off this other character’s back!
Once the game has been won, a speed run portal becomes available in the hub level. A speed run works slightly different because coins and items don’t carry over, so players always start from scratch. When active the speed run time is shown a the top of the screen. The player’s fastest speed run time is saved and shown above the portal in the hub level. In this way the hub level acts as a main menu for selecting a play mode.
All of the levels are built from a 4×4 maze which becomes the mini map. First a maze is generated using a depth first search algorithm. Then bunch of circle shapes with random parameters are stamped down to fill out the shape of the maze. Finally each tile’s shape and rotation is calculated by checking it’s neighboring tiles and some extra randomness is applied.
There are three types of tiles: solid, grass and sand. Characters move slower on sand which creates areas for the player to avoid and use strategically against enemies. Each tile can also have a bush or rock which can be destroyed though rocks take more work to break through. The entire level is cached to an off screen canvas so it can be rendered with a single draw call. This is also used for various permanent effects like footsteps, blood and slime trails. When a bush or rock is destroyed, only that small section in the level image is replaced.
Each level increases in size with more enemies of increasing variety and difficulty. To help keep difficulty consistent, harder enemies are weighted higher so there can be less of them. The enemy types are chosen from a pool that grows each level by adding an additional type or variant.
The enemies are visually inspired by Zelda, but their behavior is designed for boomerang combat. There are 3 different classes of enemies and several variations. To eliminate the need for a path finding system, I designed around the problem by giving some enemies the ability to jump over or destroy level objects. This can create some interesting combat situations because of the different ways enemies interact with the environment.
The first enemy the player fights is a blue slime that divides into two smaller/weaker slimes when killed, similar to the Zol enemies in Zelda. They move around erratically but biased towards the player. More aggressive red slimes with double health can spawn in later levels. They can’t break through bushes or rocks and move slower over sand so the player can use this to help control them.
Slimes use only a single sprite frame so they are animated by scaling their width and height using sine waves. They also leave a persistent trail behind them as they moves.
This enemy type is inspired by the Pols Voice enemy from Zelda. The image to the right shows how the visual design changed from the Zelda sprites. These enemies are able to leap over level objects to get to the player, which helps prevent them from getting stuck.
Jumping enemies also add a timing element to the boomerang combat. They can only be damaged while on the ground, so this leaves a brief window for the player to attack. Their jumping movement also works well with the shadow rendering system to help show their height above the ground. Because this enemy moves by jumping, it is not slowed by sand.
This is the most difficult type of enemy that the player will encounter, inspired by the Iron Mask enemies in Zelda. The four frame walk cycle is similar to the player’s. You can see from the image to the right how the Zelda art was used as reference. I couldn’t resist adding a butt hole!
This enemy was designed to challenge the player’s boomerang throwing ability and mix up combat. Because there is a shield guarding their front, they can only be damaged from the back or sides! If a boomerang hits their shield it will bounce off with reduced speed often leaving the player scrambling to retrieve it.
Another feature of this enemy is the ability to break through level objects and charge at the player. This eliminates the need for path finding while helping to keep the play space dynamic.
As you might have guessed, the character that steals the player’s heart in the opening scene is also the final boss. This enemy is actually a variation of the shielders with special AI and the shield on it’s back. So it can only be damaged from the front, but it also has the ability to move backwards!
The final level’s layout is set up to facilitate a fight with this enemy. The map is is preset to be a large open field completely filled with trees and rocks. There is a short scene that plays out at the start to set things up where the boss runs out from the portal towards the center of the level. A heart is created out of rocks and bushes by checking pixels values in the heart container sprite. Hopefully this is a special surprise for the player and adds to the mystery.
After some time passes or if the boss takes damage, it will become enraged and quadruple in size. It’s AI is more aggressive then other enemies and it has significantly more health. Crafty players can snag the heart container before the boss fight is triggered.
Unlike other enemies, any pickup it touches will be instantly destroyed, as is demonstrated by the opening cut scene. This was done to increase difficulty of the final battle by preventing too many pickups being available amid all the destruction.
To help keep gameplay interesting there are two variations of enemies that are introduced at later levels and can be randomly applied to any enemy type. These more difficult variants count extra towards the enemy spawn limit, so the difficulty is consistent.
Giant enemies are larger and slightly more aggressive with more health. This is a simple way to create “mini bosses” with only a tiny bit of code. Giant enemies also appear larger on the map and drop more pickups when killed.
Invisible enemies have a special rendering pass that makes them difficult but not impossible to see. They are 90% transparent but their shadow is rendered normally and they briefly become visible when damaged. They also show up on the mini map and leave trails and blood splatters the same as normal enemies. So there are multiple ways that a player is made aware of their presence.
It is possible for both variants to be applied to create a giant invisible enemy, truly a fearsome foe!
All of the money the player earns in the game can be spent in the shop. To help encourage players to continue playing, their money is never lost, even if they die or the page is reloaded. The goal of the shop is to open up choices for players about how to customize their experience and keep them invested. Giving the player some opportunities to to make long term strategic choices is an important part of any game.
The shopkeeper is inspired by Zelda as you can see in the image to the right. The character is only one frame, but animated to face the player and jump when an item is purchased. As an added twist, the shopkeeper can be killed by the player or enemies and all the shop items will also will disappear!
The shop spawns randomly on most levels and contains a 3-4 random items with slightly randomized prices. The shop in the hub world always has the same 3 items for the same prices so players can buy something at the start if they have enough money saved.
The art style for this game was based on one of my favorite games, “Zelda: Link’s Awakening” and all of the tiles were referenced from that game. I made some tweaks to everything but some much less then others like the bush and rock. Other things like the player and enemies were completely redrawn. Some tiles like the hearts are just too basic and iconic of shapes to change. Mostly I tried to lean into the nostalgia while adding some new elements that fit thematically or mechanically.
All of the sprite data fits on a 128×96 pixel image using only 14 colors and an alpha channel. The main character uses the most space with 9 frames of animation. The entire tile sheet compresses down to around a 2k png leaving 11k for code.
All of the sound effects and the intro music are played using my open source sound effect generator ZzFX. This is an audio system that I originally developed as a JS1k demo. Since then I have released a much improved version for use in JS13k games.
There are in total 16 different sound effects used in bounce back! Most of the sounds were used pasted directly from the ZzFX browser. To make the coin pickup sound more special whenever it is played, I set a trigger to play it again immediately at a higher pitch.
This is my attempt at using ZzFX for music, it was one of the last things added and space was extremely limited at that point. There are 3 different sound effects used by the music: a kick, high hat and synth. The sounds are triggered at regular half second intervals to form a tempo. The percussion is a short step sequence with some randomness. The melody is generated by taking a random walk around an A major pentatonic scale, always returning to the root note every 2 measures.
I was delighted to see that several other people used ZzFX in their JS13k games with amazing results! One game I want to highlight is “The Wandering Wraith” by Mateusz Tomczyk. The sound design is excellent and uses ZzFX to create a very convincing ambient wind track.
Over 10 other games used ZzFX audio, there is a full list in the ZzFX readme! Next year I plan to have even more options available for ZzFX and better support for music.
One of my favorite things about developing this game was designing all the little systems to enrich the experiences. While most of these use only a few lines of code, they contribute greatly to the overall polish. It was a challenge to get the most out of these systems while keeping everything as simple as possible.
Shadows & Hit Effects
For use in special effects, black and white masks of the tile set are automatically generated. These masks have the same alpha as the tile image but no color. Here’s what the actual mask look like, green shows where alpha is…
The black side of this mask is used for the perspective shadow that is automatically applied to all game objects. A canvas transform is used to skew and position each shadow based on the object’s 3D position and global alpha controls transparency.
The white side of the mask is used in several different ways. When an object takes damage, it will briefly flash with inverted colors. Pickups will randomly flash white to help make them stand out and seem special. Also when the player is dashing, there is a white trail that follows behind.
The UI shows the players health, boomerangs and money earned. If the player’s health reaches one heart or less, the hearts animate and play a warning sound. This effect speeds up even faster when the players health is at half a heart. An in game cursor shows the mouse position using inverted colors. During speed runs, the current game time is shown in the top center of the screen.
A mini map displays the level’s shape and reveals new areas as the player explores. On the map a radar system shows where enemies and objects are, but only in explored areas. The mini map has another other useful feature, lost boomerangs appear as large squares so they can be easily located.
There is no traditional menu system interface, both to conserve space and make the experience more immersive, so all other parts of the UI are integrated into the gameplay. Players can choose to warp or start a speed run by entering the appropriate portals. Items can be bought from the shop just by touching them.
I shouldn’t need to mention this, but I’ve noticed that many jam games have no way to actually pause the game! The pause system for this game is very simple and works automatically whenever the window is loses focus. The word “-paused-” is displayed on screen and all action and sounds are stopped until the window regains focus. It also clears out the keyboard info when unpaused so keys don’t get stuck.
Whenever a new level is loaded in the game, there is a circular transition effect instead of a hard cut. This is done by first saving off the current canvas when the transition starts. Then at the end of each frame, that canvas is rendered using a clipped circle that expands from the center. This creates a quick and seamless level transition effect, maybe too seamless because most players don’t even notice it!
When the hub world starts there is a short title sequence that plays out. I wanted to do something special for the title without using much extra code. So, I came up with a neat idea to bounce the two words off of each other. This code is also available as a 140 character dweet!
Blood, trails and footsteps
I added a simple system for blood trails and footsteps to give permanence to the environment. This is done by drawing colored ellipses to the level/ground canvas. Due to some early feedback, I changed all the enemy blood to be additive non-red colors. Footsteps are drawn in a similar way and change if a character is walking through sand.
The particle system was greatly reduced from what I originally implemented to make space for more gameplay features. The only particle effects that survived are used when an object takes damage or is destroyed. There is a very simple system to emit particles with some random parameters like size, color, time, etc.
The behavior of the portal to appear when the final enemy is killed and pull the player in was inspired by “Nuclear Throne”. I like how it creates a clear goal and encourages the player to keep moving forward.
For the visuals I wanted to make the exit portal stand out from all of the pixel art. The code to draw it is based on a dweet that I wrote a few months ago. The shadow is rendered using the same system as the sprite objects. There is extra code for the warp portal that shows the next level and the speed run portal that displays the best completion time. I’ve recently converted the portal effect to a dweet!
There are two types of collision detection in the game, hard and soft. Hard collision prevents movement while soft collision does not.
The level tiles comprises the hard collision which is basically a grid of squares. During each object’s physics update, it checks if the next position will overlap a solid tile. If a collision is detected, the object is prevented from moving in that direction. Movement that ends inside collision is not allowed, so there is never the possibility of interpenetration. Objects can also slide along collision because each axis is checked independently.
The soft collision is used to check if objects are touching. This compares distance between the 3D position of both objects versus their radius. Because it uses height, enemies like jumpers will not collide with boomerangs or the player when above them.
In addition to what the game actually shipped with, the development version has some extra debugging tools. Some of the most useful features are a screenshot saver, object visualizer, and debug rendering system. My own personal build also uses ccapture.js for capturing video and gifs.
It was a massive challenge fitting all the above and into a 13k zip! My primary strategy was to write clean simple code that can be minified by Google Closure. I slowly built up my game, doing a minification pass and checking the zip size every few days. I often made difficult choices about simplifying or cutting systems. This in turn inspired new ideas about how to reuse or combine systems in different ways.
Most of the code is built around an object oriented architecture where nearly everything in the game derives from GameObject. There are several other utility classes like Vector2, Color, and Timer which are stripped to their bare essentials. I tried to keep the code as simple as possible and did a daily clean up pass over the entire code base. During development I used Trello to keep my tasks and ideas organized. After the game jam I did another clean up pass and added more comments for the open source GitHub version.
My minification process was not as automated as I would have liked. First I need to combine the js files and remove all the debug code. Then run it through Google Closure, Terser, zip it, and zip it again with advzip to squeeze out a few more bytes.
If you’d like to learn more about how my code works check out the GitHub repository with the full source code for the game and engine. It’s open source under the GPLv2 so you can even use it in your own projects!
Areas for Improvement
I’ve learned a few things both during and after the game jam and here’s a some ideas I have for next time.
- Improved Minification – There are many places I could have squeezed out a few more bytes but didn’t want to risk introducing bugs. Also it would be great if my minification pass was more automated. Another problem is that Google Closure works great for most things but also adds a bunch of unwanted code to my game.
- Combining the Vector2 and Color classes into a more flexible Vector class.
- Image Compression – I thought my png couldn’t get much smaller but there are some tools available like Tinypng that can do even better. Running my tile sheet through that tool would have saved an additional .12k!
- Better Audio & Music – My ZzFX sound generator worked out great but I want to add more features for next year. Also I’d like to have built in support for music.
- After extensive testing I decided to use requestAnimationFrame as the fixed 60 fps update loop rather then a setInterval because it was noticeably smoother in both Chrome and Firefox. However I have some concern that 60 fps is not guaranteed and I may need to add some extra code to deal with that.
Thank you for reading (or at least scrolling to the end) of my epic JS13k postmortem! This is the most work I’ve ever put into a game jam game so it was a real challenge explaining it all in somewhat readable format.
If you have any questions or feedback, drop me an email or leave a reply below. I plan to release an extended version of my game with balance changes, gamepad support, and more content, so look out for that soon! Follow me on twitter for more awesome original content like this and prereleases of all my projects.Bounce Back Postmortem: A Zelda Style Boomerang Game for #JS13k Click To Tweet
Bounce Back got 2nd place! Thank you to everyone who played my game and congrats to my fellow contestants.
Here’s how my game fared in each category. I’m told these are out of 54…
- 42 – innovation
- 46 – fun
- 45 – theme
- 47 – gameplay
- 51 – graphics
- 46 – technical
- 277 – total (out of 324)
While making some late night optimizations I created a bug in more ways then one…
I just fooled myself into thinking there was a real life bug on my screen. ?— Frank Force (@KilledByAPixel) September 15, 2019
Here’s the first tweet I posted that shows gameplay…
Mostly been working on the engine so far, polishing and adding cool little features.
Some art is still from #Zelda, I'm gradually replacing it. Focusing more on gameplay now!#pixelart #gamedev pic.twitter.com/GvNh37yMp1
I made the player’s walk cycle into a 140 character dweet…
https://t.co/NHFwAWLiDn— Frank Force (@KilledByAPixel) September 15, 2019
Helping encourage people to finish up their JS13k games…