Search Unity

Best practice of handle states and transitions og a level/scene

Discussion in 'Editor & General Support' started by Laumania, Nov 22, 2014.

  1. Laumania

    Laumania

    Joined:
    Jun 27, 2012
    Posts:
    222
    Hi

    I have searched for some time now, to find some sample or discussions about how to handle a problem I'm facing right now. A pretty basic and common problem I would say :)

    Let's say I have a scene called Level. This scene were all the 100+ individual levels of my game runs. The way I load in different levels etc. isn't really interesting here, let's just say I have that part in place.

    My problem is that I have a hard time figuring out how to handle all the transitions and states of a level in a simple and maintainable way.
    If you think about a game like Candy Crush for instance, their "level" goes like this:
    - Show of a background
    - The gameboard slides in
    - A lille guy comes down from the top, telling you a brief description of what this particual level is about.
    - You touch the guy and he goes away away, in an smooth animation moving up
    - Game runs
    - You might die and a new thing slides down from the top.
    - etc.

    And all of these things are happening over time, which is kind of the problem.

    I have a "working solution" now, the game works, but it's way to complicated codewise, and way to hard to figure out - which isn't what I want. I want something more clean.

    I am doing something like this, which you can see becomes impossible to overcome very quickly.

    So my question is really, how to you guys handle this. Can you point to a resource with a good sample or anything else?

    Code (CSharp):
    1. NGUITools.SetActive (PrePlayPanel.gameObject, true);
    2.         PrePlayTween.PlayForward ();
    3.         Invoke ("BeginStartLevel", 10.0f);
    Code (CSharp):
    1. public void BeginStartLevel()
    2.     {
    3.         if(State == LevelStates.PrePlaying)
    4.         {
    5.             EventDelegate.Add (PrePlayTween.onFinished, StartLevel);
    6.             PrePlayTween.PlayReverse ();
    7.         }
    8.     }
    Code (CSharp):
    1. private void StartLevel()
    2.     {
    3.         EventDelegate.Remove (PrePlayTween.onFinished, StartLevel);
    4.         NGUITools.SetActive (PrePlayPanel.gameObject, false);
    5.         State = LevelStates.Playing;
    6.         _currentGameManager.StartLevel ();
    7.     }
     
    Last edited: Nov 22, 2014
  2. Laumania

    Laumania

    Joined:
    Jun 27, 2012
    Posts:
    222
    R-Lindsay likes this.
  3. GarBenjamin

    GarBenjamin

    Joined:
    Dec 26, 2013
    Posts:
    7,441
    Glad you found a solution.

    In general, in all development the way to approach things is to simply break complex sequences down into smaller parts.

    For example, you identified parts of the Candy Crush game sequence:

    Basically these are nothing more than the current level state.

    Imagine if you declared a simple enumeration and a variable of this type:
    Code (CSharp):
    1. public enum LevelStateType
    2. {
    3.     Inactive,
    4.     ShowBackground,
    5.     SlideGameboardIn,
    6.     LittleDudeAppearsFromTop,
    7.     LittleDudeIntroducesLevel,
    8.     LittleDudeWasSentAway,
    9.     LevelStarting,
    10.     LevelBeingPlayed,
    11.     LevelCurrentlyPaused
    12. }
    13.  
    14. private LevelStateType eLevelCurrentState = Inactive;
    In your code all you need to do is move from each step (state) to the next.
    At its simplest implementation, you basically have a Level GameObject perhaps even a LevelManager.

    The enum goes in there.

    In the update you simply have something like this:
    Code (CSharp):
    1. void Update()
    2. {
    3.     switch (eLevelCurrentState)
    4.     {
    5.         case LevelStateType.Inactive:
    6.             // probably simply waits until some condition occurs to transition to ShowBackground
    7.             // and this may not even be needed but it gives you something "default" to set the initial state to.
    8.             HandleInactiveState();
    9.             break;
    10.  
    11.         case LevelStateType.ShowBackground:
    12.             // loads and displays background maybe fading or sliding it in view
    13.             // when complete state changes to SlideGameboardIn
    14.             HandleShowBackgroundState();
    15.             break;
    16.  
    17.         case LevelStateType.SlideGameboardIn:
    18.             // slides the game board onto the playfield, when complete state changes to LittleDudeAppearsFromTop
    19.             HandleSlideGameboardInState();
    20.             break;
    21.  
    22.         case LevelStateType.LittleDudeAppearsFromTop:
    23.             // moves the Little Dude down into view, when complete state changes to LittleDudeIntroducesLevel
    24.             HandleLittleDudeAppearsFromTop();
    25.             break;
    26.  
    27.         case LevelStateType.LittleDudeIntroducesLevel:
    28.             // Audio/Speech Bubble or both introduce this level, when complete state changes to LittleDudeWasSentAway
    29.             HandleLittleDudeIntroducesLevel();
    30.             break;
    31.  
    32.         case LevelStateType.LittleDudeWasSentAway:
    33.             // Little Dude slides back up out of sight, when complete state changes to LevelStarting
    34.             HandleLittleDudeWasSentAway();
    35.             break;
    36.  
    37.         case LevelStateType.LevelStarting:
    38.             // Do any FX etc for a level starting maybe LEVEL ONE BEGIN appears and fades out for example.
    39.             // Probably sets up the game objects needed for the level as well
    40.             // When complete state changes to LevelBeingPlayed
    41.             HandleLevelStarting();
    42.             break;
    43.  
    44.         case LevelStateType.LevelBeingPlayed:
    45.             // Handles whatever is needed during play, probably just looking for certain conditions such as the
    46.             // level being completed, the level being reset, etc.
    47.             HandleLevelBeingPlayed();
    48.             break;
    49.     }
    50. }
    Obviously, this is just a general idea, exact flow from one state to another, and exactly what is going on in each state is entirely up to how you want to implement it.

    But yes, you hit the nail on the head when you looked for a state machine manager.

    I just figured I would post this in the hope that anyone else who is struggling with this sort of thing can have a better understanding of how to solve the problem.

    Always... divide and conquer. Break things down. By doing so you can basically pull off any thing you want to achieve because at any one time you are dealing with only one small part of the problem.
     
    Whippets and sluice like this.
  4. ShilohGames

    ShilohGames

    Joined:
    Mar 24, 2014
    Posts:
    3,023
    Yeah, a finite state machine is definitely the right way to handle this. I like to set up an invisible game object that handles the FSM for the level. I also like using FSM when implementing AI for NPCs. An FSM is a lot easier to work with than a wildly nested if-then structure. In some AI code, I have actually switched to using nested FSM (with state machines inside of state machines). In some instances, a nested FSM can be a really good way to break down complex AI into smaller more manageable code.
     
    GarBenjamin likes this.
  5. Tomnnn

    Tomnnn

    Joined:
    May 23, 2013
    Posts:
    4,148
    Here's a fun idea that's horrible in it's own little way. Take the objects that don't need to transition and parent them to an empty gameobject named "Level 1" and then make a prefab out of it. Then you can delete the parent and your level 1 will be gone, but in your prefab folder. Next, build your second level. Parent everything in it to an empty named "Level 2" and make a prefab of that. Then instead of scene loading and worrying about stuff like that, you can destroy level 1 and instantiate level 2.
     
  6. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,205
    Better yet determine which game objects are not shared between scenes and put those into individual level prefabs. Game objects that will be shared can thus be saved from wasting resources from instantiating and destroying them.

    There are a few object pool managers on the Asset Store that can assist with reusing objects too.
     
  7. Tomnnn

    Tomnnn

    Joined:
    May 23, 2013
    Posts:
    4,148
    Don't destroy things you need in the next level. That's what I meant for the most part, but the object pooling is a good idea too :D
     
  8. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,205
    Picked this one up a while back when it was on sale. Haven't actually put it to use yet though.

    https://www.assetstore.unity3d.com/en/#!/content/1010
     
  9. Tomnnn

    Tomnnn

    Joined:
    May 23, 2013
    Posts:
    4,148
    I don't do object pooling often because of how light my games tend to be. I don't even store references to things much haha. I'll probably do it more because it's good practice. Maybe in my next big summer project. Maybe. Probably. I have one game where the "projectile" goes something like...

    Code (CSharp):
    1. GameObject.Destroy(GameObject.Instantiate(Resources.Load("blap")), 10);
    The game just happens to be small enough for that to work.
     
  10. Laumania

    Laumania

    Joined:
    Jun 27, 2012
    Posts:
    222
    Thanks for all your answers.

    My problem is not really how to handle the different levels etc. it's the manage of the different states and the transitions between them that I had a problem with.

    But I have looked more into the project I found now, and have refactored my LevelManager to use it, and it works very well. My code it MUCH simpler now and easier to read.

    https://github.com/thefuntastic/Unity3d-Finite-State-Machine

    This makes it even easier then having to do my own switch in void Update() etc. and still I'm able to provide my own states enum to work on.
     
    GarBenjamin likes this.
  11. sluice

    sluice

    Joined:
    Jan 31, 2014
    Posts:
    416
    This is how I uses FSM in my games:

    Code (csharp):
    1.  
    2.   public enum States
    3.   {
    4.       DISABLED,
    5.       ENABLED,
    6.       ROUND1,
    7.       ROUND2
    8.   }
    9.  
    10.   private States currentState
    11.  
    12.   public States CurrentState
    13.   {
    14.       get { return currentState; }
    15.       set
    16.       {
    17.           currentState = value;
    18.           OnStateChange();
    19.       }
    20.   }
    21.  
    22. void OnStateChange()
    23. {
    24.       Switch()
    25.       {
    26.           case: States.DISABLED:
    27.           OnStateDisable();
    28.        break;
    29.           case: States.ENABLED:
    30.           OnStateDisable();
    31.        break;
    32.        //etc…
    33.       }
    34. }
    35.  
    36. void OnStateDisable()
    37. {
    38.       //DISABLED!
    39. }
    40.  
    41. void OnStateEnable()
    42. {
    43.       //ENABLED!
    44. }
    45.  
    46. //When calling:
    47. //CurrentState = States.ENABLED;
    48. //OnStateEnable() is called instantly, by running through OnStateChange()
    49.  

    @GarBenjamin, I like to avoid having stuff happening in Update(), when I don't need to.. :)

    Feel free to expand on my method guys.. Always curious to hear back and improve!:rolleyes:
     
    Ryiah and GarBenjamin like this.
  12. GarBenjamin

    GarBenjamin

    Joined:
    Dec 26, 2013
    Posts:
    7,441
    That's a good iimplementation and yes all of my projects have a SetState() method. I didn't cover that in my post or how to really do the transition etc. Just wanted to show the basic idea of breaking things down in distinct states.

    In practice, there is nothing wrong with implementing the structure as I defined in the update. It is basically just sorting out what the current state is using a switch then calling the appropriate method to handle the current state. I have implemented them a lot this way. Another way I sometimes use is to set up delegates to the state methods then the Update can simply call the method to handle the current state by using the delegate eliminating the need for the switch. I've always said there are many ways to do this stuff. Each has pros and cons including some ways connecting more with one person while others better match another person or project. Basically I always favor simplicity. Simpler implementations are easier to debug, extend and otherwise enhance.

    Regardless of how implemented... I agree state machines rule. And I also use them for AI. Heck I use them for everything. Even in my little Christmas game project the reindeer has states, the gifts have states, even the snowballs have states.
     
    sluice likes this.
  13. Tomnnn

    Tomnnn

    Joined:
    May 23, 2013
    Posts:
    4,148

    Speaking of the christmas games, I just finished the demo of the one I referred to when referring to your original christmas forum game post :D Or rather... the code for the demo is done, just waiting on a friend for the models...

     
    GarBenjamin likes this.
  14. MrBrainMelter

    MrBrainMelter

    Joined:
    Aug 26, 2014
    Posts:
    233
    PlayMaker works well if you like FSMs ...
     
  15. sluice

    sluice

    Joined:
    Jan 31, 2014
    Posts:
    416
    I agree there are a bunch of ways to implement a State Machine. Even for simple stuff, FSM are very useful. Once you start thinking that way... everything is much more clear and simple.

    @GarBenjamin,
    Regarding your example. The issue I see with using Update(), is your enter state methods (HandleInactiveState(), HandleShowBackgroundState, etc.) will be called on every frame.
    How do you counter that?
     
  16. GarBenjamin

    GarBenjamin

    Joined:
    Dec 26, 2013
    Posts:
    7,441
    What you see in the update is just looking at current state to determine which we are in and then calling the appropriate method that handles that state. In those methods, if certain conditions are met the state will be changed to progress through the sequence. So that's all that code illustrates is figuring out the current state and then calling the method that handles that state. Delegates can be set up for the state methods to call those methods directly eliminating the switch entirely. I used to do that because I was use to that implementation using function pointers in C. Over the years I find I am moving to more and more simplistic code. I think because I spent a lot of time trying things in various ways using the "hot" techniques (aka flavor of the month) and in the end it was just as well had I just wrote 1... 2... 3... instead of 1.... dependency injection nmaybe2... functionpointerdelegatehidingpathto3... basically unless I have a good reason to do otherwise I focus on simplicity.
     
    sluice likes this.
  17. sluice

    sluice

    Joined:
    Jan 31, 2014
    Posts:
    416
    GarBenjamin likes this.
  18. GarBenjamin

    GarBenjamin

    Joined:
    Dec 26, 2013
    Posts:
    7,441
    Yeah I like simplicity. My own style is a combo of many different philosophies such as KISS, IID (Incremental & Iterative Development), RERO (Release Early Release Often) and TMTOWTDI (There's More Than One Way To Do It). I am sure a few more come into play but I'd say those are my core dev principles.
     
    sluice likes this.
  19. sluice

    sluice

    Joined:
    Jan 31, 2014
    Posts:
    416
    Do not forget: DRY (Don't Repeat Yourself) :D
     
  20. GoesTo11

    GoesTo11

    Joined:
    Jul 22, 2014
    Posts:
    604
    Does anyone have any experience with Terry Norton's implementation of state machines from his book: Learning C# by Developing Games with Unity 3D Beginner's Guide? I thought it looked really cool so I implemented it in my game but I keep running into issues that need workarounds.
     
  21. GarBenjamin

    GarBenjamin

    Joined:
    Dec 26, 2013
    Posts:
    7,441
    Why do you say that? Why do you say that? Why do you say that? Why do you say that?
     
    randomperson42 likes this.
  22. GarBenjamin

    GarBenjamin

    Joined:
    Dec 26, 2013
    Posts:
    7,441
    Nope. Sometimes things look and / or sound cool but are actually just a magnificent sand sculpture.
     
  23. sluice

    sluice

    Joined:
    Jan 31, 2014
    Posts:
    416
    @GarBenjamin, you should make a motto out of that:
    STLAOSCBAAJAMSS :eek:
     
  24. GoesTo11

    GoesTo11

    Joined:
    Jul 22, 2014
    Posts:
    604
    He has a statemanager attached to a game object and makes it so that gameobject isn't destroyed when a new level is loaded. The statemanager looks like this:

    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3. using System.Collections;
    4. using Assets.Code.States;
    5. using Assets.Code.Interfaces;
    6.  
    7. public class StateManager : MonoBehaviour
    8. {
    9.     public IStateBase activeState;
    10.  
    11.     [HideInInspector]
    12.     public GameData gameDataRef;
    13.     [HideInInspector]
    14.     public PlayerSaveData playerSaveDataRef ;
    15.  
    16.    
    17.     private static StateManager instanceRef;
    18.    
    19.     void Awake ()
    20.     {
    21.         if(instanceRef == null)
    22.         {
    23.             instanceRef = this;
    24.             DontDestroyOnLoad(gameObject);
    25.         }
    26.         else
    27.         {
    28.             DestroyImmediate(gameObject);
    29.         }
    30.     }
    31.  
    32.     void Start ()
    33.     {
    34.         int level = Application.loadedLevel;
    35.         if (level == 1)
    36.         {
    37.             activeState = new GameState1(this);
    38.         }
    39.         else if (level == 2)
    40.         {
    41.             activeState = new GameState2(this);
    42.         }
    43.         else if (level == 3)
    44.         {
    45.             activeState = new GameState3(this);
    46.  
    47.         }
    48.         else if (level ==4)
    49.         {
    50.  
    51.             activeState = new GameState4(this);
    52.         }
    53.         else if (level == 9)
    54.         {
    55.  
    56.             activeState = new GameState5(this);
    57.         }
    58.         else
    59.         {
    60.             activeState = new BeginState(this);
    61.         }
    62.        
    63.         gameDataRef = GetComponent<GameData>();
    64.        
    65.     }
    66.  
    67.     void Update()
    68.     {
    69.         if (activeState != null)
    70.             activeState.StateUpdate();
    71.     }
    72.    
    73.     void FixedUpdate()
    74.     {
    75.         if (activeState != null)
    76.             activeState.StateFixedUpdate();
    77.     }
    78.    
    79.     void OnGUI()
    80.     {
    81.         if(activeState != null)
    82.             activeState.ShowIt();
    83.     }
    84.  
    85.  
    86.     public void SwitchState(IStateBase newState)
    87.     {
    88.         activeState = newState;
    89.     }
    90.  
    The interface for the states looks like this:

    Code (CSharp):
    1. namespace Assets.Code.Interfaces
    2. {
    3.     public interface IStateBase
    4.     {
    5.         void StateUpdate();
    6.         void StateFixedUpdate();
    7.         void ShowIt();
    8.     }
    9. }
    10.  
    Each state then implements StateUpdate(), StateFixedUpdate() and ShowIt(). This seemed to be great for my main menu GUI since I just needed to switch to a new state and ShowIt() would change the GUI. That is less useful with the new GUI. I also ran into issues where if a new state referenced an object in a new scene, I would get an error because it would try to set the reference before the object was loaded in, so I implemented a separate loading state. The complicated if statement is something that I added so that I could start a level in the editor directly without having to load the start scene first. I also can't run coroutines with this setup. I'm thinking of stripping out all of the game logic out of my states and just let the statemanger be responsible for switching levels and scenes.
     
  25. GoesTo11

    GoesTo11

    Joined:
    Jul 22, 2014
    Posts:
    604
    After further review, I don't think that the above statemachine is all that bad. It's kind of like a beginner's statemachine. It really needs a messaging system to work properly. In the book, he uses SendMessage which I avoided using because I heard it was really bad. Now that I finally got a .NET event system up and running, the statemachine is working a lot better.
     
  26. ben-rasooli

    ben-rasooli

    Joined:
    May 1, 2014
    Posts:
    40
    These are not the same things. You need to put apart state-full items from event-based ones. In your example, the first three items are event-based and the rest are kinda state-full. So I would say:
    - Game starting state
    - Game awaiting state
    - Game running state
    - Game over state

    and then inside the Game Starting State, I would use an event-based solution to handle the sequence of some events. So it becomes like this:
    - Show of a background
    - The gameboard slides in
    - A lille guy comes down from the top, telling you a brief description of what this particual level is about.

    We are now in year 2019, so you can use Unity Timeline as your event-based solution.
     
    Mercbaker likes this.