0.0.3 • Published 7 months ago

multyx v0.0.3

Weekly downloads
-
License
ISC
Repository
-
Last release
7 months ago

Multyx

Welcome to the first pre-release release of Multyx, a framework that prioritizes developer experience by making building multiplayer browser games easy on both the client and server-side.


As we are only in the first release, there's undoubtedly more to come, but there is already a significant number of features. Along with a seamless websocket connection functionality to allow you to jump straight into coding, Multyx includes:

Shared Read/Write State between Client and Server

Being able to communicate changes in data is necessary for a functional multiplayer game, but due to needing server-side verification, consistency across websocket endpoints, and a lack of type validation, it can be difficult to bridge the gap between the client and server data transfer. Multyx streamlines this process by including a shared state between the server and client, along with

  • Control over which data is public to whom
  • Ability to constrain and verify data sent by client
  • Ability to disable/enable client editing
  • Client-side constraint prediction

Input Controller and Event Listeners

Client interactivity is a necessity in any game, but with it being hard to standardize mouse position across varying screen sizes, and manage the state of inputs, it can be tricky to implement an input system. Multyx allows you to

  • Pick which inputs to listen to
  • Ability to standardize mouse location
  • View the state of inputs from both the server and client
  • Allow for multiple callbacks on an input type

Helpful Functionalities

Making a functional, efficient, and secure multiplayer game is notoriously hard to do, as each project has its own needs. Multyx offers a variety of helpful functionalities such as

  • Interpolation methods for client-side prediction
  • Client knowledge of constraints to reduce redundancy
  • Teams to manage public state across groups of clients

Focused on ease of use and a good developer experience, Multyx turns the difficulty and complexity while making a multiplayer browser game into a simpler process that anyone can jump into.


Setup:

Server:

import Multyx from 'multyx';
import * as express from 'express';

const server = express().listen(8080, () => console.log('server started'));
const multyx = new Multyx.MultyxServer(server);

Client:

<canvas width="400" height="400" id="canvas"></canvas>
<script src="https://cdn.jsdelivr.net/gh/seanlnge/multyx@v0.0.2-alpha/client/multyx.js"></script>
<script>
const multyx = new Multyx();
</script>

Creating a Shared Object:

Server:

multyx.on('connect', client => {
    // Initialize client with random color and position
    client.shared.set("player", {
        color: '#' + Math.floor(Math.random() * 3840 + 256).toString(16),
        position: {
            x: Math.round(Math.random() * 400),
            y: Math.round(Math.random() * 400)
        }
    });
});

Client:

const multyx = new Multyx();
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

multyx.on(Multyx.Start, () => {
    window.player = multyx.client.player;
    console.log(player.color, player.position); // #e8d, { x: 134, y: 193 }
    requestAnimationFrame(update);
});

function update() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    for(const { player } of Object.values(multyx.clients)) {
        ctx.fillStyle = player.color;
        ctx.fillRect(player.position.x-10, player.position.y-10, 20, 20);
    }
    requestAnimationFrame(update);
}

Adding Constraints on Shared Object

Server:

multyx.on('connect', client => {
    // Initialize client with random color and position
    client.shared.set("player", {
        color: '#' + Math.floor(Math.random() * 3840 + 256).toString(16),
        position: {
            x: Math.round(Math.random() * 400),
            y: Math.round(Math.random() * 400)
        }
    });

    const player = client.shared.get("player");
    player.public(); // Make client's player object public to all clients
    player.disable(); // Make client's player object read-only to client
    player.get('position').get('x').min(0).max(400); // Constrain `player.position.x` to be 0-400
    player.get('position').get('y').constrain(y => Math.min(400, Math.max(0, y))); // Constrain `player.position.y` to be 0-400
});

Listen for Inputs

The client.controller.listenTo function takes in 1 required argument and 1 optional argument.

client.controller.listenTo(
    input: Input | string | (Input | string)[],
    callback?: (state: ControllerState) => void
)

