Skip to content

Commit

Permalink
GameStates converted from enums to structs and classes (#26)
Browse files Browse the repository at this point in the history
* Changes Made
+ Persistent Inventory Items Saving
+ Bag Refactored from SO to Serializable Class
+ Custom Json Serializer for SOs using Addressable
+ Small Utils Edits and Bug Fixes

* Changes Made
+ Lighting regenerated for game scene
+ Small modifications and added burst project settings files

* Refactored Functions in to states (just renames)

* Updated Unity version to v2022 LTS

* checkpoint

* checkpoint, refactored StateStatus enum to a bool IsEnabled

* added refactored and tested new state transitions

* pooling abstraction implemented, moved SerializableVector3.cs to Data package to fix core dependency compile error

* implemented spawner to hit effect on weapons

* Changes made
- Added serialized dictionary as an attribute to Generic Dictionary
- More improvements to Spawner

* checkpoint

* finished #16

* small DeSpawn fix for Spawner.cs

* EventBus implemented

* EventBus.cs implemented/refactored into Project

* namespace and class renames

* revert from merge

* GameState converted from enums to struct/class
  • Loading branch information
rob1997 authored May 14, 2024
1 parent c8c2879 commit e144bec
Show file tree
Hide file tree
Showing 20 changed files with 294 additions and 140 deletions.
10 changes: 7 additions & 3 deletions Packages/com.ekaka.core/Runtime/Common/EventParams.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@ public struct GameManagerReady : IEventParams
{
}

public struct GameStateEnabled<T> : IEventParams where T : IGameState
{
}

public struct GameStateChanged : IEventParams
{
public GameState State { get; private set; }

public GameStateChanged(GameState state)
public IGameState State { get; private set; }
public GameStateChanged(IGameState state)
{
State = state;
}
Expand Down
14 changes: 13 additions & 1 deletion Packages/com.ekaka.core/Runtime/Common/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,18 @@ public static T Cast<T>(object obj)
}
}

public static bool FindIndex<T>(this T[] array, Predicate<T> predicate, out int index)
{
index = Array.FindIndex(array, predicate);

return index != - 1;
}

public static string TypeName(this object obj)
{
return obj?.GetType().Name;
}

