PlayCanvas Networking example: Part 01 - Basics
Learn how to start with VIVERSE Play SDK and implement Networking basics for your standalone PlayCanvas project
About
Welcome to Part 01 of the VIVERSE Play SDK Networking tutorial for PlayCanvas! In this chapter we will cover all the basics to get you started with multiplayer functionality:
Configure new PlayCanvas project for the VIVERSE SDKs
Get to know the VIVERSE Play SDK and its Matchmaking and Multiplayer Clients
Create and join the Room where multiplayer session would take place
Initialize multiplayer session and exchange data between Players
Prerequisites
In order to use VIVERSE SDKs you need to create a World first and retrieve its App ID, which can be done with VIVERSE Studio. This process is described in detail in our documentation on VIVERSE Studio — but for now you can simply create a new app and copy its App ID to get started.
NOTE: VIVERSE SDKs cannot be used with projects published via the PlayCanvas Create SDK extension, which do not have App IDs.


Step 1: Setup PlayCanvas project and add VIVERSE SDK
Let's create a new blank PlayCanvas project or use an already existing one. Go to the SETTINGS
> EXTERNAL SCRIPTS
and add a new script there: https://www.viverse.com/static-assets/viverse-sdk/index.umd.cjs
. This will ensure the VIVERSE SDK is loaded first and your PlayCanvas logic has full access to its functionality:

Step 2: Create new scripts and initialize the SDK
For the purpose of this tutorial we will be using recently introduced ESM scripts, although you can still follow it with the Classic scripting as well. We will start with setting up a couple new classes:
main.mjs
: defines Main class which would serve as an entry point of our application and instantiate a new Client. Main is extending PlayCanvas Script class and should be attached to some entity to work properlyclient.mjs
: defines Client class which would encapsulate all necessary functionality to create / join the room, start multiplayer session and send / receive messages between peers. Client is a typical ES6 class with a constructor and default export
At this stage, let's proceed with instantiating Play SDK client in the Client's constructor:
// @ts-nocheck
import { Script } from 'playcanvas';
import Client from './client.mjs';
export class Main extends Script
{
static scriptName = 'Main';
initialize ()
{
let client = new Client ();
}
}

Congratulations with a great start! Now if you launch your PlayCanvas project — you will see Play SDK client initialized and logged into the console. Please note that App ID is not required at this point, but we will definitely need it later!
Step 3: Initialize Matchmaking client and setup the Room
Now let's modify our Client so that it sets up a Room where multiplayer session will take place. For this functionality we will need to add a few extra steps:
Initialize new Matchmaking client
Look for currently available rooms
If no rooms are available - create a new Room
If the Room is already created - join the Room
To refresh your knowledge of VIVERSE Matchmaking API please feel free to consult with our Matchmaking API documentation and take a look at PlayCanvas Matchmaking Example code!
// @ts-nocheck
import { Script } from 'playcanvas';
import Client from './client.mjs';
export class Main extends Script
{
static scriptName = 'Main';
initialize ()
{
this.initClient ('Player A');
}
async initClient (username)
{
let client = new Client (username);
await client.initialize ();
return client;
}
}

Here is what's happening:
The Client class defines
async initialize ()
which would be responsible for setting up our Multiplayer client and creating / joining the RoomInside initializion method we call
await initMatchmaking ()
which instantiates new Matchmaking client with provided App ID, and returns a promise when the client is connected. Listening toonConnect
here is crucial since trying to create or join the Room before that would result in an errorAfter Matchmaking client is connected — we call
await createJoinRoom ()
which sets up an Actor with desired username and randomly generated session id, scans for available rooms and then creates / joins the Room depending on result of that scanAnd finally in the Main class we create a new instance of that Client with username
Player A
and callawait client.initialize ()
. If you did everything correctly — you should see Client instance logging various initializations steps into the console
Great progress so far! In the next step we will improve our Client even further, so it would be able to spin up an actual multiplayer session for the current Room
Step 4: Initialize Multiplayer client and subscribe to events
We have our Room created, but we still don't have any means to exchange game-specific events between Actors in that Room. To make it possible we would need to integrate Multiplayer API first, which could be done in just two simple steps:
Initialize new Multiplayer client for the current Room
Subscribe to multiplayer-specific events
Let's modify our Client class to incorporate these new changes:
// @ts-nocheck
import { Script } from 'playcanvas';
import Client from './client.mjs';
export class Main extends Script
{
static scriptName = 'Main';
initialize ()
{
this.initClient ('Player A');
}
async initClient (username)
{
let client = new Client (username);
await client.initialize ();
return client;
}
}

