Tutorial
Heading
Jun 23, 2021

P2P messaging to sync scene components

This guide is intended to help you create a simple scene with minimal gameplay

The mission

We'll explain how to synchronize a few key components of your scene using P2P (peer-to-peer) between players.

This guide is intended to help you create a simple scene with minimal gameplay. If you aim to develop a more complex multiplayer scene, consider using this as a foundation.

Resources

  • Decentraland SDK
  • Decentraland components docs
  • Decentraland P2P guide

Before start

If you already have advanced knowledge of Decentraland, you may consider skipping this guide and proceed directly to the finished tutorial project on Github.

ai47patos/dcl-p2p-example (github.com)

We assume that you possess basic knowledge of creating a simple game in TypeScript using Decentraland components. If you need more information about Decentraland components, refer to the following docs:

“Entities and components | Decentraland”

It would be helpful if you checked the Decentraland docs on using P2P messaging before starting, although it’s not necessary as we will cover it in this guide:

“About multiplayer scenes | Decentraland”

Prepare your component

If you’re already familiar with DCL components and entity input, you can skip this part.

Download the code here if you want to follow the example used in this guide.

For this guid, we'll use a straightforward component to change the color of a BoxShape entity and the spawn cube function provided by the init example DCL project. You can adapt this guide to use it with your own project and components.

Code

//Simple component that manages the color selection of a material
@Component("ColorComponent")
class ColorComponent {
	indexCube: number     //Index of the entity in the cube array
  entity: IEntity       //Entity reference
  material: Material    //Material reference
  colorArray: Color3[]  //The color options to choose
  currentColor: number  //The index of the current color
  constructor(entity: IEntity, arrayIndex: number){
    this.entity = entity
    this.indexCube = arrayIndex
    this.colorArray = [Color3.White(), Color3.Blue(), Color3.Red(), Color3.Green(), Color3.Black()]
    this.material = new Material()
    this.entity.addComponent(this.material)
    this.updateColor(0)
  }
  //Update the color
  updateColor(newColorIndex: number){
    this.currentColor = newColorIndex
    this.material.albedoColor = this.colorArray[newColorIndex]
  }
}

/// --- Spawner function ---
function spawnCube(x: number, y: number, z: number) {
  const cube = new Entity()
  cube.addComponent(new Transform({ position: new Vector3(x, y, z) }))
  cube.addComponent(new BoxShape())
  engine.addEntity(cube)
  return cube
}

/// --- Spawn a cube ---
const cube = spawnCube(8, 1, 8)
cube.addComponent(new ColorComponent(cube))

To our new “ColorComponent”, we'll add some functions that will be usefull for managing the component via player input or P2P messages.

Code

@Component("ColorComponent")
class ColorComponent {
  ...
  //Update the color
  updateColor(newColorIndex: number){...}
  //Update the color by the local player
  nextColorFromLocalPlayer(){
    let newIndex = this.currentColor+1
    if (newIndex>=this.colorArray.length) {
      newIndex = 0
    }
    this.updateColor(newIndex)
  }
  //Update the color by another player via P2P message
  updateColorFromP2P(newColorIndex: number){
    this.updateColor(newColorIndex)
  }
}

Before we dive into coding our P2P messages, let’s first finalize the update from our local player input. If you’re unsure how to do that, I recommend checking the Decentraland docs for input in entities.

Code

