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);
}
}
}