Spacewar RCWeb Implementation

This document explains how the Spacewar example leverages RCWeb technology to enable multi-player gaming across devices and provides a guide for building similar real-time distributed applications.

1. How the Spacewar Example Works

The Spacewar game relies on a decoupled architecture, splitting the logic into two seperate apps. The main spacewar game and the spacewar controller. The spacewar game app acts as the viewer, and the spacewar-control app acts as the controller. The two apps communicate within the same virtual room as facilitated by comms.js.

2. Building a Similar Game using RCWeb

To recreate this pattern for a new multi-player game, follow these implementation steps:

Create app directories

Create two directories, one for the game app and one for the control app. For example:

Configure the HTML Templates

Both apps require an index.html with rc environment variables set in the <head> section of the HTML. Variables are injected by RCWeb server.

<script>
    var rc = {
            "version": "${version}",
            "app": "${app}",
            "room": "${roomId}",
            "client": "${clientId}",
            "commsWebSocket": "${websocket}"
    };
</script>;

Then include the comms.js library and your game code.

<script src="/assets/core/comms.js">
<script src="script.js">
        

Build the Game app

(/app/mygame/script.js)

The game app should run an update loop, handle rendering, and expose a global API for controllers.

var myGame = (function() {
    var players = {};
    
    // Core game loop using requestAnimationFrame
    var tick = function() {
        // ... update physics based on frame delta ...
        // ... render changes ...
        window.requestAnimationFrame(tick);
    }

    return {
        // Public API to be executed via eval() over RCWeb
        movePlayer: function(playerId, direction) {
            if (!players[playerId]) {
                // Initialize player dynamically upon first interaction
                players[playerId] = { x: 0, y: 0 };
            }
            if (direction === 'up') players[playerId].y -= 10;
        },
        start: function() {
            // Setup game space
            tick();
        }
    };
})();

// Provide diagnostic callback overrides
rc.onUpdateNetworkStatus = function(heading, info) {
    console.log("Network:", heading, info);
};
rc.onUpdateError = function(error) {
    console.error("Eval Error:", error);
};

// Start the game, optionally initialize local keyboard listeners for testing
window.addEventListener("keydown", localKeyHandler);

// Display a QR Code pointing to your controller's path using the unique rc.room:
// "https://" + location.host + "/app-control/?r=" + rc.room

// Connect the to RCWeb room
rc.connect();

Build the Controller app

(/app/mygame-control/script.js)

The controller evaluates user input, and pushes remote function calls using rc.sendFunctionCall(target, functionName, ...args).

function sendMove(direction) {
    // Construct the remote function call to manipulate the viewer's state.
    // The first parameter specifies the target app(s) running in the room.
    // The second parameter is the name of the function to execute on those targets.
    // Subsequent parameters are passed as arguments to that function.
    rc.sendFunctionCall("mygame", "myGame.movePlayer", rc.client, direction);
}

document.getElementById('upButton').addEventListener('touchstart', function() {
    sendMove('up');
});

// Connect the to RCWeb room
rc.connect();

Summary

This approach bypasses traditional serialization/deserialization message parsing required in most multiplayer architectures. Instead of sending rigid JSON structures that the server has to validate, the controller constructs a direct function call to actuate the remote game's state. The comms.js framework manages network resilience, proxying real-time JavaScript from controllers to viewers.