0
\$\begingroup\$

I am considering to implement AI using ECS which actually contradicts working with naive FSMs. My current idea is to have multiple components which represent the particular state an Entity that has a AIControllerComponent is currently in.

Let's say an enemy starts out with the mentioned AIControllerComponent as well as a EnemyIdleComponent. Now an EnemyIdleSystem would handle all those idle enemies and if it happens that a player comes too close it'd replace the EnemyIdleComponent with an EnemyChasePlayerComponent leaving the handling of this to EnemyChasePlayerSystem.

This approach is rather generic and I would basically end up with a bunch of enemies that have the very same behaviour. I was curious to know how I should combine this approach with some more modular states. At first I thought about adding an enum variable to AIControllerComponent which holds the type of the enemy but I can't really think of a decent solution to call different implementations inside of the EnemyFooSystems based on that particular enum value. Do you have any suggestions?

// EDIT: I am using pure ECS patterns and Jobs, as such MonoBehaviours and non-blittable types inside of IComponentData are not usable.

\$\endgroup\$
1
  • \$\begingroup\$ I think that before you can make any headway you need to back up and look at the bigger picture, it feels like you're stuck in the minutiae of implementation and may be forcing that into a design that doesn't work well. I often take a breather and rethink when I run across this kind of friction when working on something new, kind of like the rubber ducky debugging concept taken to the design stage. \$\endgroup\$ Jan 7, 2019 at 18:32

2 Answers 2

1
\$\begingroup\$

I recommend you the chapter on State-Driven Agent Design in Programming AI by Example, By Matt Buckland, since a general approach of Finite State Machines is shown there. It proposes a singleton generic FiniteStateMachine System, which handles the state updating and transitions of all the agents. The states, by the way they are designed (generally and in the book) are also of the kind of singleton classes, but you could make it Component-wise, so they have their reference to their own agents, so you can handle each agent separately (not applied on my example, but it would be just a matter to add a reference to their Agents, and delete the agent parameter on their methods).

Here is a very generic example, the ECS would be separated by Agents (Entitites), States (Components) and a Finite State Machine per Agent Type (System):

/// Entity
public abstract class FSMAgent<T> : MonoBehaviour where T : MonoBehaviour
{
    public StateComponent<T> currentState, previousState;

    private void Update()
    {
        FiniteStateMachine<T>.UpdateState(this);
    }
}

/// Component
public abstract class StateComponent<T> where T : FSMAgent<T>
{
    /// I put it virtual, since maybe there'd be transition actions that you'd like to avoid in certain states.
    public virtual void Exit(T agent) { /*...*/ }
    public virtual void Enter(T agent) { /*...*/ }
    public virtual void Execute(T agent) { /*...*/ }
}

/// System
public class FiniteStateMachine<T> where T : FSMAgent<T>
{
    public static void ChangeState(T agent, StateComponent<T> state)
    {
        /// Store and execute exit of actual state. Execute new state's entrance.
        if(agent.currentState != null)
        {
            agent.previousState = agent.currentState;
            agent.previousState.Exit(agent);
        }
        agent.currentState = state;
        agent.currentState.Enter(agent);
    }

    public static void UpdateState(T agent)
    {
        /// Execute state and check for conditions and transitions...
        if(agent.currentState != null) agent.currentState.Execute(agent);
    }
}

So you just have to define classes that inherit from the base, I'll take the example with Halo's Agents, since I read "Brutes" on another answer:

public class Grunt : FSMAgent<Grunt> { /*...*/ }
public class Elite: FSMAgent<Elite> { /*...*/ }
public class Jackal : FSMAgent<Jackal> { /*...*/ }

public class GruntIdle : StateComponent<Grunt> { /*...*/ }
public class EliteIdle : StateComponent<Elite> { /*...*/ }
public class JackalIdle : StateComponent<Jackal> { /*...*/ }

/// And so on...

And they just would call the static reference of the Finite State Machine of their own kind.

Hope it helps.

