Space-Smash-Out/Assets/Scripts/Managers/GameManager.cs

491 lines
15 KiB
C#

using System;
using System.Collections;
using System.IO;
using System.Reflection;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
//using FishNet;
using System.Collections.Generic;
using UnityEditor;
using log4net;
using PrimeTween;
using System.Threading.Tasks;
using FishNet;
/// <summary>
/// The available scenes in the order they are in the build settings.
/// </summary>
public enum SceneEnum
{
GameMangement,
MainMenu,
InGameUI,
Arena,
FreeFlyArena,
OnlineLobby,
OnlineArena
}
namespace Managers
{
/// <summary>
/// Starts up the game and it's managers.
/// Provides methods for loading additional scenes and triggering custom events when loaded.
/// Orchestrates the managers for starting local and online matches.
public class GameManager : MonoBehaviour
{
private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
/// <summary>
/// Globally accessible member to use manager with.
/// </summary>
public static GameManager G { get; private set; }
[SerializeField]
private UIManager UIManager;
[SerializeField]
private CharacterManager CharacterManager;
[SerializeField]
private ControlsManager ControlsManager;
[SerializeField]
private MatchManager MatchManager;
[SerializeField]
private StatisticsManager StatisticsManager;
[SerializeField]
private PlayerManager PlayerManager;
[SerializeField]
private AudioManager AudioManager;
[SerializeField]
private GameObject startCamera;
[HideInInspector]
public SceneEnum CurrentScene = SceneEnum.GameMangement;
public bool IsTestRun = false;
public SceneEnum TestScene = SceneEnum.Arena;
public int TestSceneRule = 0;
public int TestSceneArena = 0;
public event EventHandler<SceneLoadEventArgs> CustomSceneLoaded;
void Awake()
{
G = this;
if (!IsTestRun) ShowStartScreen();
LogManager.ConfigureLogging();
//Application.targetFrameRate = 60;
Log.Info("Awake");
}
void Start()
{
InstantiateBaseManagers();
Log.Info("Space Smash Out is starting.");
if (!IsTestRun)
{
LoadMainMenu();
}
else
{
// Coroutine for waiting a frame and letting the managers run their Start methods
StartCoroutine(SetupTestMatch(TestScene));
}
}
/// <summary>
/// Instantiates the managers needed to play the game.
/// </summary>
void InstantiateBaseManagers()
{
if (UIManager != null)
{
UIManager = Instantiate(UIManager);
Log.Info("User Interface Manager instantiated.");
}
else
{
Log.Error("User Interface Manager Prefab missing.");
}
if (ControlsManager != null)
{
ControlsManager = Instantiate(ControlsManager);
Log.Info("Controls Manager instantiated.");
}
else
{
Log.Error("Controls Manager Prefab missing.");
}
if (MatchManager != null)
{
MatchManager = Instantiate(MatchManager);
Log.Info("Match Manager instantiated.");
}
else
{
Log.Error("User Interface Manager Prefab missing.");
}
if (CharacterManager != null)
{
CharacterManager = Instantiate(CharacterManager);
Log.Info("Character Manager instantiated.");
}
else
{
Log.Error("Character Manager Prefab missing.");
}
if (PlayerManager != null)
{
PlayerManager = Instantiate(PlayerManager);
Log.Info("PlayerManager Manager instantiated.");
}
if (StatisticsManager != null)
{
StatisticsManager = Instantiate(StatisticsManager);
Log.Info("Statistics Manager instantiated.");
}
else
{
Log.Error("PlayerManager Manager Prefab missing.");
}
if (AudioManager != null)
{
AudioManager = Instantiate(AudioManager);
Log.Info("PlayerManager Manager instantiated.");
}
else
{
Log.Error("PlayerManager Manager Prefab missing.");
}
}
/// <summary>
/// Starts the camera, for showing an image before the main menu is loaded.
/// </summary>
void ShowStartScreen()
{
startCamera.SetActive(true);
}
/// <summary>
/// Sets up switching cameras once the menu is loaded
/// and starts the async scene loading.
/// </summary>
void LoadMainMenu()
{
SingleUseSceneLoadedMethodCaller((args) =>
{
SceneManager.SetActiveScene(args.SceneRef);
startCamera.SetActive(false);
CurrentScene = SceneEnum.MainMenu;
}, SceneEnum.MainMenu);
SceneManager.LoadSceneAsync((int)SceneEnum.MainMenu, LoadSceneMode.Additive);
Log.Info("Main menu scene loaded.");
}
/// <summary>
/// Initiates a local battle by giving relavant information to the responsible managers
/// and starting their appropriate processes.
/// </summary>
/// <param name="sceneId">Scene id of the chosen arena.</param>
public async Task SetupLocalMatchFromMainMenu(int ruleIndex, int arenaIndex)
{
MatchManager.LoadMatchRules(ruleIndex);
MatchManager.LoadArenaProperties(arenaIndex);
MatchManager.AssignMatchObjects();
PlayerManager.LocalMatchJoinPlayers(MatchManager.ArenaProperties.minPlayerCount,
MatchManager.ArenaProperties.maxPlayerCount);
// TODO: This is in place of a character choosing menu etc.
foreach (Player p in PlayerManager.MatchPlayers)
{
CharacterManager.AssignShipFixed(p);
Log.Debug($"Ship: {p.character.name} assigned to player: {p.playerNumber}.");
}
MatchManager.StartCharacterSelect();
CharacterManager.SpawnCharacters(PlayerManager.MatchPlayers);
UIManager.StartManagingMatchUI();
UIManager.AssignHUDElementsToPlayers(PlayerManager.MatchPlayers);
Log.Debug("Assigned HUD Elements to their players.");
ControlsManager.AssignControlsToPlayers(PlayerManager.MatchPlayers);
UIManager.StartInputPrompt(ControlsManager.unassignedPlayers);
Log.Debug("Assigned HUD Elements to their players.");
// Waits with starting the match until every player has controls
while (ControlsManager.unassignedPlayers.Count > 0)
{
await Tween.Delay(0.1f);
}
MatchManager.StartMatch();
}
/// <summary>
/// Starts a match scene without the menus or input/start prompts.
/// </summary>
/// <param name="scene">The match scene which will be loaded</param>
/// <returns></returns>
public IEnumerator SetupTestMatch(SceneEnum scene)
{
yield return null;
SceneManager.LoadScene((int)scene, LoadSceneMode.Additive);
CurrentScene = scene;
yield return null;
SceneManager.LoadScene((int)SceneEnum.InGameUI, LoadSceneMode.Additive);
yield return null;
SceneManager.SetActiveScene(SceneManager.GetSceneByBuildIndex((int)scene));
yield return null;
MatchManager.LoadMatchRules(TestSceneRule);
MatchManager.LoadArenaProperties(TestSceneArena);
MatchManager.AssignMatchObjects();
PlayerManager.LocalMatchJoinPlayers(MatchManager.ArenaProperties.minPlayerCount,
MatchManager.ArenaProperties.maxPlayerCount);
foreach (Player p in PlayerManager.MatchPlayers)
{
CharacterManager.AssignShipFixed(p);
Log.Debug($"Ship: {p.character.name} assigned to player: {p.playerNumber}.");
}
MatchManager.StartCharacterSelect();
CharacterManager.SpawnCharacters(PlayerManager.MatchPlayers);
UIManager.StartManagingMatchUI();
UIManager.AssignHUDElementsToPlayers(PlayerManager.MatchPlayers);
ControlsManager.AssignControlsToPlayers(PlayerManager.MatchPlayers);
MatchManager.StartTestMatch();
}
/// <summary>
/// Initiates an online battle by giving relavant information to the responsible managers
/// and starting their appropriate processes.
/// </summary>
/// <param name="sceneId">Scene id of the chosen arena.</param>
public IEnumerator SetupOnlineMatchFromMainMenu()
{
MatchManager.LoadMatchRules(0);
MatchManager.LoadArenaProperties(0);
MatchManager.AssignMatchObjects();
// TODO: Selection for character in online play here
PlayerManager.LoadOnlinePlayer();
if (InstanceFinder.IsServerStarted)
CharacterManager.AssignShipFixed(PlayerManager.OnlinePlayer, 0);
else if (InstanceFinder.IsClientStarted)
CharacterManager.AssignShipFixed(PlayerManager.OnlinePlayer, 1);
Log.Debug($"Ship: {PlayerManager.OnlinePlayer.character.name} assigned to online player.");
CharacterManager.SpawnOnlineCharacter(PlayerManager.OnlinePlayer);
UIManager.StartManagingMatchUI();
UIManager.AssignHUDElementsToPlayers(PlayerManager.MatchPlayers);
ControlsManager.AssignControlsToPlayers(PlayerManager.MatchPlayers);
UIManager.StartInputPrompt(ControlsManager.unassignedPlayers);
// Waits with starting the match until every player has controls
while (ControlsManager.unassignedPlayers.Count > 0)
{
yield return null;
}
MatchManager.StartTestMatch();
}
/// <summary>
/// Triggers a method once, when a specific scene was loaded.
/// </summary>
/// <param name="func">The method which will be executed
/// once the scene is loaded.</param>
/// <param name="sceneEnum">The scene which will trigger the method when loaded.</param>
public void SingleUseSceneLoadedMethodCaller(CustomOnSceneLoaded method, SceneEnum sceneEnum)
{
// This is used because SceneManager.sceneLoaded only passes ref and loadMode.
// I want add the sceneEnum to this, to check if the the sceneLoaded event
// refers to the same scene as passed to this method.
UnityAction<Scene, LoadSceneMode> OnSceneLoaded = (sceneRef, loadMode) =>
{
CustomSceneLoaded.Invoke(null, new SceneLoadEventArgs(sceneEnum, sceneRef));
};
EventHandler<SceneLoadEventArgs> handler = null;
handler = (invoker, args) =>
{
// Thanks to the CustomSceneLoaded event I can check this here.
if (args.SceneRef.buildIndex == (int)args.SceneEnum)
{
method(args);
}
else
{
Log.Warn("Should have loaded scene: " + args.SceneEnum.ToString() +
" but the most recently loaded scene is: " + args.SceneRef.name +
". Be careful of loading many scenes at once from different scripts!");
}
CustomSceneLoaded -= handler;
SceneManager.sceneLoaded -= OnSceneLoaded;
};
CustomSceneLoaded += handler;
SceneManager.sceneLoaded += OnSceneLoaded;
}
/// <summary>
/// Loads all prefabs/gameobjects from a folder if the contain the given type
/// as a component and adds them to a list.
/// </summary>
/// <param name="path">Path of the folder</param>
/// <param name="prefabList">List the loaded prefabs will be added to</param>
public static void LoadPrefabsFromFolder<T>(string path, in List<GameObject> prefabList)
{
#if UNITY_EDITOR
string[] files = Directory.GetFiles(path, "*.prefab",
SearchOption.TopDirectoryOnly);
List<GameObject> gos = new List<GameObject>();
foreach (var file in files)
{
gos.Add(AssetDatabase.LoadAssetAtPath<GameObject>(file));
}
foreach (GameObject go in gos)
{
if (go.TryGetComponent<T>(out _))
{
prefabList.Add(go);
}
else
{
Log.Warn($"GameObject: {go.name} did not contain a {typeof(T).Name} component. It won't be added to the list.");
}
}
#else
Log.Error("Can only access prefabs in folders when in editor!");
#endif
}
/// <summary>
/// Loads all prefabs/gameobjects from an asset bundle if the contain the given type
/// as a component and adds them to a list.
/// TODO: DO NOT FORGET TO manually BUILD ASSET BUNDLES WHEN BUILDING
/// </summary>
/// <param name="name">Name of the asset bundle</param>
/// <param name="prefabList">List the loaded prefabs will be added to</param>
public static void LoadPrefabsFromBundle<T>(string name, in List<GameObject> prefabList)
{
#if !UNITY_WEBGL
AssetBundle assetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, name));
#else
string uri = "file:///" + Application.dataPath + "/AssetBundles/" + name;
UnityWebRequest request
= UnityWebRequestAssetBundle.GetAssetBundle(uri, 0);
request.SendWebRequest();
while (!request.isDone)
{
// Yeah I know 🤡
// But I will explain myself:
// I want these resources loaded, they are required for the game to run at all.
// When using WebGL or running on Android, I cannot get AssetBundles from disk without
// UnityWebRequest. I would have to use addressables and that would be too much time cost
// right now!
}
AssetBundle assetBundle = DownloadHandlerAssetBundle.GetContent(request);
#endif
if (assetBundle == null)
{
Log.Error("Failed to load arenas asset bundle!");
return;
}
List<GameObject> gos = new List<GameObject>();
gos.AddRange(assetBundle.LoadAllAssets<GameObject>());
foreach (GameObject go in gos)
{
if (go.TryGetComponent<T>(out _))
{
prefabList.Add(go);
}
else
{
Log.Warn($"GameObject: {go.name} did not contain a {typeof(T).Name} component. It won't be added to the list.");
}
}
}
/// <summary>
/// Loads all assets from a folder, if they are of the given type and adds them to a list.
/// </summary>
/// <param name="path">Path of the folder</param>
/// <param name="prefabList">List the loaded prefabs will be added to</param>
public static void LoadAssetsFromFolder<T>(string path, in List<T> prefabList)
where T : UnityEngine.Object
{
#if UNITY_EDITOR
string[] files = Directory.GetFiles(path, "*.asset",
SearchOption.TopDirectoryOnly);
foreach (var file in files)
{
prefabList.Add(AssetDatabase.LoadAssetAtPath<T>(file));
}
#else
Log.Error("Can only access prefabs in folders when in editor!");
#endif
}
/// <summary>
/// Loads all assets from an asset bundle, if they are of the given type and adds them to a list.
/// TODO: DO NOT FORGET TO manually BUILD ASSET BUNDLES WHEN BUILDING
/// </summary>
/// <param name="name">Name of the asset bundle</param>
/// <param name="prefabList">List the loaded prefabs will be added to</param>
public static void LoadAssetsFromBundle<T>(string name, in List<T> assetsList) where T : UnityEngine.Object
{
#if !UNITY_WEBGL
var assetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, name));
#else
string uri = "file:///" + Application.dataPath + "/AssetBundles/" + name;
UnityWebRequest request
= UnityWebRequestAssetBundle.GetAssetBundle(uri, 0);
while (!request.isDone)
{
// Yeah I know 🤡
// But I will explain myself:
// I want these resources loaded, they are required for the game to run at all.
// When using WebGL or running on Android, I cannot get AssetBundles from disk without
// UnityWebRequest. I would have to use addressables and that would be too much time cost
// right now!
}
AssetBundle assetBundle = DownloadHandlerAssetBundle.GetContent(request);
#endif
if (assetBundle == null)
{
Log.Error("Failed to load arenas asset bundle!");
return;
}
assetsList.AddRange(assetBundle.LoadAllAssets<T>());
}
}
/// <summary>
/// Custom delegate which can be executed after scene load.
/// </summary>
/// <param name="args">Data of the loaded scene</param>
public delegate void CustomOnSceneLoaded(SceneLoadEventArgs args);
public class SceneLoadEventArgs : EventArgs
{
public SceneLoadEventArgs(SceneEnum sceneEnum, Scene sceneRef)
{
SceneEnum = sceneEnum;
SceneRef = sceneRef;
}
public SceneEnum SceneEnum { get; set; }
public Scene SceneRef { get; set; }
}
}