This example employs key plugins and design patterns to establish a robust architecture:
- Zenject: Manages dependency injection and facilitates link passing through its DI containers. Link
- Addressables: Empowers a versatile content loading system, supporting both remote and local resources. Link
- UniTask: Enhances asynchronous method handling, promoting efficient task execution. Link
- Core System (Finite State Machine): Organizes the core architecture using a FSM for clarity and control Git Page.
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
- 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();
}
}
- 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;
}
}
}
}
- 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>();
}
}
- 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);
}
}
}
- Having mutiple UI screens with no confusion
- States can used not only for UI and some logic, but can travel also in game settings like AI realization or some other
- Can be easly refactored, no Monobehaviur addiction, No Coroutine addiction
- 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](https://private-user-images.githubusercontent.com/78969017/302256016-101d3895-2d6e-4ec1-a560-6919f14962c1.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQzNzU0MjEsIm5iZiI6MTcyNDM3NTEyMSwicGF0aCI6Ii83ODk2OTAxNy8zMDIyNTYwMTYtMTAxZDM4OTUtMmQ2ZS00ZWMxLWE1NjAtNjkxOWYxNDk2MmMxLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA4MjMlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwODIzVDAxMDUyMVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTA3MmJkZWZlZDZkYjY3MGFkOGM3NTFhMzk0Y2U2YWVhN2IzMDE0NWZkMWRiZTI3Zjg2ZmRjM2YxZDhmMTdlZjYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.yhJdRrSDwzgmu22AogYy8oY3pGO34kEvwD95dbe89vs)