avatarIvan Franchin

Summary

The provided content is a step-by-step guide on creating a simple multiplayer car game using Socket.IO for real-time communication, Express.js for the web app framework, and Phaser 3 as the HTML5 game engine.

Abstract

The article offers a comprehensive tutorial for developing a multiplayer car game, detailing the integration of Socket.IO for bidirectional event-based communication, Express.js to set up the server and handle web app routing, and Phaser 3 for the game's visual and interactive elements. It begins with setting up the project environment, including initializing an NPM project, installing dependencies, and creating the necessary server and client-side files. The guide then delves into implementing server logic for handling player connections, disconnections, and movements, as well as setting up the game client using Phaser 3. It concludes with instructions on how to run the server, access the game through a web browser, and view the final project structure. The author also introduces their own JavaScript library, Box2DCreateJS, and provides a link to the complete code repository on GitHub.

Opinions

  • The author believes that combining Box2D and CreateJS in their library, Box2DCreateJS, simplifies the use of 2D physics and multimedia in web development.
  • They suggest that Phaser 3 is a suitable choice for an HTML5 game engine due to its ease of use and capabilities.
  • The author expresses enthusiasm for the multiplayer game development process and encourages readers to engage with the content by clapping, sharing, following on social media, and subscribing to their newsletter.
  • They emphasize the importance of real-time communication in multiplayer games and demonstrate how Socket.IO effectively addresses this need.
  • The tutorial is designed to be user-friendly, with code snippets and comments provided to facilitate understanding and implementation.

MULTIPLAYER GAME

Creating a Multiplayer Game with Socket.IO, Express.js and Phaser 3

Step-by-step guide to implement a simple multiplayer car game

Photo by Hari Panicker on Unsplash

In this article, we will explain how to implement a simple multiplayer car game. We’ll use Socket.IO for real-time communication between clients and servers, Express.js to construct the web app, and Phaser 3 as the HTML5 game engine.

The car game is composed by a server that runs on host localhost and port 5000. Each client connecting to this server will receive a car with keyboard controls. From there, the client can drive its car on an empty terrain, which can later be customized using your own creativity. Clients will constantly communicate with the server, informing it when they connect, sending their location, and indicating when they disconnect. The server will handle all player connections, movements, and disconnections, broadcasting them to all online players.

Here will be the final result:

Before we continue, I’d like to introduce a game engine I’ve developed called Box2DCreateJS. Box2DCreateJS combines the capabilities of the Box2D 2D Physics Engine with the CreateJS suite of tools and libraries, making it easy to use. You can learn more about it by following the link below:

Without any further ado, let’s get started!

Prerequisites

If you would like to follow along, you must have Node.js and npm installed on your machine.

Creating the Multiplayer Game

Create root folder and initialize NPM project

Navigate to your workspace. Let’s create a folder called multiplayer-car-game. Then, let’s run the following command inside it:

npm init

This command will ask some questions:

package name: (multiplayer-car-game) // leave the default
version: (1.0.0)                     // leave the default
description:                         // leave it empty
entry point: (index.js)              // set index.html
test command:                        // leave it empty
git repository:                      // leave it empty
keywords:                            // leave it empty
author:                              // leave it empty
license: (ISC)                       // leave the default

It will generate the package.json that describes all the dependencies of the project.

