Our website is made possible by displaying online advertisements to our visitors. Please consider supporting us by disabling your ad blocker.

Using Socket.io to Create a Multiplayer Game with Angular and Node.js

TwitterFacebookRedditLinkedInHacker News

When it comes to client and server communication, there are quite a few approaches towards solving the problem. You could create a RESTful API or GraphQL API on your server and consume it on-demand with your client, or you can go the socket approach and interact in real-time through events. There isn’t a wrong way to do things, but some ways are better than others given the task at hand.

Let’s take for example gaming and online multiplayer games. While certain aspects of the game would make sense to use REST or GraphQL, not everything would feel responsive enough. Instead it makes sense to use sockets.

In this tutorial we’re going to explore Socket.io for our client and server communication. We’re going to create a Socket.io server with Node.js and that server will communicate with each of our games running as Angular applications.

To get an idea of what we want to accomplish, take a look at the following animated image:

Interactive Gaming with Socket.io and Angular

While not a true game, we are moving 2D graphics around on the screen. In our example there can be any number of Angular instances taking control of the 2D shapes on the HTML canvas. These client instances are sending requests to the server and the server is broadcasting that information to every other connected client. The entire process, which you’ll see, is actually quite smooth.

You might not know this, but I wrote a similar tutorial to what we’re about to see in Vue.js, which is a framework that competes with Angular.

Developing the Socket.io Server to Manage Client Data

In our example, the server is going to be doing all of the heavy lifting, even if it looks like the Angular clients are doing a lot. For this reason, we should start by dedicating our time developing our server-side game logic.

Somewhere on your computer create a new directory to represent your server. Navigate within this directory using a command line and execute the following commands:

npm init -y
npm install socket.io express --save
touch app.js

The above commands will create a package.json file and install our two dependencies to it. We’ll be installing Socket.io for all of our client and server interaction and Express Framework to broadcast our server. We’re also creating an app.js file to hold all of our application logic.

If you are using Windows or don’t have access to the touch operation, go ahead and create the app.js file manually.

The next step will be around adding boilerplate code to our server. Open the project’s app.js file and include the following:

const Express = require("express")();
const Http = require("http").Server(Express);
const Socketio = require("socket.io")(Http);

Http.listen(3000, () => {
    console.log("Listening at :3000...");
});

In the above code we are importing each of our downloaded dependencies and are initializing them. After our dependencies are initialized, we start serving on port 3000.

As of now we are not serving any API endpoints, sending message broadcasts, or are listening for messages from clients.

In typical games, you want the server to be in charge for anything important and not let the client decide important events. This is done to prevent spoofing from the client side, for example getting items they shouldn’t have or fast traveling because maybe they are defining their own position. For this reason, the server is going to maintain the client state and the clients are only going to request changes to it, not send changes to it.

Add the following to your app.js file:

var position = {
    x: 200,
    y: 200
};

Because we’re not going to use a database for this example, we’re going to store the client position in memory. Clients will request changes to the position and the server will change it and send it back.

When a new client connects, we probably want to update them with the position information:

Socketio.on("connection", socket => {
    socket.emit("position", position);
});

The connection event is a reserved event name for when new sockets connect. When a socket connects, the server will broadcast the current position to them. Now let’s listen for custom events from the clients:

Socketio.on("connection", socket => {
    socket.emit("position", position);
    socket.on("move", data => {
        switch(data) {
            case "left":
                position.x -= 5;
                Socketio.emit("position", position);
                break;
            case "right":
                position.x += 5;
                Socketio.emit("position", position);
                break;
            case "up":
                position.y -= 5;
                Socketio.emit("position", position);
                break;
            case "down":
                position.y += 5;
                Socketio.emit("position", position);
                break;
        }
    });
});

The server will listen for move events from clients. When an event is received, the data will be analyzed. Since the client isn’t sending position information, we’re checking for the direction the client has requested to be moved. With that information, we can adjust the position and broadcast it back to all connected clients.