public static float NormalizeValue(float value, float lowerLimit, float upperLimit)
{
float cachedValue = value;
Expand Down Expand Up @@ -240,7 +252,7 @@ public static bool IsNullOrEmpty(this Array array)

public static bool IsType(this object obj, object compareObj)
{
return obj.GetType() == compareObj.GetType();
return obj != null ? obj.GetType() == compareObj.GetType() : compareObj == null;
}

public static string[] GetAllSceneNamesInBuildSettings()
Expand Down
202 changes: 90 additions & 112 deletions Packages/com.ekaka.core/Runtime/Game/GameManager.cs
Original file line number Diff line number Diff line change
@@ -1,35 +1,9 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Core.Common;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;

namespace Core.Game
{
public enum GameState
{
//happens only once when game is first loaded on start
//used to load assets and initialize GameManager
Initializing,
//when loading between scenes or game states
//from landing to play from game scene to menu...
Loading,
//first landing scene (main menu)
Landing,
//in game scene/playing game
Play,
//when game is paused - still in game scene
Pause,
//when game is over (Player is dead) - still in game scene
GameOver,
//onApplicationQuit (maybe use for dispose/garbage collection)
Quitting
}

//run before everything else
[DefaultExecutionOrder(- 1)]
public class GameManager : Singleton<GameManager>
Expand All @@ -54,13 +28,25 @@ private void InvokeReady()
IsReady = true;
}

// Add new GameStates here.
private IGameState[] AllStates => new IGameState[]{
new Loading(),
new Landing(),
new Play(),
new Pause(),
new GameOver(),
new Quitting()
};

private int _currentStateIndex = - 1;

[field: SceneList] [field: SerializeField] public int LandingScene { get; private set; }

[field: SceneList] [field: SerializeField] public int GameScene { get; private set; }

public GameState State { get; private set; } = GameState.Initializing;
public IGameState CurrentState => _currentStateIndex < 0 ? null : AllStates[_currentStateIndex];

public bool InGame => State == GameState.Play || State == GameState.Pause || State == GameState.GameOver;
public bool InGame => CurrentState is IInGameState;

private void Start()
{
Expand All @@ -71,175 +57,167 @@ private void Initialize()
{
//all other managers are already initialized OnEnable
InvokeReady();

// use for loop since states are mostly structs.
for (int i = 0; i < AllStates.Length; i++)
{
AllStates[i].Initialize();
}

//now it has finished initializing change state to loading - loading landing scene
ChangeGameState(GameState.Loading);
ChangeGameState<Loading>();

//load first/landing scene
Common.Utils.LoadScene(LandingScene, delegate { ChangeGameState(GameState.Landing); });
Utils.LoadScene(LandingScene, ChangeGameState<Landing>);
}

private void ChangeGameState(GameState newState)
private void ChangeGameState<T>() where T : IGameState
{
if (newState == State)
if (!AllStates.FindIndex( s => s is T, out int index))
{
Debug.LogError($"{nameof(IGameState)} of type {typeof(T).Name} not found.");

return;
}

if (_currentStateIndex == index)
{
Debug.LogWarning($"{nameof(IGameState)} already {CurrentState}.");

return;
}

IGameState newState = AllStates[index];

if (!newState.IsReady)
{
Debug.LogWarning($"{nameof(GameState)} already {State}");
Debug.LogWarning($"{typeof(T).Name} {nameof(IGameState)} is not ready.");

return;
}

Debug.Log($"{nameof(GameState)} changed from {State} to {newState}");
// Disable previous state.
if (_currentStateIndex != - 1)
{
AllStates[_currentStateIndex].Disable();
}

Debug.Log($"{nameof(IGameState)} changed from {CurrentState.TypeName()} to {newState.TypeName()}.");

_currentStateIndex = index;

newState.Enable();

// Update current state reference since states can be structs.
AllStates[_currentStateIndex] = newState;

State = newState;
EventBus<GameStateEnabled<T>>.Invoke();

EventBus<GameStateChanged>.Invoke(new GameStateChanged(State));
EventBus<GameStateChanged>.Invoke(new GameStateChanged(CurrentState));
}

public void StartGame(bool continued)
{
if (continued)
{
ContinueGame();
LoadSavedGame();
}

else
{
StartNewGame();
LoadNewGame();
}
}

//load persistent data first
private void ContinueGame(bool reload = false)
private void LoadSavedGame(bool tryAgain = false)
{
//change to loading until scene loads async
ChangeGameState(GameState.Loading);
ChangeGameState<Loading>();

Debug.Log("Loading Game...");
Debug.Log("Loading Saved Game...");

//load game scene and call onSceneLoaded
Common.Utils.LoadScene(GameScene, NewGameStarted, reload);
Utils.LoadScene(GameScene, GameStarted, tryAgain);
}

private void StartNewGame()
private void LoadNewGame()
{
//change to loading until scene loads async
ChangeGameState(GameState.Loading);
ChangeGameState<Loading>();

Debug.Log("Loading New Game...");

//load game scene and call onSceneLoaded
Common.Utils.LoadScene(GameScene, NewGameStarted);
Utils.LoadScene(GameScene, GameStarted);
}

//when game scene finished loading
void NewGameStarted()
void GameStarted()
{
ChangeGameState(GameState.Play);
ChangeGameState<Play>();

Debug.Log("Loaded New Game");
}

public void ExitGame()
{
#if UNITY_EDITOR
EditorApplication.isPlaying = false;
#else
Application.Quit();
#endif
ChangeGameState<Quitting>();
}

//leave/unload game scene and load to Landing scene
public void ExitToMainMenu()
{
if (InGame)
{
//change to loading until scene loads async
ChangeGameState(GameState.Loading);

Debug.Log($"exiting {nameof(GameScene)}...");

//load landing scene and call onSceneLoaded
Common.Utils.LoadScene(LandingScene, GameExited);
}

else
{
Debug.LogError($"can't exit {nameof(GameScene)} when {nameof(GameState)} is not an {nameof(InGame)} {State}");
}
}

public void TryAgain()
{
if (!InGame)
{
Debug.LogError("Can't Not in Game.");
Debug.LogError($"can't exit when {nameof(CurrentState)} is not an {nameof(IInGameState)}.");

return;
}

GameExited();
//change to loading until scene loads async
ChangeGameState<Loading>();

ContinueGame(true);
Debug.Log($"exiting {nameof(GameScene)}...");

//load landing scene and call onSceneLoaded
Utils.LoadScene(LandingScene, GameExited);
}

public void TryAgain()
{
LoadSavedGame(true);
}

//called once landing scene is loaded
private void GameExited()
{
Debug.Log($"Exited {nameof(GameScene)}");
Debug.Log($"Exited {nameof(GameScene)}.");

ChangeGameState(GameState.Landing);

//reset timeScale in case it was exited in pause
Time.timeScale = 1f;
ChangeGameState<Landing>();
}

public void PauseGame()
{
//pause only from GameState.Play
if (State != GameState.Play)
{
Debug.LogWarning($"can't {nameof(PauseGame)} when {nameof(GameState)} is {State}");

return;
}

Time.timeScale = 0f;

ChangeGameState(GameState.Pause);
ChangeGameState<Pause>();
}

public void GameOver()
{
if (!InGame)
{
Debug.LogWarning($"can't {nameof(GameOver)} when {nameof(GameState)} is {State}");

return;
}

Time.timeScale = 0f;

ChangeGameState(GameState.GameOver);
ChangeGameState<GameOver>();
}

public void ResumeGame()
{
//resume only from GameState.Pause
if (State != GameState.Pause)
{
Debug.LogWarning($"can't {nameof(ResumeGame)} when {nameof(GameState)} is {State}");

return;
}

Time.timeScale = 1f;

ChangeGameState(GameState.Play);
ChangeGameState<Play>();
}

private void OnApplicationQuit()
{
ChangeGameState(GameState.Quitting);
if (!(CurrentState is Quitting))
{
ChangeGameState<Quitting>();
}
}
}
}
8 changes: 8 additions & 0 deletions Packages/com.ekaka.core/Runtime/Game/GameState.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit e144bec

Please sign in to comment.