/// --------------------------------------------- /// Ultimate Character Controller /// Copyright (c) Opsive. All Rights Reserved. /// https://www.opsive.com /// --------------------------------------------- namespace Opsive.UltimateCharacterController.ThirdPersonController.Character { using Opsive.Shared.Events; using Opsive.Shared.Game; using Opsive.UltimateCharacterController.Character; using Opsive.UltimateCharacterController.Character.Identifiers; using Opsive.UltimateCharacterController.Items; using Opsive.UltimateCharacterController.StateSystem; using System.Collections.Generic; using UnityEngine; /// /// Manages the objects that are changed between a first and third person perspective. /// public class PerspectiveMonitor : StateBehavior { /// /// Specifies which objects should be visible when the character dies in a first person view. /// public enum ObjectDeathVisiblity { AllInvisible, // The entire rig will be invisible upon death. ThirdPersonObjectDetermined, // Only the objects marked with the ThirdPersonObject and ThirdPersonObject.InvisibleOnDeath set to true will be invisible upon death. AllVisible // No objects will be invisible upon death. } [Tooltip("The material used to make the object invisible but still cast shadows.")] [SerializeField] protected Material m_InvisibleMaterial; [Tooltip("Specifies which objects should be visible when the character dies in a first person view.")] [SerializeField] protected ObjectDeathVisiblity m_DeathVisibility = ObjectDeathVisiblity.AllVisible; public Material InvisibleMaterial { get { return m_InvisibleMaterial; } set { m_InvisibleMaterial = value; } } public ObjectDeathVisiblity DeathVisiblity { get { return m_DeathVisibility; } set { m_DeathVisibility = value; } } private GameObject m_GameObject; private UltimateCharacterLocomotion m_CharacterLocomotion; private Inventory.InventoryBase m_Inventory; private bool m_FirstPersonPerspective; private List m_Renderers = new List(); private List m_RendererThirdPersonObjects = new List(); private List m_OriginalMaterials = new List(); private List m_InvisibleMaterials = new List(); private HashSet m_RegisteredRenderers = new HashSet(); private List m_ThirdPersonRenderers = new List(); /// /// Registeres for any interested events. /// protected override void Awake() { base.Awake(); m_GameObject = gameObject; m_CharacterLocomotion = m_GameObject.GetCachedComponent(); // The third person objects will be hidden with the invisible shadow caster while in first person view. var characterRenderers = m_GameObject.GetComponentsInChildren(true); if (characterRenderers != null) { for (int i = 0; i < characterRenderers.Length; ++i) { var renderer = characterRenderers[i]; if (m_RegisteredRenderers.Contains(renderer)) { continue; } CacheRendererMaterials(renderer); } } EventHandler.RegisterEvent(m_GameObject, "OnCameraChangePerspectives", OnChangePerspectives); EventHandler.RegisterEvent(m_GameObject, "OnInventoryAddItem", OnAddItem); EventHandler.RegisterEvent(m_GameObject, "OnDeath", OnDeath); EventHandler.RegisterEvent(m_GameObject, "OnWillRespawn", OnWillRespawn); } /// /// Initialize the default values. /// private void Start() { m_Inventory = m_GameObject.GetCachedComponent(); m_FirstPersonPerspective = m_CharacterLocomotion.FirstPersonPerspective; #if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER var networkInfo = m_GameObject.GetCachedComponent(); if (networkInfo != null && !networkInfo.IsLocalPlayer()) { // Remote players should always be in the third person view. OnChangePerspectives(false); EventHandler.UnregisterEvent(m_GameObject, "OnCameraChangePerspectives", OnChangePerspectives); } #endif } /// /// Caches the materials attached to the renderer so they can be switched with the invisible material. /// /// The renderer to cache. private void CacheRendererMaterials(Renderer renderer) { var thirdPersonobject = renderer.gameObject.GetCachedInactiveComponentInParent(); if (thirdPersonobject != null) { m_ThirdPersonRenderers.Add(m_Renderers.Count); } m_Renderers.Add(renderer); m_RendererThirdPersonObjects.Add(thirdPersonobject); m_RegisteredRenderers.Add(renderer); m_OriginalMaterials.Add(renderer.materials); var invisibleMaterials = new Material[renderer.materials.Length]; for (int i = 0; i < renderer.materials.Length; ++i) { invisibleMaterials[i] = m_InvisibleMaterial; } m_InvisibleMaterials.Add(invisibleMaterials); } /// /// The camera perspective between first and third person has changed. /// /// Is the camera in a first person perspective? private void OnChangePerspectives(bool firstPersonPerspective) { m_FirstPersonPerspective = firstPersonPerspective; if (!m_GameObject.activeSelf) { return; } UpdateThirdPersonMaterials(false); // The FirstPersonObjects GameObject must be changed first to prevent activation errors/warnings. After the GameObject has been changed the // character components can safely receive the event. EventHandler.ExecuteEvent(m_GameObject, "OnCharacterChangePerspectives", firstPersonPerspective); } /// /// Updates the materials of the third person objects. /// public void UpdateThirdPersonMaterials(bool forceThirdPerson) { for (int i = 0; i < m_ThirdPersonRenderers.Count; ++i) { var thirdPersonIndex = m_ThirdPersonRenderers[i]; m_Renderers[thirdPersonIndex].materials = ((m_FirstPersonPerspective && !m_RendererThirdPersonObjects[thirdPersonIndex].ForceVisible && !forceThirdPerson) ? m_InvisibleMaterials[thirdPersonIndex] : m_OriginalMaterials[thirdPersonIndex]); } } /// /// The inventory has added the specified item. /// /// The item that was added. private void OnAddItem(Item item) { // The Third Person's PerspectiveItem object will contain a reference to the ThirdPersonObject component. var perspectiveItems = item.GetComponents(); PerspectiveItem thirdPersonPerspectiveItem = null; for (int i = 0; i < perspectiveItems.Length; ++i) { if (!perspectiveItems[i].FirstPersonItem) { thirdPersonPerspectiveItem = perspectiveItems[i]; break; } } if (thirdPersonPerspectiveItem != null && thirdPersonPerspectiveItem.Object != null) { var thirdPersonObject = thirdPersonPerspectiveItem.Object.GetComponent(); // If the third person object exists then it should be added to the materials list. if (thirdPersonObject != null) { var renderers = thirdPersonObject.GetComponentsInChildren(true); for (int i = 0; i < renderers.Length; ++i) { if (!m_RegisteredRenderers.Contains(renderers[i])) { CacheRendererMaterials(renderers[i]); // If the first person perspective is active then any third person item materials should use the invisible material. if (m_CharacterLocomotion.FirstPersonPerspective) { renderers[i].materials = m_InvisibleMaterials[m_InvisibleMaterials.Count - 1]; } } } } } } /// /// The character has died. /// /// The position of the force. /// The amount of force which killed the character. /// The GameObject that killed the character. private void OnDeath(Vector3 position, Vector3 force, GameObject attacker) { TryUpdateDeathMaterials(true); } /// /// Tries to switch to the materials used when the character died. /// /// Is the method being called from the OnDeath event? /// True if the materials were updated. private bool TryUpdateDeathMaterials(bool fromDeathEvent) { // Ensure no first person weapons are equipped before enabling the third person objects. if (m_CharacterLocomotion.FirstPersonPerspective) { for (int i = 0; i < m_Inventory.SlotCount; ++i) { if (m_Inventory.GetActiveItem(i) != null) { // If an item is still equipped then the material shouldn't be switched until after it is no longer equipped. if (fromDeathEvent) { EventHandler.RegisterEvent(m_GameObject, "OnInventoryUnequipItem", OnUnequipItem); } return false; } } } // All items are unequipped. Update the renderers. for (int i = 0; i < m_Renderers.Count; ++i) { var invisibleObject = false; if (m_DeathVisibility == ObjectDeathVisiblity.AllInvisible) { invisibleObject = m_CharacterLocomotion.FirstPersonPerspective; } else if (m_DeathVisibility == ObjectDeathVisiblity.ThirdPersonObjectDetermined) { var thirdPersonObject = m_RendererThirdPersonObjects[i]; invisibleObject = m_CharacterLocomotion.FirstPersonPerspective && (thirdPersonObject != null && !thirdPersonObject.FirstPersonVisibleOnDeath && !thirdPersonObject.ForceVisible); } m_Renderers[i].materials = (invisibleObject ? m_InvisibleMaterials[i] : m_OriginalMaterials[i]); } return true; } /// /// The specified item has been unequipped. /// /// The item that was unequipped. /// private void OnUnequipItem(Item item, int slotID) { if (TryUpdateDeathMaterials(false)) { EventHandler.UnregisterEvent(m_GameObject, "OnInventoryUnequipItem", OnUnequipItem); } } /// /// The character will respawn. This should be performed before the MaterialSwapper's Respawn method is called. /// private void OnWillRespawn() { EventHandler.UnregisterEvent(m_GameObject, "OnInventoryUnequipItem", OnUnequipItem); for (int i = 0; i < m_Renderers.Count; ++i) { m_Renderers[i].materials = (!m_CharacterLocomotion.FirstPersonPerspective || (m_RendererThirdPersonObjects[i] == null)) ? m_OriginalMaterials[i] : m_InvisibleMaterials[i]; } } /// /// The GameObject has been destroyed. /// private void OnDestroy() { EventHandler.UnregisterEvent(m_GameObject, "OnCameraChangePerspectives", OnChangePerspectives); EventHandler.UnregisterEvent(m_GameObject, "OnInventoryAddItem", OnAddItem); EventHandler.UnregisterEvent(m_GameObject, "OnDeath", OnDeath); EventHandler.UnregisterEvent(m_GameObject, "OnWillRespawn", OnWillRespawn); } } }