The input parameter describes what input Multyx should listen to from the client. If passed an array, Multyx listens to all of the inputs and calls the callback - if provided - if any inputs are triggered.

The callback parameter gets called if any of the events get triggered, the parameter state is an object containing the keys currently pressed as well as the mouse location and status, granted that those events are being listened to.

If an input is not being listened to by Multyx, any changes will not show up in the controller state.

Server:

multyx.on('connect', client => {
    // ... i aint writing allat

    client.controller.listenTo([
        Multyx.Input.UpArrow,
        Multyx.Input.DownArrow,
        Multyx.Input.LeftArrow,
        Multyx.Input.RightArrow
    ]);

    client.onUpdate = (deltaTime, controllerState) => {
        const x = player.get('position').get('x');
        const y = player.get('position').get('y');

        const speed = deltaTime * 200;

        if(controllerState.keys[Multyx.Input.UpArrow]) y.set(y.value - speed);
        if(controllerState.keys[Multyx.Input.DownArrow]) y.set(y.value + speed);
        if(controllerState.keys[Multyx.Input.LeftArrow]) x.set(x.value - speed);
        if(controllerState.keys[Multyx.Input.RightArrow]) x.set(x.value + speed);
    }
});

Client:

// imagine you are holding right arrow
console.log(player.position.x); // 193.53
// imagine a frame just passed
console.log(player.position.x); // 204.81
// omg it worked

Add Linear Interpolation

The Multyx.Lerp function takes in 2 required arguments and linearly interpolates between the current value and previous value based on the time since the current value was updated. Lerp is the go-to interpolation method and creates extremely smooth animations between one frame to the next, however is delayed by half a frame (25ms) on average.

Multyx.Lerp(object: { [key: string]: any }, property: string)

Client:

// Add interpolation onto all current clients
multyx.on(Multyx.Start, () => {
    for(const client of Object.values(multyx.clients)) {
        Multyx.Lerp(client.player.position, "x");
        Multyx.Lerp(client.player.position, "y");
    }
});

// Add interpolation onto all future clients
multyx.on(Multyx.Connection, client => {
    Multyx.Lerp(client.player.position, "x");
    Multyx.Lerp(client.player.position, "y");
});

Standardize Mouse Position

Standardizing the mouse position utilizes the controller.mapMousePosition function, which takes in 1 required argument and 4 optional arguments.

controller.mapMousePosition(anchor: HTMLElement, centerX?: number, centerY?: number, scaleX?: number, scaleY?: number)

The anchor parameter tells Multyx which element the mouse should be measured relative to. For almost all purposes this is the canvas element, and by default the x and y-value of the mouse is the distance from the top-left corner (0, 0) of the element, measured in the element's relative coordinates. If the canvas size is altered in the client-side code, the mouse will be measured based not by pixels but by the canvas's relative size.

The centerX and centerY default to 0, and describe the point on the anchor element that corresponds to the mouse's origin - where x = 0 and y = 0.

The scaleX and scaleY default to 1, and describe the ratio between the number of units moved relative to the anchor element's coordiantes and the units moved relative to the mouse's coordinates. If only scaleX is defined, scaleY will default to scaleX.

Server:

multyx.on('connect', client => {
    // ... i aint writing allat

    client.controller.listenTo(Multyx.Input.MouseMove, (controllerState) => {
        console.log(controllerState.mouse);
    });
});

Client:

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

canvas.width = 2000;
canvas.height = window.innerHeight / window.innerWidth * canvas.width;
ctx.translate(200, 200);
ctx.scale(1, -1);

const multyx = new Multyx();

multyx.controller.mapMousePosition(canvas, 200, 200, 1, -1);

Server Output:

> { x: 108, y: 294, down: false }
// imagine that the mouse is mapped now
> { x: -92, y: -94, down: false }
0.0.3

7 months ago

0.0.2

7 months ago

1.0.1

8 months ago

1.0.0

8 months ago