I've used my holiday break to make an original .io game from scratch. It took me 5 days.
Here, I'll share how you can do the same.
Why?
Cuz it's my passion.
Before I ventured into a software development career, I used to make a physical card game that teaches kids about environmental issues.
I poured my soul into that game, and I've wanted to make more games ever since.
Cuz online game dev has so much learning potential.
Take a look at the above image (from MDN game dev web page).
As you can see, it has so many surface areas that you can dig into.
Being a good software developer is all about being a constant learner. This excites me.
What's in it for you?
Have you imagined yourself as a game developer someday ? You know how to program something, but you aren't really sure how to start as a game developer?
This tutorial serves as yet another article to give you that push to finally start making your own game.
Target Audience
I'm assuming that you have a little bit of programming experience already. When I describe the steps in specific terms, I expect you to be able to "Google it" for supplementary knowledge.
But don't worry.
I've also provided the commit by commit instruction in each step. When you're lost in the instruction, look at the code diff to make sure you're on track.
Table of contents
- The game I made
- Part1: Make a local development environment (Day1)
- Part2: Design the game elements (Day2, Day3)
- Part3: Adjust the elements to a "playable" state(Day4, Day5)
- Takeaways
The game I made
It's like a 2D tank game, where you'll navigate your character to beat an opponent's character. But there's one twist.
You'll have to play "Rock Paper Scissors" when you collide with opponents. So you'll need to be creative on how to adjust each character's "shape" before they collide.
You can play the game via this link. It might take a few seconds before it boots.
Part1: Make a local development environment (Day1)
First of all, let's focus on making a foundation for a local development environment.
(Make sure you have a Node.js on your machine. I'm using MacBook + Homebrew)
Part1-1: Build HTML/JS via Webpack
- Grab your favorite package manager. NPM should be fine, but I like Yarn more.
- Install Webpack for your project directory. Again, you can pick alternatives if you know what you're doing :)
- Create a JS/HTML template to conform to the Webpack build cycle.
- Try running your build, e.g.
yarn webpack --watch
- Access the file to see your progress. e.g.
open dist/index.html
This commit represents the progress so far.
You should be able to see some messages on console + HTML.
Part1-2: Draw something on canvas
Next, let's make sure you'll be able to draw/manipulate objects using the HTML canvas element. I've used this tutorial to get familiar with the basics.
- Draw a circle on the canvas.
- Add an event listener to the keydown event.
- Write a JS "game loop" function so that the drawing happens continuously as you move the circle.
- Assign dynamic coordinates(x, y) to the "draw circle" function so that that cursor keys can move the circle.
This commit represents this part of progress.
Yay! your circle should be moving as you press your cursor keys :)
Part1-3: Prepare web server
Finally, let's spin up a web server. This step prepares you for the next step, where you'll be moving your game state to the server-side.
Server-side dictating the game logic is crucial to provide a consistent online game experience.
To connect server-side logic with the user's input + rendering, we'll use a technology called WebSocket.
WebSocket enables efficient bi-directional messages between clients and servers.
Let's first set up "connections." Actual event implementations are explained in later steps.
- Install and spin up Express web server.
- Install and setup WebSocket connections between the server and the client, using a library called Socket.io.
This part of the commit looks like this.
Part2: Design the game elements (Day2, Day3)
So far, we've created the following components to as scaffolding to build games on.
1. HTML/JS + Webpack to build(package) them.
2. Canvas + EventListeners to render/interact with visual elements.
3. Express web server + WebSocket to establish bi-directional connections.
Recapping what we're building
It's useful to have a summary of what kind of game you're making at this stage. Below's my version:
Game Idea memo
<Game Objects>
- Player object: to move around in 2d canvas.
- "Rock Paper Scissors" Coins: to place in 2d canvas.
<Game Logic>
1. When the Player collides with Coins, the Player will change its shape to one of "Rock" / "Paper" / "Scissor."
2. The shape change is based on the maximum number of coins you've collected at a given moment.
3. When Players collide with each other, the actual "Rock Paper Scissor" game takes place. It uses the current shape of each player object.
4. Winner stays in the game. The loser is kicked out of the game. The last person to remain in the game wins all.
Let's build out the proof-of-concept, according to the game memo.
Part2-1: Translate the game logic to data models
One useful step is to translate the game logic described above to actual data models our server should use.
Don't try to be perfect. Pay attention to "what's the foundational properties each role has to have?". We can add more properties later as the game gets sophisticated.
I started my data model as the following:
{
players: [
{ id: yuta, x: 0, y: 0 }, ...
],
coins: [
{ kind: Rock, x: 10, y: 10 }, ...
]
}
Pretty straightforward. The game has two arrays, with each object holds its x y coordinate. The coin has kind
property to represent Rock/Paper/Scissor to have a different effect.
Separate game logic from rendering logic
To provide consistent/fair UX among players, we should express game logic on the server-side. The client-side (JS run in browsers) will only be responsible for two things: rendering the game state and sending user inputs to the server.
We can imagine this separation as a continuous cycle of inputs -> state change -> rendering -> next inputs
.
Part2-2: Connect server-side logic to client-side rendering
First, let's tackle the part where the server sends data models to the client-side. For the above, it'll simulate the state transition -> rendering part.
- Convert the data model to JS Objects. Put that in server-side JS.
- Prepare server-side game-loop.
- Have WebSocket send the data model as
gameUpdates
to clients. - Have clients output received data models.
This commit describes this part.
You should be able to see the circles at the coordinates/size defined on the server-side. 🎉
Part2-3: Connect client-side inputs to server-side logic
Next, let's express the other half of interaction: client inputs -> server state transition.
- Bridge the user input eventListener to the WebSocket event.
- Have the server-side update its data model by the inputs received from the client-side.
- Change the static data model to the dynamic one.
The commit and the successful output is as follows:
Keep it up!
Congrats on making it this far! You already have 80% of the foundational code to make a 2D online game.
Part3: Adjust the elements to a "playable" state(Day4, Day5)
Now that we have most of the elements ready, how do we turn this into a "playable" game?
Let's come back to the game idea memo:
Game Idea memo
<Game Objects>
- Player object: to move around in 2d canvas.
- "Rock Paper Scissors" Coins: to place in 2d canvas.
<Game Logic>
1. When the Player collides with Coins, the Player will change its shape to one of "Rock" / "Paper" / "Scissor."
2. The shape change is based on the maximum number of coins you've collected at a given moment.
3. When Players collide with each other, the actual "Rock Paper Scissor" game takes place. It uses the current shape of each player object.
4. Winner stays in the game. The loser is kicked out of the game. The last person to remain in the game wins all.
Part3-1: Translate game logic to a player's story
It's important to tell a "game dynamics" from a user's point of view. From the moment a player joins a game, how does it progress? How does it end? Do they want to come back after one game ends?
<Player Story>
1. At the beginning, players start with the "Rock" state. At this point, colliding with each other means "tie": nothing happens.
2. As you move the object, you'll start placing some Rock Paper Scissors coins.
3. You'll watch out what state opponents are in. Depending on that, you'll either try to change your state to a winning one or change the opponent's state to a losing one.
4. When it's ready, tackle and collide with an opponent.
5. If won, you'll readjust your state so that you can keep winning with others.
Let's use this sample player story as a guide to build the rest.
Part3-2: Enable player-to-coin interaction
Let's address the part where each player's state changes by the coins collected so far.
- Add
state
property to each player's data model.Rock:0, Paper:0, Scissor:0
. - In the server-side game loop, make collision-detection happen between players and coins.
- Change the state of the player object, depending on the sum of collected coins.
- Have client-side JS express the state change (for now, I'll just change the colors).
It will look like this:
You might wonder why coins won't appear on the screen. This is because the coin you've just placed collides with the player "right away".
There are several ways to fix this, but I'll do it in the next steps.
Collision Detection? What?
It might sound intimidating at first, but good articles/libraries are already out there to support you on this topic.
See this article for a good introduction to the subject.
Part3-3: Enable player-to-player interaction
By reusing the player-to-coin interaction above, we can build a player-to-player interaction.
One key difference here is that the player-to-player interaction will produce a "Rock Paper Scissor" event to determine the winner of that collision.
Here, I'll introduce another data model called match
to express this state.
match = { loser: webSocketID, winner: webSocketID }
This will enable rendering the win/lose result to the client-side.
- Prepare a
match
data model to express a win/lose state. - Implement player-to-player collision detection (reusing the method implemented in the previous part).
- Implement the rule of Rock Paper Scissors.
- Fill in the
match
data model with the result and send it to the client-side. - Output the match result on the client-side (for now, it'll output to the console).
Part3-4: Replace rendering objects with actual game images
You're almost there! Let's replace the placeholder circles with Rock Paper Scissor images to communicate the game concept.
- Prepare images for player objects, as well as coin objects.
- Replace the circles with the images.
- Ajudst the x y coordinate of images so that center of the image will point to the coordinate.
3rd step requires the knowledge of setTransform
from Canvas API. There're many good articles out there to do this. MDN API doc is always a good start.
Sidenote: Delay the player-to-coin collision detection
Above, I've described player-to-coin collision happens right away, hiding the coin object from appearing on the screen.
I can think of several ways to solve this (A. Change the coin concept to "bullet," shooting them from your player's object. B. Place the coins a little bit in front of you, ...).
In this post, I've chosen the option to "delay" the collision detection so that you can use coins both for you and for other players.
I've added the placedAt
property to do this. placedAt
will express the elapsed time since coin placement. This enables the small gap before the coin starts interacting with player objects.
Part3-5: Complete one game cycle
As the last step, let's supplement the player story with basic instructions.
Think from the user's perspective. Users would know nothing about how your game works when they first see it.
- Prepare a win/lose result text as HTML.
- Show the result text when the match is decided between two players.
- Implement "clean up" logic for the lost player. (Disconnect WebSocket connection, Stop rendering, Delete the server-side objects).
- Prepare Game Rule section as another HTML content.
Below's the screenshot of result display (top: winner side. bottom: loser side).
Choose where to deploy your game
If your objective is to prototype a casual game, I'd recommend Heroku Node.js deployment. It is easy to use, and the free tier should be enough to showcase your work.
The result
Congratulations!
You've now completed the first full cycle of the game-making process.
Let's recap how far we've come by looking at the repository and its commit history.
Takeaways
Making a game itself is only half the outcome. Here're a few tips I've learned along the way:
1. Revisit the process as composable steps for the next game
Organize your commit and game elements so that you can reuse the patterns in the next game.
In this post, we've learned:
- Canvas 2D rendering + key-based controls
- Games that involve Collision Detection
- Casual multiplayer online games (.io games)
Eventually, you'll build your own pattern of techniques + elements, making the process tailored for your creativity.
2. Look for relevant articles + people who are doing the same thing
MDN is always a high-quality resource for web-based technologies.
It's critical to find likely-minded people who are already doing the output of their work.
Victor Zhou's blog on How to build a .io game from scratch helped me tremendously on this.
Be careful not to be swamped by the sheer amount of articles you'll find on the Internet. Not everything is high-quality.
A good measurement on when to stop searching is, "have I collected enough keywords/terminologies to unblock myself?".
3. Do a lot of deliberate practice
This is true to any kind of learning. The Game-making process is no exception, as it is a lifelong journey to create a game that's fun and satisfying.
Focus on the learning topic one at a time. There're many relevant technologies (Audio, WebRTC, Network programming, ...) and game genres(RPG, action, puzzle, ...) you could try. Trying to learn many things at once will degrade the learning.
Don't focus too much on "How to make people play my game?". Focus on "How can I share the learning within the game-making process?"
The latter will get you to a network of like-minded people, which is sometimes more important than the game itself.
Feedback is welcome!
I'll now focus on making the next game. Please give me comments/feedback on what you've thought of the article.
My Twiter account is kenzan100. Please follow me if you've liked my content.
See you next time!
Top comments (1)
Thank you so much for taking the time to document this! You explained things very well and made it very easy to follow what you were doing in the Github.com repo. I actually took a lot of inspiration from this when designing the hackathon challenge for ng-conf 2022. It was a lot of fun to see all the pieces come together and play over the internet.