Code Routing App

State System

For this system I used the states design pattern (see pattern). I already had a finite state machine to switch between the different screens. But the more screens I made, the less clear and organized it became. So I refactored it in about a week with the design pattern.

                                
public interface IState
{
    public void OnEnter(Data data = null);
    public void Update();
    public void OnExit();
}
                                
                            
Every state inherits from this interface class. In the OnEnter you can send data from the Data class, because there is '= null', it's optional. This way if you want to switch state and for example: the next state needs the current chosen route, you can send that in the data to that state.
                                
public enum EState
{
    Idle,
    Init,
    MainMenu,
    Database,
    SearchResults,
    ChosenRouteChain,
    Route,
}
                                
                            
Each state gets its own enum, which is stored in the AllStates dictionary. So when you want to switch from states, you can give the enum as a parameter.
                                
private readonly Dictionary<EState, IState> AllStates = new();

public void Init()
{
    RegisterStates();
    SwitchState(defaultState);
}

private void RegisterStates()
{
    AllStates.Add(EState.Idle, new IdleState());
    AllStates.Add(EState.Init, new InitState());
    AllStates.Add(EState.MainMenu, new MainMenuState());
    AllStates.Add(EState.Database, new DatabaseState());
    AllStates.Add(EState.SearchResults, new SearchResultsState());
    AllStates.Add(EState.ChosenRouteChain, new ChosenRouteChainState());
    AllStates.Add(EState.Route, new RouteState());
}
                                
                            
In RegisterStates the classes get linked to a key. So if you access that key, you will get the class.
NOTE: Because this class is a singleton without MonoBehaviour, the Init gets called from an other class that initializes all manager classes.
                                
public EState CurrentState { get; private set; }
public EState PreviousState { get; private set; }
private readonly EState defaultState = EState.Init;

public void SwitchState(EState state, Data data = null)
{
    if (ValidSwitch(state))
    {
        PreviousState = CurrentState;
        AllStates[CurrentState]?.OnExit();
        CurrentState = state;
        string previousState = AllStates[PreviousState] != null ? $"{AllStates[PreviousState]}" : "null";
        Debug.LogWarning($"Switching from {previousState} to {AllStates[CurrentState]}");
        AllStates[CurrentState].OnEnter(data);
    }
}

private bool ValidSwitch(EState state)
{
    if (!AllStates.ContainsKey(state))
    {
        Debug.LogError($"State {state} is not registered.");
        return false;
    }
    if (CurrentState == state)
    {
        Debug.LogWarning($"Already in state {state}. No switch needed.");
        return false;
    }
    return true;
}
                                
                            
So when you want to switch states, it first checks if the switch is valid. It checks if the enum that you give as a parameter is registered and it checks if it isn't already in that state. If everything is good, it first does the onExit function from the state you were currently in. Then set the CurrentState and PreviousState correctly and last do the onEnter from the new CurrentState.

A lot of the time are the click events in the buttons are the ones that do SwitchState, as you can see in the clip above.
                                
public class IdleState : IState
{
    public void OnEnter(Data data = null)
    {
        ScreenManager.Instance.Open(EScreen.Idle, data);
    }
    public void OnExit()
    {
        ScreenManager.Instance.Close(EScreen.Idle);
    }
    public void Update()
    {

    }
}
                                
                            
This is an example of how a class that inherits from IState looks like. Right now this state also takes control over the screen, but that's not required.

The Full Script
|
v


                                
using System.Collections.Generic;
using UnityEngine;

public enum EState
{
    Idle,
    Init,
    MainMenu,
    Database,
    SearchResults,
    ChosenRouteChain,
    Route,
}

public class StateManager
{
    private static StateManager instance;
    public static StateManager Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new();
            }
            return instance;
        }
    }

    public EState CurrentState { get; private set; }
    public EState PreviousState { get; private set; }
    private readonly EState defaultState = EState.Init;

    private readonly Dictionary<EState, IState> AllStates = new();

    public void Init()
    {
        RegisterStates();
        SwitchState(defaultState);
    }

    private void RegisterStates()
    {
        AllStates.Add(EState.Idle, new IdleState());
        AllStates.Add(EState.Init, new InitState());
        AllStates.Add(EState.MainMenu, new MainMenuState());
        AllStates.Add(EState.Database, new DatabaseState());
        AllStates.Add(EState.SearchResults, new SearchResultsState());
        AllStates.Add(EState.ChosenRouteChain, new ChosenRouteChainState());
        AllStates.Add(EState.Route, new RouteState());
    }

    public void SwitchState(EState state, Data data = null)
    {
        if (ValidSwitch(state))
        {
            PreviousState = CurrentState;
            AllStates[CurrentState]?.OnExit();
            CurrentState = state;
            string previousState = AllStates[PreviousState] != null ? $"{AllStates[PreviousState]}" : "null";
            Debug.LogWarning($"Switching from {previousState} to {AllStates[CurrentState]}");
            AllStates[CurrentState].OnEnter(data);
        }
    }

    private bool ValidSwitch(EState state)
    {
        if (!AllStates.ContainsKey(state))
        {
            Debug.LogError($"State {state} is not registered.");
            return false;
        }
        if (CurrentState == state)
        {
            Debug.LogWarning($"Already in state {state}. No switch needed.");
            return false;
        }
        return true;
    }
}
                                
                            

Go back

Routing App

or

Home Page