Skip to content

EduardMalkhasyan/Unity-FSM-Architecture-Example

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

34 Commits
 
 
 
 

Repository files navigation

Unity FSM Architecture Example

This example employs key plugins and design patterns to establish a robust architecture:

Core Plugins:

  1. Zenject: Manages dependency injection and facilitates link passing through its DI containers. Link
  2. Addressables: Empowers a versatile content loading system, supporting both remote and local resources. Link
  3. UniTask: Enhances asynchronous method handling, promoting efficient task execution. Link

Design Pattern:

  1. Core System (Finite State Machine): Organizes the core architecture using a FSM for clarity and control Git Page.

Realization Examples:

Finite State Machine realization in project, where it can be used as core game states where state will use some game logic and UI space his inside, also can be realized as in game components like - AI state machine

  1. Initial Machine
public interface IGameState
    {
        void Enter();
        void Exit();
        void Tick();
    }

  public class StateMachine
    {
        public Type CurrentStateClass => (currentState != null) ? currentState.GetType() : null;
        private IGameState currentState;

        public void Tick()
        {
            currentState?.Tick();
        }

        public void SetState(IGameState state)
        {
            currentState?.Exit();
            currentState = state;
            currentState.Enter();
        }
    }
  1. Initalization
 public class SceneContextInstaller : MonoInstaller
    {
        public override void InstallBindings()
        {
            Container.Bind<AIGameStates>().FromNewComponentOnNewGameObject().AsTransient();
            InstallGameStates<MainAbstractGameState>();
            InstallGameStates<AIAbstractGameState>();
        }

        public void InstallGameStates<T>()
        {
            var assembly = Assembly.GetAssembly(typeof(T));

            FindAssemblyTypes.FindDerivedTypesFromAssembly(assembly, typeof(T), true).ForEach(
                 (type) =>
                 {
                     if (type.IsAbstract == false)
                     {
                         Container.UnbindInterfacesTo(type);
                         Container.BindInterfacesAndSelfTo(type).AsSingle();
                     }
                 });
        }
    }

 public class FindAssemblyTypes
    {
        public static IEnumerable<Type> FindDerivedTypesFromAssembly(Assembly assembly, Type baseType, bool classOnly)
        {
            if (assembly == null)
                throw new ArgumentNullException("assembly", "Assembly must be defined");

            if (baseType == null)
                throw new ArgumentNullException("baseType", "Parent Type must be defined");

            var types = assembly.GetTypes();

            foreach (var type in types)
            {
                if (classOnly && !type.IsClass)
                    continue;

                if (baseType.IsInterface)
                {
                    var it = type.GetInterface(baseType.FullName);

                    if (it != null)
                        yield return type;
                }
                else if (type.IsSubclassOf(baseType))
                {
                    yield return type;
                }
            }
        }
    }
  1. Core Machine
 public abstract class MainAbstractGameState : IGameState
    {
        public abstract void Enter();
        public abstract void Exit();
        public virtual void Tick() { }
    }

  public class MainGameStates : MonoBehaviour
    {
        [Inject] private DiContainer container;
        private StateMachine stateMachine;
        public System.Type CurrentStateType => stateMachine.CurrentStateClass;

        private void Awake()
        {
            stateMachine = new StateMachine();
        }

        private void FixedUpdate()
        {
            stateMachine.Tick();
        }

        public void EnterState<T>() where T : MainAbstractGameState
        {
            if (stateMachine.CurrentStateClass == typeof(T)) return;
            DebugColor.LogBlue($"Entering state: {typeof(T)}");
            var state = container.Resolve<T>();
            stateMachine.SetState(state);
        }
    }

// Example realization
   public class PlayGameState : MainAbstractGameState
    {
        [Inject] private UIScreensController screensController;
        [Inject] private VirtualCamera virtualCamera;
        [Inject] private MainGameStates mainGameStates;
        [Inject] private TutorialState tutorialState;
        [Inject] private Kitchen kitchen;

        public override void Enter()
        {
            screensController.ShowInstantUIScreen(UIScreenEnum.Gameplay);
            virtualCamera.SwitchCamera(VirtualCameraType.Close);

            kitchen.ActivateKitchen();
            TutorialObserver.OnTutorial += EnterTutorialDemonstration;
        }

        public override void Exit()
        {
            TutorialObserver.OnTutorial -= EnterTutorialDemonstration;
        }

        private void EnterTutorialDemonstration(TutorialEnum tutorialEnum)
        {
            var preset = TutorialProps.Value.GetTutorialPresetKVP(tutorialEnum);
            tutorialState.Setup(preset.tutorialObject, tutorialEnum);
            mainGameStates.EnterState<TutorialState>();
        }
    }
  1. AI Machine
 public abstract class AIAbstractGameState : IGameState
    {
        public abstract void Enter();
        public abstract void Exit();
        public virtual void Tick() { }
    }

 [InfoBox("This component is bound 'FromNewComponentOnNewGameObject' and 'AsTransient' for multiple uses in AI space")]
    public class AIGameStates : MonoBehaviour
    {
        [Inject] private DiContainer container;
        private StateMachine stateMachine;
        public System.Type CurrentStateType => stateMachine.CurrentStateClass;

        private void Awake()
        {
            stateMachine = new StateMachine();
        }

        private void FixedUpdate()
        {
            stateMachine.Tick();
        }

        public void EnterState<T>(bool canShowDebug = true) where T : AIAbstractGameState
        {
            if (stateMachine.CurrentStateClass == typeof(T)) return;

            if (canShowDebug)
            {
                DebugColor.LogBlue($"AI entering state: {typeof(T)}", bold: true);
            }

            var state = container.Resolve<T>();
            stateMachine.SetState(state);
        }
    }

// Example realization
 public class AIWorkerSorterGoToCookingPlaceState : AIAbstractGameState
    {
        [Inject] private AIWorkerSorter workerSorter;

        public override void Enter()
        {
            workerSorter.PlaySimpleWalkAnimation();
            workerSorter.GoToCookingPlace(OnComplete: () => { EnterPickupPlaceState(); });
        }

        public override void Exit()
        {

        }

        public async void EnterPickupPlaceState()
        {
            try
            {
                workerSorter.PlayIdleWithItemAnimation();
                await UniTask.WaitUntil(() => workerSorter.FoodStack.AvailableStackIsEmpty() == false);

                workerSorter.EnterState<AIWorkerSorterGoToPickupPlaceState>();
            }
            catch (Exception error)
            {
                Debug.LogWarning(error);
            }
        }
    }

Final Result:

  1. Having mutiple UI screens with no confusion
  2. States can used not only for UI and some logic, but can travel also in game settings like AI realization or some other
  3. Can be easly refactored, no Monobehaviur addiction, No Coroutine addiction
  4. Optimazed for low mobile devices (Until 2GB Ram 2016 year release mobile - with 50-60 fps benchmark and 60+ minutes gameplay with no warming up phone and droping fps) because of Addressables, Low use of Update thanks to UniTask, and no scene reload system thanks to Zenject and ISelfReset Interface and more.

Games with this archictecture can be find in this Link

Screenshot_1

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages