In a multiuser session, typically objects are instantiated using instantiateSynced as such:
import { Behaviour,GameObject,serializable,InstantiateOptions} from "@needle-tools/engine";
import { Vector3, Object3D, } from "three";
export class InstantiateObjectForAll extends Behaviour
{
@serializable(Object3D)
myPrefab?: GameObject;
public makeObject():void{
const options = new InstantiateOptions();
options.context = this.context;
options.position = new Vector3(0,0,0);
GameObject.instantiateSynced(this.myPrefab, options) as GameObject;
}
}
My particular use-case was for generating programmatically a random scene made of cubes, and that scene had to be the same for all users of the same room. I had used the example above but for some unknown reasons sometimes the scenes were partially rendered when instantiating simultaneously >400 objects. @Marcel of Needle suggested to generate a seed (position of all objects in the scene) and send that seed instead using :
this.context.connection.send()
All users using :
this.context.connection.beginListen()
would receive any seed previously sent, upon joining the same room, allowing them to instantiate cubes according to that seed (array of Vector3).
Here is a script illustrating the use of the send method and the beginListen counterpart:
import { Behaviour,GameObject,serializable,InstantiateOptions} from "@needle-tools/engine";
import { Vector3, Object3D } from "three";
export class NetworkedSeed extends Behaviour
{
@serializable(Object3D)
prefab?: GameObject;
@serializable(Object3D)
generateButton?: Object3D;
public seedSize: number = 30;
seed: Vector3[] = [];
onEnable(): void {
this.context.connection.beginListen("mySeed", this.onDataReceived);
if(this.generateButton)
{
this.generateButton.visible=true;
}
}
onDisable(): void {
this.context.connection.stopListen("mySeed", this.onDataReceived);
}
onDataReceived = (data: any) => {
console.log("Received data:", data.mySeed);
if(this.seed.length===0)
{
if(this.generateButton)
{
this.generateButton.visible=false;
}
this.seed=data.mySeed;
this.buildScene();
}
};
public generateSeed():void{
if(this.seed.length==0)
{
this.seed = [];
const uniquePositions = new Set<string>();
const startPosition = new Vector3(0, 0, 0);
this.seed.push(startPosition.clone());
uniquePositions.add(startPosition.toArray().toString());
while (this.seed.length < this.seedSize) {
const lastPosition = this.seed[this.seed.length - 1];
let newPosition: Vector3;
do {
const direction = this.getRandomDirection();
newPosition = lastPosition.clone().add(direction);
} while (uniquePositions.has(newPosition.toArray().toString()));
this.seed.push(newPosition.clone());
uniquePositions.add(newPosition.toArray().toString());
}
this.sendSeed();
if(this.generateButton)
{
this.generateButton.visible=false;
}
}
this.buildScene();
}
private sendSeed():void{
if(this.seed.length!=0)
{
this.context.connection.send("mySeed",{guid:this.guid, mySeed: this.seed});
console.log("------ SEED SENT -------");
}
}
public buildScene():void{
if(this.seed.length==0)
{
console.log("array was empty");
return;
}
if(this.gameObject.children.length>0)
{
console.log("Scene already present");
return;
}
for(let i=0; i<this.seed.length; i++)
{
const option = new InstantiateOptions();
option.context = this.context;
option.parent=this.gameObject;
option.position = this.seed[i];
if(this.prefab!=null)
{
const cube = GameObject.instantiate(this.prefab, option) as GameObject;
}
}
console.log("----------- Scene Built ---------");
}
private getRandomDirection(): Vector3 {
const x = Math.random() < 0.5 ? -1 : 1;
const y = Math.random() < 0.5 ? -1 : 1;
const z = Math.random() < 0.5 ? -1 : 1;
return new Vector3(x, y, z);
}
}
The above script is placed on an object (any Transform) and will generate an array of unique Vector3 positions for a specified length (seedSize) after generateSeed() is called (In this case it is called from a button: generateButton).
Once generated it will send the array to the server and build the scene. The building process consist of instantiating the prefab at each Vector3 position of the seed (this.seed) array.
Any user joining the same room after a seed has been generated and sent, will receive the seed from the server and trigger the callback onDataReceived() which will cache the seed array, disable the button, and build the scene with the prefab, according to the seed.
This gives a way to generate a scene and communicate the seed of that scene, for each user to build locally.
This was the solution I chose which worked better than instantiating a complex scene (>400 objects) with instantiateSynced which would occasionally cause bugs.