Adding data from entity prefab to entity instance when spawning new entities

I’m trying to build a spawn system. I have an authoring component that inherits from MonoBehaviour and implements IDeclareReferencedPrefabs, IConvertGameObjectToEntity. So far, so good. In my convert, I’m looping through the GameObject[ ] prefabs and creating and calling manager.AddComponentData on the new entities. The ComponentData I’m adding is fixed per-entity (i.e. prefab1 has e.g. data.acceleration = 1 and prefab2 has e.g. data.acceleration = 1.5 and those values are fixed/read only for every instance of an entity instantiated from that prefab):

public void Convert(Entity entity, EntityManager manager, GameObjectConversionSystem conversionSystem)
{
  _vehiclePrefabCount = vehiclePrefabs.Length;
  vehicleEntityPrefab = new Entity[vehiclePrefabs.Length];

  for (int i = 0; i < _vehiclePrefabCount; i++)
  {
    var vehicleEntity = GameObjectConversionUtility.ConvertGameObjectHierarchy(vehiclePrefabs[i], World.Active);
    manager.AddComponent(vehicleEntity, typeof(Prefab));

    var data = vehiclePrefabs[i].GetComponent<VehicleDataAuthoring>();

    if (data != null)
    {
      manager.AddComponentData(vehicleEntity, new VehicleDataComponent
      {
        acceleration = data.acceleration,
        deceleration = data.deceleration
      });
    }

    vehicleEntityPrefab[i] = vehicleEntity;
  }

  SpawnLocationComponent location = new SpawnLocationComponent
  {
    location = Location,
    chance = spawnChance,
    timeBetween = timeBetweenSpawns,
    direction = SpawnDirection,
  };

  var entitiesBuffer = manager.AddBuffer<SpawnEntityComponent>(entity);

  foreach (var vehicle in vehiclePrefabs)
  {
    entitiesBuffer.Add(new SpawnEntityComponent
    {
      entityPrefab = conversionSystem.GetPrimaryEntity(vehicle)
    });
  }

  LaneIdentityComponent id = new LaneIdentityComponent
  {
    roadId = RoadId,
    laneIndex = LaneIndex,
    laneSubIndex = LaneSubIndex,
    isLeft = IsLeft
  };

  var lanePoints = LanePointsComponentConverter(points);

  var buffer = manager.AddBuffer<LanePointsComponent>(entity);

  foreach (var point in lanePoints)
  {
    buffer.Add(point);
  }

  manager.AddComponentData(entity, new TimeSinceLastSpawnComponent { TimeSince = 0f });
  manager.AddComponentData(entity, location);
  manager.AddComponentData(entity, id);
  manager.AddComponentData(entity, new LaneSpeedComponent { value = speed });

  Debug.Log($"Converted spawner {gameObject.name}");
}

My job looks like this:

public class VehicleSpawnSystem : JobComponentSystem
{
  BeginInitializationEntityCommandBufferSystem _entityCommandBufferSystem;
  float deltaTime;

  Unity.Mathematics.Random randomSource;

  protected override void OnCreate()
  {
    // Cache in a field, so we don't have to create every frame
    _entityCommandBufferSystem = World.GetOrCreateSystem<BeginInitializationEntityCommandBufferSystem>();
    randomSource = new Unity.Mathematics.Random((uint)DateTime.Now.Millisecond);
  }

private struct VehicleSpawnJob : IJobForEachWithEntity_EBCCCC<
  SpawnEntityComponent,
  TimeSinceLastSpawnComponent,
  SpawnLocationComponent,
  LaneIdentityComponent,
  LaneSpeedComponent>
  {
    public EntityCommandBuffer.Concurrent CommandBuffer;
    public Unity.Mathematics.Random RandomSource;
    public float DeltaTime;
    public void Execute(Entity entity,int index,DynamicBuffer<SpawnEntityComponent>spawnEntityComponentBuffer,refTimeSinceLastSpawnComponent timeSinceLastSpawnComponent, [ReadOnly] ref SpawnLocationComponent locationData,[ReadOnly] ref LaneIdentityComponent laneIdentityComponent,[ReadOnly] ref LaneSpeedComponent laneSpeedComponent)
  {
    timeSinceLastSpawnComponent.TimeSince += DeltaTime;

    if (timeSinceLastSpawnComponent.TimeSince > locationData.timeBetween)
    {
      timeSinceLastSpawnComponent.TimeSince = 0f;

      if (RandomSource.NextInt(0, 100) <= locationData.chance)
      {
        // select the prefab to spawn
        var entityToSpawn = spawnEntityComponentBuffer[RandomSource.NextInt(0, spawnEntityComponentBuffer.Length - 1)];

        // set location and orientation of spawned entity
        var instance = CommandBuffer.Instantiate(index, entityToSpawn.entityPrefab);
        CommandBuffer.SetComponent(index, instance, new Translation { Value = locationData.location });

        Quaternion spawnDirection = Quaternion.identity;
        spawnDirection.SetLookRotation(locationData.direction);
        CommandBuffer.SetComponent(index, instance, new Rotation { Value = spawnDirection });

        // add data to entity
        CommandBuffer.AddComponent(index, instance, typeof(ERAutoTag));
        CommandBuffer.AddComponent(index, instance, new LaneIdentityComponent
        {
          isLeft = laneIdentityComponent.isLeft,
          laneIndex = laneIdentityComponent.laneIndex,
          laneSubIndex = laneIdentityComponent.laneSubIndex,
          roadId = laneIdentityComponent.roadId
        });
        CommandBuffer.AddComponent(index, instance, new LaneSpeedComponent { value = laneSpeedComponent.value });
        CommandBuffer.AddComponent(index, instance, new CurrentPositionIndexComponent { value = 0 });

        // CommandBuffer.AddComponent(index, instance, new VehicleDataComponent
        // {
        //   acceleration = VehicleData[instance].acceleration,
        //   deceleration = VehicleData[instance].deceleration
        // });
      }
    }
  }
}

protected override JobHandle OnUpdate(JobHandle inputDeps)
{
  var job = new VehicleSpawnJob()
  {
    DeltaTime = Time.deltaTime,
    RandomSource = randomSource,
    CommandBuffer = _entityCommandBufferSystem.CreateCommandBuffer().ToConcurrent()
  }.Schedule(this, inputDeps);

  _entityCommandBufferSystem.AddJobHandleForProducer(job);

  return job;
  }
}

