CinemachineStoryboard.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. #if !UNITY_2019_1_OR_NEWER
  2. #define CINEMACHINE_UGUI
  3. #endif
  4. using UnityEngine;
  5. #if CINEMACHINE_UGUI
  6. using System.Collections.Generic;
  7. using Cinemachine.Utility;
  8. namespace Cinemachine
  9. {
  10. /// <summary>
  11. /// An add-on module for Cinemachine Virtual Camera that places an image in screen space
  12. /// over the camera's output.
  13. /// </summary>
  14. [SaveDuringPlay]
  15. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  16. [AddComponentMenu("")] // Hide in menu
  17. [ExecuteAlways]
  18. [DisallowMultipleComponent]
  19. [HelpURL(Documentation.BaseURL + "manual/CinemachineStoryboard.html")]
  20. public class CinemachineStoryboard : CinemachineExtension
  21. {
  22. /// <summary>
  23. /// If checked, all storyboards are globally muted
  24. /// </summary>
  25. [Tooltip("If checked, all storyboards are globally muted")]
  26. public static bool s_StoryboardGlobalMute;
  27. /// <summary>
  28. /// If checked, the specified image will be displayed as an overlay over the virtual camera's output
  29. /// </summary>
  30. [Tooltip("If checked, the specified image will be displayed as an overlay over the virtual camera's output")]
  31. public bool m_ShowImage = true;
  32. /// <summary>
  33. /// The image to display
  34. /// </summary>
  35. [Tooltip("The image to display")]
  36. public Texture m_Image;
  37. /// <summary>How to fit the image in the frame, in the event that the aspect ratios don't match</summary>
  38. public enum FillStrategy
  39. {
  40. /// <summary>Image will be as large as possible on the screen, without being cropped</summary>
  41. BestFit,
  42. /// <summary>Image will be cropped if necessary so that the screen is entirely filled</summary>
  43. CropImageToFit,
  44. /// <summary>Image will be stretched to cover any aspect mismatch with the screen</summary>
  45. StretchToFit
  46. };
  47. /// <summary>
  48. /// How to handle differences between image aspect and screen aspect
  49. /// </summary>
  50. [Tooltip("How to handle differences between image aspect and screen aspect")]
  51. public FillStrategy m_Aspect = FillStrategy.BestFit;
  52. /// <summary>
  53. /// The opacity of the image. 0 is transparent, 1 is opaque
  54. /// </summary>
  55. [Tooltip("The opacity of the image. 0 is transparent, 1 is opaque")]
  56. [Range(0, 1)]
  57. public float m_Alpha = 1;
  58. /// <summary>
  59. /// The screen-space position at which to display the image. Zero is center
  60. /// </summary>
  61. [Tooltip("The screen-space position at which to display the image. Zero is center")]
  62. public Vector2 m_Center = Vector2.zero;
  63. /// <summary>
  64. /// The screen-space rotation to apply to the image
  65. /// </summary>
  66. [Tooltip("The screen-space rotation to apply to the image")]
  67. public Vector3 m_Rotation = Vector3.zero;
  68. /// <summary>
  69. /// The screen-space scaling to apply to the image
  70. /// </summary>
  71. [Tooltip("The screen-space scaling to apply to the image")]
  72. public Vector2 m_Scale = Vector3.one;
  73. /// <summary>
  74. /// If checked, X and Y scale are synchronized
  75. /// </summary>
  76. [Tooltip("If checked, X and Y scale are synchronized")]
  77. public bool m_SyncScale = true;
  78. /// <summary>
  79. /// If checked, Camera transform will not be controlled by this virtual camera
  80. /// </summary>
  81. [Tooltip("If checked, Camera transform will not be controlled by this virtual camera")]
  82. public bool m_MuteCamera;
  83. /// <summary>
  84. /// Wipe the image on and off horizontally
  85. /// </summary>
  86. [Range(-1, 1)]
  87. [Tooltip("Wipe the image on and off horizontally")]
  88. public float m_SplitView = 0f;
  89. /// <summary>
  90. /// The render mode of the canvas on which the storyboard is drawn.
  91. /// </summary>
  92. [Tooltip("The render mode of the canvas on which the storyboard is drawn.")]
  93. public StoryboardRenderMode m_RenderMode = StoryboardRenderMode.ScreenSpaceOverlay;
  94. /// <summary>
  95. /// Allows ordering canvases to render on top or below other canvases.
  96. /// </summary>
  97. [Tooltip("Allows ordering canvases to render on top or below other canvases.")]
  98. public int m_SortingOrder;
  99. /// <summary>
  100. /// How far away from the camera is the storyboard's canvas generated.
  101. /// </summary>
  102. [Tooltip("How far away from the camera is the Canvas generated.")]
  103. public float m_PlaneDistance = 100;
  104. class CanvasInfo
  105. {
  106. public GameObject mCanvas;
  107. public Canvas mCanvasComponent;
  108. public CinemachineBrain mCanvasParent;
  109. public RectTransform mViewport; // for mViewport clipping
  110. public UnityEngine.UI.RawImage mRawImage;
  111. }
  112. List<CanvasInfo> mCanvasInfo = new List<CanvasInfo>();
  113. /// <summary>Callback to display the image</summary>
  114. /// <param name="vcam">The virtual camera being processed</param>
  115. /// <param name="stage">The current pipeline stage</param>
  116. /// <param name="state">The current virtual camera state</param>
  117. /// <param name="deltaTime">The current applicable deltaTime</param>
  118. protected override void PostPipelineStageCallback(
  119. CinemachineVirtualCameraBase vcam,
  120. CinemachineCore.Stage stage, ref CameraState state, float deltaTime)
  121. {
  122. // Apply to this vcam only, not the children
  123. if (vcam != VirtualCamera || stage != CinemachineCore.Stage.Finalize)
  124. return;
  125. UpdateRenderCanvas();
  126. if (m_ShowImage)
  127. state.AddCustomBlendable(new CameraState.CustomBlendable(this, 1));
  128. if (m_MuteCamera)
  129. state.BlendHint |= CameraState.BlendHintValue.NoTransform | CameraState.BlendHintValue.NoLens;
  130. }
  131. /// <summary>
  132. /// Camera render modes supported by CinemachineStoryboard.
  133. /// </summary>
  134. public enum StoryboardRenderMode
  135. {
  136. /// <summary>
  137. /// Renders in camera screen space. This means, that the storyboard is going to be displayed in front of
  138. /// any objects in the scene. Equivalent to Unity's RenderMode.ScreenSpaceOverlay.
  139. /// </summary>
  140. ScreenSpaceOverlay = RenderMode.ScreenSpaceOverlay,
  141. /// <summary>
  142. /// Render using the vcam on which the storyboard is on. This is useful, if you'd like to render the
  143. /// storyboard at a specific distance from the vcam. Equivalent to Unity's RenderMode.ScreenSpaceCamera.
  144. /// </summary>
  145. ScreenSpaceCamera = RenderMode.ScreenSpaceCamera
  146. };
  147. void UpdateRenderCanvas()
  148. {
  149. for (int i = 0; i < mCanvasInfo.Count; ++i)
  150. {
  151. if (mCanvasInfo[i] == null || mCanvasInfo[i].mCanvasComponent == null)
  152. mCanvasInfo.RemoveAt(i--);
  153. else
  154. {
  155. mCanvasInfo[i].mCanvasComponent.renderMode = (RenderMode) m_RenderMode;
  156. mCanvasInfo[i].mCanvasComponent.planeDistance = m_PlaneDistance;
  157. mCanvasInfo[i].mCanvasComponent.sortingOrder = m_SortingOrder;
  158. }
  159. }
  160. }
  161. /// <summary>Connect to virtual camera. Adds/removes listener</summary>
  162. /// <param name="connect">True if connecting, false if disconnecting</param>
  163. protected override void ConnectToVcam(bool connect)
  164. {
  165. base.ConnectToVcam(connect);
  166. CinemachineCore.CameraUpdatedEvent.RemoveListener(CameraUpdatedCallback);
  167. if (connect)
  168. CinemachineCore.CameraUpdatedEvent.AddListener(CameraUpdatedCallback);
  169. else
  170. DestroyCanvas();
  171. }
  172. string CanvasName => "_CM_canvas" + gameObject.GetInstanceID();
  173. void CameraUpdatedCallback(CinemachineBrain brain)
  174. {
  175. bool showIt = enabled && m_ShowImage && CinemachineCore.Instance.IsLive(VirtualCamera);
  176. int layer = 1 << gameObject.layer;
  177. if (brain.OutputCamera == null || (brain.OutputCamera.cullingMask & layer) == 0)
  178. showIt = false;
  179. if (s_StoryboardGlobalMute)
  180. showIt = false;
  181. CanvasInfo ci = LocateMyCanvas(brain, showIt);
  182. if (ci != null && ci.mCanvas != null)
  183. ci.mCanvas.SetActive(showIt);
  184. }
  185. CanvasInfo LocateMyCanvas(CinemachineBrain parent, bool createIfNotFound)
  186. {
  187. CanvasInfo ci = null;
  188. for (int i = 0; ci == null && i < mCanvasInfo.Count; ++i)
  189. if (mCanvasInfo[i] != null && mCanvasInfo[i].mCanvasParent == parent)
  190. ci = mCanvasInfo[i];
  191. if (createIfNotFound)
  192. {
  193. if (ci == null)
  194. {
  195. ci = new CanvasInfo() { mCanvasParent = parent };
  196. int numChildren = parent.transform.childCount;
  197. for (int i = 0; ci.mCanvas == null && i < numChildren; ++i)
  198. {
  199. RectTransform child = parent.transform.GetChild(i) as RectTransform;
  200. if (child != null && child.name == CanvasName)
  201. {
  202. ci.mCanvas = child.gameObject;
  203. var kids = ci.mCanvas.GetComponentsInChildren<RectTransform>();
  204. ci.mViewport = kids.Length > 1 ? kids[1] : null; // 0 is mCanvas
  205. ci.mRawImage = ci.mCanvas.GetComponentInChildren<UnityEngine.UI.RawImage>();
  206. ci.mCanvasComponent = ci.mCanvas.GetComponent<Canvas>();
  207. }
  208. }
  209. mCanvasInfo.Add(ci);
  210. }
  211. if (ci.mCanvas == null || ci.mViewport == null || ci.mRawImage == null || ci.mCanvasComponent == null)
  212. CreateCanvas(ci);
  213. }
  214. return ci;
  215. }
  216. void CreateCanvas(CanvasInfo ci)
  217. {
  218. ci.mCanvas = new GameObject(CanvasName, typeof(RectTransform));
  219. ci.mCanvas.layer = gameObject.layer;
  220. ci.mCanvas.hideFlags = HideFlags.HideAndDontSave;
  221. ci.mCanvas.transform.SetParent(ci.mCanvasParent.transform);
  222. #if UNITY_EDITOR
  223. // Workaround for Unity bug case Case 1004117
  224. CanvasesAndTheirOwners.AddCanvas(ci.mCanvas, this);
  225. #endif
  226. var c = ci.mCanvasComponent = ci.mCanvas.AddComponent<Canvas>();
  227. c.renderMode = (RenderMode) m_RenderMode;
  228. c.sortingOrder = m_SortingOrder;
  229. c.planeDistance = m_PlaneDistance;
  230. c.worldCamera = ci.mCanvasParent.OutputCamera;
  231. var go = new GameObject("Viewport", typeof(RectTransform));
  232. go.transform.SetParent(ci.mCanvas.transform);
  233. ci.mViewport = (RectTransform)go.transform;
  234. go.AddComponent<UnityEngine.UI.RectMask2D>();
  235. go = new GameObject("RawImage", typeof(RectTransform));
  236. go.transform.SetParent(ci.mViewport.transform);
  237. ci.mRawImage = go.AddComponent<UnityEngine.UI.RawImage>();
  238. }
  239. void DestroyCanvas()
  240. {
  241. int numBrains = CinemachineCore.Instance.BrainCount;
  242. for (int i = 0; i < numBrains; ++i)
  243. {
  244. var parent = CinemachineCore.Instance.GetActiveBrain(i);
  245. int numChildren = parent.transform.childCount;
  246. for (int j = numChildren - 1; j >= 0; --j)
  247. {
  248. RectTransform child = parent.transform.GetChild(j) as RectTransform;
  249. if (child != null && child.name == CanvasName)
  250. {
  251. var canvas = child.gameObject;
  252. RuntimeUtility.DestroyObject(canvas);
  253. #if UNITY_EDITOR
  254. // Workaround for Unity bug case Case 1004117
  255. CanvasesAndTheirOwners.RemoveCanvas(canvas);
  256. #endif
  257. }
  258. }
  259. }
  260. mCanvasInfo.Clear();
  261. }
  262. void PlaceImage(CanvasInfo ci, float alpha)
  263. {
  264. if (ci.mRawImage != null && ci.mViewport != null)
  265. {
  266. Rect screen = new Rect(0, 0, Screen.width, Screen.height);
  267. if (ci.mCanvasParent.OutputCamera != null)
  268. screen = ci.mCanvasParent.OutputCamera.pixelRect;
  269. screen.x -= (float)Screen.width/2;
  270. screen.y -= (float)Screen.height/2;
  271. // Apply Split View
  272. float wipeAmount = -Mathf.Clamp(m_SplitView, -1, 1) * screen.width;
  273. Vector3 pos = screen.center;
  274. pos.x -= wipeAmount/2;
  275. ci.mViewport.localPosition = pos;
  276. ci.mViewport.localRotation = Quaternion.identity;
  277. ci.mViewport.localScale = Vector3.one;
  278. ci.mViewport.ForceUpdateRectTransforms();
  279. ci.mViewport.sizeDelta = new Vector2(screen.width + 1 - Mathf.Abs(wipeAmount), screen.height + 1);
  280. Vector2 scale = Vector2.one;
  281. if (m_Image != null
  282. && m_Image.width > 0 && m_Image.width > 0
  283. && screen.width > 0 && screen.height > 0)
  284. {
  285. float f = (screen.height * m_Image.width) / (screen.width * m_Image.height);
  286. switch (m_Aspect)
  287. {
  288. case FillStrategy.BestFit:
  289. if (f >= 1)
  290. scale.y /= f;
  291. else
  292. scale.x *= f;
  293. break;
  294. case FillStrategy.CropImageToFit:
  295. if (f >= 1)
  296. scale.x *= f;
  297. else
  298. scale.y /= f;
  299. break;
  300. case FillStrategy.StretchToFit:
  301. break;
  302. }
  303. }
  304. scale.x *= m_Scale.x;
  305. scale.y *= m_SyncScale ? m_Scale.x : m_Scale.y;
  306. ci.mRawImage.texture = m_Image;
  307. Color tintColor = Color.white;
  308. tintColor.a = m_Alpha * alpha;
  309. ci.mRawImage.color = tintColor;
  310. pos = new Vector2(screen.width * m_Center.x, screen.height * m_Center.y);
  311. pos.x += wipeAmount/2;
  312. ci.mRawImage.rectTransform.localPosition = pos;
  313. ci.mRawImage.rectTransform.localRotation = Quaternion.Euler(m_Rotation);
  314. ci.mRawImage.rectTransform.localScale = scale;
  315. ci.mRawImage.rectTransform.ForceUpdateRectTransforms();
  316. ci.mRawImage.rectTransform.sizeDelta = screen.size;
  317. }
  318. }
  319. static void StaticBlendingHandler(CinemachineBrain brain)
  320. {
  321. CameraState state = brain.CurrentCameraState;
  322. int numBlendables = state.NumCustomBlendables;
  323. for (int i = 0; i < numBlendables; ++i)
  324. {
  325. var b = state.GetCustomBlendable(i);
  326. CinemachineStoryboard src = b.m_Custom as CinemachineStoryboard;
  327. if (!(src == null)) // in case it was deleted
  328. {
  329. bool showIt = true;
  330. int layer = 1 << src.gameObject.layer;
  331. if (brain.OutputCamera == null || (brain.OutputCamera.cullingMask & layer) == 0)
  332. showIt = false;
  333. if (s_StoryboardGlobalMute)
  334. showIt = false;
  335. CanvasInfo ci = src.LocateMyCanvas(brain, showIt);
  336. if (ci != null)
  337. src.PlaceImage(ci, b.m_Weight);
  338. }
  339. }
  340. }
  341. #if UNITY_EDITOR
  342. [UnityEditor.InitializeOnLoad]
  343. class EditorInitialize { static EditorInitialize() { InitializeModule(); } }
  344. #endif
  345. [RuntimeInitializeOnLoadMethod]
  346. static void InitializeModule()
  347. {
  348. CinemachineCore.CameraUpdatedEvent.RemoveListener(StaticBlendingHandler);
  349. CinemachineCore.CameraUpdatedEvent.AddListener(StaticBlendingHandler);
  350. }
  351. #if UNITY_EDITOR
  352. // Workaround for the Unity bug where OnDestroy doesn't get called if Undo
  353. // bug case Case 1004117
  354. [UnityEditor.InitializeOnLoad]
  355. class CanvasesAndTheirOwners
  356. {
  357. static Dictionary<UnityEngine.Object, UnityEngine.Object> sCanvasesAndTheirOwners;
  358. static CanvasesAndTheirOwners()
  359. {
  360. UnityEditor.Undo.undoRedoPerformed -= OnUndoRedoPerformed;
  361. UnityEditor.Undo.undoRedoPerformed += OnUndoRedoPerformed;
  362. }
  363. static void OnUndoRedoPerformed()
  364. {
  365. if (sCanvasesAndTheirOwners != null)
  366. {
  367. List<UnityEngine.Object> toDestroy = null;
  368. foreach (var v in sCanvasesAndTheirOwners)
  369. {
  370. if (v.Value == null)
  371. {
  372. if (toDestroy == null)
  373. toDestroy = new List<UnityEngine.Object>();
  374. toDestroy.Add(v.Key);
  375. }
  376. }
  377. if (toDestroy != null)
  378. {
  379. foreach (var o in toDestroy)
  380. {
  381. RemoveCanvas(o);
  382. RuntimeUtility.DestroyObject(o);
  383. }
  384. }
  385. }
  386. }
  387. public static void RemoveCanvas(UnityEngine.Object canvas)
  388. {
  389. if (sCanvasesAndTheirOwners != null && sCanvasesAndTheirOwners.ContainsKey(canvas))
  390. sCanvasesAndTheirOwners.Remove(canvas);
  391. }
  392. public static void AddCanvas(UnityEngine.Object canvas, UnityEngine.Object owner)
  393. {
  394. if (sCanvasesAndTheirOwners == null)
  395. sCanvasesAndTheirOwners = new Dictionary<UnityEngine.Object, UnityEngine.Object>();
  396. sCanvasesAndTheirOwners.Add(canvas, owner);
  397. }
  398. }
  399. #endif
  400. }
  401. }
  402. #else
  403. // We need this dummy MonoBehaviour for Unity to properly recognize this script asset.
  404. namespace Cinemachine
  405. {
  406. [AddComponentMenu("")] // Hide in menu
  407. public class CinemachineStoryboard : MonoBehaviour {}
  408. }
  409. #endif