This is what we've added so far:
We still have our
async initialize ()
as the main initialization routine, but we've added one extra step at the end of it —await initMultiplayer ()
async initMultiplayer ()
instantiates new Multiplayer client with current Room ID and App ID and returns a promise when the client is connected. Similar to Matchmaking, listening toonConnected
is crucial since web sockets need some time to settleAfter Multiplayer client is connected, we subscribe it to incoming messages and actions / competition via corresponding methods
handleMessage (data)
andhandleAction (data)
. Messages and actions are two main data structures that Multiplayer clients of different peers can exchange with each other, and we'll see them in action a bit later, during our final testingNote that Multiplayer client is using
.onConnected (callback)
signature while Matchmaking client relies on.on ('onConnect', callback)
one. This is temporary inconsistency that might be resolved in the future
Alright, our Client is all set and ready to go! In the next and the final step, we'll test it all together via browser console, see how messages are sent between peers, how actions work and how competition is resolved by Play SDK servers.
Step 5: Final modifications and testing
5.1 Preparing app for testing
We're almost good to go about testing our multiplayer! But before we proceed any further, let's expose our Main :: async initClient ()
globally by attaching it to the window object. Here is what the final result could look like:
// @ts-nocheck
import { Script } from 'playcanvas';
import Client from './client.mjs';
export class Main extends Script
{
static scriptName = 'Main';
initialize ()
{
// We're exposing our initClient method globally
// So we can test all functionality in browser console
// Without involving PlayCanvas UI system at this point
window.init = this.initClient.bind (this);
}
async initClient (username)
{
let client = new Client (username);
await client.initialize ();
return client;
}
}
5.2 Broadcasting and receiving messages
Now we can finally proceed to testing! Let's launch our PlayCanvas app in a new tab and open browser console. Then try the following:
Instantiate two players in a sequence:
let player1 = await init ('Player A')
let player2 = await init ('Player B')
Send message from
player1
to all players in the Room:player1.multiplayer.general.sendMessage ({...}) // arbitrary data object
Observe how
player2
logs the received message:

NOTE: For the purpose of this tutorial we're testing our app in a single tab, but you can also launch it in multiple tabs or even on different devices! All multplayer functionality should still work as expected
5.3 Actions and competition
Alright, broadcasting data between peers works beautifully, but what if player wants to trigger an event that should affect all players? What if multiple players are trying to trigger that event at the same time, but only one player should be decided as a winner?
This is what actions and competition were designed for. A typical example is collecting an item — when player moves across let's say a powerup, it sends a competition request to VIVERSE Play SDK servers. If no other player sent that same request during the last server update — the server sends a confirmation to everyone in the room that this powerup was collected by this player. But if multiple players came across that powerup at the same time — the server decides the winner by choosing the one whose action arrived the earliest, and then notifies everyone in the room.
Let's see it in practise:
First log peer ids for both players in your room:
player1.multiplayer.peerId // --> '....'
player2.multiplayer.peerId // --> '....'
Now trigger some action by both players simultaneously. Please note that all three params should match between actions to participate in the same competition run! Sending two actions with different params will simply result in two separate competition runs — one for each action:
player1.multiplayer.actionsync.competition ('action', 'message', 'id') player2.multiplayer.actionsync.competition ('action', 'message', 'id')
After both actions are triggered simultaneously — observe how both players are notified about competition results. Notice that successor refers to peer id of a player that won the competiton:

Wrapping up
And that's it! Now you can use essential Multiplayer functionality from VIVERSE Play SDK in your custom projects. You can initialize Multiplayer client for the current Room, broadcast and receive Messages between Players, and send Actions that would be resolved by the server via the process of Competition.
In the second part of this tutorial we'll revisit our multipalyer app and redesign it to be more robust and production-ready. Stay tuned!
Last updated
Was this helpful?