Destroying enemies with projectiles

Hi there! If you haven’t seen the previous post (weapon handling #2), then here it is, or here are the very beginning.

A little preparation

The logic behind of making decesion what will be destroyed is simple. We mark the entity which we want to vanish. We call for help for this an empty ComponentData, named DeathMarkData. Every enemy what makes contact with a projectile will have this ComponentData attached to it, and at the end of the frame we will gather these entities.

ComponentData


// Assets/Scripts/Physics/DeathMarkData.cs

using Unity.Entities;

public struct DeathMarkData : IComponentData { }

We won't attach this to any of the entities, so we don't need a Proxy for this.

Systems

We made a collosion system before, we will alter that a little. We have the CollisionEventSystem, it's good as is, but we need to modify our CollisionResultResolverSystem's OnUpdate a bit.

protected override void OnUpdate()
{
  EntityCommandBuffer commandBuffer = _entityCommandBuffer.CreateCommandBuffer();

  Entities.With(_query).ForEach((Entity entity, ref CollisionData collisionData) =>
  {
    if (entity == Entity.Null || collisionData.CollidedEntity == Entity.Null) return;

    if (EntityManager.HasComponent<EnemyData>(entity))
    {
      if (EntityManager.HasComponent<EnemyData>(collisionData.CollidedEntity)) return;
      if (EntityManager.HasComponent<ProjectileData>(collisionData.CollidedEntity)) ResetProjectile(collisionData.CollidedEntity);

      commandBuffer.AddComponent(entity, new DeathMarkData());
    }
    else if (EntityManager.HasComponent<EnemyData>(collisionData.CollidedEntity))
    {
      if (EntityManager.HasComponent<EnemyData>(entity)) return;
      if (EntityManager.HasComponent<PrjectileData>(entity)) ResetProjectile(entity);

      commandBuffer.AddComponent(collisionData.CollidedEntity, new DeathMarkData());
    }

    commandBuffer.RemoveComponent<CollisionData>(entity);
  });
}

The changes have been made are that the PlayerData and EnemyData ComponentDatas changed to EnemyData and ProjectileData. When the entity has one of them and the collided entity has the other, then  we add the DethMarkerData to the enemy's entity and we put the projectile back to the pool.
We doing this with the ResetProjectile(Entity entity) method. This looks like this:

private void ResetProjectile(Entity entity)
{
  ProjectileData pd = EntityManager.GetComponentData<ProjectileData>(entity);
  pd.Status = ProjectileStatus.Disabled;
  EntityManager.SetComponentData(entity, pd);

  PhysicsVelocity pv = EntityManager.GetComponentData<PhysicsVelocity>(entity);
  pv.Linear = float3.zero;
  pv.Angular = float3.zero;
  EntityManager.SetComponentData(entity, pv);

  Translation t = EntityManager.GetComponentData<Translation>(entity);
  t.Value = new float3(.0f, 100.0f, .0f);
  EntityManager.SetComponentData(entity, t);
}

In this method, we set the ProjectileData's status, reset the forces that affects that projectile through the PhysicsVelocity and place it far away through it's Translation.

The new stuff

Now we can shoot, we have projectiles, we have enemies, we check if they colliding with each other, we mark if they are. We have one thing to do, in a JobSystem we must clear when it happens.

System

using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;

[UpdateInGroup(typeof(SimulationSystemGroup)), UpdateAfter(typeof(CollisionResultSolverSystem))]
public class KillDeathMarkedEntitesSystem : JobComponentSystem
{
  private EndSimulationEntityCommandBufferSystem _entityCommandBufferSystem;
  private EntityQuery _deathMarkedEntityQuery;

  protected override void OnCreate()
  {
    _entityCommandBufferSystem = World.Active.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    _deathMarkedEntityQuery = GetEntityQuery(ComponentType.ReadOnly<Entity>(), ComponentType.ReadOnly<DeathMarkData>());
  }

  [BurstCompile]
  private struct KillDeathMarkerEntitiesJob : IJobParallelFor
  {
    [ReadOnly] public EntityCommandBuffer CommandBuffer;
    [ReadOnly] public NativeArray DeathMarkedEntities;

    public void Execute(int index)
    {
      for (int i = 0; i < DeathMarkedEntities.Length; i++) CommandBuffer.DestroyEntity(DeathMarkedEntities[i]);
    }
  }

  protected override JobHandle OnUpdate(JobHandle inputDeps)
  {
    NativeArray<Entity> deathMarkedEntities = _deathMarkedEntityQuery.ToEntityArray(Allocator.TempJob);

    JobHandle killEntitiesJob = new KillDeathMarkerEntitiesJob
    {
      CommandBuffer = _entityCommandBufferSystem.CreateCommandBuffer(),
      DeathMarkedEntities = deathMarkedEntities
    }.Schedule(1, 1, inputDeps);

    killEntitiesJob.Complete();
    deathMarkedEntities.Dispose();

    _entityCommandBufferSystem.AddJobHandleForProducer(killEntitiesJob);

    return killEntitiesJob;
  }
}

With the help of query, we search for all entities what has the DeathMarkData ComponentData and we create a NativeArray from it, so we can pass them to our Job.
In the Job, we doing nothing special. We iterate on all the Deathmarked entities, and tell the EntityCommandBuffer to destroy them, that's all.

And here we go, we can shoot down our enemies, have a good shootin' around :)