/// --------------------------------------------- /// Ultimate Character Controller /// Copyright (c) Opsive. All Rights Reserved. /// https://www.opsive.com /// --------------------------------------------- namespace Opsive.UltimateCharacterController.Game { using UnityEngine; using UnityEngine.SceneManagement; using System.Collections.Generic; /// /// Determines which SpawnPoint to use in order to determine where the object should spawn. /// public class SpawnPointManager : MonoBehaviour { private static SpawnPointManager s_Instance; private static SpawnPointManager Instance { get { if (!s_Initialized) { s_Instance = new GameObject("Spawn Point Manager").AddComponent(); s_Initialized = true; } return s_Instance; } } private static bool s_Initialized; [Tooltip("Checks the first spawn point before checking the other spawn points.")] [SerializeField] protected bool m_FirstSpawnPreferred; private Dictionary> m_SpawnPointGroupings = new Dictionary>(); private Dictionary m_FirstSpawnPoint = new Dictionary(); /// /// The object has been enabled. /// private void OnEnable() { // The object may have been enabled outside of the scene unloading. if (s_Instance == null) { s_Instance = this; s_Initialized = true; SceneManager.sceneUnloaded -= SceneUnloaded; } } /// /// Adds the specified spawn point to the manager. /// /// The spawn point to add. public static void AddSpawnPoint(SpawnPoint spawnPoint) { Instance.AddSpawnPointInternal(spawnPoint); } /// /// Internal method which adds the specified spawn point to the manager. /// /// The spawn point to add. private void AddSpawnPointInternal(SpawnPoint spawnPoint) { AddSpawnPointGrouping(spawnPoint, spawnPoint.Grouping); } /// /// Adds the SpawnPoint to the specified grouping. /// /// The SpawnPoint that should be added. /// The value of the grouping index. private void AddSpawnPointGrouping(SpawnPoint spawnPoint, int groupingIndex) { List spawnPoints; if (!m_SpawnPointGroupings.TryGetValue(groupingIndex, out spawnPoints)) { spawnPoints = new List(); m_SpawnPointGroupings.Add(groupingIndex, spawnPoints); } spawnPoints.Add(spawnPoint); if (!m_FirstSpawnPoint.ContainsKey(groupingIndex)) { m_FirstSpawnPoint.Add(groupingIndex, spawnPoint); } } /// /// The SpawnPoint's grouping value has changed. Update the internal group mapping. /// /// The SpawnPoint whose grouping value changed. /// The new grouping index of the SpawnPoint. public static void UpdateSpawnPointGrouping(SpawnPoint spawnPoint, int newGroupingIndex) { // If the manager isn't initialized yet then the grouping will be updated when the spawn point is added to the manager. if (!s_Initialized) { return; } Instance.UpdateSpawnPointGroupingInternal(spawnPoint, newGroupingIndex); } /// /// The SpawnPoint's grouping value has changed. Internal method which updates the internal group mapping. /// /// The SpawnPoint whose grouping value changed. /// The new grouping value of the SpawnPoint. private void UpdateSpawnPointGroupingInternal(SpawnPoint spawnPoint, int newGroupingIndex) { // Remove from the old grouping map. RemoveSpawnPoint(spawnPoint); // Add to the updated grouping map. AddSpawnPointGrouping(spawnPoint, newGroupingIndex); } /// /// Gets the position and rotation of the spawn point with the specified grouping. /// If false is returned then the point wasn't successfully retrieved. /// /// The object that is spawning. /// The grouping index of spawn points to select from. /// The position of the spawn point. /// The rotation of the spawn point. /// True if the spawn point was successfully retrieved. public static bool GetPlacement(GameObject spawningObject, int grouping, ref Vector3 position, ref Quaternion rotation) { return Instance.GetPlacementInternal(spawningObject, grouping, ref position, ref rotation); } /// /// Internal method which gets the position and rotation of the spawn point with the specified grouping. /// If false is returned then the point wasn't successfully retrieved. /// /// The object that is spawning. /// The grouping index of spawn points to select from. /// The position of the spawn point. /// The rotation of the spawn point. /// True if the spawn point was successfully retrieved. protected virtual bool GetPlacementInternal(GameObject spawningObject, int grouping, ref Vector3 position, ref Quaternion rotation) { List spawnPoints; if (!m_SpawnPointGroupings.TryGetValue(grouping, out spawnPoints)) { Debug.LogError("Error: Unable to find a spawn point with the grouping index " + grouping); return false; } // Optionally try to spawn in the first spawn point. SpawnPoint firstSpawnPoint; if (m_FirstSpawnPreferred && m_FirstSpawnPoint.TryGetValue(grouping, out firstSpawnPoint)) { if (firstSpawnPoint.GetPlacement(spawningObject, ref position, ref rotation)) { return true; } } // Choose a random spawn point and get the spawn placement. ShuffleSpawnPoints(spawnPoints); var attempt = 0; while (attempt < spawnPoints.Count) { if (spawnPoints[attempt].GetPlacement(spawningObject, ref position, ref rotation)) { return true; } attempt++; } return false; } /// /// Shuffles the spawn points list. /// /// The list that should be shuffled. private void ShuffleSpawnPoints(List spawnPoints) { var n = spawnPoints.Count - 1; while (n > 0) { var k = Random.Range(0, n); var temp = spawnPoints[n]; spawnPoints[n] = spawnPoints[k]; spawnPoints[k] = temp; n--; } } /// /// Returns the list of spawn points that belong to the specified grouping. /// /// The grouping of spawn points that should be retrieved. /// The list of spawn points that belong to the specifeid grouping. public static List GetSpawnPoints(int grouping) { return Instance.GetSpawnPointsInternal(grouping); } /// /// Internal method which returns the list of spawn points that belong to the specified grouping. /// /// The grouping of spawn points that should be retrieved. /// The list of spawn points that belong to the specifeid grouping. private List GetSpawnPointsInternal(int grouping) { List spawnPoints; if (!m_SpawnPointGroupings.TryGetValue(grouping, out spawnPoints)) { Debug.LogError("Error: Unable to find a spawn point with the grouping index " + grouping); } return spawnPoints; } /// /// Removes the specified spawn point from the manager. /// /// The spawn point to remove. public static void RemoveSpawnPoint(SpawnPoint spawnPoint) { Instance.RemoveSpawnPointInternal(spawnPoint); } /// /// Internal method which removes the specified spawn point from the manager. /// /// The spawn point to remove. private void RemoveSpawnPointInternal(SpawnPoint spawnPoint) { List spawnPoints; if (m_SpawnPointGroupings.TryGetValue(spawnPoint.Grouping, out spawnPoints)) { spawnPoints.Remove(spawnPoint); SpawnPoint firstSpawnPoint; if (m_FirstSpawnPoint.TryGetValue(spawnPoint.Grouping, out firstSpawnPoint)) { // Update the first spawn point with the new first spawn point element. if (spawnPoints.Count > 0) { m_FirstSpawnPoint[spawnPoint.Grouping] = spawnPoints[0]; } else { m_FirstSpawnPoint.Remove(spawnPoint.Grouping); } } } } /// /// The object has been disabled. /// private void OnDisable() { SceneManager.sceneUnloaded += SceneUnloaded; } /// /// Reset the initialized variable when the scene is no longer loaded. /// /// The scene that was unloaded. private void SceneUnloaded(Scene scene) { s_Initialized = false; s_Instance = null; SceneManager.sceneUnloaded -= SceneUnloaded; } #if UNITY_2019_3_OR_NEWER /// /// Reset the static variables for domain reloading. /// [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void DomainReset() { s_Initialized = false; s_Instance = null; } #endif } }