Scene instancing
To create multiple instances of the same scene in a world, use SceneSystem.LoadSceneAsync
with the flag SceneLoadFlags.NewInstance
. This is useful if for example you have a different tiles (each tile represented by a scene) and you want to populate the world with those tiles.
When you create a scene in this way, the scene meta entity returned from the load call will refer to the newly created instance.
The instances from a scene are exact copies of each other, because the streaming system loads the exact same data multiple times from the entity scene file. To make sure that each instance isn't exactly the same, you can apply a unique transform on each instance by combining the ProcessAfterLoadGroup
system group and PostLoadCommandBuffer
. You can apply any other kind of changes to the entities in the scene, not just a transform.
Note
Any custom section metadata is exactly the same on each instance because the meta data is stored in the entity scene file.
ProcessAfterLoadGroup system group
The loading of a section doesn't happen in the main world, but on a separate world called the streaming world. Each section loads into its own streaming world. When the load is complete, the content of the streaming world is moved into the main world.
The system group ProcessAfterLoadGroup
runs in the streaming world when all the content is loaded, but before the final move into the main world is performed. You can add custom systems into that group to apply transformations to scene instances.
For example, you could create a system to offset all the entities in the instance to a certain position of the world. In this case you need to pass to the system the offset that you want to apply to the instance. This offset can't be stored inside the entity scene file because it needs to be different for each instance. You can use PostLoadCommandBuffer
to provide this data during loading.
PostLoadCommandBuffer
PostLoadCommandBuffer
is a managed component that contains an EntityCommandBuffer
. During the section loading, the streaming system checks if there is a PostLoadCommandBuffer
present in the section meta entity. If the component is found, the streaming system executes its EntityCommandBuffer
just before running the system group ProcessAfterLoadGroup
.
If you add a PostLoadCommandBuffer
to the scene meta entity instead, all the sections in the scene apply the EntityCommandBuffer
when they're loaded.
The EntityCommandBuffer
inside this component is just a regular entity command buffer and it can be used for any kind of command that we want to execute on the streaming world. For example, if you want to supply per instance information to systems, you can create a new entity for this:
- Use the
EntityCommandBuffer
inside aPostLoadCommandBuffer
to create a new entity - Add components with the instance information to that entity.
This means that you can add a component to a new entity with the offset that you want to apply to the instance.
Scene instancing overview
As a summary, these are the steps to instantiate scenes and apply unique transformations to them:
- Use
SceneSystem.LoadSceneAsync
with the flagSceneLoadFlags.NewInstance
to load a scene. - Store the returned scene meta entity to use it in the next steps.
- Add a
PostLoadCommandBuffer
to the scene meta entity:- Create an
EntityCommandBuffer
. - Create a new entity using that
EntityCommandBuffer
. Add components to it with unique instance information. - Create a
PostLoadCommandBuffer
and store theEntityCommandBuffer
inside. - Add the
PostLoadCommandBuffer
component to the scene meta entity. Don't dispose theEntityCommandBuffer
.
- Create an
- Write a system to apply a unique transformation to the instanced scene.
- Create the system and assign it to the
ProcessAfterLoadGroup
. - Query the instance information from the entity created in the
EntityCommandBuffer
. - Use that information to apply the transforms to the entities in the instance.
- Create the system and assign it to the
For example, to instantiate a scene on a certain position in the world you can do the following:
var loadParameters = new SceneSystem.LoadParameters()
{ Flags = SceneLoadFlags.NewInstance };
var sceneEntity = SceneSystem.LoadSceneAsync(state.WorldUnmanaged,
sceneReference, loadParameters);
var ecb = new EntityCommandBuffer(Allocator.Persistent,
PlaybackPolicy.MultiPlayback);
var postLoadEntity = ecb.CreateEntity();
var postLoadOffset = new PostLoadOffset
{
Offset = sceneOffset
};
ecb.AddComponent(postLoadEntity, postLoadOffset);
var postLoadCommandBuffer = new PostLoadCommandBuffer()
{
CommandBuffer = ecb
};
state.EntityManager.AddComponentData(sceneEntity, postLoadCommandBuffer);
The code before uses a component called PostLoadOffset
to store the offset to apply to the instance.
public struct PostLoadOffset : IComponentData
{
public float3 Offset;
}
Finally, use this system to apply the transformation:
[UpdateInGroup(typeof(ProcessAfterLoadGroup))]
public partial struct PostprocessSystem : ISystem
{
private EntityQuery offsetQuery;
public void OnCreate(ref SystemState state)
{
offsetQuery = new EntityQueryBuilder(Allocator.Temp)
.WithAll<PostLoadOffset>()
.Build(ref state);
state.RequireForUpdate(offsetQuery);
}
public void OnUpdate(ref SystemState state)
{
// Query the instance information from the entity created in the EntityCommandBuffer.
var offsets = offsetQuery.ToComponentDataArray<PostLoadOffset>(Allocator.Temp);
foreach (var offset in offsets)
{
// Use that information to apply the transforms to the entities in the instance.
foreach (var transform in SystemAPI.Query<RefRW<LocalTransform>>())
{
transform.ValueRW.Position += offset.Offset;
}
}
state.EntityManager.DestroyEntity(offsetQuery);
}
}