CinemachineMixer.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. #if !UNITY_2019_1_OR_NEWER
  2. #define CINEMACHINE_TIMELINE
  3. #endif
  4. #if CINEMACHINE_TIMELINE
  5. using UnityEngine;
  6. using UnityEngine.Playables;
  7. using Cinemachine;
  8. using System.Collections.Generic;
  9. //namespace Cinemachine.Timeline
  10. //{
  11. internal sealed class CinemachineMixer : PlayableBehaviour
  12. {
  13. public delegate PlayableDirector MasterDirectorDelegate();
  14. static public MasterDirectorDelegate GetMasterPlayableDirector;
  15. // The brain that this track controls
  16. private ICameraOverrideStack m_BrainOverrideStack;
  17. private int m_BrainOverrideId = -1;
  18. private bool m_PreviewPlay;
  19. #if UNITY_EDITOR && UNITY_2019_2_OR_NEWER
  20. class ScrubbingCacheHelper
  21. {
  22. // Remember the active clips of the previous frame so we can track camera cuts
  23. public int ActivePlayableA;
  24. public int ActivePlayableB;
  25. struct ClipObjects
  26. {
  27. public List<List<CinemachineVirtualCameraBase>> Cameras;
  28. public float MaxDampTime;
  29. }
  30. List<ClipObjects> CachedObjects;
  31. static List<CinemachineVirtualCameraBase> scratch = new List<CinemachineVirtualCameraBase>();
  32. public void Init(Playable playable)
  33. {
  34. // Build our vcam registry for scrubbing updates
  35. CachedObjects = new List<ClipObjects>(playable.GetInputCount());
  36. for (int i = 0; i < playable.GetInputCount(); ++i)
  37. {
  38. var cs = new ClipObjects
  39. {
  40. Cameras = new List<List<CinemachineVirtualCameraBase>>(),
  41. };
  42. var clip = (ScriptPlayable<CinemachineShotPlayable>)playable.GetInput(i);
  43. CinemachineShotPlayable shot = clip.GetBehaviour();
  44. if (shot != null && shot.IsValid)
  45. {
  46. var mainVcam = shot.VirtualCamera;
  47. cs.Cameras.Add(new List<CinemachineVirtualCameraBase>());
  48. // Add all child cameras
  49. scratch.Clear();
  50. mainVcam.GetComponentsInChildren(scratch);
  51. for (int j = 0; j < scratch.Count; ++j)
  52. {
  53. var vcam = scratch[j];
  54. int nestLevel = 0;
  55. for (ICinemachineCamera p = vcam.ParentCamera;
  56. p != null && p != (ICinemachineCamera)mainVcam; p = p.ParentCamera)
  57. {
  58. ++nestLevel;
  59. }
  60. while (cs.Cameras.Count <= nestLevel)
  61. cs.Cameras.Add(new List<CinemachineVirtualCameraBase>());
  62. cs.Cameras[nestLevel].Add(vcam);
  63. cs.MaxDampTime = Mathf.Max(cs.MaxDampTime, vcam.GetMaxDampTime());
  64. }
  65. }
  66. CachedObjects.Add(cs);
  67. }
  68. }
  69. public void ScrubToHere(float currentTime, int playableIndex, bool isCut, float timeInClip, Vector3 up)
  70. {
  71. TargetPositionCache.CurrentTime = currentTime;
  72. if (TargetPositionCache.CacheMode == TargetPositionCache.Mode.Record)
  73. {
  74. // If the clip is newly activated, force the time to clip start,
  75. // in case timeline skipped some frames. This will avoid target lerps between shots.
  76. if (Time.frameCount != TargetPositionCache.CurrentFrame)
  77. TargetPositionCache.IsCameraCut = false;
  78. TargetPositionCache.CurrentFrame = Time.frameCount;
  79. if (isCut)
  80. TargetPositionCache.IsCameraCut = true;
  81. return;
  82. }
  83. if (!TargetPositionCache.HasCurrentTime)
  84. return;
  85. var cs = CachedObjects[playableIndex];
  86. float stepSize = TargetPositionCache.CacheStepSize;
  87. // Impose upper limit on damping time, to avoid simulating too many frames
  88. float maxDampTime = Mathf.Max(0, timeInClip - stepSize);
  89. maxDampTime = Mathf.Min(cs.MaxDampTime, Mathf.Min(maxDampTime, 4.0f));
  90. var endTime = TargetPositionCache.CurrentTime;
  91. var startTime = Mathf.Max(
  92. TargetPositionCache.CacheTimeRange.Start + stepSize, endTime - maxDampTime);
  93. var numSteps = Mathf.FloorToInt((endTime - startTime) / stepSize);
  94. for (int step = numSteps; step >= 0; --step)
  95. {
  96. var t = Mathf.Max(startTime, endTime - step * stepSize);
  97. TargetPositionCache.CurrentTime = t;
  98. var deltaTime = (step == numSteps) ? -1
  99. : (t - startTime < stepSize ? t - startTime : stepSize);
  100. // Update all relevant vcams, leaf-most first
  101. for (int i = cs.Cameras.Count - 1; i >= 0; --i)
  102. {
  103. var sublist = cs.Cameras[i];
  104. for (int j = sublist.Count - 1; j >= 0; --j)
  105. {
  106. var vcam = sublist[j];
  107. if (vcam == null)
  108. continue;
  109. if (deltaTime < 0)
  110. vcam.ForceCameraPosition(
  111. TargetPositionCache.GetTargetPosition(vcam.transform),
  112. TargetPositionCache.GetTargetRotation(vcam.transform));
  113. vcam.InternalUpdateCameraState(up, deltaTime);
  114. }
  115. }
  116. }
  117. }
  118. }
  119. ScrubbingCacheHelper m_ScrubbingCacheHelper;
  120. #endif
  121. #if UNITY_EDITOR && UNITY_2019_2_OR_NEWER
  122. public override void OnGraphStart(Playable playable)
  123. {
  124. base.OnGraphStart(playable);
  125. m_ScrubbingCacheHelper = null;
  126. }
  127. #endif
  128. public override void OnPlayableDestroy(Playable playable)
  129. {
  130. if (m_BrainOverrideStack != null)
  131. m_BrainOverrideStack.ReleaseCameraOverride(m_BrainOverrideId); // clean up
  132. m_BrainOverrideId = -1;
  133. #if UNITY_EDITOR && UNITY_2019_2_OR_NEWER
  134. m_ScrubbingCacheHelper = null;
  135. #endif
  136. }
  137. public override void PrepareFrame(Playable playable, FrameData info)
  138. {
  139. m_PreviewPlay = false;
  140. #if UNITY_EDITOR && UNITY_2019_2_OR_NEWER
  141. var cacheMode = TargetPositionCache.Mode.Disabled;
  142. if (!Application.isPlaying)
  143. {
  144. if (GetMasterPlayableDirector != null)
  145. {
  146. var d = GetMasterPlayableDirector();
  147. if (d != null && d.playableGraph.IsValid())
  148. m_PreviewPlay = GetMasterPlayableDirector().playableGraph.IsPlaying();
  149. }
  150. if (TargetPositionCache.UseCache)
  151. {
  152. cacheMode = m_PreviewPlay ? TargetPositionCache.Mode.Record : TargetPositionCache.Mode.Playback;
  153. if (m_ScrubbingCacheHelper == null)
  154. {
  155. m_ScrubbingCacheHelper = new ScrubbingCacheHelper();
  156. m_ScrubbingCacheHelper.Init(playable);
  157. }
  158. }
  159. }
  160. TargetPositionCache.CacheMode = cacheMode;
  161. #endif
  162. }
  163. public override void ProcessFrame(Playable playable, FrameData info, object playerData)
  164. {
  165. base.ProcessFrame(playable, info, playerData);
  166. // Get the object that this track controls
  167. m_BrainOverrideStack = playerData as ICameraOverrideStack;
  168. if (m_BrainOverrideStack == null)
  169. return;
  170. // Find which clips are active. We can process a maximum of 2.
  171. // In the case that the weights don't add up to 1, the outgoing weight
  172. // will be calculated as the inverse of the incoming weight.
  173. int activeInputs = 0;
  174. int clipIndexA = -1;
  175. int clipIndexB = -1;
  176. bool incomingIsA = false; // Assume that incoming clip is clip B
  177. float weightB = 1;
  178. for (int i = 0; i < playable.GetInputCount(); ++i)
  179. {
  180. float weight = playable.GetInputWeight(i);
  181. var clip = (ScriptPlayable<CinemachineShotPlayable>)playable.GetInput(i);
  182. CinemachineShotPlayable shot = clip.GetBehaviour();
  183. if (shot != null && shot.IsValid
  184. && playable.GetPlayState() == PlayState.Playing
  185. && weight > 0)
  186. {
  187. clipIndexA = clipIndexB;
  188. clipIndexB = i;
  189. weightB = weight;
  190. if (++activeInputs == 2)
  191. {
  192. // Deduce which clip is incoming (timeline doesn't know)
  193. var clipA = playable.GetInput(clipIndexA);
  194. // Incoming has later start time (therefore earlier current time)
  195. incomingIsA = clip.GetTime() >= clipA.GetTime();
  196. // If same start time, longer clip is incoming
  197. if (clip.GetTime() == clipA.GetTime())
  198. incomingIsA = clip.GetDuration() < clipA.GetDuration();
  199. break;
  200. }
  201. }
  202. }
  203. // Special case: check for only one clip that's fading out - it must be outgoing
  204. if (activeInputs == 1 && weightB < 1
  205. && playable.GetInput(clipIndexB).GetTime() > playable.GetInput(clipIndexB).GetDuration() / 2)
  206. {
  207. incomingIsA = true;
  208. }
  209. if (incomingIsA)
  210. {
  211. (clipIndexA, clipIndexB) = (clipIndexB, clipIndexA);
  212. weightB = 1 - weightB;
  213. }
  214. ICinemachineCamera camA = null;
  215. if (clipIndexA >= 0)
  216. {
  217. CinemachineShotPlayable shot
  218. = ((ScriptPlayable<CinemachineShotPlayable>)playable.GetInput(clipIndexA)).GetBehaviour();
  219. camA = shot.VirtualCamera;
  220. }
  221. ICinemachineCamera camB = null;
  222. if (clipIndexB >= 0)
  223. {
  224. CinemachineShotPlayable shot
  225. = ((ScriptPlayable<CinemachineShotPlayable>)playable.GetInput(clipIndexB)).GetBehaviour();
  226. camB = shot.VirtualCamera;
  227. }
  228. // Override the Cinemachine brain with our results
  229. m_BrainOverrideId = m_BrainOverrideStack.SetCameraOverride(
  230. m_BrainOverrideId, camA, camB, weightB, GetDeltaTime(info.deltaTime));
  231. #if UNITY_EDITOR && UNITY_2019_2_OR_NEWER
  232. if (m_ScrubbingCacheHelper != null && TargetPositionCache.CacheMode != TargetPositionCache.Mode.Disabled)
  233. {
  234. bool isNewB = (m_ScrubbingCacheHelper.ActivePlayableA != clipIndexB
  235. && m_ScrubbingCacheHelper.ActivePlayableB != clipIndexB);
  236. m_ScrubbingCacheHelper.ActivePlayableA = clipIndexA;
  237. m_ScrubbingCacheHelper.ActivePlayableB = clipIndexB;
  238. if (clipIndexA >= 0)
  239. m_ScrubbingCacheHelper.ScrubToHere(
  240. (float)GetMasterPlayableDirector().time, clipIndexA, false,
  241. (float)playable.GetInput(clipIndexA).GetTime(), m_BrainOverrideStack.DefaultWorldUp);
  242. if (clipIndexB >= 0)
  243. m_ScrubbingCacheHelper.ScrubToHere(
  244. (float)GetMasterPlayableDirector().time, clipIndexB, isNewB && weightB > 0.99f,
  245. (float)playable.GetInput(clipIndexB).GetTime(), m_BrainOverrideStack.DefaultWorldUp);
  246. }
  247. #endif
  248. }
  249. float GetDeltaTime(float deltaTime)
  250. {
  251. if (m_PreviewPlay || Application.isPlaying)
  252. return deltaTime;
  253. // We're scrubbing or paused
  254. if (TargetPositionCache.CacheMode == TargetPositionCache.Mode.Playback
  255. && TargetPositionCache.HasCurrentTime)
  256. {
  257. return 0;
  258. }
  259. return -1;
  260. }
  261. }
  262. //}
  263. #endif