When I’ve attempted to use var x = GetComponentDataFromEntity<VehicleDataComponent>(true), when I attempt to add it to the entity in Execute, I get an error saying it is still deferred.

It doesn’t seem to me that it should be all that difficult to make a copy of an entity prefab and the data associated with it, but I have no clue where to start.

Good news is im going to try help, bad news is I play for the B team.

Looking at your code it really hard to understand what you are trying to do. Can you share the snips.

Thanks @francois85 ! I’ve updated the code in the original post.

For GetComponentDataFromEntity you need to pass the entity to ComponentDataFromEntity. Stupid example below but hope it still useful

            // In job, not working code just to give you an idea
            [ReadOnly] public ComponentDataFromEntity<VehicleDataComponent> EntityData;

            public void Execute(Entity entity /*....*/)
            {
                var data = EntityData[entity].acceleration;
            }
            inputDeps = new DataFromEnity
            {
                EntityData = GetComponentDataFromEntity<VehicleDataComponent>(true)
            }.Schedule(this, inputDeps);

t

That takes me back to my original error:
ArgumentException: All entities created using EntityCommandBuffer.CreateEntity must be realized via playback(). One of the entities is still deferred (Index: -1).

In Execute:

CommandBuffer.AddComponent(index, instance, new VehicleDataComponent
{
acceleration = VehicleData[instance].acceleration,
deceleration = VehicleData[instance].deceleration
});

I think the issue may be that I’m trying to get the ComponentData from the referenced prefabs, then add it to the newly spawned entity. One work-around may be to add the vehicle data on the spawner entity as DynamicBuffer<VehicleDataComponent> and extract the values from there instead. I’m fairly certain that will work, but it seems like more work than should be required because I’d think there would be a way to schedule things so that the data gets assigned when the entity is no longer deferred.

To confirm, passing the data through the spawner and then to the spawned entities did work around the issue:

  • MonoBehaviour: convert prefabs to entities and add to buffer (manager.AddBuffer<SpawnEntityComponent>(entity)) and add vehicle data (stats) to another buffer (manager.AddBuffer<VehicleDataComponent>(entity))
  • Spawn job: IJobForEachWithEntity_EBBCCCC<SpawnEntityComponent,VehicleDataComponent,TimeSinceLastSpawnComponent,SpawnLocationComponent,LaneIdentityComponent,LaneSpeedComponent> adds new buffer
  • In Execute, add the data to the new entity: CommandBuffer.AddComponent(index, instance, new VehicleStatsComponent { acceleration = vehicleDataComponentBuffer[prefabIndex].acceleration, deceleration = vehicleDataComponentBuffer[prefabIndex].deceleration}).

While it works, I’m not overly fond of this solution since it requires creating 2 buffers with the correct items in the correct order. Hoping there is something a little “cleaner” but also recognize that since we’re somewhat optimizing for performance, we might sometimes have to accept less-than-ideal things on occasion.

1 Like

I’m an idiot. I just needed to add additional fields to the entity component:

public struct SpawnEntityComponent : IBufferElementData
{
    public Entity entityPrefab;
    public float acceleration;
    public float deceleration;
}

And now I only need to handle a single DynamicBuffer (and only need one loop to do so). Much, much cleaner.

That definitely works and is pretty clean.

Your suspicion about the error coming from getting component data from newly “created” prefab is correct - the prefab isn’t actually created at the time you call GetComponent()… it’s just “scheduled” to be created in the main thread. You would somehow need to call AddJobHandleForProducer() and JobHandle.Complete() first, but I don’t think you can do that in your spawn system since it needs to be done on the main thread. You’d have to create another system and then it gets much more messy than your solution.

One problem I will note about your solution of adding the accel/decel to SpawnEntityComponent… it works fine now, but if you ever add another vehicle prefab, you won’t be able to give it a different accel/decel.

The SpawnEntityComponent is actually attached to each vehicle prefab, not the spawner itself (that contains a buffer of SpawnEntityComponents). Something like:

SpawnDetailsComponent // spawner properties
    SpawnEntityComponent // buffer of properties for each entity that can be spawned

So far, working really well. Systems are easy to understand, nicely segmented (single responsibility principle) and easy to monitor in the Entity Debugger.

// Side note: we need a standard way to diagram ECS systems, especially if we could do it in the forum

1 Like

That’s a really cool… I like that design pattern!

Thanks! It just seemed like the obvious solution. I’ve been doing a lot of ES6/React and functional stuff in C# and I’ve been trying to bring some of that thinking into Unity. ECS makes that much, much easier than GameObjects.