0.4.1 • Published 1 year ago

netplayjs v0.4.1

Weekly downloads
132
License
ISC
Repository
github
Last release
1 year ago

NetplayJS

Node.js CI npm

Make peer-to-peer WebRTC-based multiplayer games in JavaScript, no server hosting or network synchronization code required!

Quick Start

Here's how NetplayJS works:

  • You create your game within a single static HTML file.
  • You can use a variety of HTML5 game frameworks, including Three.js.
  • You can host this file anywhere (GitHub Pages, Itch.io, Glitch, and many more).

NetplayJS handles most of the complicated aspects of multiplayer game development, letting you create games almost as if they were local multiplayer games. Synchronization and matchmaking are handled automatically under the hood - and best of all you don't have to host any servers!

Let's make a very simple game. Create an HTML file and add the following script tag.

<script src="https://unpkg.com/netplayjs@0.3.0/dist/netplay.js"></script>

Now add this javascript code to the same HTML.

<script>
class SimpleGame extends netplayjs.Game {
  // In the constructor, we initialize the state of our game.
  constructor() {
    super();
    // Initialize our player positions.
    this.aPos = { x: 100, y: 150 };
    this.bPos = { x: 500, y: 150 };
  }

  // The tick function takes a map of Player -> Input and
  // simulates the game forward. Think of it like making
  // a local multiplayer game with multiple controllers.
  tick(playerInputs) {
    for (const [player, input] of playerInputs.entries()) {
      // Generate player velocity from input keys.
      const vel = {
        x:
          (input.pressed.ArrowLeft ? -1 : 0) +
          (input.pressed.ArrowRight ? 1 : 0),
        y:
          (input.pressed.ArrowDown ? -1 : 0) +
          (input.pressed.ArrowUp ? 1 : 0),
      };

      // Apply the velocity to the appropriate player.
      if (player.getID() == 0) {
        this.aPos.x += vel.x * 5;
        this.aPos.y -= vel.y * 5;
      } else if (player.getID() == 1) {
        this.bPos.x += vel.x * 5;
        this.bPos.y -= vel.y * 5;
      }
    }
  }

  // Normally, we have to implement a serialize / deserialize function
  // for our state. However, there is an autoserializer that can handle
  // simple states for us. We don't need to do anything here!
  // serialize() {}
  // deserialize(value) {}

  // Draw the state of our game onto a canvas.
  draw(canvas) {
    const ctx = canvas.getContext("2d");

    // Fill with black.
    ctx.fillStyle = "black";
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // Draw squares for the players.
    ctx.fillStyle = "red";
    ctx.fillRect(this.aPos.x - 5, this.aPos.y - 5, 10, 10);
    ctx.fillStyle = "blue";
    ctx.fillRect(this.bPos.x - 5, this.bPos.y - 5, 10, 10);
  }
}

SimpleGame.timestep = 1000 / 60; // Our game runs at 60 FPS
SimpleGame.canvasSize = { width: 600, height: 300 };

// Because our game can be easily rewound, we will use Rollback netcode
// If your game cannot be rewound, you should use LockstepWrapper instead.
new netplayjs.RollbackWrapper(SimpleGame).start();
</script>

And voila - a real-time networked game with rollback and client-side prediction.

Overview

NetplayJS is a framework designed to make the process of creating multiplayer browser games simple and fun. It consists of several different components.

  • netplayjs-server - The matchmaking and signaling server. You can host your own or use the public instance.
  • (WIP) netplayjs-netcode - Implementations of rollback netcode and lockstep netcode.
  • (WIP) netplayjs-connection - The client side code that communicates with the matchmaking server to establish connections.
  • netplayjs - A prototyping framework that lets you rapidly create multiplayer games.
  • netplayjs-demos - A collection of demos built in the prototyping framework to show off how to use it.

Installation

For simple usage, you can include NetplayJS directly from a script tag in an HTML file.

<script src="https://unpkg.com/netplayjs@0.3.0/dist/netplay.js"></script>

For larger projects, you should install NetplayJS from npm and bundle it with your application using Webpack or a similar module bundler.

npm install --save netplayjs

I also highly recommend that you use it with TypeScript, though this is not required. The examples following will be in TypeScript.

Usage

To create a game using NetplayJS, you create a new class that extends netplayjs.Game.

  • This class should implement functions for initailizing, updating, and drawing the game.
  • It should implement functions for serializing / deserializing the state (more info in the next section).
  • It should contain static properties used to configure the netcode (see here).
class MyGame extends netplayjs.Game {
  // NetplayJS games use a fixed timestep.
  static timestep = 1000 / 60;

  // NetplayJS games use a fixed canvas size.
  static canvasSize = { width: 600, height: 300 };

  // Initialize the game state.
  constructor(canvas: HTMLCanvasElement, players: Array<NetplayPlayer>) {}

  // Tick the game state forward given the inputs for each player.
  tick(playerInputs: Map<NetplayPlayer, DefaultInput>): void {}

  // Draw the current state of the game to a canvas.
  draw(canvas: HTMLCanvasElement) {}

  // Serialize the state of a game to JSON-compatible value.
  serialize(): JsonValue {}

  // Load the state of a game from a serialized JSON value.
  deserialize(value: JsonValue) {}
}

You can now start the game by passing your game class to one of several wrappers.

  • (WIP) new LocalWrapper(MyGame).start(); - Runs mutiple instances of the game in the same browser page. Use for local testing and rapid iteration.
  • new RollbackWrapper(MyGame).start(); - Runs the game using rollback netcode. Use for game states that can be rewound and replayed.
  • new LockstepWrapper(MyGame).start(); - Runs the game using lockstep netcode. Use for game states that can't be rewound.

Game State Serialization

The client-side prediction and rewind capabilities of netplayjs are based off of the ability to serialize and deserialize the state of the game. In the quickstart example above, we let the autoserializer take care of this. For most games, however, you will need to implement your own logic. You can do this by overriding Game.serialize and Game.deserialize in your subclass.

If you cannot serialize the game state, you can still use NetplayJS, but you will need to use Lockstep netcode, rather than predictive netcodes like Rollback, and you need to mark your game as deterministic.

Determinism

By default NetplayJS does not assume determinsim. It corrects for drift by having one player (the host) send authoritative state updates to the others. If you mark your game as deterministic, NetplayJS will skip sending these updates.

Whether or not JavaScript operations are cross-platform deterministic is a difficult question. We can safely assume integer arithmatic is deterministic. I can also confirm that floating point operations with WASM are generally cross platform deterministic (required by WASM spec). Anything else is potentially up in the air.

NetplayPlayer

A NetplayPlayer represents one player in a game. NetplayPlayer.getID() returns an ID that is stable across each network replication of the game.

DefaultInput

NetplayJS games are synchronized by sending inputs across a network. DefaultInput automatically captures and replicates keyboard events, mouse events, and touch events.

Assets Used from Other Projects

This repo contains code and assets from other open source projects.