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.Config; using log4net; using UnityEngine.Networking; /// /// The available scenes in the order they are in the build settings. /// public enum SceneEnum { GameMangement, MainMenu, InGameUI, Arena, FreeFlyArena } namespace Managers { /// /// 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); /// /// Globally accessible member to use manager with. /// 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 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)); } } /// /// Instantiates the managers needed to play the game. /// 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."); } 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."); } } /// /// Starts the camera, for showing an image before the main menu is loaded. /// void ShowStartScreen() { startCamera.SetActive(true); } /// /// Sets up switching cameras once the menu is loaded /// and starts the async scene loading. /// 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."); } /// /// Initiates a local battle by giving relavant information to the responsible managers /// and starting their appropriate processes. /// /// Scene id of the chosen arena. public IEnumerator 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); 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.StartMatch(); } /// /// Starts a match scene without the menus or input/start prompts. /// /// The match scene which will be loaded /// 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(); } /// /// Initiates an online battle by giving relavant information to the responsible managers /// and starting their appropriate processes. /// /// Scene id of the chosen arena. 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(); } /// /// Triggers a method once, when a specific scene was loaded. /// /// The method which will be executed /// once the scene is loaded. /// The scene which will trigger the method when loaded. 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 OnSceneLoaded = (sceneRef, loadMode) => { CustomSceneLoaded.Invoke(null, new SceneLoadEventArgs(sceneEnum, sceneRef)); }; EventHandler 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; } /// /// Loads all prefabs/gameobjects from a folder if the contain the given type /// as a component and adds them to a list. /// /// Path of the folder /// List the loaded prefabs will be added to public static void LoadPrefabsFromFolder(string path, in List prefabList) { #if UNITY_EDITOR string[] files = Directory.GetFiles(path, "*.prefab", SearchOption.TopDirectoryOnly); List gos = new List(); foreach (var file in files) { gos.Add(AssetDatabase.LoadAssetAtPath(file)); } foreach (GameObject go in gos) { if (go.TryGetComponent(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 } /// /// 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 /// /// Name of the asset bundle /// List the loaded prefabs will be added to public static void LoadPrefabsFromBundle(string name, in List 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 gos = new List(); gos.AddRange(assetBundle.LoadAllAssets()); foreach (GameObject go in gos) { if (go.TryGetComponent(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."); } } } /// /// Loads all assets from a folder, if they are of the given type and adds them to a list. /// /// Path of the folder /// List the loaded prefabs will be added to public static void LoadAssetsFromFolder(string path, in List 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(file)); } #else Log.Error("Can only access prefabs in folders when in editor!"); #endif } /// /// 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 /// /// Name of the asset bundle /// List the loaded prefabs will be added to public static void LoadAssetsFromBundle(string name, in List 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()); } } /// /// Custom delegate which can be executed after scene load. /// /// Data of the loaded scene 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; } } }