{
  "name": "multiplayer-car-game",
  "version": "1.0.0",
  "description": "",
  "main": "index.html",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

For now, it does not have any dependency. We will add in the coming section.

Add Socker.IO and Express.js dependencies

Inside the multiplayer-car-game root folder, run the command below to add Socker.IO and Express.js dependencies:

npm install express socket.io

Create the server.js

Inside the multiplayer-car-game folder, let’s create the server.js file with the following content:

const express = require('express')
const http = require('http')
const path = require('path')
const socketIO = require('socket.io')

// Creates an Express application instance
const app = express()

// Creates an HTTP server using the Express app
var server = http.Server(app)

// Sets up Socket.IO to work with the HTTP server
// The pingTimeout property is infomed to configue the socket connection
var io = socketIO(server, {
  pingTimeout: 60000,
})

// Sets the port number to 5000 for the Express app
app.set('port', 5000)

// Sets up a static file server to serve files from the static directory
app.use('/static', express.static(__dirname + '/static'))

// Defines a route handler for the root URL (/) that sends the index.html file in response.
app.get('/', function (request, response) {
  response.sendFile(path.join(__dirname, 'index.html'))
})

// Starts the HTTP server on port 5000
server.listen(5000, function () {
  console.log('Starting server on port 5000')
})

var players = {}

// Listens for a new client connection and handles it
io.on('connection', function (socket) {
  console.log('player [' + socket.id + '] connected')

  // Initializes a new player object with a unique ID and random color
  players[socket.id] = {
    rotation: 0,
    x: 60,
    y: 40,
    playerId: socket.id,
    color: getRandomColor()
  }

  // Emits events to inform other clients about the new player
  socket.emit('currentPlayers', players)
  socket.broadcast.emit('newPlayer', players[socket.id])
 
  // Listens for a client disconnect event and handles it.
  socket.on('disconnect', function () {
    console.log('player [' + socket.id + '] disconnected')
    
    // Removes the disconnected player
    delete players[socket.id]

    // Broadcasts the disconnected player to all other clients
    io.emit('playerDisconnected', socket.id)
  })

  // Listens for player movement events
  socket.on('playerMovement', function (movementData) {
    // Updates the player's position and rotation
    players[socket.id].x = movementData.x
    players[socket.id].y = movementData.y
    players[socket.id].rotation = movementData.rotation

    // Broadcasts the updated player position to all other clients
    socket.broadcast.emit('playerMoved', players[socket.id])
  })
})

// Generates a random hexadecimal color code
function getRandomColor() {
  return '0x' + Math.floor(Math.random() * 16777215).toString(16)
}

This code, which I’ve commented for clarity, sets up the server-side of a multiplayer car game. It handles player connections, movements and disconnections.

Create the index.html

Inside the multiplayer-car-game root folder, let’s create the index.html file with the following content:

<html>

<head>
    <title>A Multiplayer Game</title>
    <style>
        body {
          margin: 0
        }
        canvas {
          border: 2px solid gray;
        }
    </style>
    <script src="//cdn.jsdelivr.net/npm/[email protected]/dist/phaser.min.js"></script>
    <script src="/socket.io/socket.io.js"></script>
    <script src="/static/game.js"></script>
</head>

</html>

This HTML file prepares everything needed to host the multiplayer car game. It includes Phaser 3 and Socket.IO, and the game’s logic and setup are handled in the game.js file, as explained later.

Create some folders

Inside the multiplayer-car-game root folder, let’s create the folder static. Then, inside the static folder, let’s create the subfolder assets.

Download and save the car image

Download the car image from this link and save it inside the multiplayer-car-game/static/assets subfolder.

Note: the car image was designed by Freepik.

Create the game.js

Inside the multiplayer-car-game/static folder, let’s create the game.js file with the content below:

// Defines the game configuration
var config = {
  type: Phaser.AUTO,
  parent: 'phaser-example',
  width: 800,
  height: 600,
  backgroundColor: '#ffffff',
  physics: {
    default: 'arcade',
    arcade: {
      // debug: true,
      gravity: { y: 0 }
    }
  },
  scene: { preload, create, update }
}

// Creates a new Phaser Game instance using the configuration
var game = new Phaser.Game(config)

// Loads the car image
function preload() {
  this.load.image('car', 'static/assets/car.png')
}

// Initializes the game state when it's created
function create() {
  const self = this

  // Sets up Socket.IO connection
  this.socket = io()
  this.otherPlayers = this.physics.add.group()

  // Handles events for current players
  this.socket.on('currentPlayers', function (players) {
    Object.keys(players).forEach(function (id) {
      if (players[id].playerId === self.socket.id) {
        addPlayer(self, players[id])
      } else {
        addOtherPlayers(self, players[id])
      }
    })
  })

  // Handles events for new players
  this.socket.on('newPlayer', function (playerInfo) {
    addOtherPlayers(self, playerInfo)
  })

  // Handles events for player disconnections
  this.socket.on('playerDisconnected', function (playerId) {
    self.otherPlayers.getChildren().forEach(function (otherPlayer) {
      if (playerId === otherPlayer.playerId) {
        otherPlayer.destroy()
      }
    })
  })

  // Sets up keyboard
  this.cursors = this.input.keyboard.createCursorKeys()

  // Handles events for player moviments
  this.socket.on('playerMoved', function (playerInfo) {
    self.otherPlayers.getChildren().forEach(function (otherPlayer) {
      if (playerInfo.playerId === otherPlayer.playerId) {
        otherPlayer.setRotation(playerInfo.rotation)
        otherPlayer.setPosition(playerInfo.x, playerInfo.y)
      }
    })
  })
}

// Creates and adds the player's car to the game world
function addPlayer(self, playerInfo) {
  self.car = self.physics.add.image(playerInfo.x, playerInfo.y, 'car')
    .setOrigin(0.5, 0.5)
    .setDisplaySize(100, 70)

  self.car.setCollideWorldBounds(true)
  self.car.setTint(playerInfo.color)
  self.car.setDrag(1000)
}

// Creates and adds other players' cars to the game world
function addOtherPlayers(self, playerInfo) {
  const otherPlayer = self.physics.add.image(playerInfo.x, playerInfo.y, 'car')
    .setOrigin(0.5, 0.5)
    .setDisplaySize(100, 70)
    .setRotation(playerInfo.rotation)
    
  otherPlayer.playerId = playerInfo.playerId
  otherPlayer.setTint(playerInfo.color)
  self.otherPlayers.add(otherPlayer)
}

// Handles game logic and input handling
function update() {
  if (this.car) {
    // It updates the player's car based on keyboard input
    if (this.cursors.left.isDown && (this.cursors.up.isDown || this.cursors.down.isDown)) {
      this.car.setAngularVelocity(-100)
    } else if (this.cursors.right.isDown && (this.cursors.up.isDown || this.cursors.down.isDown)) {
      this.car.setAngularVelocity(100)
    } else {
      this.car.setAngularVelocity(0)
    }

    const velX = Math.cos((this.car.angle - 360) * 0.01745)
    const velY = Math.sin((this.car.angle - 360) * 0.01745)
    if (this.cursors.up.isDown) {
      this.car.setVelocityX(200 * velX)
      this.car.setVelocityY(200 * velY)
    } else if (this.cursors.down.isDown) {
      this.car.setVelocityX(-100 * velX)
      this.car.setVelocityY(-100 * velY)
    } else {
      this.car.setAcceleration(0)
    }

    const currPosition = {
      x: this.car.x,
      y: this.car.y,
      rotation: this.car.rotation
    }

    if (this.car.oldPosition && (
          currPosition.x !== this.car.oldPosition.x ||
          currPosition.y !== this.car.oldPosition.y ||
          currPosition.rotation !== this.car.oldPosition.rotation)) {
      // Sends the player's position to the server
      this.socket.emit('playerMovement', currPosition)
    }

    this.car.oldPosition = currPosition
  }
}

This code, which I’ve commented for clarity, sets up the client-side of a multiplayer car game using Phaser 3, allowing players to control their cars and see the movements of other players in real-time.

Project final structure

By the end, our project should be organized with the following folder and file structure:

.
├── index.html
├── node_modules
│   ├── ...
...
├── package-lock.json
├── package.json
├── server.js
└── static
    ├── assets
    │   └── car.png
    └── game.js

Time to play

Install packages and start the server

In a terminal and inside the multiplayer-car-game folder, run the following command:

npm install

Note: the npm install command just need run once.

Then, execute the command below to start the server:

node server.js

Playing the game

Open two or more separate browsers (e.g., Chrome, Safari, and Firefox) or use different browser profiles (e.g., Chrome, Incognito Chrome, and Firefox) and access http://localhost:5000.

Here is my setup.

Conclusion

In this article, we have shown you how to create a simple multiplayer car game with Socket.IO for real-time communication between clients and servers, Express.js to construct the web app, and Phaser 3 as the HTML5 game engine.

The complete code can be found in the ivangfr/socketio-express-phaser3 GitHub repository.

Additional Readings

Support and Engagement

If you enjoyed this article and would like to show your support, please consider taking the following actions:

  • 👏 Engage by clapping, highlighting, and replying to my story. I’ll be happy to answer any of your questions;
  • 🌐 Share my story on Social Media;
  • 🔔 Follow me on: Medium | LinkedIn | Twitter | GitHub;
  • ✉️ Subscribe to my newsletter, so you don’t miss out on my latest posts.
Multiplayer
Socketio
Expressjs
Phaserjs
Technology
Recommended from ReadMedium