DEV Community

Yuta Miyama
Yuta Miyama

Posted on

Make an original io game in 5 days

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.

game screen

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.

MDN game dev

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

  1. The game I made
  2. Part1: Make a local development environment (Day1)
  3. Part2: Design the game elements (Day2, Day3)
  4. Part3: Adjust the elements to a "playable" state(Day4, Day5)
  5. 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.

game-screen

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

  1. Grab your favorite package manager. NPM should be fine, but I like Yarn more.
  2. Install Webpack for your project directory. Again, you can pick alternatives if you know what you're doing :)
  3. Create a JS/HTML template to conform to the Webpack build cycle.
  4. Try running your build, e.g. yarn webpack --watch
  5. 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.

1-1 progress


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.

  1. Draw a circle on the canvas.
  2. Add an event listener to the keydown event.
  3. Write a JS "game loop" function so that the drawing happens continuously as you move the circle.
  4. 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 :)
circle-moving


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.

  1. Install and spin up Express web server.
  2. 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.
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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 }, ...    
  ]
}
Enter fullscreen mode Exit fullscreen mode

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.

input->state->render->next-input

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.


  1. Convert the data model to JS Objects. Put that in server-side JS.
  2. Prepare server-side game-loop.
  3. Have WebSocket send the data model as gameUpdates to clients.
  4. Have clients output received data models.

This commit describes this part.

server -> client rendering

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.


  1. Bridge the user input eventListener to the WebSocket event.
  2. Have the server-side update its data model by the inputs received from the client-side.
  3. Change the static data model to the dynamic one.

The commit and the successful output is as follows:

circle moving


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.
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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.


  1. Add state property to each player's data model. Rock:0, Paper:0, Scissor:0.
  2. In the server-side game loop, make collision-detection happen between players and coins.
  3. Change the state of the player object, depending on the sum of collected coins.
  4. Have client-side JS express the state change (for now, I'll just change the colors).

The commit

It will look like this:

player-coin interaction

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 }
Enter fullscreen mode Exit fullscreen mode

This will enable rendering the win/lose result to the client-side.


  1. Prepare a match data model to express a win/lose state.
  2. Implement player-to-player collision detection (reusing the method implemented in the previous part).
  3. Implement the rule of Rock Paper Scissors.
  4. Fill in the match data model with the result and send it to the client-side.
  5. Output the match result on the client-side (for now, it'll output to the console).

The commit is here

player-to-player interaction


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.


  1. Prepare images for player objects, as well as coin objects.
  2. Replace the circles with the images.
  3. 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.

delay collision

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.

The commit


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.


  1. Prepare a win/lose result text as HTML.
  2. Show the result text when the match is decided between two players.
  3. Implement "clean up" logic for the lost player. (Disconnect WebSocket connection, Stop rendering, Delete the server-side objects).
  4. Prepare Game Rule section as another HTML content.

The commit

Below's the screenshot of result display (top: winner side. bottom: loser side).

winning side instruction

losing side instruction

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)

Collapse
 
joeskeen profile image
Joe Skeen

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.