geckos.io Snapshot Interpolation

repository·master·Indexed 43 days ago

https://github.com/geckosio/snapshot-interpolation

A library for real-time multiplayer games to implement Snapshot Interpolation (Entity/Buffer Interpolation) to smooth out entity movement despite network latency. Version 1.1.1 provides tools for creating snapshots on the server, managing them via a Vault, and calculating interpolated states on the client using linear, degree, radian, and quaternion interpolation.

Tokens
5.3K
Snippets
24
Records
27
Agent score
56%

What's inside geckos.io snapshot-interpolation

  1. Configure World State and Interpolation Parameters

    master

    The World State must be an Array of non-nested Objects (except for Quaternions). You can specify how specific keys should be interpolated by appending suffixes to the parameter string in calcInterpolation.

    Supported Suffixes:

    • (deg): For degrees
    • (rad): For radians
    • (quat): For quaternions

    Examples:

    Linear Interpolation: If your state is [{ id: 'hero', x: 1, y: 2 }], use SI.calcInterpolation('x y').

    Angular/Quaternion Interpolation: If your state is [{ id: 'hero', direction: 1.5, rotationInDeg: 90, q: {x:0, y:0.7, z:0, w:0.7} }], use: SI.calcInterpolation('direction(rad) rotationInDeg(deg) q(quat)').

    Interpolating Nested States: If your world state is a nested object (e.g., { players: [...], towers: [...] }), you can target a specific sub-state using the deep parameter: SI.calcInterpolation('x y', 'players').

    // Interpolating specific keys in a nested 'players' object
    const snapshot = SI.calcInterpolation('x y', 'players')
  2. Implement Snapshot Interpolation on the Server

    master

    On the server, you use the SnapshotInterpolation instance to create snapshots of your world state. You can optionally add these snapshots to the vault to access them later (e.g., for lag compensation).

    1. Initialize SnapshotInterpolation.
    2. In your server update loop, call SI.snapshot.create(worldState).
    3. (Optional) Call SI.vault.add(snapshot) to store the snapshot.
    4. Emit the snapshot to clients.
    import { SnapshotInterpolation } from '@geckos.io/snapshot-interpolation'
    
    const SI = new SnapshotInterpolation()
    
    // your server update loop
    update() {
      const snapshot = SI.snapshot.create(worldState)
      SI.vault.add(snapshot)
      this.emit('update', snapshot)
    }
  3. Compress snapshots with @geckos.io/typed-array-buffer-schema

    master

    To reduce bandwidth, you can compress snapshots using @geckos.io/typed-array-buffer-schema before sending them over the network.

    Workflow:

    1. Define a schema for your player and snapshot data.
    2. Server: Create a snapshot using SI.snapshot.create(), then convert it to a buffer using model.toBuffer(snapshot).
    3. Client: Receive the buffer, convert it back to a snapshot using model.fromBuffer(buffer), and add it to the interpolation system using SI.snapshot.add(snapshot).
    // server.js
    const snapshot = SI.snapshot.create({ players: [...] })
    const buffer = snapshotModel.toBuffer(snapshot)
    send(buffer)
    
    // client.js
    receive(buffer => {
      const snapshot = snapshotModel.fromBuffer(buffer)
      SI.snapshot.add(snapshot)
    })
  4. Implement Snapshot Interpolation on the Client

    master

    On the client, you initialize the library with the server's FPS and add incoming snapshots to the interpolation buffer.

    1. Initialize SnapshotInterpolation(serverFPS).
    2. When receiving a snapshot via your network layer, call SI.snapshot.add(snapshot).
    3. In your client update loop, call SI.calcInterpolation(parameters) to get the interpolated state.
    4. Apply the interpolated values to your game objects.
    import { SnapshotInterpolation } from '@geckos.io/snapshot-interpolation'
    
    const SI = new SnapshotInterpolation(serverFPS)
    
    // when receiving the snapshot on the client
    this.on('update', (snapshot) => {
      SI.snapshot.add(snapshot)
    })
    
    // your client update loop
    update() {
      const snapshot = SI.calcInterpolation('x y')
      const { state } = snapshot
    
      // apply the interpolated values to you game objects
      const { id, x, y } = state[0]
      if (hero.id === id) {
        hero.x = x
        hero.y = y
      }
    }
  5. Install @geckos.io/snapshot-interpolation

    master

    You can install the library via npm for Node.js/TypeScript projects, or use the bundled version via a <script> tag for browser-based projects.

    NPM Installation:

    npm install @geckos.io/snapshot-interpolation

    Browser (Bundled):

    <script src="https://unpkg.com/@geckos.io/snapshot-interpolation@1.0.1/bundle/snapshot-interpolation.js"></script>
    <script>
      const { SnapshotInterpolation, Vault } = Snap
    </script>
  6. Use the Vault to manage snapshots

    master

    The Vault holds and secures snapshots. Each SnapshotInterpolation instance has its own vault. You can also create standalone Vault instances.

    Common Vault Operations:

    • vault.add(snapshot): Add a snapshot to the vault.
    • vault.getById(id): Retrieve a specific snapshot by its ID.
    • vault.get(): Get the latest snapshot.
    • vault.get(time): Get the two snapshots surrounding a specific timestamp (returns { older, newer }).
    • vault.get(time, closest): Get the closest snapshot to a specific time.
    • vault.setMaxSize(size) / vault.getMaxSize(): Manage the capacity of the vault.
    import { Vault } from '@geckos.io/snapshot-interpolation'
    
    const customVault = new Vault()
    // ... use vault methods
  7. Reference: Vault API

    master

    The Vault class manages the storage and retrieval of snapshots.

    Methods:

    • getById(id: ID): Snapshot
    • get(): Snapshot | undefined
    • get(time: number): { older: Snapshot; newer: Snapshot; } | undefined
    • get(time: number, closest: boolean): Snapshot | undefined
    • add(snapshot: Snapshot): void
    • size(): number
    • setMaxSize(size: number): void
    • getMaxSize(): number
  8. Reference: SnapshotInterpolation API

    master

    The SnapshotInterpolation class is the primary interface for managing interpolation.

    Static Methods:

    • CreateSnapshot(state: State): Snapshot: Create a new Snapshot.
    • NewId(): string: Create a new unique ID.
    • Now(): number: Get current time in milliseconds.

    Instance Methods:

    • vault: Vault: Access the instance's vault.
    • interpolationBuffer.get(): number: Get current buffer time (ms).
    • interpolationBuffer.set(milliseconds: number): void: Set buffer time (ms).
    • snapshot.create(state: State): Snapshot: Create a snapshot on the server.
    • snapshot.add(snapshot: Snapshot): void: Add a received snapshot on the client.
    • interpolate(snapshotA: Snapshot, snapshotB: Snapshot, timeOrPercentage: number, parameters: string, deep: string): InterpolatedSnapshot: Manual interpolation between two snapshots.
    • calcInterpolation(parameters: string, deep: string): InterpolatedSnapshot | undefined: Get the calculated interpolation on the client.
  9. Initialize SnapshotInterpolation

    master

    To use the library, instantiate the SnapshotInterpolation class. You can optionally provide the server's frame rate (FPS) to automatically calculate a default interpolation buffer, or provide a custom configuration object.

    If serverFPS is provided, the interpolationBuffer is automatically set to (1000 / serverFPS) * 3 milliseconds.

    Config Options:

    • autoCorrectTimeOffset (boolean, default: true): If enabled, the library will automatically adjust the client-server timeOffset if the difference exceeds 50ms.
    import { SnapshotInterpolation } from './snapshot-interpolation';
    
    // Initialize with server FPS (e.g., 60 FPS)
    const interpolation = new SnapshotInterpolation(60);
    
    // Or initialize with custom config
    const interpolation = new SnapshotInterpolation(null, { autoCorrectTimeOffset: false });
  10. Create and add snapshots

    master

    The snapshot property provides methods to manage the lifecycle of snapshots.

    1. On the Server: Use snapshot.create(state) to generate a new Snapshot. The state can be an array of entities or an object mapping keys to entity arrays. Each entity must have a unique id (string or number).
    2. On the Client: Use snapshot.add(snapshot) to add a received snapshot to the internal vault. This allows the library to calculate interpolation between snapshots.

    Note: CreateSnapshot is a static method and can be used independently.

    // Server-side: Create a snapshot
    const state = [{ id: 'player1', x: 10, y: 20 }];
    const snapshot = interpolation.snapshot.create(state);
    
    // Client-side: Add the received snapshot
    interpolation.snapshot.add(snapshot);
  11. Configure the interpolation buffer

    master

    The interpolationBuffer determines how far in the past the client renders relative to the latest received snapshot. This buffer helps smooth out jitter and network fluctuations.

    You can get or set this value in milliseconds.

    // Get current buffer
    console.log(interpolation.interpolationBuffer.get());
    
    // Set a custom buffer of 100ms
    interpolation.interpolationBuffer.set(100);