Spawning objects is really common in games. It's a great way to show effects when things happen, or alter the world as the player interacts with it. In dotbigbang objects can be spawned by building and adding them or adding a Template to the game.
Spawned objects also have a networked state. They can be spawned for only the local client, or for all clients. This is important to use correctly as it can affect performance or cause unintended behavior.
Contents
There are two methods that can be used to add objects to a game at runtime. These are this.game.addEntity()
and this.game.addEntityFromTemplate()
. These will be explained in the examples below.
Entities can be added by simply creating them and adding them to the game. Doing this will add an invisible object with a default position, rotation and scale in the local clients game called "unnamed entity".
start() {
// Create the Entity.
const myEntity = new Entity();
// Add the Entity to the game.
this.game.addEntity(myEntity);
}
You probably want to see and position the added Entity, so lets do that. We can also give the Entity a name.
// Assign a Voxel Object to add to the Entity in the Entity Panel.
voxelObject = new VoxelObjectRef();
// The world position the Entity should spawn at.
position = new Vector3();
// The rotation the Entity should have.
rotation = new Euler();
// The scale of the spawned Entity.
scale = new Vector3(1, 1, 1);
start() {
// Create the Entity.
const myEntity = new Entity();
// Name the Entity.
myEntity.name = "Spawned Entity";
// Create a VoxelObjectComponent for the visuals.
const voxelObjectComponent = new VoxelObjectComponent();
voxelObjectComponent.voxelObject = this.voxelObject.get();
// Add the component to the Entity.
myEntity.addComponent(voxelObjectComponent);
// Transform the Entity.
myEntity.worldTransform.position.copy(this.position);
myEntity.worldTransform.rotation.copy(this.rotation);
myEntity.worldTransform.scale.copy(this.scale);
// Add the Entity to the game.
this.game.addEntity(myEntity);
}
Adding a Template to the game take a little more initial setup but makes it much easier to add complex objects to the game. Templates already have all of the components and scripts set up so you don't need to manually add those when you spawn one.
Templates save and spawn with whatever position, rotation and scale they were last saved at. We'll look at how to change these in the next example.
Note: To make a Template of an object all you need to do is select the object, expand the "Entity" section in the Entity Panel and click "New Template".
// Select the Template to add to the game.
template = new TemplateRef();
start() {
// Ensure that the Template exists.
if (!this.template.exists()) {
return;
}
// Add the Template to the game. We know it exists so we use ! to tell the compiler it can't be null.
this.game.addEntityFromTemplate(this.template.get()!);
}
Here we will add a template to a spacific spot in the world. This example will also show that you can get the added Entity as a variable and do further operations on it.
// Select the Template to add to the game.
template = new TemplateRef();
// The world position the Entity should spawn at.
position = new Vector3();
// The rotation the Entity should have.
rotation = new Euler();
// The scale of the spawned Entity.
scale = new Vector3(1, 1, 1);
start() {
// Ensure that the Template exists.
if (!this.template.exists()) {
return;
}
// Add the Template to the game and get a reference to it. We know it exists so we use ! to tell the compiler it can't be null.
// We're setting networking to false since that is covered below.
const spawnedEntity = this.game.addEntityFromTemplate(this.template.get()!, false, false, this.position, this.rotation, this.scale);
// Set the name.
spawnedEntity.name = "Spawned Template";
}
When you add an Entity to the game you can optionally choose to network it and its transform. This is an easy way to get stuff happening on all clients if used correctly.
this.game.addEntity()
and this.game.addEntityFromTemplate()
both have networked
and networkTransform
arguments.
networked
- If true, the Entity will be replicated over the network on all the other Peers in the Session. If false, the Entity will only exist on the local Peer.networkTransform
- If true, the transform of the Entity will automatically be replicated over the network to all the other Peers in the Session. This can be true only if the networked
argument is true.Add the networked
tag to the Entity that this script is on. This will assign it an owner so that we can spawn a single object and have it replicate to the other clients. If this tag isn't set each client will spawn and replicate an object.
// Select the Template to add to the game.
template = new TemplateRef();
start() {
// Restrict this to the owner so that only one Entity spawns.
if (!this.entity.isOwnedLocally) {
return;
}
// Ensure that the Template exists.
if (!this.template.exists()) {
return;
}
// Only network the Entity, we'll go over transform later.
const networked = true;
const networkTransform = false;
// Add the Template to the game. We know it exists so we use ! to tell the compiler it can't be null.
this.game.addEntityFromTemplate(this.template.get()!, networked, networkTransform);
}
Add the networked
tag to the Entity that this script is on. This will assign it an owner so that we can spawn a single object and have it replicate to the other clients. If this tag isn't set each client will spawn and replicate an object.
// Select the Template to add to the game.
template = new TemplateRef();
// Keep track of the spawned Entity so we can move it.
private _spawnedEntity?: Entity;
// Some variables to help move the object over time.
private _origin = new Vector3();
private _movementVector = new Vector3();
start() {
// Restrict this to the owner so that only one Entity spawns.
if (!this.entity.isOwnedLocally) {
return;
}
// Ensure that the Template exists.
if (!this.template.exists()) {
return;
}
// Network the Entity and the transform.
const networked = true;
const networkTransform = true;
// Add the Template to the game. We know it exists so we use ! to tell the compiler it can't be null.
this._spawnedEntity = this.game.addEntityFromTemplate(this.template.get()!, networked, networkTransform);
// Store the initial position so we can return to it later.
this._origin.copy(this._spawnedEntity.worldTransform.position);
}
tick() {
// Only allow the owner to move the Entity.
if (!this.entity.isOwnedLocally) {
return;
}
// Exit if an Entity hasn't been spawned yet.
if (!this._spawnedEntity) {
return;
}
// Do some math to determine a scale value.
const scale = Math.cos(this.game.frameTime + Math.PI) * 100;
// Move the Entity along it's up direction by the scaled amount.
this._movementVector.copy(this._spawnedEntity.worldTransform.getUp()).normalize().multiplyScalar(scale);
this._spawnedEntity.worldTransform.position.copy(this._origin).add(this._movementVector);
}
In some cases you may not want to network the spawned Entity itself and would rather have each client spawn their own copy of an entity.
When initiateSpawn()
is called we will spawn a local copy of a Template on each client. This means that any clients joining later will not get a copy, so it should only be used for transient things like effects.
Below we'll set up a delay timer to allow you to get multiple clients into the same game so you can see each client spawn an object. You can also rejoin with a non-owner client to see that they don't spawn a copy since the message has already been sent.
Add the networked
tag to the Entity that this script is on. This will allow it to send and recieve network messages.
// Select the Template to add to the game.
template = new TemplateRef();
// Set up a simple delay timer.
private _delayRemaining = 3;
tick() {
// Initiate the spawn when the timer hits 0.
if (this._delayRemaining > 0) {
this._delayRemaining -= this.game.frameDeltaTime;
if (this._delayRemaining <= 0) {
this.initiateSpawn();
}
}
}
// Called by some game mechanism to spawn a non-networked entity on all clients.
initiateSpawn() {
// Restrict this to the owner so that only one spawn message is sent.
if (!this.entity.isOwnedLocally) {
return;
}
// Spawn a copy for the owner.
this.spawnEntity();
// Send the spawn message to the other clients.
this.game.session.sendMessage(this.entity, "spawn", null);
}
onMessage(name: string, data: any, sender: Peer) {
if (name == "spawn" && !this.entity.isOwnedLocally) {
// Spawn the Entity.
this.spawnEntity();
}
}
// A helper method to span the Entity since the owner and other clients do this at different places in the code.
private spawnEntity() {
// Ensure that the Template exists.
if (!this.template.exists()) {
return;
}
// Do not network the Entity or the transform.
const networked = false;
const networkTransform = false;
// Add the Template to the game. We know it exists so we use ! to tell the compiler it can't be null.
this.game.addEntityFromTemplate(this.template.get()!, networked, networkTransform);
}