/// --------------------------------------------- /// Ultimate Character Controller /// Copyright (c) Opsive. All Rights Reserved. /// https://www.opsive.com /// --------------------------------------------- namespace Opsive.UltimateCharacterController.ThirdPersonController.Camera { using Opsive.Shared.Events; using Opsive.Shared.Game; using Opsive.Shared.Utility; using Opsive.UltimateCharacterController.Camera; using Opsive.UltimateCharacterController.Character; using Opsive.UltimateCharacterController.Character.Identifiers; using Opsive.UltimateCharacterController.Items; using Opsive.UltimateCharacterController.StateSystem; using Opsive.UltimateCharacterController.Utility; using System.Collections.Generic; using UnityEngine; /// /// Can fade the character's materials if the camera gets too close or any materials which are obstructing the view between the camera and the character. /// public class ObjectFader : StateBehavior { [Tooltip("The name of the material property that should be faded.")] [SerializeField] protected string m_ColorPropertyName = "_Color"; [Tooltip("Should the character fade out when the camera is near?")] [SerializeField] protected bool m_CharacterFade = true; [Tooltip("Should the character materials be cached at the start? If false the material values will be saved each time the character starts to fade.")] [SerializeField] protected bool m_CacheCharacterMaterials = true; [Tooltip("The distance between the character and camera that the character materials should start to fade.")] [SerializeField] protected float m_StartFadeDistance = 1.8f; [Tooltip("The distance between the character and camera that the character materials should be completely invisible.")] [SerializeField] protected float m_EndFadeDistance = 1; [Tooltip("Prevents the character change from updating for the specified number of seconds after a state change.")] [SerializeField] protected float m_CharacterFadeStateChangeCooldown = 0.3f; [Tooltip("Should any objects obstructing the camera's view fade out?")] [SerializeField] protected bool m_ObstructingObjectsFade; [Tooltip("The radius of the camera's collision sphere to prevent it from clipping with other objects.")] [SerializeField] protected float m_CollisionRadius = 0.05f; [Tooltip("Specifies the speed at which the obstructing material can fade.")] [SerializeField] protected float m_FadeSpeed = 0.02f; [Tooltip("The color that the obstructed object will fade to.")] [SerializeField] protected Color m_FadeColor = new Color(1, 1, 1, 0); [Tooltip("Should the material mode be set automatically when an object is obstructing the view?")] [SerializeField] protected bool m_AutoSetMode; [Tooltip("Should the obstructing object's collider be disabled when the material is faded?")] [SerializeField] protected bool m_DisableCollider = true; [Tooltip("The maximum number of obstructing colliders that can be faded at one time.")] [SerializeField] protected int m_MaxObstructingColliderCount = 20; [Tooltip("The maximum number of obstricting materials that can be faded at one time. This value should be greater than the collider count.")] [SerializeField] protected int m_MaxObstructingMaterialCount = 30; [Tooltip("The offset to apply to the character when determining if the character should fade/is considered obstructed.")] [SerializeField] protected Vector3 m_TransformOffset = new Vector3(0, 0.9f, 0); public bool CharacterFade { get { return m_CharacterFade; } set { m_CharacterFade = value; if (!value) { DisableCharacterFade(); } } } public float StartFadeDistance { get { return m_StartFadeDistance; } set { m_StartFadeDistance = value; } } public float EndFadeDistance { get { return m_EndFadeDistance; } set { m_EndFadeDistance = value; } } public float CharacterFadeStateChangeCooldown { get { return m_CharacterFadeStateChangeCooldown; } set { m_CharacterFadeStateChangeCooldown = value; } } public bool ObstructingObjectsFade { get { return m_ObstructingObjectsFade; } set { m_ObstructingObjectsFade = value; if (!value) { DisableObstructingFade(); } } } public float CollisionRadius { get { return m_CollisionRadius; } set { m_CollisionRadius = value; } } public float FadeSpeed { get { return m_FadeSpeed; } set { m_FadeSpeed = value; } } public Color FadeColor { get { return m_FadeColor; } set { m_FadeColor = value; } } public bool AutoSetMode { get { return m_AutoSetMode; } set { m_AutoSetMode = value; } } public bool DisableCollider { get { return m_DisableCollider; } set { m_DisableCollider = value; } } public Vector3 TransformOffset { get { return m_TransformOffset; } set { m_TransformOffset = value; } } private Transform m_Transform; private CameraController m_CameraController; private GameObject m_Character; private UltimateCharacterLocomotion m_CharacterLocomotion; private CharacterLayerManager m_CharacterLayerManager; private Transform m_CharacterTransform; private Material[] m_CharacterFadeMaterials; private int m_IndependentCharacterFadeCount; private int m_CharacterFadeMaterialsCount; private HashSet m_RegisteredMaterial; private Vector3 m_FadeOffset; private bool m_CharacterFaded; private Dictionary m_OriginalMaterialValuesMap = new Dictionary(); private RaycastHit[] m_RaycastsHit; private Material[] m_ObstructingMaterials; private int m_ObstructingMaterialsCount; private Collider[] m_ObstructingColliders; private int m_ObstructingCollidersCount; private HashSet m_ObstructionHitSet; private Dictionary m_CanObstructionFade; private HashSet m_MaterialModeSet; private float m_CharacterFadeCooldownElapsedTime; private int m_ColorID; /// /// Initialize the default values. /// protected override void Awake() { // PropertyToID cannot be initialized within a MonoBehaviour constructor. m_ColorID = Shader.PropertyToID(m_ColorPropertyName); base.Awake(); m_Transform = transform; m_CameraController = gameObject.GetCachedComponent(); if (m_CharacterFade) { m_RegisteredMaterial = new HashSet(); m_MaterialModeSet = new HashSet(); } if (m_ObstructingObjectsFade) { m_RaycastsHit = new RaycastHit[m_MaxObstructingColliderCount]; m_ObstructingMaterials = new Material[m_MaxObstructingMaterialCount]; m_ObstructingColliders = new Collider[m_MaxObstructingMaterialCount]; m_ObstructionHitSet = new HashSet(); m_CanObstructionFade = new Dictionary(); } m_CharacterFadeCooldownElapsedTime = -m_CharacterFadeStateChangeCooldown; EventHandler.RegisterEvent(gameObject, "OnCameraAttachCharacter", OnAttachCharacter); // Enable after the character has been attached. enabled = false; } /// /// Attaches the component to the specified character. /// /// The handler to attach the camera to. protected virtual void OnAttachCharacter(GameObject character) { enabled = character != null && !m_CameraController.ActiveViewType.FirstPersonPerspective; // Disable the fade on the previous active character. if (m_CharacterFade) { if (m_Character != null && m_Character != character) { DisableFades(); // Clear the previous mappings. if (m_CharacterFadeMaterials != null) { if (m_CacheCharacterMaterials) { for (int i = 0; i < m_CharacterFadeMaterials.Length; ++i) { GenericObjectPool.Return(m_OriginalMaterialValuesMap[m_CharacterFadeMaterials[i]]); m_OriginalMaterialValuesMap.Remove(m_CharacterFadeMaterials[i]); } } m_CharacterFadeMaterials = null; } m_OriginalMaterialValuesMap.Clear(); EventHandler.UnregisterEvent(m_Character, "OnCameraChangePerspectives", OnChangePerspectives); EventHandler.UnregisterEvent(m_Character, "OnCharacterIndependentFade", OnIndependentFade); EventHandler.UnregisterEvent(m_Character, "OnInventoryAddItem", OnAddItem); EventHandler.UnregisterEvent(m_Character, "OnShootableWeaponShowProjectile", OnShowProjectile); EventHandler.UnregisterEvent(m_Character, "OnRespawn", OnRespawn); } } m_Character = character; if (m_Character != null) { m_CharacterTransform = m_Character.transform; m_CharacterLocomotion = m_Character.GetCachedComponent(); m_CharacterLayerManager = m_Character.GetCachedComponent(); if (m_CharacterFade) { // Determine the number of renderers that will be faded so their materials can be cached. m_RegisteredMaterial.Clear(); var count = 0; var renderers = m_Character.GetComponentsInChildren(true); for (int i = 0; i < renderers.Length; ++i) { // The renderer fade can be ignored. if (renderers[i].gameObject.GetCachedComponent() != null) { continue; } var materials = renderers[i].materials; for (int j = 0; j < materials.Length; ++j) { if (materials[j].HasProperty(m_ColorID)) { count++; } } } if (count > 0) { if (m_CharacterFadeMaterials == null) { m_CharacterFadeMaterials = new Material[count]; } else if (m_CharacterFadeMaterials.Length != count) { if (m_CacheCharacterMaterials) { // The mapping may exist from a previous character. for (int i = 0; i < m_CharacterFadeMaterials.Length; ++i) { GenericObjectPool.Return(m_OriginalMaterialValuesMap[m_CharacterFadeMaterials[i]]); m_OriginalMaterialValuesMap.Remove(m_CharacterFadeMaterials[i]); } } System.Array.Resize(ref m_CharacterFadeMaterials, count); } // Cache a reference to all of the faded materials. m_CharacterFadeMaterialsCount = 0; for (int i = 0; i < renderers.Length; ++i) { // The renderer fade can be ignored. if (renderers[i].gameObject.GetCachedComponent() != null) { continue; } var materials = renderers[i].materials; for (int j = 0; j < materials.Length; ++j) { if (m_RegisteredMaterial.Contains(materials[j])) { continue; } if (materials[j].HasProperty(m_ColorID)) { if (materials[j].HasProperty(OriginalMaterialValue.ModeID)) { m_MaterialModeSet.Add(materials[j]); } m_CharacterFadeMaterials[m_CharacterFadeMaterialsCount] = materials[j]; m_RegisteredMaterial.Add(materials[j]); m_CharacterFadeMaterialsCount++; if (m_CacheCharacterMaterials) { var originalMaterialValues = GenericObjectPool.Get(); originalMaterialValues.Initialize(materials[j], m_ColorID, m_MaterialModeSet.Contains(materials[j])); m_OriginalMaterialValuesMap.Add(materials[j], originalMaterialValues); } } } } } EventHandler.RegisterEvent(m_Character, "OnCameraChangePerspectives", OnChangePerspectives); EventHandler.RegisterEvent(m_Character, "OnInventoryAddItem", OnAddItem); EventHandler.RegisterEvent(m_Character, "OnShootableWeaponShowProjectile", OnShowProjectile); EventHandler.RegisterEvent(m_Character, "OnCharacterIndependentFade", OnIndependentFade); EventHandler.RegisterEvent(m_Character, "OnRespawn", OnRespawn); } // Fade the obstructing objects immediately after the character has been assigned. if (m_ObstructingObjectsFade) { FadeObstructingObjects(true); } } } /// /// Update the fade values after the scene has finished positioning. /// private void Update() { FadeCharacter(); FadeObstructingObjects(false); } /// /// Fade the character to prevent the camera from seeing inside the character if the camera gets too close. /// private void FadeCharacter() { if (!m_CharacterFade || Time.time < m_CharacterFadeCooldownElapsedTime) { return; } m_FadeOffset = MathUtility.InverseTransformPoint(m_CharacterTransform.TransformPoint(m_TransformOffset), m_CharacterTransform.rotation, m_Transform.position); // Other components can control when the materials fade. if (m_IndependentCharacterFadeCount != 0) { return; } if (m_FadeOffset.magnitude <= m_StartFadeDistance) { EnableCharacterFade(m_FadeOffset.magnitude); } else if (m_CharacterFaded) { // The camera is not near the character - no fade necessary. DisableCharacterFade(); } } /// /// The MaterialSwapper component has started or stopped rendering. /// /// Did the MaterialSwapper component start to render? public void MultiCameraRender(bool start) { if (!enabled) { return; } if (start && m_FadeOffset.magnitude <= m_StartFadeDistance) { EnableCharacterFade(m_FadeOffset.magnitude); } else if (!start) { DisableCharacterFade(); } } /// /// Starts to fade the character materials. /// /// The distance from the character and camera. private void EnableCharacterFade(float distance) { if (m_CharacterFadeMaterials != null) { // Slowly fade the character away as the camera gets closer. var amount = Mathf.Clamp01((distance - m_EndFadeDistance) / (m_StartFadeDistance - m_EndFadeDistance)); for (int i = 0; i < m_CharacterFadeMaterialsCount; ++i) { if (!m_CharacterFaded) { EnableFadeMaterial(m_CharacterFadeMaterials[i]); } var color = m_CharacterFadeMaterials[i].GetColor(m_ColorID); color.a = Mathf.Lerp(0, 1, amount); m_CharacterFadeMaterials[i].SetColor(m_ColorID, color); } } m_CharacterFaded = true; } /// /// Updates the shader to support a fade. /// /// The material to update. private void EnableFadeMaterial(Material material) { // If the character's materials change at runtime then the values need to be saved every time fading is enabled. if (!m_CacheCharacterMaterials && !m_OriginalMaterialValuesMap.ContainsKey(material)) { var originalMaterialValues = GenericObjectPool.Get(); originalMaterialValues.Initialize(material, m_ColorID, m_MaterialModeSet.Contains(material)); m_OriginalMaterialValuesMap.Add(material, originalMaterialValues); } if (m_MaterialModeSet.Contains(material)) { material.SetFloat(OriginalMaterialValue.ModeID, 2); material.SetInt(OriginalMaterialValue.SrcBlendID, (int)UnityEngine.Rendering.BlendMode.SrcAlpha); material.SetInt(OriginalMaterialValue.DstBlendID, (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); } material.EnableKeyword(OriginalMaterialValue.AlphaBlendString); material.renderQueue = 3000; } /// /// Reverts all of the character materials back to the original non-faded value. /// private void DisableCharacterFade() { if (m_CharacterFadeMaterials != null) { for (int i = 0; i < m_CharacterFadeMaterialsCount; ++i) { if (m_OriginalMaterialValuesMap.TryGetValue(m_CharacterFadeMaterials[i], out var materialValue)) { RevertMaterial(m_CharacterFadeMaterials[i], materialValue); if (!m_CacheCharacterMaterials) { m_OriginalMaterialValuesMap.Remove(m_CharacterFadeMaterials[i]); } } } } m_CharacterFaded = false; } /// /// Reverts the material back to the original non-faded value. /// /// The material to revert. /// A storage struct with the original values. private void RevertMaterial(Material material, OriginalMaterialValue originalMaterialValue) { material.SetColor(m_ColorID, originalMaterialValue.Color); if (originalMaterialValue.ContainsMode) { material.SetFloat(OriginalMaterialValue.ModeID, originalMaterialValue.Mode); material.SetInt(OriginalMaterialValue.SrcBlendID, originalMaterialValue.SrcBlend); material.SetInt(OriginalMaterialValue.DstBlendID, originalMaterialValue.DstBlend); } if (!originalMaterialValue.AlphaBlend) { material.DisableKeyword(OriginalMaterialValue.AlphaBlendString); } material.renderQueue = originalMaterialValue.RenderQueue; } /// /// Fade any objects that get in the way between the character and the camera. /// /// Should the fade material be changed immediately? private void FadeObstructingObjects(bool immediateFade) { if (!m_ObstructingObjectsFade) { return; } // Disable any obstructing colliders so the sphere cast can detect which objects are obstructing. for (int i = 0; i < m_ObstructingCollidersCount; ++i) { m_ObstructingColliders[i].enabled = true; } m_ObstructingCollidersCount = 0; var characterPosition = m_CharacterTransform.TransformPoint(m_TransformOffset); var direction = (m_Transform.position - characterPosition); var start = characterPosition - direction.normalized * m_CollisionRadius; m_CharacterLocomotion.EnableColliderCollisionLayer(false); // Fire a sphere to prevent the camera from colliding with other objects. var hitCount = Physics.SphereCastNonAlloc(start, m_CollisionRadius, direction.normalized, m_RaycastsHit, direction.magnitude, m_CharacterLayerManager.IgnoreInvisibleCharacterWaterLayers, QueryTriggerInteraction.Ignore); m_CharacterLocomotion.EnableColliderCollisionLayer(true); m_ObstructionHitSet.Clear(); if (hitCount > 0) { // Loop through all of the hit colliders. For any collider that has been hit get all of the renderers. The materials that are used on the // renderers then need to be checked to determine if they can be faded. If the material can be faded place it in a set which will then // be checked in the next block to determine if the material should be faded. for (int i = 0; i < hitCount; ++i) { var renderers = m_RaycastsHit[i].transform.gameObject.GetCachedComponents(); var obstructing = false; for (int j = 0; j < renderers.Length; ++j) { var materials = renderers[j].materials; for (int k = 0; k < materials.Length; ++k) { if (!m_CanObstructionFade.TryGetValue(materials[k], out var canFade)) { // Objects can fade if they have the color property and can be transparent. canFade = materials[k].HasProperty(m_ColorID) && (m_AutoSetMode || materials[k].renderQueue >= (int)UnityEngine.Rendering.RenderQueue.Transparent); m_CanObstructionFade.Add(materials[k], canFade); } if (canFade) { var material = materials[k]; // Any material contained within the hit set should be faded. m_ObstructionHitSet.Add(material); if (m_DisableCollider && !obstructing) { obstructing = CanMaterialFade(material); } // The same material may be applied to multiple renderers. if (!m_OriginalMaterialValuesMap.ContainsKey(material)) { // Don't set the mode automatically just because it has the property - not all objects in the environment should fade. if (m_AutoSetMode && material.HasProperty(OriginalMaterialValue.ModeID)) { m_MaterialModeSet.Add(material); } var originalMaterialValues = GenericObjectPool.Get(); originalMaterialValues.Initialize(material, m_ColorID, m_MaterialModeSet.Contains(material)); m_OriginalMaterialValuesMap.Add(material, originalMaterialValues); m_ObstructingMaterials[m_ObstructingMaterialsCount] = material; m_ObstructingMaterialsCount++; EnableFadeMaterial(material); } } } } // If the object is faded then the collider has the option of being disabled to prevent it from causing collisions. if (m_DisableCollider && obstructing) { m_RaycastsHit[i].collider.enabled = false; m_ObstructingColliders[m_ObstructingCollidersCount] = m_RaycastsHit[i].collider; m_ObstructingCollidersCount++; } } } // Once the obstructing objects have been found they should be faded. Note that this can cause a lot of overdraw so the FadeObject method can be // overridden to provide a custom effect such as the one described on https://madewith.unity.com/stories/dissolving-the-world-part-1. for (int i = m_ObstructingMaterialsCount - 1; i >= 0; --i) { if (!FadeMaterial(m_ObstructingMaterials[i], m_ObstructionHitSet.Contains(m_ObstructingMaterials[i]), immediateFade)) { RemoveObstructingMaterial(i); } } } /// /// Can the obstructing material fade? /// /// The material that may be able to fade. /// True if the material can fade. public bool CanMaterialFade(Material material) { // The material can fade if: // - The fader can disable the obstructing material's collider AND // - The material has the Mode property AND // - The Mode property can be set to fade OR // - The Mode property is already set to fade. return m_DisableCollider && material.HasProperty(OriginalMaterialValue.ModeID) && (m_AutoSetMode || material.GetInt(OriginalMaterialValue.ModeID) == 2); } /// /// Fades the specified material by the amount. /// /// The material to fade. /// Is the object faded? /// Should the fade material be changed immediately? /// True if the object should continue to be faded, false if it is no longer faded. protected virtual bool FadeMaterial(Material material, bool fade, bool immediateFade) { // Set the alpha value to 0 if the object should be faded, otherwise set it to 1. As soon as the alpha value reaches 1 it should no longer be faded so // the method can return false. var color = material.GetColor(m_ColorID); var targetColor = fade ? m_FadeColor : Color.white; if (immediateFade) { color = targetColor; } else { color.r = Mathf.MoveTowards(color.r, targetColor.r, m_FadeSpeed); color.g = Mathf.MoveTowards(color.g, targetColor.g, m_FadeSpeed); color.b = Mathf.MoveTowards(color.b, targetColor.b, m_FadeSpeed); color.a = Mathf.MoveTowards(color.a, targetColor.a, m_FadeSpeed); } material.SetColor(m_ColorID, color); return color.a != 1; } /// /// Removes the obstructing material at the specified index from the ObstructingMaterials array. /// /// The index of the obstructing material to remove. private void RemoveObstructingMaterial(int index) { var originalMaterialValue = m_OriginalMaterialValuesMap[m_ObstructingMaterials[index]]; RevertMaterial(m_ObstructingMaterials[index], originalMaterialValue); GenericObjectPool.Return(originalMaterialValue); m_OriginalMaterialValuesMap.Remove(m_ObstructingMaterials[index]); // The object is no longer faded. Move the array elements down one. for (int j = index; j < m_ObstructingMaterialsCount - 1; ++j) { m_ObstructingMaterials[j] = m_ObstructingMaterials[j + 1]; } m_ObstructingMaterialsCount--; } /// /// Disables all of the faded obstructing materials. /// private void DisableObstructingFade() { for (int i = m_ObstructingMaterialsCount - 1; i >= 0; --i) { RemoveObstructingMaterial(i); } for (int i = 0; i < m_ObstructingCollidersCount; ++i) { if (m_ObstructingColliders[i] != null) { m_ObstructingColliders[i].enabled = true; } } m_ObstructingCollidersCount = 0; } /// /// The camera perspective between first and third person has changed. /// /// Is the camera in a first person perspective? private void OnChangePerspectives(bool firstPersonPerspective) { // While in a first person view no objects should be faded. if (firstPersonPerspective) { // Disable the fading within the update loop to prevent it from being disabled too early. Scheduler.Schedule(Time.fixedDeltaTime, DisableFades); } enabled = !firstPersonPerspective; } /// /// Disables the character and obstruction fades. /// private void DisableFades() { DisableCharacterFade(); DisableObstructingFade(); } /// /// The inventory has added the specified item. /// /// The item that was added. private void OnAddItem(Item item) { // All of the item's materials should be faded when it is added to the character. if (m_CharacterFade) { var perspectiveItem = item.GetComponent(); if (perspectiveItem == null || perspectiveItem.Object == null) { return; } InitializeCharacterFadeRenderers(perspectiveItem.Object.GetComponentsInChildren(true)); } } /// /// Another object has started or stopped taking control of the character fade. /// /// Is the component controlling the fade? /// Should the fade be reverted? private void OnIndependentFade(bool enable, bool revertFade) { m_IndependentCharacterFadeCount += enable ? 1 : -1; if (enable && revertFade) { DisableCharacterFade(); } } /// /// Initializes the materials on the renderers for character fade. /// /// The renderers that should be initilaizes for character fade. private void InitializeCharacterFadeRenderers(Renderer[] renderers) { var count = 0; for (int i = 0; i < renderers.Length; ++i) { // The renderer fade can be ignored. if (renderers[i].gameObject.GetCachedComponent() != null) { continue; } var materials = renderers[i].materials; for (int j = 0; j < materials.Length; ++j) { if (!m_RegisteredMaterial.Contains(materials[j]) && materials[j].HasProperty(m_ColorID)) { count++; } } } if (count > 0) { var totalCount = m_CharacterFadeMaterialsCount + count; if (totalCount >= m_CharacterFadeMaterials.Length) { System.Array.Resize(ref m_CharacterFadeMaterials, totalCount); } // Cache a reference to all of the faded materials. for (int i = 0; i < renderers.Length; ++i) { // The renderer fade can be ignored. if (renderers[i].gameObject.GetCachedComponent() != null) { continue; } var materials = renderers[i].materials; for (int j = 0; j < materials.Length; ++j) { if (!m_RegisteredMaterial.Contains(materials[j]) && materials[j].HasProperty(m_ColorID)) { if (materials[j].HasProperty(OriginalMaterialValue.ModeID)) { m_MaterialModeSet.Add(materials[j]); } m_CharacterFadeMaterials[m_CharacterFadeMaterialsCount] = materials[j]; m_RegisteredMaterial.Add(materials[j]); m_CharacterFadeMaterialsCount++; if (m_CacheCharacterMaterials && !m_OriginalMaterialValuesMap.ContainsKey(materials[j])) { var originalMaterialValues = GenericObjectPool.Get(); originalMaterialValues.Initialize(materials[j], m_ColorID, m_MaterialModeSet.Contains(materials[j])); m_OriginalMaterialValuesMap.Add(materials[j], originalMaterialValues); } } } } } } /// /// The ShootableWeapon ItemAction has shown or hidden a projectile. /// /// The projectile shown or hidden. /// Is the projectile being shown? private void OnShowProjectile(GameObject projectile, bool show) { // The projectile may not have a renderer. var renderers = projectile.GetCachedComponents(); if (renderers == null || renderers.Length == 0) { return; } if (show) { // Initalize all of the materials. InitializeCharacterFadeRenderers(renderers); } else { // Remove all of the materials from the fade. for (int i = renderers.Length - 1; i > -1; --i) { var materials = renderers[i].materials; for (int j = materials.Length - 1; j > -1; --j) { if (!m_RegisteredMaterial.Contains(materials[j])) { continue; } // Remove the material from the array. The array won't resize when the material is removed so start from the end to // reduce the number of likely iterations. var index = -1; for (int k = m_CharacterFadeMaterialsCount - 1; k > -1; --k) { if (m_CharacterFadeMaterials[k] == materials[j]) { index = k; break; } } for (int k = index; k < m_CharacterFadeMaterialsCount - 1; ++k) { m_CharacterFadeMaterials[k] = m_CharacterFadeMaterials[k + 1]; } m_CharacterFadeMaterialsCount--; m_CharacterFadeMaterials[m_CharacterFadeMaterialsCount] = null; m_RegisteredMaterial.Remove(materials[j]); if (m_OriginalMaterialValuesMap.TryGetValue(materials[j], out var materialValue)) { RevertMaterial(materials[j], materialValue); if (!m_CacheCharacterMaterials) { m_OriginalMaterialValuesMap.Remove(materials[j]); } } } } } } /// /// The character has respawned. /// private void OnRespawn() { // The character should no longer be faded. DisableFades(); } /// /// Callback when the StateManager has changed the active state on the current object. /// public override void StateChange() { if (m_CharacterFade && m_RegisteredMaterial == null) { m_RegisteredMaterial = new HashSet(); m_MaterialModeSet = new HashSet(); } else if (!m_CharacterFade) { DisableCharacterFade(); } // In some cases the character fade should not update immediately - such as while zooming in third person to going back to a non-zoom state. This will prevent // the character from fading when the camera is moving back to the non-zoomed state. if (m_CharacterFadeStateChangeCooldown > 0) { m_CharacterFadeCooldownElapsedTime = Time.time + m_CharacterFadeStateChangeCooldown; } if (m_ObstructingObjectsFade && m_ObstructingMaterials == null) { m_RaycastsHit = new RaycastHit[m_MaxObstructingColliderCount]; m_ObstructingMaterials = new Material[m_MaxObstructingMaterialCount]; m_ObstructingColliders = new Collider[m_MaxObstructingMaterialCount]; m_ObstructionHitSet = new HashSet(); m_CanObstructionFade = new Dictionary(); } else if (!m_ObstructingObjectsFade) { DisableObstructingFade(); } } /// /// The camera has been destroyed. /// private void OnDestroy() { EventHandler.UnregisterEvent(gameObject, "OnCameraAttachCharacter", OnAttachCharacter); } } }