Search Unity

Question Army battle

Discussion in 'Entity Component System' started by lavos2300, Apr 19, 2023.

  1. lavos2300

    lavos2300

    Joined:
    Jun 15, 2015
    Posts:
    10
    I am trying to simulate an army battle with DOTS.
    I want each soldier to find the nearest enemy and attack him. When a soldier is wounded, his health decreases.
    Since a soldier can be hit by several enemies at the same time, I use a DynamicBuffer to store all the hits.
    When EndSimulationEntityCommandBufferSystem is finished, a system is executed to apply the final damage to the soldier's health attribute by looping through the DynamicBuffer.
    I use an EntityCommandBuffer to add all the keys to the DynamicBuffer. My problem is that ecb.AppendToBuffer requires an Entity as the first parameter and I can't find any way to get that information.

    Am I using the wrong method?
     
  2. boyaregames

    boyaregames

    Joined:
    Jun 23, 2021
    Posts:
    76
    foreach (var (component, entity) in SystemAPI.Query<YOUR_COMPONENT>().WithEntityAccess())
     
  3. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,780
    You could avoid writing hits into dynamic buffer, since you don't know the amount of hits and the size of buffer, you may need to increase the size. Unless you allocate enough size prior, which means, less entities per chunk.

    The easiest approach would be active list, where you write damage and corresponding I'd to the entity. Then iterate through and set damage / health.
    But tha will require multi components random access.

    More organise way, you could use for example native multi hash map collections, where you write all damages per entities, and then in the next job iterate through all keys (reference to entity), sum the damage, and write results back to the entities (optionally do the kill event).

    You could also use Native list as 2d array, with unsafe array, or unsafe list, but you will need to be extra careful with using it, so you don't run into overflows. Also there is more into managing entities references. So it is lesser of recommending solution.
     
  4. lavos2300

    lavos2300

    Joined:
    Jun 15, 2015
    Posts:
    10
    @boyaregames : Thank you. This could help. However I do not think I can have multi-threading with this solution as I will not be able to use SystemAPI in a job. Am I wrong ?

    @Antypodish : Thanks a lot for your help. I am little confused to be honest. :)

    In your first solution, wouldn't there be a possible race condition when using multi-threading if the list is accessed by several instances at the same time ?
    Also what do you mean by multi components random access ?

    In order to implement your second solution, I need to find a way to access the entity of the soldier that was hit by the attacker inside a parallel job. I don't know how to do that.

    I would like to avoid your third solution if possible.
     
  5. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    How are you figuring out that a soldier is hit? Can you perhaps show code of where you are trying to use the ECB but are stuck?

    I suspect this can be done without ECB, but I need to better understand what you are doing.
     
  6. lavos2300

    lavos2300

    Joined:
    Jun 15, 2015
    Posts:
    10
    Sure, here is some simplified code for a battle between an army of orcs and a human army :

    Code (CSharp):
    1. [BurstCompile]
    2. public partial struct AttackClosestOrcJob : IJobEntity
    3. {
    4.     public float DeltaTime;
    5.     public EntityCommandBuffer ECB;
    6.     public DynamicBuffer<Orc> Orcs;
    7.  
    8.     [BurstCompile]
    9.     private void Execute(HumanSoldierAspect humanSoldier)
    10.     {
    11.         Orc closestOrc = new();
    12.         float bestDistancesq = float.MaxValue;
    13.  
    14.         foreach (Orc orc in Orcs)
    15.         {
    16.             float distancesq = math.distancesq(humanSoldier.LocalPosition, orc.Position);
    17.             if (distancesq < bestDistancesq)
    18.             {
    19.                 closestOrc = orc;
    20.                 bestDistancesq = distancesq;
    21.             }
    22.         }
    23.  
    24.         if (humanSoldier.IsWithinDistance(closestOrc.Position)) humanSoldier.Attack(ref closestOrc, DeltaTime, ECB, Damage);
    25.     }
    26. }
    27.  
    28. public readonly partial struct HumanSoldierAspect : IAspect
    29. {
    30.     public readonly Entity Entity;
    31.  
    32.     private readonly TransformAspect transformAspect;
    33.  
    34.     private readonly HumanSoldierProperties properties;
    35.  
    36.     private readonly RefRW<Health> health;
    37.  
    38.     public float3 LocalPosition
    39.     {
    40.         get { return transformAspect.LocalPosition; }
    41.     }
    42.  
    43.     public bool IsWithinDistance(float3 orcPosition)
    44.     {
    45.         float3 humanSoldierPosition = transformAspect.LocalPosition;
    46.         float orcDistancesq = math.distancesq(orcPosition, humanSoldierPosition);
    47.         return orcDistancesq <= Threshold;
    48.     }
    49.  
    50.     public void Attack(ref Orc orc, float deltaTime, EntityCommandBuffer ecb, float damage)
    51.     {
    52.         DamageBufferElement damageBufferElement = new DamageBufferElement
    53.         {
    54.             Value = damage,
    55.         };
    56.         ecb.AppendToBuffer<DamageBufferElement>(I_NEED_THE_CLOSEST_ORC_ENTITY_HERE, damageBufferElement);
    57.     }
    58. }
    59.  
    60. [BurstCompile]
    61. public partial struct DecreaseOrcHealthJob : IJobEntity
    62. {
    63.     public EntityCommandBuffer.ParallelWriter ECB;
    64.  
    65.     [BurstCompile]
    66.     private void Execute(OrcAspect orc, [EntityIndexInQuery] int sortKey)
    67.     {
    68.         foreach (var damageBufferElement in orc.DamageBuffer)
    69.         {
    70.             orc.Health -= damageBufferElement.Value;
    71.         }
    72.         orc.DamageBuffer.Clear();
    73.  
    74.         if (orc.IsDying())
    75.         {
    76.             ECB.DestroyEntity(sortKey, orc.Entity);
    77.         }
    78.     }
    79. }
     
  7. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    I won't talk about performance optimizations here right now. There's a lot of stuff that can be improved. But my first question is:
    Why can't you store the orc entity in the
    Orc
    type?
     
  8. lavos2300

    lavos2300

    Joined:
    Jun 15, 2015
    Posts:
    10
    How would you do this ? I am using an Entity Command Buffer to spawn the orcs at initialization. Each instantiation returns an entity but from my understanding, it is like a temporary reference that can only be used by that entity command buffer and not furthermore in your game. I don't know the right way to get each orc entity and store it.
     
  9. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    Assign it the Entity you get from the baker when baking the component. It will remap during instantiation.
     
    lavos2300 likes this.
  10. lavos2300

    lavos2300

    Joined:
    Jun 15, 2015
    Posts:
    10
    Ok I got it. Turns out I have to reuse the Entity Command Buffer that I use to instantiate the orcs to append them to the orc army buffer. Otherwise, the remapping doesn't happen.
    Thanks a lot for your help @DreamingImLatios !

    Now, you were talking about possible improvements. I would love to hear your suggestions.
     
  11. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    lavos2300 likes this.