Search Unity

Learning StateMachine "Brain" Decision Making approaches

Discussion in 'Scripting' started by sentar, Sep 25, 2018.

  1. sentar

    sentar

    Joined:
    Feb 27, 2017
    Posts:
    44
    Hello Colleagues,

    This thread i'd like to discuss the approaches for creating StateMachine's "Brain" process.

    I understand how to make classes, states, and using action <> to retrieve information back to mono behavior for storing chosen fields. I will provide what I have learnt so far with making statemachines, and hopefully understand more in depth on the decision making process for the "Brain".

    What got me started:

    I seen the mini-game, shadow of war 'pit fights', and I can understand the surface of what they are doing (If i'm wrong please let me know) in this scene.

    AI action: chose a random/procedural action based on a class it's apart of. 'Tank' class for instance. Everything it does is based on its state to get in range, attack, taunt, fight closest or most important enemy, heal, and defend with animations to be more lifelike.

    What I understand:

    I understand the states of what the AI does next, and the movement from one state to another.

    What I don't understand:
    The logic of why it chooses what it does. Whether it would be to walk around the ring, attack, heal etc. This part is the scope that I don't see in code of how to do it. I understand that it is in void update() or alternative, but how to does it fluently.

    ------------------------------------------------------------------------------------------------------------------------------------------------
    What I have setup right now as a demo:

    Script IState:
    -----------------
    Code (=csharp):
    1.  
    2. public interface IState
    3. {
    4.  
    5.     void Enter();
    6.  
    7.  
    8.     void Exit();
    9.  
    10.  
    11.     void Execute();
    12. }
    13.  
    Script Foundation_StateMachine:
    --------------------------------------------
    Code (=csharp):
    1.  
    2. public class Foundation_StateMachine : MonoBehaviour {
    3.  
    4.     private IState CurrectlyRunningState;
    5.  
    6.     private IState PreviousState;
    7.  
    8.     public void ChangeState(IState newState)
    9.     {
    10.         if (CurrectlyRunningState != null)
    11.         {
    12.             this.CurrectlyRunningState.Exit();
    13.         }
    14.  
    15.         this.PreviousState = this.CurrectlyRunningState;
    16.  
    17.         CurrectlyRunningState = newState;
    18.         this.CurrectlyRunningState.Enter();
    19.     }
    20.  
    21.     public void ExecuteStateUpdate()
    22.     {
    23.         var runningState = CurrectlyRunningState;
    24.         if (runningState != null)
    25.         {
    26.             runningState.Execute();
    27.         }
    28.     }
    29.  
    30.     public void SwitchToPreviousState()
    31.     {
    32.         CurrectlyRunningState.Exit();
    33.         CurrectlyRunningState = this.PreviousState;
    34.         CurrectlyRunningState.Enter();
    35.    
    36.     }
    37. }
    38.  
    Script SearchFor:
    -----------------------

    //One state in this case, to find the object, and then another state like this one, to set the destination etc.
    Code (=csharp):
    1.  
    2. public class SearchFor : IState
    3. {
    4.     private LayerMask SearchLayer;
    5.     private GameObject ownerGameobject;
    6.     private float searchRadius;
    7.     private string tagtolookfor;
    8.  
    9.     public bool searchCompleted;
    10.  
    11.     private Action<SearchResults> searchResultsCallback;
    12.  
    13.     //The criteria of how we use the SearchFor class
    14.     public SearchFor(LayerMask SearchLayer, GameObject ownerGameobject, float searchRadius, string tagtolookfor, Action<SearchResults> searchResultsCallback, bool searchCompleted)
    15.     {
    16.         this.SearchLayer = SearchLayer;
    17.         this.ownerGameobject = ownerGameobject;
    18.         this.searchRadius = searchRadius;
    19.         this.tagtolookfor = tagtolookfor;
    20.         this.searchResultsCallback = searchResultsCallback;
    21.         this.searchCompleted = searchCompleted;
    22.     }
    23.  
    24.     public void Enter()
    25.     {
    26.    
    27.     }
    28.  
    29.     public void Execute()
    30.     {
    31.         if (!searchCompleted)
    32.         {
    33.             var hitObjects = Physics.OverlapSphere(this.ownerGameobject.transform.position, searchRadius);
    34.  
    35.             Debug.Log("" + hitObjects);
    36.             var allObjectsWithTheRequiredTag = new List<Collider>();
    37.             //for loop
    38.             for (int i = 0; i < hitObjects.Length; i++)
    39.             {
    40.                 //find objects with the appropriate tag from our AI_Script_FSM
    41.                 if (hitObjects[I].CompareTag(this.tagtolookfor))
    42.                 {
    43.                         //    this.navMeshAgent.SetDestination(hitObjects[I].transform.position);
    44.                     //      Debug.Log("" + this.navMeshAgent.SetDestination(hitObjects[I].transform.position));
    45.  
    46.  
    47.                     //Found an object with the right tag
    48.                     allObjectsWithTheRequiredTag.Add(hitObjects[I]);
    49.                     //
    50.                 }
    51.  
    52.             }
    53.             //end of loop
    54.             //Once search is finished with its results, stop the Execute() from running this anymore
    55.             this.searchCompleted = true;
    56.             //
    57.  
    58.             //Calls class SearchResults and stores the collider it found
    59.             var searchResults = new SearchResults(hitObjects, allObjectsWithTheRequiredTag, searchCompleted);
    60.             //
    61.  
    62.             //this is where we should send the information back.
    63.             this.searchResultsCallback(searchResults);
    64.             //
    65.         }
    66.     }
    67.  
    68.     public void Exit()
    69.     {
    70.      
    71.     }
    72.  
    73.     //Store all results from Execute()
    74.     public class SearchResults
    75.     {
    76.         public Collider[] allHitObjectsInSearchRadius;
    77.         public List<Collider> allHitObjectsWithRequiredTag;
    78.         public bool searched;
    79.  
    80.         // use closest object or fathest object if necessary
    81.  
    82.         public SearchResults(Collider[] allHitObjectsInSearchRadius, List<Collider> allHitObjectsWithRequiredTag, bool searched)
    83.         {
    84.             this.allHitObjectsInSearchRadius = allHitObjectsInSearchRadius;
    85.             this.allHitObjectsWithRequiredTag = allHitObjectsWithRequiredTag;
    86.             this.searched = searched;
    87.  
    88.             // understand method calls to further process this data.
    89.         }
    90.  
    91.     }
    92. }
    93.  
    "The Brain" Script AI_Script_FSM:
    ---------------------------------------------
    Code (=csharp):
    1.  
    2. public class AI_Script_FSM : MonoBehaviour {
    3.  
    4.     //*** Understand how state machines change their decision making***\\
    5.     //*** Repeat until all necessary state's in the AI become a live AI ***\\
    6.  
    7.     //Use stats based on Foundation_StateMachine
    8.     private Foundation_StateMachine stateMachine = new Foundation_StateMachine();
    9.     [SerializeField]
    10.     private LayerMask stuffLayer;
    11.     [SerializeField]
    12.     private float viewRange;
    13.     [SerializeField]
    14.     private string foodItemsTag;
    15.     private NavMeshAgent navMeshAgent;
    16.  
    17.  
    18.     private List<Collider> foundobject;
    19.     SetDestinationTo destinationto;
    20.     SearchFor searchfor;
    21.  
    22.     public bool stuffsearch;
    23.     public bool destination;
    24.     public bool destinationReached;
    25.     private void Start()
    26.     {
    27.         this.navMeshAgent = GetComponent<NavMeshAgent>();
    28.      
    29.     }
    30.  
    31.     private void Update()
    32.     {
    33.         this.stateMachine.ExecuteStateUpdate();
    34.  
    35.      
    36.         if (foundobject == null && stuffsearch == false)
    37.         {
    38.             FindFood();
    39.         }
    40.  
    41.         if (foundobject != null && destination == false)
    42.         {
    43.             GoToDestination();
    44.             Debug.Log("triggering destination");
    45.         }
    46.  
    47.         if (destinationReached == false)
    48.         {
    49.          
    50.             DestinationFinilized();
    51.         }
    52.     }
    53.  
    54.     //If hungry or hurt find food
    55.     public void FindFood()
    56.     {
    57.         //this uses the state machine.
    58.         //it changes its state.
    59.         //it will use the script SearchFor with the class to find any object with the correct criteria.
    60.         this.stateMachine.ChangeState(new SearchFor(this.stuffLayer, this.gameObject, this.viewRange, this.foodItemsTag, this.FoodFound, this.foodsearch));
    61.         Debug.Log("finding object");
    62.     }
    63.  
    64.     public void FoodFound(SearchFor.SearchResults searchResults)
    65.     {
    66.         Debug.Log("food object");
    67.         foundobject = searchResults.allHitObjectsWithRequiredTag;
    68.         this.stuffsearch = searchResults.searched;
    69.         //decide next step
    70.     }
    71.  
    ------------------------------------------------------End of Scripts-----------------------------------------------------------------------
    Any help or knowledge would be much appreciated for decision making processes!
     
    Last edited: Sep 26, 2018
  2. GroZZleR

    GroZZleR

    Joined:
    Feb 1, 2015
    Posts:
    3,201
    Make sure you use code tags friend: https://forum.unity.com/threads/using-code-tags-properly.143875/

    As for your question: if the objective of your agent isn't to follow a sequence of events but rather adapt to a wide variety of situations based on needs, a vanilla state machine probably isn't the ideal structure as it's fairly rigid in its goal planning. I would do some reading on Goal Oriented Action Planning (GOAP) or any similar task-based AI.
     
  3. sentar

    sentar

    Joined:
    Feb 27, 2017
    Posts:
    44
    Hi GroZZleR,

    Thank you for the link to properly tagging code. I appreciate it! Correct I do notice the rigid effect in it's goal planning that is where in my development I am getting the most stuck at.

    As far as research, I have done extensive googling, and the majority come up with an article like this one. Where it is immensely generic and/or in other coded languages.

    What is the best option for a Pure Decision AI in Unity?
    Link: https://www.quora.com/What-is-the-best-option-for-a-Pure-Decision-AI-in-Unity

    Fortunately, it appears this one gives an extensive look at GOAP with using more of an abstract class with monobehavior. Would that process be more recommended and using hash sets?


    Goal Oriented Action Planning for a Smarter AI:
    Link: https://gamedevelopment.tutsplus.co...d-action-planning-for-a-smarter-ai--cms-20793
     
    Last edited: Sep 26, 2018
  4. sentar

    sentar

    Joined:
    Feb 27, 2017
    Posts:
    44
    From further research:

    It sounds like I need to:

    1.) Have a priority script to manage which states need what.
    2.) Manager that decides the priority script, if a priority is failed to revert to proper state. (ex: wandering then suddenly getting attacked).

    I'll attempt to add some more code in with these two script ideas to help with this process. Any other further help/guide to staying on track to making AAA style code would be most beneficial as I keep adding/adjusting code for this thread.

    **Update**

    I took another step and called my main script that is running the show to be a sub script to only do the following states:

    1.) Search Enemy
    2.) Go to Enemy
    3.) Engage Enemy (to attack)

    I am in the process now in creating the manager "brain" to make decisions on 3 classes.

    Here is the manager so far:

    Code (=csharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. public class State_Manager : MonoBehaviour {
    7.  
    8.  
    9.     //*** Learn How To Interact and Change States ***\\
    10.  
    11.         //classes derived from monobehavior
    12.     private Attack_State Attack_State;
    13.     private Defend_State Defend_State;
    14.     private Movement_State Movement_State;
    15.  
    16.     //long animations that hold the AI in place
    17.     //Did AI attack? Did AI react in time to block from the attack animation?
    18.     //Did AI wander after attacking?
    19.     //Generic classes who am i? A tank, marauder?? etc.??
    20.     //Make structured stats of who I am that can be randomly generate per AI for another state.
    21.  
    22.     // Use this for initialization
    23.     void Start () {
    24.      
    25.     }
    26.  
    27.     // Update is called once per frame
    28.     void Update () {
    29.      
    30.     }
    31. }
    32.  
     
    Last edited: Sep 26, 2018