Search Unity

[Help] FSM for Player using Co-routines

Discussion in 'Scripting' started by LazyDog_Rich, Oct 24, 2016.

  1. LazyDog_Rich

    LazyDog_Rich

    Joined:
    Apr 27, 2014
    Posts:
    70
    Hi everyone!

    I'm trying to make a player controller(for a 2D game) using Finite State Machine.

    Looking for examples, I saw someone using FSM with co-routines and I decide to give it a try. This is what I made so far:

    http://paste.ofcode.org/35xtazcPfsw48LHYQwB3V7X

    I'm looking for advice to improvment this code, I want to know how bad or good is use co-routines for FSM. Or if I'm using it bad.

    Thanks a lot!
     
  2. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Though there is nothing wrong with using Coroutines for a FSM, it does seem to be an extra layer of complication. You have to always make sure your ending the Coroutines, and could lead to hard to find bugs. Your code seems to be implemented correctly though, so not sure what you want optimized.

    Here is an idea of a FSM that just uses update. It breaks up the initialization of a state, the main core of the state, and the check of the state into 3 different methods. Its fairly easy to add new states and fill in the methods for what you want them to do. I converted Idle and Move from your pasted code to this idea. But you could see it would be easy to convert them all over
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class FSM_Player : MonoBehaviour
    5. {
    6.     //Public
    7.     public float speed = 14f;
    8.     public float accel = 6f;
    9.     public float airAccel = 3f;
    10.     public float jump = 14f;
    11.  
    12.     //Private
    13.     private Rigidbody2D rb;
    14.     private Vector2 input;
    15.     private Controller controller;
    16.     private SpriteRenderer sprite;
    17.  
    18.     delegate void FSMFunction();
    19.     class FiniteState
    20.     {
    21.         public FSMFunction init;
    22.         public FSMFunction main;
    23.         public FSMFunction stateCheck;
    24.  
    25.         // All your custom data would go here
    26.         float timeStamp;
    27.         float someOtherVariableYouNeeded;
    28.     }
    29.  
    30.     Dictionary<string, FiniteState> FSM = new Dictionary<string, FiniteState>();
    31.     string currentState;
    32.     string oldState;
    33.  
    34.     void Awake()
    35.     {
    36.         InitializeStates();
    37.         oldState = "Idle";
    38.         currentState = "Idle";
    39.     }
    40.  
    41.     void Update()
    42.     {
    43.         FiniteState state = FSM[currentState];
    44.         if (currentState != oldState)
    45.         {
    46.             state.init();
    47.             oldState = currentState;
    48.         }
    49.  
    50.         state.main();
    51.         state.stateCheck();
    52.     }
    53.  
    54.     void InitializeStates()
    55.     {
    56.         FiniteState state;
    57.  
    58.         state = new FiniteState();
    59.         state.init = IdleInit;
    60.         state.main = IdleMain;
    61.         state.stateCheck = IdleCheck;
    62.         FSM.Add("Idle", state);
    63.  
    64.         state = new FiniteState();
    65.         state.init = MoveInit;
    66.         state.main = MoveMain;
    67.         state.stateCheck = MoveCheck;
    68.         FSM.Add("Move", state);
    69.     }
    70.  
    71.     void IdleInit()
    72.     {
    73.         sprite.color = Color.blue;
    74.     }
    75.  
    76.     void IdleMain()
    77.     {
    78.         Debug.Log("Running Idle");
    79.     }
    80.  
    81.     void IdleCheck()
    82.     {
    83.         if (input_x != 0)
    84.             currentState = "Move";
    85.         if (controller.input().y > 0)
    86.             currentState = "Jump";
    87.         if (!controller.isGround())
    88.             currentState = "Air";
    89.     }
    90.  
    91.     void MoveInit()
    92.     {
    93.         sprite.color = Color.green;
    94.     }
    95.  
    96.     void MoveMain()
    97.     {
    98.         Debug.Log("Moving");
    99.         float input_x = controller.input().x;
    100.         //Move Player
    101.         rb.AddForce(new Vector2(((input_x * speed) - rb.velocity.x) * accel, 0));
    102.     }
    103.  
    104.     void MoveCheck()
    105.     {
    106.         if (input_x == 0)
    107.             currentState = "Idle";
    108.         if (controller.input().y > 0)
    109.             currentState = "Jump";
    110.         if (!controller.isGround())
    111.             currentState = "Air";
    112.     }
    113.  
    114. }
    115.  
    I also think this might be easier to read and understand months (years) later if you come back and look at it.
     
    Last edited: Oct 24, 2016
    LazyDog_Rich likes this.
  3. LazyDog_Rich

    LazyDog_Rich

    Joined:
    Apr 27, 2014
    Posts:
    70
    Hey I liked your example, I gonna test it later with all the states.

    I just wanted to know if there was something wrong with my code, or I was doing something that is not recomended for performance issues, I'm pretty noob and maybe there was something I dint know, and I want to be 100% before go start add states like crazy.

    I also saw some similar examples to yours using delegate, and I wanted to know what was better if using delegate, coroutine, or maybe some other alternative.

    Your options looks better, more easy to add new states and stuff. Just a quick question, should I use FixedUpdate instead of Update for those funcitions that uses physics? Or there's some way to change betwen Update/FixedUpdate? Thats what I like of coroutines

    Thanks for your answer!
     
  4. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Like I said, there is absolutely nothing wrong with Coroutines for implementing a FSM other than the extra layer of complexity they add. Coroutines are just extra Updates called by Unity. They aren't in a different thread, they are in the same thread your Update function is called in.

    Coroutines have a few features you might need over an Update function:
    • They can stop their execution at any point and continue next frame. -- We don't use this with FSM we run the entire code every frame. Notice your FSMs are basically running all their code then waiting till next FixedUdpate and looping again.
    • They can pause for longer than "Wait till next Frame" -- again we are not using this feature. Your Coroutines just wait till the next frame
    So since your not actually using any of the features Coroutines are designed to implement, your just adding complexity to your code that could make it harder to read, easier to introduce and find logical bugs. For that reason there doesn't seem to be any point to using them.

    So to have the option of running it during Regulare or Fixed Update you add a boolean to the state called isFixed. Set it when you initialize the state and have code that looks like this:
    Code (CSharp):
    1. oid Update()
    2.     {
    3.         FiniteState state = FSM[currentState];
    4.  
    5.         if (!state.isFixed)
    6.         {
    7.            if (currentState != oldState)
    8.            {
    9.               state.init();
    10.               oldState = currentState;
    11.            }
    12.  
    13.            state.main();
    14.            state.stateCheck();
    15.         }
    16.     }
    17.     void FixedUpdate()
    18.     {
    19.        FiniteState state = FSM[currentState];
    20.  
    21.         if (state.isFixed)
    22.         {
    23.            if (currentState != oldState)
    24.            {
    25.               state.init();
    26.               oldState = currentState;
    27.            }
    28.  
    29.            state.main();
    30.            state.stateCheck();
    31.         }
    32. }
    33.            
     
    LazyDog_Rich likes this.
  5. LazyDog_Rich

    LazyDog_Rich

    Joined:
    Apr 27, 2014
    Posts:
    70
    Great! Thanks for taking the time to explain this to me! :D
    Gonna move my code to one like the one you show me. Thanks again!