/// --------------------------------------------- /// Ultimate Character Controller /// Copyright (c) Opsive. All Rights Reserved. /// https://www.opsive.com /// --------------------------------------------- namespace Opsive.UltimateCharacterController.FirstPersonController.Camera { using Opsive.Shared.Events; using Opsive.Shared.Game; using Opsive.UltimateCharacterController.FirstPersonController.Character.Identifiers; using Opsive.UltimateCharacterController.FirstPersonController.Items; using Opsive.UltimateCharacterController.Camera; using Opsive.UltimateCharacterController.Character; using Opsive.UltimateCharacterController.Character.Identifiers; using Opsive.UltimateCharacterController.Items; using System.Collections.Generic; using UnityEngine; /// /// Swaps the first and third person materials so multiple cameras can render the scene within the same instance. /// public class MaterialSwapper : MonoBehaviour { /// /// Specifies which renderers should be swapped. /// private enum SwapMask { FirstPerson = 1, // The first person perspective renderers should be swapped. ThirdPerson = 2, // The third person perspective renderers should be swapped. } [Tooltip("The material used to make the object invisible but still cast shadows.")] [SerializeField] protected Material m_InvisibleMaterial; private GameObject m_GameObject; private Camera m_Camera; private CameraController m_CameraController; private UltimateCharacterLocomotion m_CharacterLocomotion; private SwapMask m_SwapMask = 0; private HashSet m_AddedFirstPersonRenderers; private List m_FirstPersonRenderers; private List m_FirstPersonOriginalMaterials; private List m_FirstPersonInvisibleMaterials; private HashSet m_FirstPersonBaseObjects; private HashSet m_AddedThirdPersonRenderers; private List m_ThirdPersonRenderers; private List m_ThirdPersonOriginalMaterials; private List m_ThirdPersonInvisibleMaterials; #if THIRD_PERSON_CONTROLLER private ThirdPersonController.Camera.ObjectFader m_ObjectFader; #endif /// /// Initializes the default values. /// private void Awake() { m_GameObject = gameObject; m_CameraController = m_GameObject.GetCachedComponent(); if (m_CameraController == null) { // If the camera controller is null then the component has been added to the first person child camera. m_CameraController = gameObject.GetCachedParentComponent(); m_SwapMask = SwapMask.FirstPerson; } else { var viewTypes = m_CameraController.ViewTypes; // The component exists on the main camera GameObject. If there is no child first person camera then the single component is responsible for // swapping both first and third person renderer materials. for (int i = 0; i < viewTypes.Length; ++i) { if (viewTypes[i] is ViewTypes.FirstPerson) { var firstPersonViewType = viewTypes[i] as ViewTypes.FirstPerson; #if ULTIMATE_CHARACTER_CONTROLLER_LWRP || ULTIMATE_CHARACTER_CONTROLLER_UNIVERSALRP m_SwapMask = firstPersonViewType.OverlayRenderType == ViewTypes.FirstPerson.ObjectOverlayRenderType.SecondCamera ? SwapMask.ThirdPerson : (SwapMask.FirstPerson | SwapMask.ThirdPerson); #else m_SwapMask = firstPersonViewType.UseFirstPersonCamera ? SwapMask.ThirdPerson : (SwapMask.FirstPerson | SwapMask.ThirdPerson); #endif #if UNITY_EDITOR if ((m_SwapMask & SwapMask.ThirdPerson) != 0) { // Ensure the child camera has the Material Swapper component. if (firstPersonViewType.FirstPersonCamera != null) { var firstPersonMaterialSwapper = firstPersonViewType.FirstPersonCamera.GetComponent(); if (firstPersonMaterialSwapper == null) { Debug.LogWarning("Warning: The First Person Camera should have the Material Swapper component added to the GameObject."); } } } #endif break; } } #if THIRD_PERSON_CONTROLLER m_ObjectFader = m_CameraController.GetComponent(); #endif } m_Camera = m_CameraController.GetComponent(); // Instantiate the storage objects based on the swap mode. if ((m_SwapMask & SwapMask.FirstPerson) != 0) { m_AddedFirstPersonRenderers = new HashSet(); m_FirstPersonRenderers = new List(); m_FirstPersonOriginalMaterials = new List(); m_FirstPersonInvisibleMaterials = new List(); m_FirstPersonBaseObjects = new HashSet(); } if ((m_SwapMask & SwapMask.ThirdPerson) != 0) { m_AddedThirdPersonRenderers = new HashSet(); m_ThirdPersonRenderers = new List(); m_ThirdPersonOriginalMaterials = new List(); m_ThirdPersonInvisibleMaterials = new List(); } enabled = false; EventHandler.RegisterEvent(m_CameraController.gameObject, "OnCameraAttachCharacter", OnAttachCharacter); } #if UNIVERSAL_RENDER_PIPELINE /// /// The component has been enabled. /// private void OnEnable() { UnityEngine.Rendering.RenderPipelineManager.beginCameraRendering += BeginCameraRendering; UnityEngine.Rendering.RenderPipelineManager.endCameraRendering += EndCameraRendering; } #endif /// /// Attaches the component to the specified character. /// /// The handler to attach the camera to. protected virtual void OnAttachCharacter(GameObject character) { // Don't do anything if the character is the same. if (m_CharacterLocomotion != null && character == m_CharacterLocomotion.gameObject) { return; } if (m_CharacterLocomotion != null) { // The character is being changed. if (m_FirstPersonRenderers != null) { m_AddedFirstPersonRenderers.Clear(); m_FirstPersonRenderers.Clear(); m_FirstPersonOriginalMaterials.Clear(); m_FirstPersonInvisibleMaterials.Clear(); m_FirstPersonBaseObjects.Clear(); } if (m_ThirdPersonRenderers != null) { m_AddedThirdPersonRenderers.Clear(); m_ThirdPersonRenderers.Clear(); m_ThirdPersonOriginalMaterials.Clear(); m_ThirdPersonInvisibleMaterials.Clear(); } EventHandler.UnregisterEvent(m_CharacterLocomotion.gameObject, "OnCameraChangePerspectives", OnChangePerspectives); EventHandler.UnregisterEvent(m_CharacterLocomotion.gameObject, "OnInventoryAddItem", OnAddItem); EventHandler.UnregisterEvent(m_CharacterLocomotion.gameObject, "OnRespawn", OnRespawn); EventHandler.ExecuteEvent(m_CharacterLocomotion.gameObject, "OnCharacterIndependentFade", false, false); m_CharacterLocomotion = null; } enabled = character != null; if (character == null) { return; } m_CharacterLocomotion = character.GetCachedComponent(); #if THIRD_PERSON_CONTROLLER // Force the character into using third person materials so the correct materials can be cached. var perspectiveMonitor = character.GetCachedComponent(); perspectiveMonitor.UpdateThirdPersonMaterials(true); #endif if ((m_SwapMask & SwapMask.FirstPerson) != 0) { var firstPersonBaseObjects = character.GetComponentsInChildren(true); for (int i = 0; i < firstPersonBaseObjects.Length; ++i) { CacheFirstPersonRenderers(firstPersonBaseObjects[i].gameObject); // Remember the base objects so they are not added again if a runtime item is picked up. m_FirstPersonBaseObjects.Add(firstPersonBaseObjects[i].gameObject); } } if ((m_SwapMask & SwapMask.ThirdPerson) != 0) { var thirdPersonObjects = character.GetComponentsInChildren(true); for (int i = 0; i < thirdPersonObjects.Length; ++i) { CacheThirdPersonRenderers(thirdPersonObjects[i].gameObject); } } #if THIRD_PERSON_CONTROLLER perspectiveMonitor.UpdateThirdPersonMaterials(false); #endif EventHandler.RegisterEvent(m_CharacterLocomotion.gameObject, "OnCameraChangePerspectives", OnChangePerspectives); EventHandler.RegisterEvent(m_CharacterLocomotion.gameObject, "OnInventoryAddItem", OnAddItem); EventHandler.RegisterEvent(m_CharacterLocomotion.gameObject, "OnRespawn", OnRespawn); EventHandler.ExecuteEvent(m_CharacterLocomotion.gameObject, "OnCharacterIndependentFade", true, false); // Assume the first person objects are not rendering. EnableThirdPersonMaterials(); } /// /// The camera perspective between first and third person has changed. /// /// Is the camera in a first person perspective? private void OnChangePerspectives(bool firstPersonPerspective) { enabled = firstPersonPerspective #if THIRD_PERSON_CONTROLLER || m_ObjectFader != null #endif ; if (!enabled) { EnableThirdPersonMaterials(); } } /// /// The inventory has added the specified item. /// /// The item that was added. private void OnAddItem(Item item) { var perspectiveItems = item.GetComponents(); for (int i = 0; i < perspectiveItems.Length; ++i) { if (perspectiveItems[i].FirstPersonItem) { if ((m_SwapMask & SwapMask.FirstPerson) != 0) { GameObject firstPersonObject; if (m_FirstPersonBaseObjects.Contains(perspectiveItems[i].Object)) { firstPersonObject = (perspectiveItems[i] as FirstPersonPerspectiveItem).VisibleItem; } else { firstPersonObject = perspectiveItems[i].Object; m_FirstPersonBaseObjects.Add(firstPersonObject); } CacheFirstPersonRenderers(firstPersonObject); } } else if ((m_SwapMask & SwapMask.ThirdPerson) != 0) { CacheThirdPersonRenderers(perspectiveItems[i].Object); } } } /// /// Caches the renderers on the specified first person object. /// /// The first person object to cache the renderers of. private void CacheFirstPersonRenderers(GameObject firstPersonObject) { if (firstPersonObject == null) { return; } var renderers = firstPersonObject.GetComponentsInChildren(true); var emptyMaterial = new Material[0]; for (int i = 0; i < renderers.Length; ++i) { if (m_AddedFirstPersonRenderers.Contains(renderers[i])) { continue; } m_AddedFirstPersonRenderers.Add(renderers[i]); m_FirstPersonRenderers.Add(renderers[i]); m_FirstPersonOriginalMaterials.Add(renderers[i].materials); m_FirstPersonInvisibleMaterials.Add(emptyMaterial); } } /// /// Caches the renderers on the specified third person object. /// /// The third person object to cache the renderers of. private void CacheThirdPersonRenderers(GameObject thirdPersonObject) { if (thirdPersonObject == null) { return; } var renderers = thirdPersonObject.GetComponentsInChildren(true); for (int i = 0; i < renderers.Length; ++i) { if (m_AddedThirdPersonRenderers.Contains(renderers[i])) { continue; } m_AddedThirdPersonRenderers.Add(renderers[i]); m_ThirdPersonRenderers.Add(renderers[i]); m_ThirdPersonOriginalMaterials.Add(renderers[i].materials); var invisibleMaterials = new Material[renderers[i].materials.Length]; for (int j = 0; j < invisibleMaterials.Length; ++j) { invisibleMaterials[j] = m_InvisibleMaterial; } m_ThirdPersonInvisibleMaterials.Add(invisibleMaterials); } } #if !UNIVERSAL_RENDER_PIPELINE /// /// The camera has started to render. /// private void OnPreRender() { BeginCameraRendering(m_Camera); } /// /// The camera has stopped rendering. /// private void OnPostRender() { EndCameraRendering(m_Camera); } #endif /// /// The camera has started to render. /// /// The context of the SRP. /// The camera that is rendering. #if UNIVERSAL_RENDER_PIPELINE private void BeginCameraRendering(ScriptableRenderContext context, Camera camera) #else private void BeginCameraRendering(Camera camera) #endif { if (camera != m_Camera) { return; } #if THIRD_PERSON_CONTROLLER if (m_ObjectFader != null) { m_ObjectFader.MultiCameraRender(true); } #endif if (!m_CharacterLocomotion.FirstPersonPerspective) { return; } // Swap the first person objects to the original material so the current camera can see the arms mesh. if ((m_SwapMask & SwapMask.FirstPerson) != 0) { for (int i = 0; i < m_FirstPersonRenderers.Count; ++i) { m_FirstPersonRenderers[i].materials = m_FirstPersonOriginalMaterials[i]; } } // The third person objects should be swapped to the invisible materials because the arms material will be rendered. if ((m_SwapMask & SwapMask.ThirdPerson) != 0) { for (int i = 0; i < m_ThirdPersonRenderers.Count; ++i) { m_ThirdPersonRenderers[i].materials = m_ThirdPersonInvisibleMaterials[i]; } } } /// /// The camera has stopped rendering. /// /// The context of the SRP. /// The camera that stopped rendering. #if UNIVERSAL_RENDER_PIPELINE private void EndCameraRendering(ScriptableRenderContext context, Camera camera) #else private void EndCameraRendering(Camera camera) #endif { if (camera != m_Camera) { return; } #if THIRD_PERSON_CONTROLLER if (m_ObjectFader != null) { m_ObjectFader.MultiCameraRender(false); } #endif if (!m_CharacterLocomotion.FirstPersonPerspective) { return; } EnableThirdPersonMaterials(); } /// /// Swaps the materials for the third person mateirals. /// private void EnableThirdPersonMaterials() { // Swap the first person objects back to the invisible material so the separate arms are not seen by other cameras. if ((m_SwapMask & SwapMask.FirstPerson) != 0) { for (int i = 0; i < m_FirstPersonRenderers.Count; ++i) { m_FirstPersonRenderers[i].materials = m_FirstPersonInvisibleMaterials[i]; } } // The third person objects should be swapped back to the original materials so the full mesh is rendered by other cameras. if ((m_SwapMask & SwapMask.ThirdPerson) != 0) { for (int i = 0; i < m_ThirdPersonRenderers.Count; ++i) { m_ThirdPersonRenderers[i].materials = m_ThirdPersonOriginalMaterials[i]; } } } /// /// The character has respawned. /// private void OnRespawn() { OnChangePerspectives(m_CharacterLocomotion.FirstPersonPerspective); } #if UNIVERSAL_RENDER_PIPELINE /// /// The component has been disabled. /// private void OnDisable() { UnityEngine.Rendering.RenderPipelineManager.beginCameraRendering -= BeginCameraRendering; UnityEngine.Rendering.RenderPipelineManager.endCameraRendering -= EndCameraRendering; } #endif /// /// The camera has been destroyed. /// private void OnDestroy() { OnAttachCharacter(null); EventHandler.UnregisterEvent(m_CameraController.gameObject, "OnCameraAttachCharacter", OnAttachCharacter); } } }