In a multiplayer world you need to make sure all of the clients are aware of the current state. New clients joining need to be told what the current state is, and state changes need to be sent to all existing clients.
The following examples outline some ways to handle this. You can also read Multiplayer Basics for some additional information.
Contents
In order for an Entity to be networked it must have the networked
or net
Entity Tags.
networked
is a more basic tag that allows the Entity to use the this.game.session.sendMessage()
method.net
does the above but also automatically networks transform and animation changes.Any public properties with valid Types in scripts on a networked Entity will automatically be replicated from the current owner to clients when they join.
Note: At the moment any changes made by script are not replicated to other clients during runtime, but changes made in the Entity Panel in "Edit Mode" are. You shouldn't write code that relies on public properties replicating state to other clients, but you can use them to store state for new clients.
Only the following Types will be networked:
// These will be replicated to new clients.
myNumber = 42;
public myVector3 = new Vector3();
// This will not be replicated to new clients.
private myPrivateNumber = 12;
You can hide a public property from displaying on the Entity Panel but maintain the networking by using the @hidden()
decorator on the property.
@hidden()
myNumber = 42;
In order to synchronize state during runtime you need to make use of networked messages. These can be sent from one client to the others with some data. Networked messages are sent within individual Entities, so depending on the complexity of what you are building, you might need to pair these up with some ideas on Cross Script Communication.
Networked Messages are sent using the this.game.session.sendMessage()
method. This method takes the target Entity, the message name and any data you want to pass. Messages are received using the onMessage()
event.
There are some restrictions on what kind of data can be sent. In this case any data that can safely be run through JSON.stringify()
can be sent over the network. More complex types like Vector3 will need to be broken down into their parts and reassembled on the other side.
This example shows the basic setup to send and receive a networked message.
start() {
// This can be any string. It is used in the onMessage event to help understand what kind of message was sent.
const messageName = "myMessage";
// Data must be an object or null.
const data = {
myNumber: 42
};
// This sends the message.
this.game.session.sendMessage(this.entity, messageName, data);
}
onMessage(name: string, data: any, sender: Peer) {
console.log(`${sender.user.handle} sent a ${name} message with the value: ${data.myNumber}`);
}
Running this code as the only player in a game will show you that the sender does not receive their own messages.
If you run this with multiple players in a game you will all send a message and you will all receive messages from every other player. This is quite possibly not something you want. You may want some sort of owner or "server" to maintain and replicate state to other players.
In dot big bang every Entity has an owner. This is usually the first player to join the game, but this is not always the case. Entities spawned by a player will be owned by them. If an owner leaves the game their owned Entities will be assigned a new owner from one of the remaining players.
In this example only the current owner of the Entity is allowed to send messages. If you run this you will only see logs on other clients.
start() {
// Restrict this to the owner.
if (!this.entity.isOwnedLocally) {
return;
}
// This sends the message.
this.game.session.sendMessage(this.entity, "mymessage", {
myNumber: 42
});
}
onMessage(name: string, data: any, sender: Peer) {
console.log(`${sender.user.handle} sent a ${name} message with the value: ${data.myNumber}`);
}
So far we've been sending events immediately in start()
. These events will be missed by any players that join the game later since they weren't around when the message was sent.
To handle this you can have new players ask for the current state when they join and have the owner send it.
In the below example a new player will ask the owner to send them the current state (The number of times clients have asked to synchronize state) when they join. If they happen to be the owner then no state syncronizing is needed and the value starts at 0.
// The object's state
private _state = {
numberOfSyncRequests: 0
};
start() {
// Restrict this to non owners.
if (this.entity.isOwnedLocally) {
return;
}
// Ask for a state update.
this.game.session.sendMessage(this.entity, "syncRequest", null);
}
onMessage(name: string, data: any, sender: Peer) {
// Only the owner can send a response to a sync request.
if (name == "syncRequest" && this.entity.isOwnedLocally) {
// Increment the number of requests.
this._state.numberOfSyncRequests++;
// Send the state.
this.game.session.sendMessage(this.entity, "syncResponse", {
// We send along the peer id of the sender so only that sender can update their state.
targetPeerId: sender.id,
numberOfSyncRequests: this._state.numberOfSyncRequests
});
// Log it out.
console.log(`Got request: Number of requests = ${this._state.numberOfSyncRequests}`);
}
// Only the sender of the sync request should handle a sync response.
else if (name == "syncResponse" && data.targetPeerId == this.game.session.localPeer.id) {
// Update the state.
this._state.numberOfSyncRequests = data.numberOfSyncRequests;
// Log it out.
console.log(`Got response: Number of requests = ${this._state.numberOfSyncRequests}`);
}
}