How to Build an Arcade Style Video Game in JavaScript
I built a game in JavaScript. Here's how I did it.
In this article, I share my experiences in building a fun, javascript canvas game for the DEPT® marketing team. You can play it here or see the the code in the Github repository.
Recently, I received a ping from my manager asking if I wanted to join a quick brainstorming session with a marketing manager we work closely with to ideate something fun. I’m always up for fun, so I eagerly hopped on to Zoom.
The goal was to come up with something interactive that we could use to showcase our experience building Digital Products here at DEPT®. We have many case studies (and they’re all great!) but we wanted something fun and interactive. Something that a user in this industry could engage with — and perhaps even have a few laughs.
It took about five minutes before we decided it should be a playable game. “How about a pixelated, old-school Nintendo game like Zelda or Pokémon?”
These top-down RPG-style dungeon-crawlers were a blast to play, so we figured we’d throw a contextual spin on the idea. Rather than playing the role of a heroic character exploring a world and fighting enemies, you were someone in the tech industry exploring a modern office and dealing with common problems that we’ve all experienced in some way or another within the realm of product development.
The idea was set! The only problem is that none of us had ever made a game before. After all, this was just an internal side-project. Is this something we can actually pull off in a short amount of time… or is it going to take months? Realistically, we only had a couple of weeks, so we had to work fast and smart.
One of the best answers an engineer can give when faced with a question of this nature is “I can’t definitively answer that question right now, but let me do some digging and get back to you soon.”
So I went digging.
Exploration
I enjoy working with all technology but my career started in front-end development, so I’ve certainly done my fair of motion design with CSS transitions. I’ve also worked with more powerful JS-based libraries like Greensock and Framer Motion. These libraries are excellent, and while you could certainly use them to build a game of sorts, they didn’t feel like a silver-bullet in that regard.
If I was going to pull this off, I knew I needed a framework that could handle basic game mechanics. Things like loading and managing assets, the ability to control them with a keyboard or touch screen, detecting proximity and collision with other elements in the world I was building, and some sort of state to manage progress throughout the game.
I’m not an expert in the web-based gaming space, but one thing I do know is that many games are built on HTML5 canvas. And for good reason; canvas is powerful, performant, and widely supported across different browsers and devices. I knew this wasn’t the only way to achieve my solution, but it was the one that felt the most promising based on my prior experience and the short amount of time we had to pull this off.
Over the past couple of years, I worked on some small 3D projects using three.js, which renders complex WebGL graphics to a canvas. Reaching for three.js was my first inclination for this reason, but I quickly recalled some of the hurdles I ran into with my past projects. Perhaps the most pertinent of which was that it required 3D models — and someone had to make those. I know enough to be dangerous in programs like Blender, but modeling game assets would have taken me ages, and they probably would have looked pretty awful.
The good news is that this was meant to be a tongue in cheek project with 2D arcade-style pixel art. Low fidelity was good in this case.
After some quick googling, I found that there were a number of JS-based gaming frameworks that cater to this specific use case, a wealth of free and reasonably-priced royalty free sprites (characters, textures, world objects, etc.), and a vibrant community of passionately knowledgeable developers that had created some truly inspiring games.
Sigh… I love the open source community…
Technical Approach
In most scenarios, I opt to spend as much time as reasonably possible doing my research before deciding on a definitive solution, particularly when it’s a scenario I have little or no experience in solving for. There’s nothing worse than finding out you’ve painted yourself into a corner towards the end because you were cutting corners in the beginning.
To be honest, I was feeling anxious about pulling this game off. Not only did I have to learn a new framework, but I also had to build the actual game using the new framework. I’m confident I can do anything I set my mind to, but I didn’t have much runway to get this done.
I decided that the best approach I could take was to just pick one and start building something. I didn’t know exactly what the end-product was going to look like, but I did have a good understanding of what aspects were going to be needed. Can I render a character, move it around an empty box, and have it bump into something? If so, that’d be a big step in the right direction, and then I can work from there.
Phaser
I spent the better part of a week test-driving a wealth of different framework options. Each one was unique and impressive, but I quickly found myself drawn to Phaser for its comprehensive documentation, countless interactive examples of community-driven solutions, and its approachable API. As if that wasn’t enough, it was also well-typed (I love me some TypeScript!).
Phaser also boasts built-in solutions for all of the integral features I knew I would need to draw upon, including but certainly not limited:
- A WebGL and canvas engine with device scaling; in other words, the ability to reliably display the game at an optimal size and frame rate across modern devices.
- Asset preloading and scene management; the ability to show a progress bar while the assets load and have an intro and game over screens.
- Camera capabilities; the ability to follow our character around the screen based on its movement.
- Input control bindings; the ability to support keyboard, mouse, and touch across modern devices.
- Sprite animations and tilemap support; more details on this in a later section
I spent a couple of days following tutorials to build some small and extremely basic games, but I was excited by how quickly I was able to get up and running. I’d like to give a huge shout-out to the hundreds of contributors who provided examples on the Phaser site.
The first game I pieced together was a simple “Flappy Bird” game where you make a bird fly as far as you can through a series of pipes. For each set of pipes the bird flies through, you get a point. If the bird hits a pipe, the game restarts.
Go easy! I know it’s laughably simple, but there’s more mechanics here than you might think. At a high level:
- The bird is an animated sprite that can be controlled by mouse or keyboard input.
- The pipe sets are randomly generated and get recycled to be closer and closer together once they go off-screen, which makes the game progressively harder.
- There is collision detection between the bird and pipes, which trigger an event to restart the game.
- There is the concept of “state management” with regards to the score and the number of times the game is retried.
These are the kind of mechanics I needed to understand. Once they’re understood, they can be employed in any context. If you think about it, this isn’t terribly different from the finished product. Sure, it’s much simpler, but it still has the concept of a controllable character within a canvas “world” full of interaction points.
Anyway, that was enough flapping about for me. I was ready to move on to building the actual game.
Sourcing Pixel Art
I’d love to be able to say I designed all of this amazing pixel art, but the credit goes to LimeZu (https://limezu.itch.io/). LimeZu is an incredible artist and asset designer and we were able to bring our vision to life with their Modern Office and Modern Interiors sprite packs.
If you are interested in game development, you should absolutely check out their offerings. Not only were they cohesive and easy to work with, but the selection of assets in each pack is vast.
Read on to see how we took these incredible assets and built a unique world with them.
Setting Up the Repository
Having never built a game before, I wasn’t entirely sure how to structure the codebase at first. However, since Phaser merely renders a canvas, it became apparent I could structure the codebase in any way I pleased. At DEPT®, the majority of the projects I work on leverage PWA (progressive web app) frameworks like Next.js, so I opted for this direction.
Considering the final product, Next.js might have been a little overkill, but it was important to choose something that had the flexibility to scale. If we were to iterate on this game in the future, it might be convenient to have page routing and API support available. Plus, Vercel makes deploying a production app a cinch.
Within mere minutes, we had our environments setup and with continuous deployments going out on merges to the main branch, which made it convenient to share progress with the team as I iterated through the build.
We’ve also made an instance of the repository publicly available. You can download or clone it here if you’d like to follow along.
Out of respect for the artist, we’ve heavily watermarked the imagery we licensed, so running the game in your local environment will look odd with superimposed watermark text on the sprites, but it will still be functional. If you’d like to use the original assets in your own project, you can purchase them, and other great artwork from https://limezu.itch.io/.
Execution
The idea of rendering an object and moving it around isn’t a tough concept to grasp. But one of my first questions was “How does one actually code a 2D world that includes a floor you can walk on, with walls and objects that you can’t walk through?”
This was the one major thing that Flappy Bird didn’t reveal to me, but it was the next problem I wanted to solve since the vision of the game was based on this virtual office that you could move your character through.
Building the World with Sprites & Tiled
How do we code the “world”? The answer is actually quite simple. It’s entirely composed of slices of pixel art sprite sheets that are then mapped into a certain position on a matrix of coordinates. This matrix of coordinates represents our “world”. At the end of the day, this matrix (along with paths to the sprite images it uses) is exported to an auto-generated JSON file that can be consumed by our framework.
Perhaps that doesn’t quite read as simply as it truly is, so let’s break it down further.
A sprite or sprite sheet is an image — typically a PNG for the purposes of alpha transparency — that contains a bunch of other images within it. Here’s an example of a small portion of one of the sprite sheets we used to construct our office:
At first glance, it looks a little odd, but bear with me. It’s not visible, but sprite sheets are positioned on a grid, and each grid item (referred to as “tile” in the context of map) or groups of adjacent grid items can then be designated to tile(s) on our map.
This sounds like a pretty tedious process, and it absolutely would be if we were to do it manually, but thankfully, there’s a free tool called Tiled to do the heavy-lifting for us.
With Tiled, all we have to do is create a new empty map, set its size, and specify how big our tiles are. In our game, our map is 100 x 100 tiles with a tile size of 48px. That’s a total map size of 4,800px by 4,800px.
48px might sound arbitrary, but it’s actually the format of the spritesheets that we had acquired. To accompany our map, we can import these image assets into Tiled and begin placing portions of the sprite on our map.
Perhaps one of the most convenient features of Tiled is that you can create different “tile layers”. This is what allowed me to create a floor as well as walls and other decor (such as desks, plants, and chairs) that our player can’t walk through. Phaser allows us to granularly parse this map and set the specific layers we specify in Tiled to allow or disallow collisions.
Building the map was a blast and thanks to Tiled, it didn’t take long at all. Here’s this finished map:
If you’d like to better understand how this map was built, you can download Tiled, clone the repo and open the file located at public/office/world/map.json in Tiled.
Main Character & NPCs
Now that I had figured out how to build a virtual office using Tiled, it was time to bring it to life! The premise of the game is for the main character to move through the map and interact with different employees in the office, so the next logical step was to add those characters to the map.
Phaser’s development patterns encourage class-based inheritance, which naturally makes reusing common functionality convenient and keeps the codebase clean. Once I programmed the main character, it was easy to program the NPCs (non-playable characters) since they all have similar qualities. For example, each character can walk up, down, left, or right — or stand idle while facing up, down, left, or right.
The only thing inherently different about how they behave in the world is that you can only control one of them (the main character versus the NPCs) and that each one has a different sprite sheet (so each character appears aesthetically different).This means that we can simply create n number of class instances (one for each character) and associate each instance to a unique character sprite file.
Check out this character sprite:
If you look closely, you’ll notice each frame is slightly different. Phaser allows us to take in this sprite sheet and designate an array of frames for every combination of their current state (idle or moving) and their current direction (up, down, left or right). Each array of frames represents a loopable animation and we simply trigger the appropriate animation based on what the character is doing. For example, if the character is walking to the right, we tell phaser to play the character’s rightward walking animation. When the character stops, we tell Phaser to play the character’s rightward idle animation.
In the end, we have our one main character and 11 NPCs. Each NPC is an instance of a single class and each is mapped to their own sprite sheet. Now I just had to place them somewhere on the map. Of course, I could have crunched some numbers and placed them at specific map coordinates, but fortunately Tiled makes it much easier than that. All I had to do was create an “object layer”, place some markers on the map. Each marker can be named and I can then use this name to associate the designated placement point to one of my character class instances.
Here’s the map, now updated with NPC placement points:
Interaction Points & Dialogs
We’re making good progress so far! We have an office to walk around in and people sprinkled throughout. Next, I wanted to make it so you can interact with a character.
To make the interactions easy to initiate, I figured it made sense to have the NPCs approach the main character when the main character was within a certain distance from the NPC, rather than forcing the player to move the main character right up against the NPC.
This took a little bit of trial and error, but the solution I ended up going with was quite simple and worked just as I had envisioned. Back in Tiled, I created invisible interaction zones with object layers for each NPC.
Once I registered each zone in Phaser, I was able to make it so an NPC would face the direction of and walk up to the main character, where a prompt dialog would then be shown.
There’s nothing particularly special about the prompts; they’re simply a React modal that pops up over the canvas and feeds question, answer, and response content from as JS object. I opted to handle these content interactions outside of the canvas. Since the canvas can scale based on the device’s screen size, the text within it could potentially shrink to an illegible size. As a result, rendering them outside of the canvas for accessibility purposes made the most sense.
Finishing Touches
At this point, we had a working game, but I also wanted to create a splash screen from which the game could be started, as well as an outro screen that gave the player their score when they left the office at the end of the game.
Phaser has a “scene” feature that comes in quite handy. When a game first mounts in the browser, you specify the first scene it should display. In the case of our game, we had several scenes:
- The “Loading” scene: This simply preloaded all of the assets and displayed a progress bar.
- The “Intro” scene: Once the assets are loaded, the “Loading” scene triggers this scene to begin. It has some text and imagery along with a button to start the game.
- The “Game” scene: Once the button in the “intro” scene is clicked, the player can then play the game and move their character throughout the office.
- The “Outro” scene: Once the main character collides with a special “door” at the end of the level, the “Outro” scene is triggered where the player can see their score. From here, they can play again, or go to a special landing page to learn more about their choices.
Conclusion
That’s our game! We started from scratch and launched a finished product in about 3 weeks. If you happen to know anyone at Bethesda or CD Projekt Red, be sure to send them our way ;)
In all seriousness, the game is silly and perhaps a little rough around the edges, but it’s meant to be. And it’s a great example of how quickly we can bring things to life here at DEPT®. We love technology, and we love a good challenge.
If you haven’t checked it out yet, you can play the game here. Interested in the code? Here’s the Github repository.