Why Am I here?

You were sent here from issues. Its important that you implement code that is deterministic for multiplayer, and this is an overview to get you up to speed.

How does our Multiplayer Work?

We are using Deterministic Lockstep which mean we keep simulations in sync by sending only inputs.

We might outgrow lockstep but currently its perfect for a client-to-client connection between two players.

When a multiplayer game occurs, one player generates a seed eg. picardvsjaneway, its sent to the other player, and then both games are generate based off of that seed.

We feed that seed into seedrandom which allows us to have a predictable random number generator.

When the game is running we take snapshots of object data on each frame for upto a 120 frames at any given time.

When games go out of sync, we will load an old snapshot, and then replay inputs until they are both back in sync.

How do I implement things to Deterministic?

Game objects will have snap, load, render and update functions eg.

class ComponentPanel {
    get snap() {
        return [this.kind, this.x, this.y]
    }

    load(snapshot) {
        this.kind = snapshot[0]
        this.x    = snapshot[1]
        this.y    = snapshot[2]
    }

    update() {
        this.kind = //assign based on some logic
        this.x += 2;
        thix.y += 2;
    }

    render() {
        this.sprite.x = x
        this.sprite.y = y
    }
}
  • update runs at a fixed framerate, meaning that every 60frames = 1 second.
  • render can run as many times as it likes.
  • snap is used to snapshot game object data needed to reload later
  • load is used when want to load a snapshot to restore a previous state of a game object.

When render is called we should already determined that game logic. Game logic should never change inside the render method. Because it doesn't run at a fixed framerate, and it has to be work regardless of state.

This is bad:

render() {
    this.sprite.x += 1
}

This is good:

render() {
    this.sprite.x += 1
}

Gotchas

One of trickiest things is how do you ensure you can go back to any frame and ensure the animations are perfectly at the correct frame.

In Phaser you would be tempted to use the built in phaser animations:

update() {
    this.character.sprite.play("losing");
}

However this is not deterministic because you have to be able to go back to any frame. So in this case you need to have a variable such as a counter that you will snapshot, and use that counter as the way to manage which frame you are on.

class Character {
    snap() {
        return [this.counter]
    }

    load(snapshot) {
        this.counter = snapshot[0]
    }

    update() {
        this.counter++
    }

    render() {
          this.sprite.frame = this.counter
    }
}

results matching ""

    No results matching ""