/// --------------------------------------------- /// Ultimate Character Controller /// Copyright (c) Opsive. All Rights Reserved. /// https://www.opsive.com /// --------------------------------------------- namespace Opsive.UltimateCharacterController.Demo { using Opsive.Shared.Events; using Opsive.UltimateCharacterController.Character; using Opsive.UltimateCharacterController.Demo.Objects; using Opsive.UltimateCharacterController.Demo.UI; using Opsive.UltimateCharacterController.Game; using Opsive.UltimateCharacterController.Inventory; using Opsive.UltimateCharacterController.Traits; using Opsive.UltimateCharacterController.Utility; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; /// /// The DemoManager will control the objects in the demo scene as well as the text shown. /// public class DemoManager : MonoBehaviour { /// /// Container for each zone within the demo scene. /// [System.Serializable] public class DemoZone { [Tooltip("The header text.")] [SerializeField] protected string m_Header; [Tooltip("The description text.")] [SerializeField] protected string m_Description; [Tooltip("The text that appears beneath the header requiring action.")] [SerializeField] protected string m_Action; [Tooltip("The trigger that enables the header and description text.")] [SerializeField] protected DemoZoneTrigger m_DemoZoneTrigger; [Tooltip("The objects that the trigger should enable.")] [SerializeField] protected MonoBehaviour[] m_EnableObjects; [Tooltip("The objects that the trigger should activate/deactivate.")] [SerializeField] protected GameObject[] m_ToggleObjects; public string Header { get { return m_Header; } } public string Description { get { return m_Description; } } public string Action { get { return m_Action; } } public DemoZoneTrigger DemoZoneTrigger { get { return m_DemoZoneTrigger; } } public MonoBehaviour[] EnableObjects { get { return m_EnableObjects; } } public GameObject[] ToggleObjects { get { return m_ToggleObjects; } } private int m_Index; public int Index { get { return m_Index; } } /// /// Initializes the zone. /// /// The index of the DemoZone. public void Initialize(int index) { m_Index = index; // Assign the spawn point so the character will know where to spawn upon death. var spawnPoints = m_DemoZoneTrigger.GetComponentsInChildren(); for (int i = 0; i < spawnPoints.Length; ++i) { spawnPoints[i].Grouping = index; } // The toggled objects should start disabled. for (int i = 0; i < m_ToggleObjects.Length; ++i) { m_ToggleObjects[i].SetActive(false); } } } [Tooltip("A reference to the character.")] [SerializeField] protected GameObject m_Character; [Tooltip("Is the character allowed to free roam the scene at the very start?")] [SerializeField] protected bool m_FreeRoam; [Tooltip("A reference used to determine the character's perspective selection at the start.")] [SerializeField] protected GameObject m_PerspectiveSelection; [Tooltip("A reference to the panel which shows the demo text.")] [SerializeField] protected GameObject m_TextPanel; [Tooltip("A reference to the Text component which shows the demo header text.")] [SerializeField] protected Text m_Header; [Tooltip("A reference to the Text component which shows the demo description text.")] [SerializeField] protected Text m_Description; [Tooltip("A reference to the Text component which shows the demo action text.")] [SerializeField] protected Text m_Action; [Tooltip("A reference to the GameObject which shows the next zone arrow.")] [SerializeField] protected GameObject m_NextZoneArrow; [Tooltip("A reference to the GameObject which shows the previous zone arrow.")] [SerializeField] protected GameObject m_PreviousZoneArrow; [Tooltip("A list of all of the zones within the scene.")] [SerializeField] protected DemoZone[] m_DemoZones; [Tooltip("Should the ItemIdentifiers be picked up when the character spawns within free roam mode?")] [SerializeField] protected bool m_FreeRoamPickupItemDefinitions = true; [Tooltip("An array of ItemIdentifiers to be picked up when free roaming.")] [UnityEngine.Serialization.FormerlySerializedAs("m_FreeRoamItemTypeCounts")] [UnityEngine.Serialization.FormerlySerializedAs("m_FreeRoamItemIdentifierAmounts")] [SerializeField] protected ItemDefinitionAmount[] m_FreeRoamItemDefinitionAmounts; [Tooltip("The title that should be displayed when the character is not in a zone.")] [SerializeField] protected string m_NoZoneTitle; [Tooltip("The description that should be displayed when the character is not in a zone.")] [SerializeField] protected string m_NoZoneDescription; [Tooltip("Is this manager part of an add-on?")] [UnityEngine.Serialization.FormerlySerializedAs("m_AddonDemoManager")] [SerializeField] protected bool m_AddOnDemoManager; #if FIRST_PERSON_CONTROLLER && THIRD_PERSON_CONTROLLER [Tooltip("Specifies the perspective that the character should start in if there is no perspective selection GameObject.")] [SerializeField] protected bool m_DefaultFirstPersonStart = true; #endif public GameObject Character { get { return m_Character; } } public bool FreeRoam { get { return m_FreeRoam; } set { m_FreeRoam = value; } } public GameObject PerspectiveSelection { get { return m_PerspectiveSelection; } set { m_PerspectiveSelection = value; } } public DemoZone[] DemoZones { get { return m_DemoZones; } } private UltimateCharacterLocomotion m_CharacterLocomotion; private Health m_CharacterHealth; private Respawner m_CharacterRespawner; private Dictionary m_DemoZoneTriggerDemoZoneMap = new Dictionary(); private List m_ActiveZoneIndices = new List(); private int m_LastZoneIndex = -1; private List m_Doors = new List(); private int m_EnterFrame; private bool m_FullAccess; /// /// Initialize the default values. /// protected virtual void Awake() { #if !FIRST_PERSON_CONTROLLER || !THIRD_PERSON_CONTROLLER var demoZones = new List(m_DemoZones); for (int i = demoZones.Count - 1; i > -1; --i) { // The demo zone may belong to the other perspective. if (demoZones[i].DemoZoneTrigger == null) { demoZones.RemoveAt(i); } } m_DemoZones = demoZones.ToArray(); #endif for (int i = 0; i < m_DemoZones.Length; ++i) { if (m_DemoZones[i].DemoZoneTrigger == null) { continue; } m_DemoZones[i].Initialize(i); m_DemoZoneTriggerDemoZoneMap.Add(m_DemoZones[i].DemoZoneTrigger, m_DemoZones[i]); } // Enable the UI after the character has spawned. if (m_TextPanel != null) { m_TextPanel.SetActive(false); } if (m_PreviousZoneArrow != null) { m_PreviousZoneArrow.SetActive(false); } if (m_NextZoneArrow != null) { m_NextZoneArrow.SetActive(false); } if (m_Action != null) { m_Action.enabled = false; } // The controller updates within Update. Limit the update rate. Application.targetFrameRate = 60; } /// /// Initializes the character. /// protected virtual void Start() { InitializeCharacter(m_Character, true, true); } /// /// Initializes the Demo Manager with the specified character. /// /// The character that should be initialized/ /// Should the starting perspective be selected? /// Should the character be teleported to the first demo zone? protected void InitializeCharacter(GameObject character, bool selectStartingPerspective, bool teleport) { m_Character = character; if (m_Character == null) { return; } m_CharacterLocomotion = m_Character.GetComponent(); m_CharacterHealth = m_Character.GetComponent(); m_CharacterRespawner = m_Character.GetComponent(); // Disable the demo components if the character is null. This allows for free roaming within the demo scene. if (m_FreeRoam) { m_FullAccess = true; if (m_PerspectiveSelection != null) { m_PerspectiveSelection.SetActive(false); } var uiZones = GetComponentsInChildren(); for (int i = 0; i < uiZones.Length; ++i) { uiZones[i].enabled = false; } // All of the doors should be opened with free roam. for (int i = 0; i < m_Doors.Count; ++i) { m_Doors[i].CloseOnTriggerExit = false; m_Doors[i].OpenClose(true, true, false); } // The enable objects should be enabled. for (int i = 0; i < m_DemoZones.Length; ++i) { for (int j = 0; j < m_DemoZones[i].EnableObjects.Length; ++j) { m_DemoZones[i].EnableObjects[j].enabled = true; } } // The character needs to be assigned to the camera. var camera = UnityEngineUtility.FindCamera(null); var cameraController = camera.GetComponent(); cameraController.SetPerspective(m_CharacterLocomotion.FirstPersonPerspective, true); cameraController.Character = m_Character; // The character doesn't start out with any items. if (m_FreeRoamItemDefinitionAmounts != null && m_FreeRoamPickupItemDefinitions) { var inventory = m_Character.GetComponent(); if (inventory != null) { for (int i = 0; i < m_FreeRoamItemDefinitionAmounts.Length; ++i) { if (m_FreeRoamItemDefinitionAmounts[i].ItemDefinition == null) { continue; } inventory.Pickup(m_FreeRoamItemDefinitionAmounts[i].ItemIdentifier, m_FreeRoamItemDefinitionAmounts[i].Amount, -1, true, false); } } } if (m_Character.activeInHierarchy) { EventHandler.ExecuteEvent(m_Character, "OnCharacterSnapAnimator"); } enabled = false; return; } // The cursor needs to be visible. Cursor.lockState = CursorLockMode.None; Cursor.visible = true; if (!selectStartingPerspective) { return; } #if FIRST_PERSON_CONTROLLER && THIRD_PERSON_CONTROLLER // Show the perspective selection menu. if (m_PerspectiveSelection != null) { // The character should be disabled until the perspective is set. m_CharacterLocomotion.SetActive(false, true); m_PerspectiveSelection.SetActive(true); } else { SelectStartingPerspective(m_DefaultFirstPersonStart, teleport); } #elif FIRST_PERSON_CONTROLLER SelectStartingPerspective(true, teleport); #else SelectStartingPerspective(false, teleport); #endif } /// /// Keep the mouse visible when the perspective screen is active. /// private void Update() { Cursor.lockState = CursorLockMode.None; Cursor.visible = true; } /// /// Registers the door with the DemoManager. /// /// The door that should be registered. public void RegisterDoor(Door door) { m_Doors.Add(door); } /// /// The character has entered a trigger zone. /// /// The trigger zone that the character entered. /// The GameObject that entered the trigger. public void EnteredTriggerZone(DemoZoneTrigger demoZoneTrigger, GameObject other) { var characterLocomotion = other.GetComponentInParent(); if (characterLocomotion == null || characterLocomotion.gameObject != m_Character) { return; } DemoZone demoZone; if (!m_DemoZoneTriggerDemoZoneMap.TryGetValue(demoZoneTrigger, out demoZone)) { return; } if (m_CharacterHealth != null && m_CharacterHealth.Value == 0) { return; } ActivateDemoZone(demoZone, false); } /// /// Activates the specified demo zone. /// /// The demo zone to active. /// Should the character be teleported to the demo zone? private void ActivateDemoZone(DemoZone demoZone, bool teleport) { if (m_ActiveZoneIndices.Count == 0 || m_ActiveZoneIndices[m_ActiveZoneIndices.Count - 1] != demoZone.Index) { m_ActiveZoneIndices.Add(demoZone.Index); } m_LastZoneIndex = demoZone.Index; ShowText(demoZone.Header, demoZone.Description, demoZone.Action); if (m_PreviousZoneArrow != null) { m_PreviousZoneArrow.SetActive(demoZone.Index != 0); } if (m_NextZoneArrow != null) { m_NextZoneArrow.SetActive(demoZone.Index != m_DemoZones.Length - 1); } m_EnterFrame = Time.frameCount; for (int i = 0; i < demoZone.EnableObjects.Length; ++i) { demoZone.EnableObjects[i].enabled = true; } for (int i = 0; i < demoZone.ToggleObjects.Length; ++i) { demoZone.ToggleObjects[i].SetActive(true); } // When the character reaches the outside section all doors should be unlocked. if (!m_AddOnDemoManager && !m_FullAccess && demoZone.Index >= m_DemoZones.Length - 6) { for (int i = 0; i < m_Doors.Count; ++i) { m_Doors[i].CloseOnTriggerExit = false; m_Doors[i].OpenClose(true, true, false); } m_FullAccess = true; } if (teleport) { var position = Vector3.zero; var rotation = Quaternion.identity; SpawnPointManager.GetPlacement(m_Character, demoZone.Index, ref position, ref rotation); m_CharacterLocomotion.SetPositionAndRotation(position, rotation, true); } // Set the group after the state so the default state doesn't override the grouping value. m_CharacterRespawner.Grouping = demoZone.Index; } /// /// The character has exited a trigger zone. /// /// The trigger zone that the character exited. public void ExitedTriggerZone(DemoZoneTrigger demoZoneTrigger) { DemoZone demoZone; if (!m_DemoZoneTriggerDemoZoneMap.TryGetValue(demoZoneTrigger, out demoZone)) { return; } for (int i = 0; i < demoZone.ToggleObjects.Length; ++i) { demoZone.ToggleObjects[i].SetActive(false); } m_ActiveZoneIndices.Remove(demoZone.Index); // Show standard text if the demo zone isn't the last demo zone. if (m_ActiveZoneIndices.Count == 0 && (m_AddOnDemoManager || demoZone.Index != m_DemoZones.Length - 1) && m_EnterFrame != Time.frameCount) { ShowText(m_NoZoneTitle.Replace("{AssetName}", AssetInfo.Name), m_NoZoneDescription, string.Empty); } else if (m_ActiveZoneIndices.Count > 0 && m_LastZoneIndex != m_ActiveZoneIndices[m_ActiveZoneIndices.Count - 1]) { ActivateDemoZone(m_DemoZones[m_ActiveZoneIndices[m_ActiveZoneIndices.Count - 1]], false); } } /// /// Teleports the character to the next or pervious zone. /// /// Should the character be teleported to the next zone? If false the previous zone will be used. public void Teleport(bool next) { var targetIndex = Mathf.Clamp(m_LastZoneIndex + (next ? 1 : -1), 0, m_DemoZones.Length - 1); if (m_ActiveZoneIndices.Count > 0 && targetIndex == m_ActiveZoneIndices[m_ActiveZoneIndices.Count - 1]) { return; } ActivateDemoZone(m_DemoZones[targetIndex], true); } /// /// Sets the starting perspective on the character. /// /// Should the character start in a first person perspective? public virtual void SelectStartingPerspective(bool firstPersonPerspective) { SelectStartingPerspective(firstPersonPerspective, true); } /// /// Sets the starting perspective on the character. /// /// Should the character start in a first person perspective? /// Should the character be teleported to the demo zone? protected void SelectStartingPerspective(bool firstPersonPerspective, bool teleport) { // Set the starting position. m_LastZoneIndex = -1; ActivateDemoZone(m_DemoZones[0], teleport); // The character should be activated after positioned so the fall surface impacts don't play. m_CharacterLocomotion.SetActive(true, true); // Set the perspective on the camera. var camera = UnityEngineUtility.FindCamera(null); var cameraController = camera.GetComponent(); // Ensure the camera starts with the correct view type. cameraController.FirstPersonViewTypeFullName = GetViewTypeFullName(true); cameraController.ThirdPersonViewTypeFullName = GetViewTypeFullName(false); cameraController.SetPerspective(firstPersonPerspective, true); cameraController.Character = m_Character; // The cursor should be hidden to start the demo. Cursor.lockState = CursorLockMode.Locked; Cursor.visible = false; enabled = false; // The character and camera are ready to go - disable the perspective selection panel. if (m_PerspectiveSelection != null) { m_PerspectiveSelection.SetActive(false); } } /// /// Returns the full name of the view type for the specified perspective. /// /// Should the first person perspective be returned? /// The full name of the view type for the specified perspective. protected virtual string GetViewTypeFullName(bool firstPersonPerspective) { return firstPersonPerspective ? "Opsive.UltimateCharacterController.FirstPersonController.Camera.ViewTypes.Combat" : "Opsive.UltimateCharacterController.ThirdPersonController.Camera.ViewTypes.Adventure"; } /// /// Shows the text in the UI with the specified header and description. /// /// The header that should be shown. /// The description that should be shown. /// The action that should be shown. private void ShowText(string header, string description, string action) { if (m_TextPanel == null) { return; } if (string.IsNullOrEmpty(header)) { m_TextPanel.SetActive(false); return; } m_TextPanel.SetActive(true); m_Header.text = "--- " + header + " ---"; m_Description.text = description.Replace("{AssetName}", AssetInfo.Name); if (m_Action != null) { m_Action.text = action; m_Action.enabled = !string.IsNullOrEmpty(action); } } } }