Using Spline with Playroom Kit

👷🏼‍♀️

This section is under development and very experimental.

Spline (opens in a new tab) is a free, real-time collaborative 3D design tool to create interactive experiences within the browser.

We, at Playroom love making game prototypes with Spline. Instead of remaking them in a game engine, wouldn't it be cool if we could just export the Spline project and add game logic and multiplayer on top of it?

Spline publishes @splinetool/runtime (opens in a new tab) NPM package. This package can be used to load Spline projects in your own website and has minimal API to control the scene.

This demo uses some internal APIs of Spline. Which is why it's not very stable for now.

Touch the screen to move the ball.

You can see the full source code here (opens in a new tab).

How does this work?

We create a Spline project with physics enabled. We also pre-create all player objects in the scene and name them Ball1, Ball2, etc. Then, in a local HTML project, we initialize Playroom to show the UI and connect players:

const { onPlayerJoin, insertCoin, isHost, myPlayer } = Playroom;
// ...
await insertCoin();

Then, we use Spline's runtime (opens in a new tab) to load the Spline project in the browser. Which is essentially something like this:

import { Application } from '@splinetool/runtime';
 
const canvas = document.getElementById('canvas3d');
const app = new Application(canvas);
await app.load('https://path/to/scene.splinecode'); // Fetch this by clicking export on your Spline project

Then, we connect a joystick and send state to Playroom:

const joystick = nipplejs.create();
joystick.on("move", (e, data) => {
  myPlayer().setState("dir", data.vector);
});
joystick.on("end", () => {
  myPlayer().setState("dir", {x:0, y:0});
});

After this, we use Spline's internal APIs to get the right ball for joining players:

let players = [];
function getObjectFromSpline(name){
  return app._scene.children.find((child)=> child.name === name);
};
 
onPlayerJoin((state) => {
  const ball = getObjectFromSpline("Ball" + (players.length+1));
  players.push({state, ball});
});

Finally, we want to move the ball when player moves the joystick. We do this in a loop (see full code for details):

const loop = new AnimationFrame(120, () => {
  if (isHost()){
    for (const player of players) {
      const controls = player.state.getState("dir") || {};
      player.ball.rigidBody.setLinvel({ 
        x: controls?.x * 4.0 || 0.0,
        y:player.ball.rigidBody.linvel().y, 
        z: controls?.y * -4.0 || 0.0
      }, true);
      player.state.setState("pos", player.ball.rigidBody.translation());
    }
  }
  else{
    for (const player of players) {
      const pos = player.state.getState("pos");
      if (pos){
        player.ball.rigidBody.setTranslation(pos, true);
      }
    }
  }
});