Search Unity

Game State

Discussion in 'Scripting' started by Genovine, Mar 31, 2022.

  1. Genovine

    Genovine

    Joined:
    May 25, 2014
    Posts:
    21
    I've created a state machine to start my project. My perfectionist tendencies lead me to go over things just to make sure they're done properly. So when looking at tutorials and being lost because the internet looks like big mess of everything and partial projects, I tend to doubt myself.

    My plan is to start off with the basics and work my way toward the center of my mechanics. I'm not going to be doin anything revolutionary, just the basic foundation for a simple game. Take the word "simple" with a grain of salt, because of coarse it's going to take time, effort, blood, sweat and tears to finish a quality product.

    The start of this plan is to create a basic state machine to keep track of the players input. When adding more content to the game, more states will be added and transitions will be easier for me to follow this way. My goal was to use the update loop as a way to listen to the player, but also ignore them depending on the state.

    My first hurdle was creating a script and having the scene not show my any debug logs. A note to anyone who has a similar problem, make sure when you hit play, go to the top of the console tap and to the right and make sure the exclamation bubble is on to see the debug logs. Then make sure to press the game scene with your mouse to make sure, that the game scene is actually highlighted.

    I got frustrated because nothing seemed to work. Then decided to create a simple hello kitty script, just to find out that my scripts weren't the problem. It was that damn button and the scene not being selected. The hello kitty script didn't seem to work, until I found a single post online that talked about the exclamation button.

    Finally, I checked my other scripts and they worked fine. It was just that button.

    ----

    So here's my state machine. It seems to work, but I'm not an experienced programmer. Is there any advice anyone could give to improve upon or am I worrying for nothing. This should persist through scenes and depending on the players input, should trigger an event of some kind, which should change the state.

    Code (CSharp):
    1. public class GameState : MonoBehaviour
    2. {
    3.     void Awake()
    4.     {
    5.         DontDestroyOnLoad(gameObject);
    6.     }
    7.  
    8.     private static BaseState CurrentState;
    9.  
    10.     void Start()
    11.     {
    12.         CurrentState = new TestState();
    13.         CurrentState.Enter();
    14.     }
    15.  
    16.     void Update()
    17.     {
    18.    
    19.         CurrentState.Execute();
    20.     }
    21.  
    22.     public static void ChangeState(BaseState NewState)
    23.     {
    24.         CurrentState.Exit();
    25.         CurrentState = NewState;
    26.         CurrentState.Enter();
    27.     }
    28.  
    29. }
    Here's a sample state, listening for the user to do something.

    Code (CSharp):
    1. public interface  BaseState
    2. {
    3.     public void Enter();
    4.  
    5.     public void Execute();
    6.  
    7.     public void Exit();
    8.  
    9. }
    Code (CSharp):
    1. public class TestState : BaseState
    2. {
    3.  
    4.     private bool StateActive = false;
    5.  
    6.     public void Enter()
    7.     {
    8.         Debug.Log("Setting Up : State One.");
    9.         StateActive = true;
    10.     }
    11.  
    12.     public void Execute()
    13.     {
    14.         if (StateActive == true)
    15.         {
    16.             if (Input.GetKeyDown(KeyCode.A))
    17.             {
    18.                 Debug.Log("Test State One!");
    19.             }
    20.             if (Input.GetKeyDown(KeyCode.Space))
    21.             {
    22.                 StateActive = false;
    23.                 GameState.ChangeState(new TestStateTwo());
    24.             }
    25.         }      
    26.     }
    27.  
    28.     public void Exit()
    29.     {
    30.         Debug.Log("Cleaning Up : State One.");
    31.     }
    32.  
    33. }
    I've picked apart dozens of tutorials and came up with this. Am I missing anything important?
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,726
  3. Genovine

    Genovine

    Joined:
    May 25, 2014
    Posts:
    21
    It's mostly for joystick commands. Or lack of commands...

    Splash Screen
    Main Menu
    Start Game
    Character Controller
    Inventory Menu
    Battle Menu (Turn Based JRPG)

    And others...

    Dialog State
    Story/Cinematic State
    Open Chest State
    Climb Ladder State
    Open Door State
    Load Scene State

    Putting all that into one file... Seems unorganized.
     
  4. Stardog

    Stardog

    Joined:
    Jun 28, 2010
    Posts:
    1,913
    It seems fine, but you might want to wrap it in a StateMachine class so you can reuse it, and think about how you are changing/transitioning from state-to-state.

    Right now you have embedded one single state machine, and you are allowing the jumping from any state to any other state.

    It could look more like this:
    Code (csharp):
    1. public class GameState : MonoBehaviour
    2. {
    3.     public StateMachine FSM = new StateMachine();
    4.  
    5.     void Start() => FSM.ChangeState(new TestState());
    6.  
    7.     void Update() => FSM.Update();
    8. }
    Later, you might also want to look into the idea that functions can be stored as variables, and store your states in a dictionary with data about what state they can transition to.
     
  5. Genovine

    Genovine

    Joined:
    May 25, 2014
    Posts:
    21
    That was the goal. It's a single machine and persists through scenes. Doesn't destroy on load.

    Example Transitions:

    Splash Screen > Main Menu > New Game > Story Mode > World Map

    World Map > Player Menu
    World Map > Battle Mode
    World Map > Story Mode

    Battle Mode > Player Death > Splash Screen
    Battle Mode > Win Battle > World Map
    Battle Mode > Win Battle > Story Mode

    Player Menu Example:
    When you open, close, and use the Player Menu.

    The "CurrentState.Enter" would activate open menu animation.
    The "CurrentState.Execute" would allow you to use joystick controls.
    The "CurrentState.Exit" would activate closing menu animation.

    Not sure the reason for making a dictionary to overcomplicate things.
    It's a single player game. So each state should be self explanatory.

    The state will declare where it wants to go, depending on the condition.
     
  6. Genovine

    Genovine

    Joined:
    May 25, 2014
    Posts:
    21
    Started to use the new input system. I've altered the state machine to compensate.

    This is the current manager. I took out the update function.
    Code (CSharp):
    1. public class GameManager : MonoBehaviour
    2. {
    3.     public void Awake()
    4.     {
    5.         DontDestroyOnLoad(gameObject);
    6.     }
    7.  
    8.     private static BaseState CurrentState;  
    9.  
    10.     private void Start()
    11.     {      
    12.         CurrentState = new MainMenu();
    13.         CurrentState.Enter();
    14.     }
    15.  
    16.     public static void ChangeState(BaseState NewState)
    17.     {
    18.         CurrentState.Exit();
    19.         CurrentState = NewState;
    20.         CurrentState.Enter();
    21.     }
    22.  
    23. }
    Here is the sample state.
    Code (CSharp):
    1. public class MainMenu : BaseState
    2. {
    3.  
    4.     private PlayerActionInput PlayerInput = new PlayerActionInput();
    5.  
    6.     public void Enter()
    7.     {
    8.         PlayerInput.Gamepad.ButtonSouth.performed += TestStateTwo;
    9.         PlayerInput.Gamepad.ButtonEast.performed += ChangeState;
    10.  
    11.         PlayerInput.Enable();
    12.     }
    13.  
    14.     public void Exit()
    15.     {
    16.         PlayerInput.Disable();
    17.  
    18.         PlayerInput.Gamepad.ButtonSouth.performed -= TestStateTwo;
    19.         PlayerInput.Gamepad.ButtonEast.performed -= ChangeState;
    20.     }
    21.  
    22.     public void TestStateTwo(InputAction.CallbackContext Context)
    23.     {
    24.         Debug.Log("Test State Two!");
    25.     }
    26.  
    27.     public void ChangeState(InputAction.CallbackContext Context)
    28.     {
    29.         GameManager.ChangeState(new PlayerMenu());
    30.         Debug.Log("Changing State!");
    31.     }
    32.  
    33. }
    This just subscribes and unsubscribes the functions to the gamepad. This should allow me to change state from the player to the inventory, then to dialog and story events and such. I've been watching videos and trying to wrap my head around things, mostly on how to switch between events, menus and the player.

    Are there any memory issues with this? Every time I look at "new PlayerActionInput(); or "new PlayerMenu();" I'm not sure what this does to performance. I'm not creating piles of garbage by using the new word every time the state changes am I?
     
  7. Genovine

    Genovine

    Joined:
    May 25, 2014
    Posts:
    21
    Currently checking out dictionaries using enums for the index and the code still looks small and easy to read. Thanks for the suggestion. Was trying to wrap my head around being able to keep some data, while being able to transition between states. But not make a monolith of unreadable stuff.

    Code (CSharp):
    1. public class StateManager : MonoBehaviour
    2. {
    3.  
    4.     private static Dictionary<GameState, BaseState> CurrentState = new Dictionary<GameState, BaseState>();
    5.     private static GameState CurrentIndex = GameState.Start;
    6.  
    7.     private void Awake()
    8.     {
    9.         DontDestroyOnLoad(gameObject);
    10.     }
    11.  
    12.     private void Start()
    13.     {
    14.         CurrentState.Add(GameState.Start, new TestState());
    15.         CurrentState.Add(GameState.Player, new PlayerMenu());
    16.         CurrentState.Add(GameState.Menu, new TestState());
    17.         CurrentState.Add(GameState.Combat, new TestState());
    18.         CurrentState.Add(GameState.Store, new TestState());
    19.         CurrentState.Add(GameState.Trigger, new TestState());
    20.         CurrentState.Add(GameState.Cutscene, new TestState());
    21.  
    22.         CurrentState[CurrentIndex].Enter();
    23.     }
    24.  
    25.     private void Update()
    26.     {
    27.         CurrentState[CurrentIndex].Execute();
    28.     }
    29.  
    30.     public static void ChangeState(GameState NewState)
    31.     {
    32.         CurrentState[CurrentIndex].Exit();
    33.         CurrentIndex = NewState;
    34.         CurrentState[CurrentIndex].Enter();
    35.     }
    36.  
    37. }
    38.  
    39. public enum GameState
    40. {
    41.     Start,
    42.     Player,
    43.     Menu,
    44.     Combat,
    45.     Store,
    46.     Trigger,
    47.     Cutscene
    48. }
     
    Last edited: Apr 30, 2022
  8. Genovine

    Genovine

    Joined:
    May 25, 2014
    Posts:
    21
    Does anyone else have problems with perfectionism when planning their code? Like spending too much time on the names you want to use for scripts or looking at the code and debating on how organized it looks? Sometimes I get stuck in an analysis paralysis loop.