Search Unity

  1. Unity 2019.1 is now released.
    Dismiss Notice

What's the most ECS-like way of writing simplistic AI

Discussion in 'Data Oriented Technology Stack' started by verenion, May 11, 2019.

  1. verenion

    verenion

    Joined:
    Mar 25, 2013
    Posts:
    10
    I'm not new to coding, or to Unity for that matter, but I am fairly new to the ECS pattern. I've been getting my feet wet with it, and seem to be getting on okay. I understand that components store data, which are then 'tagged' to an Entity. Systems can then be developed to interact and respond with these components and entities. So far, so good. I have however hit a bit of a mental wall when I have a very simple AI.

    For all intents and purposes, let's say we have an Entity, with an Enemy Component (simply used to tag that this Entity is an enemy), a Health Component and the usual transform components (Translation, Rotation etc.).

    Now let's say we have another Entity with a Player Component (again, just a tag), along with Health Component and the usual suspects.

    I've been struggling to think of the most pure ECS way of simply having a system whereby the Enemy Entity can "look" for players within a certain range. If you get within, say 10 units, of the enemy, an "AttackPlayer" component can be send to the Enemy Entity and off it goes. The tricky bit for me, mentally, is the spacial awareness of the AI.

    If there is only one player, this is trivial, have a system pull every EnemyComponent, along with the enemies Translation then compare that position to the player's position. But let's say the player can control lots of units, say, 1000 units. This could become a problem - we can't just pull 1000 Enemies, then for each of those compare to each of the 1000 Player units. Standard O(N)

    Alternatively, I could take all the entities I want to query and build a tree of some sort, like a quad tree, every frame. I could then use a system to populate the quad tree (maybe pull all entities with Translation and maybe a "TargetableComponent"), and other systems can use that quadtree to query for nearby entities. This doesn't feel like the right solution to the problem, but maybe I'm just misunderstanding something.

    Is there any tips anyone has for solving this problem? Thanks in advance!
     
  2. Xerioz

    Xerioz

    Joined:
    Aug 13, 2013
    Posts:
    103
    What you're looking for is a spatial tracking and a signaling system.

    The way I approach things in my Utility AI system for spatial awareness is still partially OOP.

    I have a spatial tracking system that registers/unregisters entities which uses kdtree internally.
    Anything that has SpatialTrackTag registers that entity into the kdtree, adds SpatialTrackTagInternal, and updates its position based on position component data. If SpatialTrackTag is removed and SpatialTrackTagInternal is still there, we unregister the entity and remove it from the kdtree.

    Afterwards I also have a signaling system. This one loops through every entity with signal tag + bunch of other signal data and uses that other system's kdtree to find nearest entities, and once everything is correct and all filters are good, I add a buffer element ( e.g. SignalReceived ) with data from that signal.

    For example:
    Code (CSharp):
    1. -AgentA: SpatialTrackTag, Position, SignalSend ( radius: 10, max-receivers: 5 ), SignalFaction ( friendly), DynamicBuffer<SignalReceived>
    2. -TurretA: SpatialTrackTag, SpatialTrackOnceTag, Position, SignalSend ( radius: 30, max-receivers: 5 ), SignalFaction ( enemy ), DynamicBuffer<SignalReceived>
    3.  
    4. SpatialSystem updates every x seconds
    5. SignalBroadcastSystem updates afterwards
    6.  
    Then you have your systems that digests the SignalReceived buffer, does something with it and clears it.

    Obviously this isn't the most efficient approach to this problem. I'm planning on removing kdtree and replacing it with spatial grid array, I believe that its possible to convert that structure to ECS properly.

    I suggest you read http://www.gameaipro.com/ books, they contain a lot of techniques on this matter.
     
    pakfront and verenion like this.
  3. verenion

    verenion

    Joined:
    Mar 25, 2013
    Posts:
    10
    Thanks for your reply! Very helpful.

    This sounds similar to my quadtree solution. I really like your idea of using components to send signals, which are then picked up by other systems and results being sent back to another component. This also solves the issue that I didn't want these systems to be running every frame, signals makes it much easier to control.

    I'm not sure how you are receiving the data with DynamicBuffer, but that's my lack of knowledge of them. I will do more reading.

    If you were to create a spatial grid array with ECS, would you simply have entities with a component for each of the grids?
     
  4. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    2,160
    Don't discard linear iteration out of hand. And get familiar with ECS and burst so you have a feel for how it performs before making too many assumptions.

    Ai is one of those areas where gameplay reasons and larger game design give you a lot of ways to do less work then what it might appear at first. Like targeting is almost always fairly sticky. Most ai decisions can run at intervals of say 200 ms to several seconds. Like our targeting runs every 200 ms, but it has an early out for stickyness, selecting new targets happens on average very 10 seconds or so per agent. Plus we process agents N at a time per frame so we don't get big jumps in cpu usage even if all agents decide at the same time they need a new target.

    Design often gives you ways to group things also. So the above combined with groups that make for fewer iterations, and linear starts to be the best bang for the buck in a lot of cases. Plus linear has an advantage over random access, and trees have a lot of random access. So you have to factor that in also.
     
    verenion likes this.
  5. verenion

    verenion

    Joined:
    Mar 25, 2013
    Posts:
    10
    Thanks. I guess I was more talking about extremes. I realize that with jobs and burst iteration, especially concurrently, is very fast - but I just want to make sure there isn't a better way. As you say, I think the best way is to take each problem and use a design that is appropriate. For reasonable amounts of entities, linear iteration will probably be fine (I'm already doing that in other areas of this project and I've made notes to come back and see if there is a better way, but honestly, when it's taking 0.4ms, it's probably not worth worrying about). For larger sets of entities, a mixture of spatial trees, frame and time limits can all be used.

    Thanks for the sanity check guys. I think I have a better picture of what I'm doing now.
     
  6. eterlan

    eterlan

    Joined:
    Sep 29, 2018
    Posts:
    42
    Hi, we can use physics now, so what about using raycast to find target?
     
    verenion likes this.
  7. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    4,573
    To be honest, you cold do that, before physics was implemented. In 2018.x.

    OP. Depending how many players are, and enemies, simple position vector subtraction and getting distance from that, maybe all you need. Or Bounding boxes intersection checks.
     
    verenion likes this.
  8. eterlan

    eterlan

    Joined:
    Sep 29, 2018
    Posts:
    42
    That require a linear iteration to get every position first right? I thought we are talking about how to avoid that.
     
    verenion likes this.
  9. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    4,573
    Ah yes, I re read OP post and mentioned 1000x1000 units potential checks. So indeed, spatials and entities filters will be more suitable. Plus as mentioned, reduce time, of how often checks are made, if accuracy is not critical. Always can implement prediction, if anything.

    Having some spatial tree and raycast, can be good option for certain cases. Or with bounding boxes. Just similar principle for 2d maps, as for 3d like octree.
     
    verenion likes this.
  10. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    860
    So your are asking about a good broad phase approach for collision (with a 10 unit extended collider). The answer is likely dependent on (a) are you looking for a general solution or a solution optimized for your case and (b) if you look for the latter, some more info about your case (i.e. 2d / 3d, movement behaviors of enemies / players, AI update frequency/accuracy etc.)

    I would start with something simple (a) see if you can use the broadphase of unity physics --- i have not looked into it or (b) just assign players / enemies to a spacial grid
     
    verenion likes this.
  11. verenion

    verenion

    Joined:
    Mar 25, 2013
    Posts:
    10
    I did try using the new physics system, but it just seemed very overkill. I can get away with updating this quadtree every 0.5s. Maybe I'll come back and look at the physics system when it's a little more evolved and documented.

    This is what I've done for now - just using a fairly simple quadtree that gets updated every 0.1s. This is simply for spatial awareness, the accuracy is not that important.