\$\endgroup\$
3
  • \$\begingroup\$ Unfortunately, your suggestion requires the use of MonoBehaviour since you rely on non-blittable data which is inherently incompatible with pure ECS. Nonetheless your suggestion is making me consider to write a system for each enemy type (AiGruntSystem, AiEliteSystem, AiJackalSystem) that has one job per state indicated by component (AiGruntIdleComponent, AiGruntFollowComponent, ...) of that particular enemy type and run those in parallel in the system leveraging the Job system. \$\endgroup\$ Aug 31, 2018 at 12:19
  • \$\begingroup\$ I am sorry, I didn't consider that you wanted to exclude MonoBehaviour and use pure ECS. But, the implementation does not depend on it, I just put the inheritance for the sake of putting it into a Unity context, you may take out the MonoBehaviour keywords and it would be the same, as technically components are decoupled of their entities (unlike Unity's components). \$\endgroup\$ Aug 31, 2018 at 12:28
  • \$\begingroup\$ The general gist is actually right and useful which helped me get a better picture of what I might attempt. It's just that a component won't be able to store objects (especially with functions). I will update my question to reflect that I am going for pure ECS in the first place. \$\endgroup\$ Aug 31, 2018 at 12:39
0
\$\begingroup\$

I dont really understand, why you would want several components with their own system. You basically just need one BehaviourComponent, whice either has only data (System works on Components) or also has functions (System calls Components functions), depending on your liking.

Just to make it clear, i got no idea of unity. And in my following explanation i assume you only use components with data.

Lets assume you have 3 types of enemies with equivalent equipment:

  • Brutes that charge and attack your Character in Close Combat. They wont block or retreat

  • Balanced enemies that approach you carefully, block and attack you mostly the same time. if Hurt, they may retreat

  • anxious enemies that wont approach you, mostly block and may flee if a friend gets hurt or even dies

    There three enemy types are basicly the same except for their behavior.

Now, for the sake of it, lets assume they each have three states in their fsm: idle, aware and hurt. On creating an enemy, you set the BehaviourComponentwith behavior pattern. In Idle, the brute may actively search for an enemy, called Search, when he found an enemy, he is Attackingand when hurt he is still Attacking. Each of these Actions you implement as Enums.

enum Action {Search, Attack}

Now every state needs to know, when to transition to another state. You could do something like simple events.

enum Event {FoundCharacter, GotHurt}

So. how to implement this? For every state you have you could implement a struct (if you are using something like c#).

public struct State{
    public Action 
    public Event
}

This would be the most simple case. On initiating your BehaviourComponent you create to states Idle and Aware. For Idle set Action to Search and Event to FoundEnemy, for Aware set Action to Attack and leave Event just empty, as he fights to the death. Your BehaviourComponent would then have a starting state, e.g. Idle and the information to go from one state to another.

Your BehaviourSystem then iterates over the BehaviourComponent, checks if an event occures and in case switches the state, then if follows the set Action.

To make it a bit more difficult, our balanced enemy tries to attack and block when in State of Aware. For that you could set several actions, like in an array or another struct to give them probabilities.

Your anxious guard may also have several states. When switching from Idle he might go to Aware or directly to Run or something else. This would be the same like several different actions.

The best with this approach is, your BehaviourSystem only needs to know what to do on different Actions, as the switch of states is 'sort of' implemented in the BehaviourComponent.

If you want it even more complex, you would need something like a script language, to load the 'BehaviourComponents' for each enemy type and maybe even for every Action, so you dont even have to implement them in the system. This could be loaded at runtime every time you would need it.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ A single component used for entities controlled by the AI with a single system will cause performance issues. It's likely more efficient to write systems that run in parallel that each update their respective entities. Furthermore a single system violates the separation of concerns and makes testing hard. Moreover, you suggest storing non-blittable data in the BehaviourComponent. \$\endgroup\$ Aug 31, 2018 at 12:07
  • \$\begingroup\$ @ChristianIvicevic Single component in single thread systems actually is much better, as you can avoid cache misses much better. In parallel systems (with 1:1 component to system), that might be different, yes. But assuming that each of your behaviour systems can run parallel is very unlikely. Most times, several enemies will have the same state and only a few will have different. The non-blittable data problem is true, but this was only a simple example. \$\endgroup\$
    – PSquall
    Sep 3, 2018 at 7:30

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .