namespace Zinnia.Visual { using System; using System.Collections; using UnityEngine; using UnityEngine.Events; using UnityEngine.Rendering; using Zinnia.Extension; using Zinnia.Rule; /// /// Applies a color over the valid cameras and can be used to fade the screen view. /// public class CameraColorOverlay : MonoBehaviour { /// /// Holds data about a event. /// [Serializable] public class EventData { [Tooltip("The Color being applied to the camera overlay.")] [SerializeField] private Color color; /// /// The being applied to the camera overlay. /// public Color Color { get { return color; } set { color = value; } } public EventData Set(EventData source) { return Set(source.Color); } public EventData Set(Color color) { Color = color; return this; } /// public override string ToString() { string[] titles = new string[] { "Color" }; object[] values = new object[] { Color }; return StringExtensions.FormatForToString(titles, values); } public void Clear() { Set(default(Color)); } } /// /// Defines the event with the . /// [Serializable] public class UnityEvent : UnityEvent { } [Tooltip("The rules to determine which scene cameras to apply the overlay to.")] [SerializeField] private RuleContainer cameraValidity; /// /// The rules to determine which scene cameras to apply the overlay to. /// public RuleContainer CameraValidity { get { return cameraValidity; } set { cameraValidity = value; } } [Tooltip("The Color of the overlay.")] [SerializeField] private Color overlayColor = Color.black; /// /// The of the overlay. /// public Color OverlayColor { get { return overlayColor; } set { overlayColor = value; } } [Tooltip("The Material to use for the overlay.")] [SerializeField] private Material overlayMaterial; /// /// The to use for the overlay. /// public Material OverlayMaterial { get { return overlayMaterial; } set { overlayMaterial = value; if (this.IsMemberChangeAllowed()) { OnAfterOverlayMaterialChange(); } } } [Tooltip("The duration of time to apply the overlay Color.")] [SerializeField] private float addDuration; /// /// The duration of time to apply the overlay . /// public float AddDuration { get { return addDuration; } set { addDuration = value; if (this.IsMemberChangeAllowed()) { OnAfterAddDurationChange(); } } } [Tooltip("The duration of time to remove the overlay Color.")] [SerializeField] private float removeDuration = 1f; /// /// The duration of time to remove the overlay . /// public float RemoveDuration { get { return removeDuration; } set { removeDuration = value; } } [Tooltip("The duration of time to wait once the overlay Color is applied before it is removed.")] [SerializeField] private float appliedDuration; /// /// The duration of time to wait once the overlay is applied before it is removed. /// public float AppliedDuration { get { return appliedDuration; } set { appliedDuration = value; if (this.IsMemberChangeAllowed()) { OnAfterAppliedDurationChange(); } } } /// /// Emitted when the method is called. /// public UnityEvent Added = new UnityEvent(); /// /// Emitted when the target overlay is reached. /// public UnityEvent AddTransitioned = new UnityEvent(); /// /// Emitted when the method is called. /// public UnityEvent Removed = new UnityEvent(); /// /// Emitted when the target overlay is reached. /// public UnityEvent RemoveTransitioned = new UnityEvent(); /// /// Emitted when an overlay has changed from the previous render frame. /// public UnityEvent Changed = new UnityEvent(); /// /// Whether an overlay add transition is in progress. /// public bool IsAddTransitioning { get; protected set; } /// /// Whether an overlay remove transition is in progress. /// public bool IsRemoveTransitioning { get; protected set; } /// /// The target duration to process the color change for. /// protected float targetDuration; /// /// A copy of the to apply the transition overlay color on. /// protected Material workingMaterial; /// /// The target color to apply to the camera overlay during the process. /// protected Color targetColor = new Color(0f, 0f, 0f, 0f); /// /// The current color of the camera overlay during the process. /// protected Color currentColor = new Color(0f, 0f, 0f, 0f); /// /// The difference in color of the camera overlay during the process. /// protected Color deltaColor = new Color(0f, 0f, 0f, 0f); /// /// The routine for handling the fade in and out of the camera overlay. /// protected Coroutine blinkRoutine; /// /// Delays the reset of by plus seconds. /// protected WaitForSecondsRealtime resetDelayYieldInstruction; /// /// The event data to be emitted throughout the process. /// protected readonly EventData eventData = new EventData(); /// /// The last used camera in the fade routine. /// protected Camera lastUsedCamera; /// /// A container for holding the Universal Render Pipeline fade overlay mesh. /// protected GameObject urpFadeOverlay; /// /// The mesh for use with the Universal Render Pipeline fade overlay. /// protected MeshRenderer fadeRenderer; /// /// The dimensions of the fade mesh used with the Universal Render Pipeline fade overlay. /// protected virtual Vector3 FadeMeshDimensions { get; set; } = new Vector3(10f, 10f, 1f); /// /// The triganles of the fade mesh used with the Universal Render Pipeline fade overlay. /// protected virtual int[] FadeMeshTriangles { get; set; } = new int[6] { 0, 2, 1, 2, 3, 1 }; /// /// The normals of the fade mesh used with the Universal Render Pipeline fade overlay. /// protected virtual Vector3[] FadeMeshNormals { get; set; } = new Vector3[4] { -Vector3.forward, -Vector3.forward, -Vector3.forward, -Vector3.forward }; /// /// The texture UV of the fade mesh used with the Universal Render Pipeline fade overlay. /// protected virtual Vector2[] FadeMeshUV { get; set; } = new Vector2[] { new Vector2(0, 0), new Vector2(1, 0), new Vector2(0, 1), new Vector2(1, 1) }; /// /// Sets the current and with the given parameters and applies the to the cameras via over the given . /// /// The to apply to the overlay. /// The duration of time to apply the overlay . public virtual void AddColorOverlay(Color overlayColor, float addDuration) { if (!this.IsValidState()) { return; } OverlayColor = overlayColor; AddDuration = addDuration; AddColorOverlay(); } /// /// Applies the to the cameras via over the given . /// public virtual void AddColorOverlay() { if (!this.IsValidState()) { return; } ApplyColorOverlay(OverlayColor, AddDuration); } /// /// Removes the to the cameras via over the given . /// public virtual void RemoveColorOverlay() { if (!this.IsValidState()) { return; } ApplyColorOverlay(Color.clear, RemoveDuration); Removed?.Invoke(eventData.Set(Color.clear)); } /// /// Applies the to the cameras via over the given then waits for the given then removes the over the given . /// public virtual void Blink() { if (!this.IsValidState()) { return; } ApplyColorOverlay(OverlayColor, AddDuration); blinkRoutine = StartCoroutine(ResetBlink()); } /// /// Destroys the fade mesh used in the Universal Render Pipeline. To recreate it, the component must be disabled then re-enabled. /// public virtual void DestroyFadeMesh() { if (urpFadeOverlay == null) { return; } urpFadeOverlay.transform.SetParent(null); Destroy(urpFadeOverlay); } protected virtual void OnEnable() { lastUsedCamera = null; CopyMaterialOverlayToWorking(); SetResetDelayInstruction(); if (GraphicsSettings.renderPipelineAsset != null) { CreateFadeMesh(); #if UNITY_2019_1_OR_NEWER RenderPipelineManager.beginCameraRendering += UrpPreRender; #endif } else { Camera.onPostRender += PostRender; } } protected virtual void OnDisable() { CancelBlinkRoutine(); if (GraphicsSettings.renderPipelineAsset != null) { if (fadeRenderer != null) { fadeRenderer.enabled = false; } #if UNITY_2019_1_OR_NEWER RenderPipelineManager.beginCameraRendering -= UrpPreRender; #endif } else { Camera.onPostRender -= PostRender; } } /// /// Generates the fade mesh vertices. /// /// The fade mesh vertices protected virtual Vector3[] GenerateFadeMeshVertices() { Vector3[] vertices = new Vector3[] { new Vector3(-FadeMeshDimensions.x, -FadeMeshDimensions.y, FadeMeshDimensions.z), new Vector3(FadeMeshDimensions.x, -FadeMeshDimensions.y, FadeMeshDimensions.z), new Vector3(-FadeMeshDimensions.x, FadeMeshDimensions.y, FadeMeshDimensions.z), new Vector3(FadeMeshDimensions.x, FadeMeshDimensions.y, FadeMeshDimensions.z) }; return vertices; } /// /// Creates the fade mesh used in the Universal Render Pipeline. /// protected virtual void CreateFadeMesh() { if (urpFadeOverlay != null) { return; } urpFadeOverlay = new GameObject("Zinnia.URPFadeOverlay." + name); MeshFilter fadeMesh = urpFadeOverlay.AddComponent(); fadeRenderer = urpFadeOverlay.AddComponent(); Mesh mesh = new Mesh(); fadeMesh.mesh = mesh; mesh.vertices = GenerateFadeMeshVertices(); mesh.triangles = FadeMeshTriangles; mesh.normals = FadeMeshNormals; mesh.uv = FadeMeshUV; } /// /// Applies the given to the cameras via over the given duration. /// /// to apply to the overlay. /// The duration over which the is applied. protected virtual void ApplyColorOverlay(Color newColor, float duration) { CancelBlinkRoutine(); if (newColor != targetColor || !duration.ApproxEquals(targetDuration)) { targetDuration = duration; targetColor = newColor; if (duration > 0.0f) { deltaColor = (targetColor - currentColor) / duration; } else { currentColor = newColor; } IsAddTransitioning = false; IsRemoveTransitioning = false; if (newColor != Color.clear) { IsAddTransitioning = true; Added?.Invoke(eventData.Set(newColor)); } else { IsRemoveTransitioning = true; } } } /// /// Waits for the given wait duration and then removes the overlay over the remove duration. /// /// An Enumerator to manage the running of the Coroutine. protected virtual IEnumerator ResetBlink() { yield return resetDelayYieldInstruction; RemoveColorOverlay(); } /// /// Cancels any existing running Coroutine. /// protected virtual void CancelBlinkRoutine() { if (blinkRoutine != null) { StopCoroutine(blinkRoutine); } } /// /// Processes the transition of the current color to the target color. /// protected virtual void ProcessColorTransition() { if (currentColor != targetColor) { if (Mathf.Abs(currentColor.a - targetColor.a) < Mathf.Abs(deltaColor.a) * Time.unscaledDeltaTime) { currentColor = targetColor; deltaColor = new Color(0, 0, 0, 0); } else { currentColor += deltaColor * Time.unscaledDeltaTime; } Changed?.Invoke(eventData.Set(currentColor)); } else if (IsAddTransitioning) { AddTransitioned?.Invoke(eventData.Set(currentColor)); IsAddTransitioning = false; } else if (IsRemoveTransitioning) { RemoveTransitioned?.Invoke(eventData.Set(currentColor)); IsRemoveTransitioning = false; } } /// /// Determines whether a transition should occur and sets the transition color if required. /// /// Whether the transition should occur. protected virtual bool ShouldTransition() { if (currentColor.a > 0f && workingMaterial != null) { currentColor.a = (targetColor.a > currentColor.a && currentColor.a > 0.98f ? 1f : currentColor.a); workingMaterial.color = currentColor; return true; } return false; } /// /// The moment before render that will apply the overlay. /// /// The to apply onto. protected virtual void PostRender(Camera sceneCamera) { if (!CameraValidity.Accepts(sceneCamera)) { return; } ProcessColorTransition(); if (ShouldTransition()) { workingMaterial.SetPass(0); GL.PushMatrix(); GL.LoadOrtho(); GL.Color(workingMaterial.color); GL.Begin(GL.QUADS); GL.Vertex3(0f, 0f, 0.9999f); GL.Vertex3(0f, 1f, 0.9999f); GL.Vertex3(1f, 1f, 0.9999f); GL.Vertex3(1f, 0f, 0.9999f); GL.End(); GL.PopMatrix(); } } /// /// The Universal Render Pipeline moment before the camera render that will apply the overlay. /// /// The render context. /// The to apply onto. #if UNITY_2019_1_OR_NEWER protected virtual void UrpPreRender(ScriptableRenderContext context, Camera sceneCamera) { ProcessColorTransition(); if (urpFadeOverlay == null || fadeRenderer == null) { return; } if (ShouldTransition()) { if (lastUsedCamera != sceneCamera) { urpFadeOverlay.transform.SetParent(sceneCamera.transform); urpFadeOverlay.transform.localPosition = Vector3.forward; urpFadeOverlay.transform.localRotation = Quaternion.identity; urpFadeOverlay.transform.localScale = Vector3.one; } lastUsedCamera = sceneCamera; fadeRenderer.material = workingMaterial; fadeRenderer.enabled = true; } else { fadeRenderer.enabled = false; } } #endif /// /// Copies the data to the data. /// protected virtual void CopyMaterialOverlayToWorking() { if (Application.isPlaying) { Destroy(workingMaterial); } else { DestroyImmediate(workingMaterial); } if (OverlayMaterial != null) { workingMaterial = new Material(OverlayMaterial); } } /// /// Sets the value based on the current plus the . /// protected virtual void SetResetDelayInstruction() { resetDelayYieldInstruction = new WaitForSecondsRealtime(AddDuration + AppliedDuration); } /// /// Called after has been changed. /// protected virtual void OnAfterOverlayMaterialChange() { CopyMaterialOverlayToWorking(); } /// /// Called after . /// protected virtual void OnAfterAddDurationChange() { SetResetDelayInstruction(); } /// /// Called after . /// protected virtual void OnAfterAppliedDurationChange() { SetResetDelayInstruction(); } [Obsolete("Use `SetResetDelayInstruction` instead.")] protected virtual void OnAfterCheckDelayChange() { resetDelayYieldInstruction = new WaitForSecondsRealtime(AddDuration + AppliedDuration); } } }