Rasagar/Library/PackageCache/com.unity.cinemachine/Runtime/Behaviours/CinemachineStoryboard.cs

452 lines
18 KiB
C#
Raw Normal View History

2024-08-26 13:07:20 -07:00
#if !UNITY_2019_1_OR_NEWER
#define CINEMACHINE_UGUI
#endif
using UnityEngine;
#if CINEMACHINE_UGUI
using System.Collections.Generic;
using Cinemachine.Utility;
namespace Cinemachine
{
/// <summary>
/// An add-on module for Cinemachine Virtual Camera that places an image in screen space
/// over the camera's output.
/// </summary>
[SaveDuringPlay]
[DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
[AddComponentMenu("")] // Hide in menu
[ExecuteAlways]
[DisallowMultipleComponent]
[HelpURL(Documentation.BaseURL + "manual/CinemachineStoryboard.html")]
public class CinemachineStoryboard : CinemachineExtension
{
/// <summary>
/// If checked, all storyboards are globally muted
/// </summary>
[Tooltip("If checked, all storyboards are globally muted")]
public static bool s_StoryboardGlobalMute;
/// <summary>
/// If checked, the specified image will be displayed as an overlay over the virtual camera's output
/// </summary>
[Tooltip("If checked, the specified image will be displayed as an overlay over the virtual camera's output")]
public bool m_ShowImage = true;
/// <summary>
/// The image to display
/// </summary>
[Tooltip("The image to display")]
public Texture m_Image;
/// <summary>How to fit the image in the frame, in the event that the aspect ratios don't match</summary>
public enum FillStrategy
{
/// <summary>Image will be as large as possible on the screen, without being cropped</summary>
BestFit,
/// <summary>Image will be cropped if necessary so that the screen is entirely filled</summary>
CropImageToFit,
/// <summary>Image will be stretched to cover any aspect mismatch with the screen</summary>
StretchToFit
};
/// <summary>
/// How to handle differences between image aspect and screen aspect
/// </summary>
[Tooltip("How to handle differences between image aspect and screen aspect")]
public FillStrategy m_Aspect = FillStrategy.BestFit;
/// <summary>
/// The opacity of the image. 0 is transparent, 1 is opaque
/// </summary>
[Tooltip("The opacity of the image. 0 is transparent, 1 is opaque")]
[Range(0, 1)]
public float m_Alpha = 1;
/// <summary>
/// The screen-space position at which to display the image. Zero is center
/// </summary>
[Tooltip("The screen-space position at which to display the image. Zero is center")]
public Vector2 m_Center = Vector2.zero;
/// <summary>
/// The screen-space rotation to apply to the image
/// </summary>
[Tooltip("The screen-space rotation to apply to the image")]
public Vector3 m_Rotation = Vector3.zero;
/// <summary>
/// The screen-space scaling to apply to the image
/// </summary>
[Tooltip("The screen-space scaling to apply to the image")]
public Vector2 m_Scale = Vector3.one;
/// <summary>
/// If checked, X and Y scale are synchronized
/// </summary>
[Tooltip("If checked, X and Y scale are synchronized")]
public bool m_SyncScale = true;
/// <summary>
/// If checked, Camera transform will not be controlled by this virtual camera
/// </summary>
[Tooltip("If checked, Camera transform will not be controlled by this virtual camera")]
public bool m_MuteCamera;
/// <summary>
/// Wipe the image on and off horizontally
/// </summary>
[Range(-1, 1)]
[Tooltip("Wipe the image on and off horizontally")]
public float m_SplitView = 0f;
/// <summary>
/// The render mode of the canvas on which the storyboard is drawn.
/// </summary>
[Tooltip("The render mode of the canvas on which the storyboard is drawn.")]
public StoryboardRenderMode m_RenderMode = StoryboardRenderMode.ScreenSpaceOverlay;
/// <summary>
/// Allows ordering canvases to render on top or below other canvases.
/// </summary>
[Tooltip("Allows ordering canvases to render on top or below other canvases.")]
public int m_SortingOrder;
/// <summary>
/// How far away from the camera is the storyboard's canvas generated.
/// </summary>
[Tooltip("How far away from the camera is the Canvas generated.")]
public float m_PlaneDistance = 100;
class CanvasInfo
{
public GameObject mCanvas;
public Canvas mCanvasComponent;
public CinemachineBrain mCanvasParent;
public RectTransform mViewport; // for mViewport clipping
public UnityEngine.UI.RawImage mRawImage;
}
List<CanvasInfo> mCanvasInfo = new List<CanvasInfo>();
/// <summary>Callback to display the image</summary>
/// <param name="vcam">The virtual camera being processed</param>
/// <param name="stage">The current pipeline stage</param>
/// <param name="state">The current virtual camera state</param>
/// <param name="deltaTime">The current applicable deltaTime</param>
protected override void PostPipelineStageCallback(
CinemachineVirtualCameraBase vcam,
CinemachineCore.Stage stage, ref CameraState state, float deltaTime)
{
// Apply to this vcam only, not the children
if (vcam != VirtualCamera || stage != CinemachineCore.Stage.Finalize)
return;
UpdateRenderCanvas();
if (m_ShowImage)
state.AddCustomBlendable(new CameraState.CustomBlendable(this, 1));
if (m_MuteCamera)
state.BlendHint |= CameraState.BlendHintValue.NoTransform | CameraState.BlendHintValue.NoLens;
}
/// <summary>
/// Camera render modes supported by CinemachineStoryboard.
/// </summary>
public enum StoryboardRenderMode
{
/// <summary>
/// Renders in camera screen space. This means, that the storyboard is going to be displayed in front of
/// any objects in the scene. Equivalent to Unity's RenderMode.ScreenSpaceOverlay.
/// </summary>
ScreenSpaceOverlay = RenderMode.ScreenSpaceOverlay,
/// <summary>
/// Render using the vcam on which the storyboard is on. This is useful, if you'd like to render the
/// storyboard at a specific distance from the vcam. Equivalent to Unity's RenderMode.ScreenSpaceCamera.
/// </summary>
ScreenSpaceCamera = RenderMode.ScreenSpaceCamera
};
void UpdateRenderCanvas()
{
for (int i = 0; i < mCanvasInfo.Count; ++i)
{
if (mCanvasInfo[i] == null || mCanvasInfo[i].mCanvasComponent == null)
mCanvasInfo.RemoveAt(i--);
else
{
mCanvasInfo[i].mCanvasComponent.renderMode = (RenderMode) m_RenderMode;
mCanvasInfo[i].mCanvasComponent.planeDistance = m_PlaneDistance;
mCanvasInfo[i].mCanvasComponent.sortingOrder = m_SortingOrder;
}
}
}
/// <summary>Connect to virtual camera. Adds/removes listener</summary>
/// <param name="connect">True if connecting, false if disconnecting</param>
protected override void ConnectToVcam(bool connect)
{
base.ConnectToVcam(connect);
CinemachineCore.CameraUpdatedEvent.RemoveListener(CameraUpdatedCallback);
if (connect)
CinemachineCore.CameraUpdatedEvent.AddListener(CameraUpdatedCallback);
else
DestroyCanvas();
}
string CanvasName => "_CM_canvas" + gameObject.GetInstanceID();
void CameraUpdatedCallback(CinemachineBrain brain)
{
bool showIt = enabled && m_ShowImage && CinemachineCore.Instance.IsLive(VirtualCamera);
int layer = 1 << gameObject.layer;
if (brain.OutputCamera == null || (brain.OutputCamera.cullingMask & layer) == 0)
showIt = false;
if (s_StoryboardGlobalMute)
showIt = false;
CanvasInfo ci = LocateMyCanvas(brain, showIt);
if (ci != null && ci.mCanvas != null)
ci.mCanvas.SetActive(showIt);
}
CanvasInfo LocateMyCanvas(CinemachineBrain parent, bool createIfNotFound)
{
CanvasInfo ci = null;
for (int i = 0; ci == null && i < mCanvasInfo.Count; ++i)
if (mCanvasInfo[i] != null && mCanvasInfo[i].mCanvasParent == parent)
ci = mCanvasInfo[i];
if (createIfNotFound)
{
if (ci == null)
{
ci = new CanvasInfo() { mCanvasParent = parent };
int numChildren = parent.transform.childCount;
for (int i = 0; ci.mCanvas == null && i < numChildren; ++i)
{
RectTransform child = parent.transform.GetChild(i) as RectTransform;
if (child != null && child.name == CanvasName)
{
ci.mCanvas = child.gameObject;
var kids = ci.mCanvas.GetComponentsInChildren<RectTransform>();
ci.mViewport = kids.Length > 1 ? kids[1] : null; // 0 is mCanvas
ci.mRawImage = ci.mCanvas.GetComponentInChildren<UnityEngine.UI.RawImage>();
ci.mCanvasComponent = ci.mCanvas.GetComponent<Canvas>();
}
}
mCanvasInfo.Add(ci);
}
if (ci.mCanvas == null || ci.mViewport == null || ci.mRawImage == null || ci.mCanvasComponent == null)
CreateCanvas(ci);
}
return ci;
}
void CreateCanvas(CanvasInfo ci)
{
ci.mCanvas = new GameObject(CanvasName, typeof(RectTransform));
ci.mCanvas.layer = gameObject.layer;
ci.mCanvas.hideFlags = HideFlags.HideAndDontSave;
ci.mCanvas.transform.SetParent(ci.mCanvasParent.transform);
#if UNITY_EDITOR
// Workaround for Unity bug case Case 1004117
CanvasesAndTheirOwners.AddCanvas(ci.mCanvas, this);
#endif
var c = ci.mCanvasComponent = ci.mCanvas.AddComponent<Canvas>();
c.renderMode = (RenderMode) m_RenderMode;
c.sortingOrder = m_SortingOrder;
c.planeDistance = m_PlaneDistance;
c.worldCamera = ci.mCanvasParent.OutputCamera;
var go = new GameObject("Viewport", typeof(RectTransform));
go.transform.SetParent(ci.mCanvas.transform);
ci.mViewport = (RectTransform)go.transform;
go.AddComponent<UnityEngine.UI.RectMask2D>();
go = new GameObject("RawImage", typeof(RectTransform));
go.transform.SetParent(ci.mViewport.transform);
ci.mRawImage = go.AddComponent<UnityEngine.UI.RawImage>();
}
void DestroyCanvas()
{
int numBrains = CinemachineCore.Instance.BrainCount;
for (int i = 0; i < numBrains; ++i)
{
var parent = CinemachineCore.Instance.GetActiveBrain(i);
int numChildren = parent.transform.childCount;
for (int j = numChildren - 1; j >= 0; --j)
{
RectTransform child = parent.transform.GetChild(j) as RectTransform;
if (child != null && child.name == CanvasName)
{
var canvas = child.gameObject;
RuntimeUtility.DestroyObject(canvas);
#if UNITY_EDITOR
// Workaround for Unity bug case Case 1004117
CanvasesAndTheirOwners.RemoveCanvas(canvas);
#endif
}
}
}
mCanvasInfo.Clear();
}
void PlaceImage(CanvasInfo ci, float alpha)
{
if (ci.mRawImage != null && ci.mViewport != null)
{
Rect screen = new Rect(0, 0, Screen.width, Screen.height);
if (ci.mCanvasParent.OutputCamera != null)
screen = ci.mCanvasParent.OutputCamera.pixelRect;
screen.x -= (float)Screen.width/2;
screen.y -= (float)Screen.height/2;
// Apply Split View
float wipeAmount = -Mathf.Clamp(m_SplitView, -1, 1) * screen.width;
Vector3 pos = screen.center;
pos.x -= wipeAmount/2;
ci.mViewport.localPosition = pos;
ci.mViewport.localRotation = Quaternion.identity;
ci.mViewport.localScale = Vector3.one;
ci.mViewport.ForceUpdateRectTransforms();
ci.mViewport.sizeDelta = new Vector2(screen.width + 1 - Mathf.Abs(wipeAmount), screen.height + 1);
Vector2 scale = Vector2.one;
if (m_Image != null
&& m_Image.width > 0 && m_Image.width > 0
&& screen.width > 0 && screen.height > 0)
{
float f = (screen.height * m_Image.width) / (screen.width * m_Image.height);
switch (m_Aspect)
{
case FillStrategy.BestFit:
if (f >= 1)
scale.y /= f;
else
scale.x *= f;
break;
case FillStrategy.CropImageToFit:
if (f >= 1)
scale.x *= f;
else
scale.y /= f;
break;
case FillStrategy.StretchToFit:
break;
}
}
scale.x *= m_Scale.x;
scale.y *= m_SyncScale ? m_Scale.x : m_Scale.y;
ci.mRawImage.texture = m_Image;
Color tintColor = Color.white;
tintColor.a = m_Alpha * alpha;
ci.mRawImage.color = tintColor;
pos = new Vector2(screen.width * m_Center.x, screen.height * m_Center.y);
pos.x += wipeAmount/2;
ci.mRawImage.rectTransform.localPosition = pos;
ci.mRawImage.rectTransform.localRotation = Quaternion.Euler(m_Rotation);
ci.mRawImage.rectTransform.localScale = scale;
ci.mRawImage.rectTransform.ForceUpdateRectTransforms();
ci.mRawImage.rectTransform.sizeDelta = screen.size;
}
}
static void StaticBlendingHandler(CinemachineBrain brain)
{
CameraState state = brain.CurrentCameraState;
int numBlendables = state.NumCustomBlendables;
for (int i = 0; i < numBlendables; ++i)
{
var b = state.GetCustomBlendable(i);
CinemachineStoryboard src = b.m_Custom as CinemachineStoryboard;
if (!(src == null)) // in case it was deleted
{
bool showIt = true;
int layer = 1 << src.gameObject.layer;
if (brain.OutputCamera == null || (brain.OutputCamera.cullingMask & layer) == 0)
showIt = false;
if (s_StoryboardGlobalMute)
showIt = false;
CanvasInfo ci = src.LocateMyCanvas(brain, showIt);
if (ci != null)
src.PlaceImage(ci, b.m_Weight);
}
}
}
#if UNITY_EDITOR
[UnityEditor.InitializeOnLoad]
class EditorInitialize { static EditorInitialize() { InitializeModule(); } }
#endif
[RuntimeInitializeOnLoadMethod]
static void InitializeModule()
{
CinemachineCore.CameraUpdatedEvent.RemoveListener(StaticBlendingHandler);
CinemachineCore.CameraUpdatedEvent.AddListener(StaticBlendingHandler);
}
#if UNITY_EDITOR
// Workaround for the Unity bug where OnDestroy doesn't get called if Undo
// bug case Case 1004117
[UnityEditor.InitializeOnLoad]
class CanvasesAndTheirOwners
{
static Dictionary<UnityEngine.Object, UnityEngine.Object> sCanvasesAndTheirOwners;
static CanvasesAndTheirOwners()
{
UnityEditor.Undo.undoRedoPerformed -= OnUndoRedoPerformed;
UnityEditor.Undo.undoRedoPerformed += OnUndoRedoPerformed;
}
static void OnUndoRedoPerformed()
{
if (sCanvasesAndTheirOwners != null)
{
List<UnityEngine.Object> toDestroy = null;
foreach (var v in sCanvasesAndTheirOwners)
{
if (v.Value == null)
{
if (toDestroy == null)
toDestroy = new List<UnityEngine.Object>();
toDestroy.Add(v.Key);
}
}
if (toDestroy != null)
{
foreach (var o in toDestroy)
{
RemoveCanvas(o);
RuntimeUtility.DestroyObject(o);
}
}
}
}
public static void RemoveCanvas(UnityEngine.Object canvas)
{
if (sCanvasesAndTheirOwners != null && sCanvasesAndTheirOwners.ContainsKey(canvas))
sCanvasesAndTheirOwners.Remove(canvas);
}
public static void AddCanvas(UnityEngine.Object canvas, UnityEngine.Object owner)
{
if (sCanvasesAndTheirOwners == null)
sCanvasesAndTheirOwners = new Dictionary<UnityEngine.Object, UnityEngine.Object>();
sCanvasesAndTheirOwners.Add(canvas, owner);
}
}
#endif
}
}
#else
// We need this dummy MonoBehaviour for Unity to properly recognize this script asset.
namespace Cinemachine
{
[AddComponentMenu("")] // Hide in menu
public class CinemachineStoryboard : MonoBehaviour {}
}
#endif