CinemachineStoryboard.cs 16 KB

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