CinemachineMixer.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  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 CinemachineBrain mBrain;
  17. private int mBrainOverrideId = -1;
  18. private bool mPreviewPlay;
  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.HasHurrentTime)
  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 (deltaTime < 0)
  108. vcam.ForceCameraPosition(
  109. TargetPositionCache.GetTargetPosition(vcam.transform),
  110. TargetPositionCache.GetTargetRotation(vcam.transform));
  111. vcam.InternalUpdateCameraState(up, deltaTime);
  112. }
  113. }
  114. }
  115. }
  116. }
  117. ScrubbingCacheHelper m_ScrubbingCacheHelper;
  118. #endif
  119. #if UNITY_EDITOR && UNITY_2019_2_OR_NEWER
  120. public override void OnGraphStart(Playable playable)
  121. {
  122. base.OnGraphStart(playable);
  123. m_ScrubbingCacheHelper = null;
  124. }
  125. #endif
  126. public override void OnPlayableDestroy(Playable playable)
  127. {
  128. if (mBrain != null)
  129. mBrain.ReleaseCameraOverride(mBrainOverrideId); // clean up
  130. mBrainOverrideId = -1;
  131. #if UNITY_EDITOR && UNITY_2019_2_OR_NEWER
  132. m_ScrubbingCacheHelper = null;
  133. #endif
  134. }
  135. public override void PrepareFrame(Playable playable, FrameData info)
  136. {
  137. mPreviewPlay = false;
  138. #if UNITY_EDITOR && UNITY_2019_2_OR_NEWER
  139. var cacheMode = TargetPositionCache.Mode.Disabled;
  140. if (!Application.isPlaying)
  141. {
  142. if (GetMasterPlayableDirector != null)
  143. {
  144. var d = GetMasterPlayableDirector();
  145. if (d != null && d.playableGraph.IsValid())
  146. mPreviewPlay = GetMasterPlayableDirector().playableGraph.IsPlaying();
  147. }
  148. if (TargetPositionCache.UseCache)
  149. {
  150. cacheMode = mPreviewPlay ? TargetPositionCache.Mode.Record : TargetPositionCache.Mode.Playback;
  151. if (m_ScrubbingCacheHelper == null)
  152. {
  153. m_ScrubbingCacheHelper = new ScrubbingCacheHelper();
  154. m_ScrubbingCacheHelper.Init(playable);
  155. }
  156. }
  157. }
  158. TargetPositionCache.CacheMode = cacheMode;
  159. #endif
  160. }
  161. public override void ProcessFrame(Playable playable, FrameData info, object playerData)
  162. {
  163. base.ProcessFrame(playable, info, playerData);
  164. // Get the brain that this track controls.
  165. // Older versions of timeline sent the gameObject by mistake.
  166. GameObject go = playerData as GameObject;
  167. if (go == null)
  168. mBrain = (CinemachineBrain)playerData;
  169. else
  170. mBrain = go.GetComponent<CinemachineBrain>();
  171. if (mBrain == null)
  172. return;
  173. // Find which clips are active. We can process a maximum of 2.
  174. // In the case that the weights don't add up to 1, the outgoing weight
  175. // will be calculated as the inverse of the incoming weight.
  176. int activeInputs = 0;
  177. int clipIndexA = -1;
  178. int clipIndexB = -1;
  179. bool incomingIsA = false; // Assume that incoming clip is clip B
  180. float weightB = 1;
  181. for (int i = 0; i < playable.GetInputCount(); ++i)
  182. {
  183. float weight = playable.GetInputWeight(i);
  184. var clip = (ScriptPlayable<CinemachineShotPlayable>)playable.GetInput(i);
  185. CinemachineShotPlayable shot = clip.GetBehaviour();
  186. if (shot != null && shot.IsValid
  187. && playable.GetPlayState() == PlayState.Playing
  188. && weight > 0)
  189. {
  190. clipIndexA = clipIndexB;
  191. clipIndexB = i;
  192. weightB = weight;
  193. if (++activeInputs == 2)
  194. {
  195. // Deduce which clip is incoming (timeline doesn't know)
  196. var clipA = playable.GetInput(clipIndexA);
  197. // Incoming has later start time (therefore earlier current time)
  198. incomingIsA = clip.GetTime() >= clipA.GetTime();
  199. // If same start time, longer clip is incoming
  200. if (clip.GetTime() == clipA.GetTime())
  201. incomingIsA = clip.GetDuration() < clipA.GetDuration();
  202. break;
  203. }
  204. }
  205. }
  206. // Special case: check for only one clip that's fading out - it must be outgoing
  207. if (activeInputs == 1 && weightB < 1
  208. && playable.GetInput(clipIndexB).GetTime() > playable.GetInput(clipIndexB).GetDuration() / 2)
  209. {
  210. incomingIsA = true;
  211. }
  212. if (incomingIsA)
  213. {
  214. (clipIndexA, clipIndexB) = (clipIndexB, clipIndexA);
  215. weightB = 1 - weightB;
  216. }
  217. ICinemachineCamera camA = null;
  218. if (clipIndexA >= 0)
  219. {
  220. CinemachineShotPlayable shot
  221. = ((ScriptPlayable<CinemachineShotPlayable>)playable.GetInput(clipIndexA)).GetBehaviour();
  222. camA = shot.VirtualCamera;
  223. }
  224. ICinemachineCamera camB = null;
  225. if (clipIndexB >= 0)
  226. {
  227. CinemachineShotPlayable shot
  228. = ((ScriptPlayable<CinemachineShotPlayable>)playable.GetInput(clipIndexB)).GetBehaviour();
  229. camB = shot.VirtualCamera;
  230. }
  231. // Override the Cinemachine brain with our results
  232. mBrainOverrideId = mBrain.SetCameraOverride(
  233. mBrainOverrideId, camA, camB, weightB, GetDeltaTime(info.deltaTime));
  234. #if UNITY_EDITOR && UNITY_2019_2_OR_NEWER
  235. if (m_ScrubbingCacheHelper != null && TargetPositionCache.CacheMode != TargetPositionCache.Mode.Disabled)
  236. {
  237. bool isNewB = (m_ScrubbingCacheHelper.ActivePlayableA != clipIndexB
  238. && m_ScrubbingCacheHelper.ActivePlayableB != clipIndexB);
  239. m_ScrubbingCacheHelper.ActivePlayableA = clipIndexA;
  240. m_ScrubbingCacheHelper.ActivePlayableB = clipIndexB;
  241. if (clipIndexA >= 0)
  242. m_ScrubbingCacheHelper.ScrubToHere(
  243. (float)GetMasterPlayableDirector().time, clipIndexA, false,
  244. (float)playable.GetInput(clipIndexA).GetTime(), mBrain.DefaultWorldUp);
  245. if (clipIndexB >= 0)
  246. m_ScrubbingCacheHelper.ScrubToHere(
  247. (float)GetMasterPlayableDirector().time, clipIndexB, isNewB && weightB > 0.99f,
  248. (float)playable.GetInput(clipIndexB).GetTime(), mBrain.DefaultWorldUp);
  249. }
  250. #endif
  251. }
  252. float GetDeltaTime(float deltaTime)
  253. {
  254. if (mPreviewPlay || Application.isPlaying)
  255. return deltaTime;
  256. // We're scrubbing or paused
  257. if (TargetPositionCache.CacheMode == TargetPositionCache.Mode.Playback
  258. && TargetPositionCache.HasHurrentTime)
  259. {
  260. return 0;
  261. }
  262. return -1;
  263. }
  264. }
  265. //}
  266. #endif