AI Architecture

    This site uses cookies. By continuing to browse this site, you are agreeing to our Cookie Policy.

    • AI Architecture

      So before diving into the intricacies of AI, I want to make sure my architecture is at least at a good starting point. (I understand I could use the GCC4 code base as an example, but I'm having a hard time searching through and understanding the LUA scripts and since I'm in the XNA world, scripting isn't really necessary so I don't want to take a ton of time learning it.) Anyways, right now I have an "IBehavior" abstraction (essentially the State) with different implementations (Patrol, Attack, etc.).

      A Brain is responsible for determining the best IBehavior to apply, and the AIStateMachine is responsible for 2 things...

      1) Telling the Brain to figure out which IBehavior it should currently apply

      2) Telling the current IBehavior to update

      ...then each actor that needs AI gets an AIComponent who on each tick tells it's state machine to...

      1) ChooseBestBehavior()

      2) Update()

      ...this is then all wrapped up in an AIProcess that on each tick loops through each Actor in the system and for those who have an AIComponent, tells the component to update.

      So from front to back for any given Tick it looks like...

      (-> can be though of "resulting in")

      AIProcess.Update ->
      Actor.AIComponent.Update -> (2 things)
      1) AIStateMachine.ChooseBestBehavior -> Brain.Think,
      2) AIStateMachine.Update ->
      IBehavior.Update ->
      Actor doing some intelligent stuff

      Does this look like a good start? Anyone have any ideas as to how I could improve this?

      Thanks!
      -bullgoose
    • RE: AI Architecture

      What's your goal? If you're just trying to play around with some AI, then your proposal sounds reasonable. It's very simple so spinning up new states will be very easy, but your system won't scale well. If you're trying to build a complex simulation or strategy game, you'll hit the limitations of a simple state machine very quickly. If you want hundreds of states per actor, scoring all of them will get slow very quickly.

      If your goal is to make platformer or simple zelda-style AI, then this system is perfect. It's somewhat similar to what I built for Drawn to Life.

      Here are some random thoughts and things to consider:

      * Are the states totally generic? In other words, can all actors attempt to switch to any state at any time, or are states only reachable from specific states? In other words, are you building a graph or just a cloud of states?
      * How will you score each state? Why would I attack vs patrol vs run away? Try to think about this from a generic point of view. If possible, you want a single scoring function that will score any state. Sims often have hundreds of interactions available to them, but it all goes through a single scoring function (well, two depending on the type of interaction). There should be as few brains as possible; all the complexity should be in the state machine graph and tuning the desirability on those nodes.
      * How granular are the behaviors? Are they very specific (follow this list of waypoints) or abstract (run this search algorithm to try to track the player and maybe attack them)? If it's the latter, you might want to consider a hierarchical finite state machine (HFSM) rather than a basic FSM.
      * Why are you going through every actor in your AI Process? The AI Process should just have a list of weak ref's to the AIComponent. Everything it can do should be done on that component. That way, you don't have non-AI actors in there. As you walk through the list, you can also remove actors that are dead. This keeps you from having to search the list every time someone dies; you just remove them on the next update.
      * Are you ticking the AI every frame? That can get expensive, especially if you're running a complex scoring algorithm. On The Sims, I very rarely run the full AI update. On The Sims Medieval, Sims only ran their AI when they had no interactions left, and that update was amortized across multiple frames. You probably don't have to do anything that crazy, but you should allow the AI Process to be re-entrant so you can bail after updating just a few of your actors.

      Hope that helps. :)

      -Rez
    • RE: AI Architecture

      What's up Rez...

      Awesome feedback this is really giving me some things to consider.

      Source Code

      1. * Are the states totally generic? In other words, can all actors attempt to switch to any state at any time, or are states only reachable from specific states? In other words, are you building a graph or just a cloud of states?


      Now that you mention it the graph seems like the more powerful / flexible approach. I had originally envisioned them being very generic, though I hoped that maybe through the composition pattern I could combine them into more situation specific behaviors. For example an AggressivePatrol implementation would be a composite of the Aggressive and Patrol behaviors...

      Source Code

      1. * There should be as few brains as possible; all the complexity should be in the state machine graph and tuning the desirability on those nodes.


      Could you maybe elaborate a little bit here? I have one state machine per AI controlled actor and each state machine has a brain. Are you saying that AI controlled actors may want to share a single state machine / brain? That AI logic runs on groups of actors as opposed to single actors?

      Source Code

      1. If it's the latter, you might want to consider a hierarchical finite state machine (HFSM) rather than a basic FSM.


      Very interesting, I'm going to research the HFSM ASAP...

      Source Code

      1. * Why are you going through every actor in your AI Process? The AI Process should just have a list of weak ref's to the AIComponent.


      Great point! So this would be similar to the Scene graph registration process where the Initialization of an AIComponent would send an event allowing the AIProcess to catch it and add the component to the list! (As opposed to right now where the Process executes a CallBack / delegate to get the current list of Actors...)

      Source Code

      1. * Are you ticking the AI every frame? That can get expensive


      Another great point, though I was fully aware of this issue I just hadn't figured out a good way to mitigate it yet. Once the AIProcess is spun up, how do I interrupt it and whose responsibility is it to perform the interrupt? More importantly though, when the AIProcess does tick is it necessary to perform both the ChooseBestState and Update operations or should each be handled in it's own process?

      I'm starting to see why you spend most of your time in AI, there are some very interesting problems to solve in this domain and so far it's standing out as my favorite.

      Thanks!
      -bullgoose
    • RE: AI Architecture

      Originally posted by bullgoose311
      What's up Rez...

      Now that you mention it the graph seems like the more powerful / flexible approach. I had originally envisioned them being very generic, though I hoped that maybe through the composition pattern I could combine them into more situation specific behaviors. For example an AggressivePatrol implementation would be a composite of the Aggressive and Patrol behaviors...

      What would that actually do? Aggressive seems like a scoring modifier, not a full-fledged behavior. Having multiple states is certainly something that's possible, but it adds another layer of complexity. You have to decide how they interact with each other.


      Could you maybe elaborate a little bit here? I have one state machine per AI controlled actor and each state machine has a brain. Are you saying that AI controlled actors may want to share a single state machine / brain? That AI logic runs on groups of actors as opposed to single actors?

      I'm saying that you should decouple the decision making from the actual updating of the state. For an example, take a look at ChooseBestState() and Update() in the TeapotStateMachine class in TeapotAi.lua:

      Source Code

      1. function TeapotStateMachine:ChooseBestState()
      2. if (self._brain) then
      3. local newState = self._brain:Think();
      4. if (newState ~= nil) then
      5. print("Teapot chose " .. newState.name);
      6. else
      7. print("Teapot chose nil");
      8. end
      9. self:SetState(newState);
      10. end
      11. end
      12. function TeapotStateMachine:Update(deltaMs)
      13. if (self._currentState) then
      14. self._currentState:Update(deltaMs);
      15. end
      16. end
      Display All


      The decision making happens in the _brain object, which can be anything. In Teapot Wars, I have a few examples, like a decision tree and a hard-coded brain. This decides which state to switch to. If that state is different than the current state, it switches to the new state (this happens in SetState()). Otherwise, it does nothing.

      The update is done in Update(), which actually does the logic of running that state. You can look at TeapotStates.lua to see an example of what they do, but it will likely be very game-specific.

      In a real game, I would actually separate them even more. For example, my simulation game uses Actions to make entities do things. In fact, my action system is really an HFSM. Each Action is the higher level state and each ActionState is the lower-level state. Each of those can be in some set of states as well (running, initializing, sleeping, etc.)

      The AI decision making is done somewhere else. It's a function in Lua that's called whenever the AI entity isn't running an action. It goes through and scores all possible actions, then chooses the best one and sets it up to run.

      Having these two concepts be completely different is really helpful. If you don't do this, you end up having decision making logic in every state. The reality is that you probably want one or two brains, but no more. My simulation only has one AI decision making function. The Teapots in Teapot Wars only use one brain at a time. The others exist just as a demonstration.


      Once the AIProcess is spun up, how do I interrupt it and whose responsibility is it to perform the interrupt?

      Interrupt it? You don't. You'd probably have a queue of AIComponents that need to be updated and just add them to the end when you determine that it's time to make a new decision.


      More importantly though, when the AIProcess does tick is it necessary to perform both the ChooseBestState and Update operations or should each be handled in it's own process?

      No, these should be two completely different processes. The ChooseBestState process should run very infrequently where the state's Update() might run every frame to perform movement, change animations, etc. The state's Update() isn't really AI, it's the result of the decision.

      -Rez