Take note of the socket.emit versus the Socketio.emit usage. When using socket.emit only the one socket will receive the message where with Socketio.emit all connected sockets will receive the message.

At this point in time our simple server is configured and ready to go. Make sure you start running the server before moving onto the next step.

Creating a Game Client with Angular and Socket.io

For this example we’ll have a single server and any number of Angular clients. The clients will essentially render the game and request changes. Outside of animations or similar, the client probably wouldn’t be doing the heavy lifting in a game.

Assuming you have the Angular CLI installed, execute the following:

ng new client

When prompted, choose the default options as part of the configuration wizard.

To keep things simple and easy to understand, we’re going to be spending all of our time in the project’s src/app/app.component.html and src/app/app.component.ts files. We won’t be creating any custom routes or components for this example, but the effort to do so is minimal.

Let’s start by designing our UI. Open the project’s src/app/app.component.html file and include the following:

<canvas #game width="640" height="480" style="border: 1px solid black"></canvas>
<p>
    <button>Right</button>
    <button>Left</button>
    <button>Up</button>
    <button>Down</button>
</p>

We’re not quite done with the HTML for this example, but this is a starting point. We’re creating a canvas to draw our 2D game. This canvas has a reference attribute of #game because we shouldn’t be accessing DOM elements through querySelector methods in Angular. For now the buttons won’t do anything, but they will in the future.

Now let’s take a look at the src/app/app.component.ts file:

import { Component, ViewChild, ElementRef, OnInit } from '@angular/core';
import io from "socket.io-client";

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
    
    @ViewChild("game")
    private gameCanvas: ElementRef;

    private context: any;
    private socket: any;

    public ngOnInit() {
        this.socket = io("http://localhost:3000");
    }

    public ngAfterViewInit() {
        this.context = this.gameCanvas.nativeElement.getContext("2d");
    }

}

In the above code we are mapping our reference attribute by using the @ViewChild annotation. Using the Angular lifecycle events we are establishing a connection to our Socket.io server and obtaining access to the canvas.

Let’s obtain the position from the server and render it on our canvas:

public ngAfterViewInit() {
    this.context = this.gameCanvas.nativeElement.getContext("2d");
    this.socket.on("position", data => {
        this.context.clearRect(0, 0, this.gameCanvas.nativeElement.width, this.gameCanvas.nativeElement.height);
        this.context.fillRect(data.x, data.y, 20, 20);
    });
}

Remember, the server is broadcasting messages with the position event. When the Angular application receives those messages, we clear the drawing canvas and draw a simple rectangle based on that position. As the position changes on the server, so will the position change on the canvas.

Now let’s introduce movement into our game. Add a move method like the following:

public move(direction: string) {
    this.socket.emit("move", direction);
}

When the move method is executed with a direction, it is broadcast to the server. Remember the server will take that direction information and apply it towards changing the actual position of our client.

We need to make use of the new move method. Open the project’s src/app/app.component.html file and make the following changes:

<canvas #game width="640" height="480" style="border: 1px solid black"></canvas>
<p>
    <button (click)="move('right')">Right</button>
    <button (click)="move('left')">Left</button>
    <button (click)="move('up')">Up</button>
    <button (click)="move('down')">Down</button>
</p>

Notice that each button now has a click event that will call the move method.

If you start serving the Angular application and open a few browsers, you should notice movement on each of them. This would be the same as if you had multiple connections from around the world.

Conclusion

You just saw how to use Socket.io and Angular to make a simple game. While the game was not competitive, it did make use of the HTML canvas and multiplayer interaction between the many possible clients and the server.

If you’re interested in seeing more examples of Angular with Socket.io, check out this classic tutorial I wrote titled, Create a Real Time Chat Application with the CEAN Stack and Socket.io.

A video version of this tutorial can be found below.

Nic Raboy

Nic Raboy

Nic Raboy is an advocate of modern web and mobile development technologies. He has experience in C#, JavaScript, Golang and a variety of frameworks such as Angular, NativeScript, and Unity. Nic writes about his development experiences related to making web and mobile development easier to understand.