@Component("ColorComponent")
class ColorComponent {
  constructor(entity: IEntity){
    ...
    var self = this
    this.entity.addComponent(new OnPointerDown(
        function () {
          self.nextColorFromLocalPlayer()
        },
        {
          button: ActionButton.PRIMARY,
          hoverText: "Change color",
          distance: 8
        }
      )
    )
  }

One last thing, let’s make an array of 3 cubes to manage instead of just one.

Code

/// --- Spawn a cube ---
var cubeArray = [
  spawnCube(8, 1, 8),
  spawnCube(5, 1, 8),
  spawnCube(11, 1, 8)
]
cubeArray[0].addComponent(new ColorComponent(cubeArray[0], 0))
cubeArray[1].addComponent(new ColorComponent(cubeArray[1], 1))
cubeArray[2].addComponent(new ColorComponent(cubeArray[2], 2))

Now we have 3 cubes that change color, all set up in a local-only scene.

P2P messages

To simulate multiple players in your local scene, open the Decentraland preview in two different browser windows.

First, we need to define the structure of our messages with the data we want to transmit. In this tutorial, the structure is simple, but in your game, be cautious with this part. The more information you need to transmit, the slower the updates will be. Always try to design your game to reduce the amount of transmitted data.

Code

//Cube state info for P2P
type CubeState = {
  indexCube: number         //Index of the cube in the array
  cubeColorIndex?: number   //Opcional, if present change the color of the cube
  cubePosition?: Vector3   //Opcional, if present change the position of the cube
}

//P2P message struct
type P2PMessage = {
  playerId: string,         //Unique player ID of the emmiter of the message
  cubeData?: CubeState[],   //Array of CubeStates to modify
}

Create a function to update the entities with a received CubeState.

Code

//Load cube state from a P2P message
function loadCubeState(state: CubeState){
  //Find the cube to update
  if (cubeArray[state.indexCube]) {
    const cube = cubeArray[state.indexCube]
    //If a color index was recived, change its color
    if(state.hasOwnProperty('cubeColorIndex')){
      if (cube.hasComponent(ColorComponent)) {
        cube.getComponent(ColorComponent).updateColorFromP2P(state.cubeColorIndex)
      }
    }
    //If a position was recived, change it
    if(state.hasOwnProperty('cubePosition')){
      if (cube.hasComponent(Transform)) {
        cube.getComponent(Transform).position = state.cubePosition
      }
    }
  }
}

Before creating any messages, we need to have a unique player ID. You can design your own system, but since Decentraland player names are unique, we'll use them in this tutorial.

Note: messages that are sent by a player are also picked up by that same player. The “.on” method can’t distinguish between a message that emitted by that same player and a message emitted from other players.

For this reason, it’s a good idea to have unique player IDs to prevent state updates with your own emitted messages.

Code

import { getUserData } from '@decentraland/Identity'

//Our own player ID, must be unique
var selfPlayerId: string
//Load user name/id
executeTask(async () => {
    try {
      const userData = await getUserData()
      selfPlayerId = userData.displayName
    } catch (error) {
      log(error.toString())
    }
})

Now we can start with the P2P code. Create a function to capture emitted messages.

Code

//Decentraland P2P sync
const sceneMessageBus = new MessageBus()

//Catch "cubeupdate" emmited messages
sceneMessageBus.on("cubeupdate", (info: P2PMessage) => {
  //Check the emmiter of the message isn't yourself
  if (info.playerId!=selfPlayerId) {
    if (info.cubeData) {
      //Load every recived CubeState in the message
      for (let i = 0; i < info.cubeData.length; i++) {
        loadCubeState(info.cubeData[i])
      }
    }
  }
})

In the ColorComponent, when the local player changes the color, send a message with the new color index.

Code

@Component("ColorComponent")
class ColorComponent {
  ...
  //Update the color by local the player
  nextColorFromLocalPlayer(){
    ...
    let message: P2PMessage = {
      playerId: selfPlayerId,
      cubeData:[{
        indexCube: this.indexCube,
        cubeColorIndex: newIndex
      }],
    }
    sceneMessageBus.emit("cubeupdate", message)
  }
}

The game is now ready. Open the preview in two browsers and observe the cubes changing colors in both windows.

Decentraland
Code
Alejandro Picazo
Lead programmer
Tutorial
How to import Decentraland SkyboxEditor into Unity
Tutorial
Doing a MANA transaction in a Decentraland scene
Tutorial
Canvas (2D UI) Manager in Decentraland