CinemachineStateDrivenCamera.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. #if !UNITY_2019_3_OR_NEWER
  2. #define CINEMACHINE_UNITY_ANIMATION
  3. #endif
  4. using Cinemachine.Utility;
  5. using System;
  6. using System.Collections.Generic;
  7. using UnityEngine;
  8. namespace Cinemachine
  9. {
  10. #if CINEMACHINE_UNITY_ANIMATION
  11. /// <summary>
  12. /// This is a virtual camera "manager" that owns and manages a collection
  13. /// of child Virtual Cameras. These child vcams are mapped to individual states in
  14. /// an animation state machine, allowing you to associate specific vcams to specific
  15. /// animation states. When that state is active in the state machine, then the
  16. /// associated camera will be activated.
  17. ///
  18. /// You can define custom blends and transitions between child cameras.
  19. ///
  20. /// In order to use this behaviour, you must have an animated target (i.e. an object
  21. /// animated with a state machine) to drive the behaviour.
  22. /// </summary>
  23. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  24. [DisallowMultipleComponent]
  25. [ExecuteAlways]
  26. [ExcludeFromPreset]
  27. [AddComponentMenu("Cinemachine/CinemachineStateDrivenCamera")]
  28. [HelpURL(Documentation.BaseURL + "manual/CinemachineStateDrivenCamera.html")]
  29. public class CinemachineStateDrivenCamera : CinemachineVirtualCameraBase
  30. {
  31. /// <summary>Default object for the camera children to look at (the aim target),
  32. /// if not specified in a child rig. May be empty</summary>
  33. [Tooltip("Default object for the camera children to look at (the aim target), "
  34. + "if not specified in a child camera. May be empty if all of the children "
  35. + "define targets of their own.")]
  36. [NoSaveDuringPlay]
  37. [VcamTargetProperty]
  38. public Transform m_LookAt = null;
  39. /// <summary>Default object for the camera children wants to move with (the body target),
  40. /// if not specified in a child rig. May be empty</summary>
  41. [Tooltip("Default object for the camera children wants to move with (the body target), "
  42. + "if not specified in a child camera. May be empty if all of the children "
  43. + "define targets of their own.")]
  44. [NoSaveDuringPlay]
  45. [VcamTargetProperty]
  46. public Transform m_Follow = null;
  47. /// <summary>The state machine whose state changes will drive this camera's choice of active child</summary>
  48. [Space]
  49. [Tooltip("The state machine whose state changes will drive this camera's choice of active child")]
  50. [NoSaveDuringPlay]
  51. public Animator m_AnimatedTarget;
  52. /// <summary>Which layer in the target FSM to observe</summary>
  53. [Tooltip("Which layer in the target state machine to observe")]
  54. [NoSaveDuringPlay]
  55. public int m_LayerIndex;
  56. /// <summary>When enabled, the current camera and blend will be indicated in
  57. /// the game window, for debugging</summary>
  58. [Tooltip("When enabled, the current child camera and blend will be indicated in "
  59. + "the game window, for debugging")]
  60. public bool m_ShowDebugText = false;
  61. /// <summary>Internal API for the editor. Do not use this field</summary>
  62. [SerializeField][HideInInspector][NoSaveDuringPlay]
  63. internal CinemachineVirtualCameraBase[] m_ChildCameras = null;
  64. /// <summary>This represents a single instrunction to the StateDrivenCamera. It associates
  65. /// an state from the state machine with a child Virtual Camera, and also holds
  66. /// activation tuning parameters.</summary>
  67. [Serializable]
  68. public struct Instruction
  69. {
  70. /// <summary>The full hash of the animation state</summary>
  71. [Tooltip("The full hash of the animation state")]
  72. public int m_FullHash;
  73. /// <summary>The virtual camera to activate when the animation state becomes active</summary>
  74. [Tooltip("The virtual camera to activate when the animation state becomes active")]
  75. public CinemachineVirtualCameraBase m_VirtualCamera;
  76. /// <summary>How long to wait (in seconds) before activating the virtual camera.
  77. /// This filters out very short state durations</summary>
  78. [Tooltip("How long to wait (in seconds) before activating the virtual camera. "
  79. + "This filters out very short state durations")]
  80. public float m_ActivateAfter;
  81. /// <summary>The minimum length of time (in seconds) to keep a virtual camera active</summary>
  82. [Tooltip("The minimum length of time (in seconds) to keep a virtual camera active")]
  83. public float m_MinDuration;
  84. };
  85. /// <summary>The set of instructions associating virtual cameras with states.
  86. /// These instructions are used to choose the live child at any given moment</summary>
  87. [Tooltip("The set of instructions associating virtual cameras with states. "
  88. + "These instructions are used to choose the live child at any given moment")]
  89. public Instruction[] m_Instructions;
  90. /// <summary>
  91. /// The blend which is used if you don't explicitly define a blend between two Virtual Camera children.
  92. /// </summary>
  93. [CinemachineBlendDefinitionProperty]
  94. [Tooltip("The blend which is used if you don't explicitly define a blend between two Virtual Camera children")]
  95. public CinemachineBlendDefinition m_DefaultBlend
  96. = new CinemachineBlendDefinition(CinemachineBlendDefinition.Style.EaseInOut, 0.5f);
  97. /// <summary>
  98. /// This is the asset which contains custom settings for specific child blends.
  99. /// </summary>
  100. [Tooltip("This is the asset which contains custom settings for specific child blends")]
  101. public CinemachineBlenderSettings m_CustomBlends = null;
  102. /// <summary>Internal API for the Inspector editor. This implements nested states.</summary>
  103. [Serializable]
  104. [DocumentationSorting(DocumentationSortingAttribute.Level.Undoc)]
  105. internal struct ParentHash
  106. {
  107. /// <summary>Internal API for the Inspector editor</summary>
  108. public int m_Hash;
  109. /// <summary>Internal API for the Inspector editor</summary>
  110. public int m_ParentHash;
  111. /// <summary>Internal API for the Inspector editor</summary>
  112. public ParentHash(int h, int p) { m_Hash = h; m_ParentHash = p; }
  113. }
  114. /// <summary>Internal API for the Inspector editor</summary>
  115. [HideInInspector][SerializeField] internal ParentHash[] m_ParentHash = null;
  116. /// <summary>Gets a brief debug description of this virtual camera, for use when displayiong debug info</summary>
  117. public override string Description
  118. {
  119. get
  120. {
  121. // Show the active camera and blend
  122. if (mActiveBlend != null)
  123. return mActiveBlend.Description;
  124. ICinemachineCamera vcam = LiveChild;
  125. if (vcam == null)
  126. return "(none)";
  127. var sb = CinemachineDebug.SBFromPool();
  128. sb.Append("["); sb.Append(vcam.Name); sb.Append("]");
  129. string text = sb.ToString();
  130. CinemachineDebug.ReturnToPool(sb);
  131. return text;
  132. }
  133. }
  134. /// <summary>Get the current "best" child virtual camera, that would be chosen
  135. /// if the State Driven Camera were active.</summary>
  136. public ICinemachineCamera LiveChild { get; set; }
  137. /// <summary>Check whether the vcam a live child of this camera.</summary>
  138. /// <param name="vcam">The Virtual Camera to check</param>
  139. /// <param name="dominantChildOnly">If true, will only return true if this vcam is the dominat live child</param>
  140. /// <returns>True if the vcam is currently actively influencing the state of this vcam</returns>
  141. public override bool IsLiveChild(ICinemachineCamera vcam, bool dominantChildOnly = false)
  142. {
  143. return vcam == LiveChild || (mActiveBlend != null && mActiveBlend.Uses(vcam));
  144. }
  145. /// <summary>The State of the current live child</summary>
  146. public override CameraState State { get { return m_State; } }
  147. /// <summary>Get the current LookAt target. Returns parent's LookAt if parent
  148. /// is non-null and no specific LookAt defined for this camera</summary>
  149. override public Transform LookAt
  150. {
  151. get { return ResolveLookAt(m_LookAt); }
  152. set { m_LookAt = value; }
  153. }
  154. /// <summary>Get the current Follow target. Returns parent's Follow if parent
  155. /// is non-null and no specific Follow defined for this camera</summary>
  156. override public Transform Follow
  157. {
  158. get { return ResolveFollow(m_Follow); }
  159. set { m_Follow = value; }
  160. }
  161. /// <summary>This is called to notify the vcam that a target got warped,
  162. /// so that the vcam can update its internal state to make the camera
  163. /// also warp seamlessly.</summary>
  164. /// <param name="target">The object that was warped</param>
  165. /// <param name="positionDelta">The amount the target's position changed</param>
  166. public override void OnTargetObjectWarped(Transform target, Vector3 positionDelta)
  167. {
  168. UpdateListOfChildren();
  169. foreach (var vcam in m_ChildCameras)
  170. vcam.OnTargetObjectWarped(target, positionDelta);
  171. base.OnTargetObjectWarped(target, positionDelta);
  172. }
  173. /// <summary>
  174. /// Force the virtual camera to assume a given position and orientation
  175. /// </summary>
  176. /// <param name="pos">Worldspace pposition to take</param>
  177. /// <param name="rot">Worldspace orientation to take</param>
  178. public override void ForceCameraPosition(Vector3 pos, Quaternion rot)
  179. {
  180. UpdateListOfChildren();
  181. foreach (var vcam in m_ChildCameras)
  182. vcam.ForceCameraPosition(pos, rot);
  183. base.ForceCameraPosition(pos, rot);
  184. }
  185. /// <summary>Notification that this virtual camera is going live.</summary>
  186. /// <param name="fromCam">The camera being deactivated. May be null.</param>
  187. /// <param name="worldUp">Default world Up, set by the CinemachineBrain</param>
  188. /// <param name="deltaTime">Delta time for time-based effects (ignore if less than or equal to 0)</param>
  189. public override void OnTransitionFromCamera(
  190. ICinemachineCamera fromCam, Vector3 worldUp, float deltaTime)
  191. {
  192. base.OnTransitionFromCamera(fromCam, worldUp, deltaTime);
  193. InvokeOnTransitionInExtensions(fromCam, worldUp, deltaTime);
  194. m_TransitioningFrom = fromCam;
  195. InternalUpdateCameraState(worldUp, deltaTime);
  196. }
  197. ICinemachineCamera m_TransitioningFrom;
  198. /// <summary>Internal use only. Do not call this method.
  199. /// Called by CinemachineCore at designated update time
  200. /// so the vcam can position itself and track its targets. This implementation
  201. /// updates all the children, chooses the best one, and implements any required blending.</summary>
  202. /// <param name="worldUp">Default world Up, set by the CinemachineBrain</param>
  203. /// <param name="deltaTime">Delta time for time-based effects (ignore if less than or equal to 0)</param>
  204. public override void InternalUpdateCameraState(Vector3 worldUp, float deltaTime)
  205. {
  206. UpdateListOfChildren();
  207. CinemachineVirtualCameraBase best = ChooseCurrentCamera();
  208. if (best != null && !best.gameObject.activeInHierarchy)
  209. {
  210. best.gameObject.SetActive(true);
  211. best.UpdateCameraState(worldUp, deltaTime);
  212. }
  213. ICinemachineCamera previousCam = LiveChild;
  214. LiveChild = best;
  215. // Are we transitioning cameras?
  216. if (previousCam != LiveChild && LiveChild != null)
  217. {
  218. // Notify incoming camera of transition
  219. LiveChild.OnTransitionFromCamera(previousCam, worldUp, deltaTime);
  220. // Generate Camera Activation event in the brain if live
  221. CinemachineCore.Instance.GenerateCameraActivationEvent(LiveChild, previousCam);
  222. if (previousCam != null)
  223. {
  224. // Create a blend (will be null if a cut)
  225. mActiveBlend = CreateBlend(
  226. previousCam, LiveChild,
  227. LookupBlend(previousCam, LiveChild), mActiveBlend);
  228. // If cutting, generate a camera cut event if live
  229. if (mActiveBlend == null || !mActiveBlend.Uses(previousCam))
  230. CinemachineCore.Instance.GenerateCameraCutEvent(LiveChild);
  231. }
  232. }
  233. // Advance the current blend (if any)
  234. if (mActiveBlend != null)
  235. {
  236. mActiveBlend.TimeInBlend += (deltaTime >= 0)
  237. ? deltaTime : mActiveBlend.Duration;
  238. if (mActiveBlend.IsComplete)
  239. mActiveBlend = null;
  240. }
  241. if (mActiveBlend != null)
  242. {
  243. mActiveBlend.UpdateCameraState(worldUp, deltaTime);
  244. m_State = mActiveBlend.State;
  245. }
  246. else if (LiveChild != null)
  247. {
  248. if (m_TransitioningFrom != null)
  249. LiveChild.OnTransitionFromCamera(m_TransitioningFrom , worldUp, deltaTime);
  250. m_State = LiveChild.State;
  251. }
  252. m_TransitioningFrom = null;
  253. InvokePostPipelineStageCallback(this, CinemachineCore.Stage.Finalize, ref m_State, deltaTime);
  254. PreviousStateIsValid = true;
  255. }
  256. /// <summary>Makes sure the internal child cache is up to date</summary>
  257. protected override void OnEnable()
  258. {
  259. base.OnEnable();
  260. InvalidateListOfChildren();
  261. mActiveBlend = null;
  262. CinemachineDebug.OnGUIHandlers -= OnGuiHandler;
  263. CinemachineDebug.OnGUIHandlers += OnGuiHandler;
  264. }
  265. /// <summary>
  266. /// Uninstall the GUI handler
  267. /// </summary>
  268. protected override void OnDisable()
  269. {
  270. base.OnDisable();
  271. CinemachineDebug.OnGUIHandlers -= OnGuiHandler;
  272. }
  273. /// <summary>Makes sure the internal child cache is up to date</summary>
  274. public void OnTransformChildrenChanged()
  275. {
  276. InvalidateListOfChildren();
  277. }
  278. /// Will only be called if Unity Editor - never in build
  279. private void OnGuiHandler()
  280. {
  281. if (!m_ShowDebugText)
  282. CinemachineDebug.ReleaseScreenPos(this);
  283. else
  284. {
  285. var sb = CinemachineDebug.SBFromPool();
  286. sb.Append(Name); sb.Append(": "); sb.Append(Description);
  287. string text = sb.ToString();
  288. Rect r = CinemachineDebug.GetScreenPos(this, text, GUI.skin.box);
  289. GUI.Label(r, text, GUI.skin.box);
  290. CinemachineDebug.ReturnToPool(sb);
  291. }
  292. }
  293. CameraState m_State = CameraState.Default;
  294. /// <summary>The list of child cameras. These are just the immediate children in the hierarchy.</summary>
  295. public CinemachineVirtualCameraBase[] ChildCameras { get { UpdateListOfChildren(); return m_ChildCameras; }}
  296. /// <summary>Is there a blend in progress?</summary>
  297. public bool IsBlending => mActiveBlend != null;
  298. /// <summary>
  299. /// Get the current active blend in progress. Will return null if no blend is in progress.
  300. /// </summary>
  301. public CinemachineBlend ActiveBlend => mActiveBlend;
  302. /// <summary>API for the inspector editor. Animation module does not have hashes
  303. /// for state parents, so we have to invent them in order to implement nested state
  304. /// handling</summary>
  305. /// <param name="parentHash">Parent state's hash</param>
  306. /// <param name="clip">The clip to create the fake hash for</param>
  307. /// <returns>The fake hash</returns>
  308. public static int CreateFakeHash(int parentHash, AnimationClip clip)
  309. {
  310. return Animator.StringToHash(parentHash.ToString() + "_" + clip.name);
  311. }
  312. // Avoid garbage string manipulations at runtime
  313. struct HashPair { public int parentHash; public int hash; }
  314. Dictionary<AnimationClip, List<HashPair>> mHashCache;
  315. int LookupFakeHash(int parentHash, AnimationClip clip)
  316. {
  317. if (mHashCache == null)
  318. mHashCache = new Dictionary<AnimationClip, List<HashPair>>();
  319. List<HashPair> list = null;
  320. if (!mHashCache.TryGetValue(clip, out list))
  321. {
  322. list = new List<HashPair>();
  323. mHashCache[clip] = list;
  324. }
  325. for (int i = 0; i < list.Count; ++i)
  326. if (list[i].parentHash == parentHash)
  327. return list[i].hash;
  328. int newHash = CreateFakeHash(parentHash, clip);
  329. list.Add(new HashPair() { parentHash = parentHash, hash = newHash });
  330. mStateParentLookup[newHash] = parentHash;
  331. return newHash;
  332. }
  333. float mActivationTime = 0;
  334. Instruction mActiveInstruction;
  335. float mPendingActivationTime = 0;
  336. Instruction mPendingInstruction;
  337. private CinemachineBlend mActiveBlend = null;
  338. void InvalidateListOfChildren() { m_ChildCameras = null; LiveChild = null; }
  339. void UpdateListOfChildren()
  340. {
  341. if (m_ChildCameras != null && mInstructionDictionary != null && mStateParentLookup != null)
  342. return;
  343. List<CinemachineVirtualCameraBase> list = new List<CinemachineVirtualCameraBase>();
  344. CinemachineVirtualCameraBase[] kids = GetComponentsInChildren<CinemachineVirtualCameraBase>(true);
  345. foreach (CinemachineVirtualCameraBase k in kids)
  346. if (k.transform.parent == transform)
  347. list.Add(k);
  348. m_ChildCameras = list.ToArray();
  349. ValidateInstructions();
  350. }
  351. private Dictionary<int, int> mInstructionDictionary;
  352. private Dictionary<int, int> mStateParentLookup;
  353. /// <summary>Internal API for the inspector editor.</summary>
  354. internal void ValidateInstructions()
  355. {
  356. if (m_Instructions == null)
  357. m_Instructions = Array.Empty<Instruction>();
  358. mInstructionDictionary = new Dictionary<int, int>();
  359. for (int i = 0; i < m_Instructions.Length; ++i)
  360. {
  361. if (m_Instructions[i].m_VirtualCamera != null
  362. && m_Instructions[i].m_VirtualCamera.transform.parent != transform)
  363. {
  364. m_Instructions[i].m_VirtualCamera = null;
  365. }
  366. mInstructionDictionary[m_Instructions[i].m_FullHash] = i;
  367. }
  368. // Create the parent lookup
  369. mStateParentLookup = new Dictionary<int, int>();
  370. if (m_ParentHash != null)
  371. foreach (var i in m_ParentHash)
  372. mStateParentLookup[i.m_Hash] = i.m_ParentHash;
  373. // Zap the cached current instructions
  374. mHashCache = null;
  375. mActivationTime = mPendingActivationTime = 0;
  376. mActiveBlend = null;
  377. }
  378. List<AnimatorClipInfo> m_clipInfoList = new List<AnimatorClipInfo>();
  379. private CinemachineVirtualCameraBase ChooseCurrentCamera()
  380. {
  381. if (m_ChildCameras == null || m_ChildCameras.Length == 0)
  382. {
  383. mActivationTime = 0;
  384. return null;
  385. }
  386. CinemachineVirtualCameraBase defaultCam = m_ChildCameras[0];
  387. if (m_AnimatedTarget == null || !m_AnimatedTarget.gameObject.activeSelf
  388. || m_AnimatedTarget.runtimeAnimatorController == null
  389. || m_LayerIndex < 0 || !m_AnimatedTarget.hasBoundPlayables
  390. || m_LayerIndex >= m_AnimatedTarget.layerCount)
  391. {
  392. mActivationTime = 0;
  393. return defaultCam;
  394. }
  395. // Get the current state
  396. int hash;
  397. if (m_AnimatedTarget.IsInTransition(m_LayerIndex))
  398. {
  399. // Force "current" state to be the state we're transitioning to
  400. AnimatorStateInfo info = m_AnimatedTarget.GetNextAnimatorStateInfo(m_LayerIndex);
  401. m_AnimatedTarget.GetNextAnimatorClipInfo(m_LayerIndex, m_clipInfoList);
  402. hash = GetClipHash(info.fullPathHash, m_clipInfoList);
  403. }
  404. else
  405. {
  406. AnimatorStateInfo info = m_AnimatedTarget.GetCurrentAnimatorStateInfo(m_LayerIndex);
  407. m_AnimatedTarget.GetCurrentAnimatorClipInfo(m_LayerIndex, m_clipInfoList);
  408. hash = GetClipHash(info.fullPathHash, m_clipInfoList);
  409. }
  410. // If we don't have an instruction for this state, find a suitable default
  411. while (hash != 0 && !mInstructionDictionary.ContainsKey(hash))
  412. hash = mStateParentLookup.ContainsKey(hash) ? mStateParentLookup[hash] : 0;
  413. float now = CinemachineCore.CurrentTime;
  414. if (mActivationTime != 0)
  415. {
  416. // Is it active now?
  417. if (mActiveInstruction.m_FullHash == hash)
  418. {
  419. // Yes, cancel any pending
  420. mPendingActivationTime = 0;
  421. return mActiveInstruction.m_VirtualCamera;
  422. }
  423. // Is it pending?
  424. if (PreviousStateIsValid)
  425. {
  426. if (mPendingActivationTime != 0 && mPendingInstruction.m_FullHash == hash)
  427. {
  428. // Has it been pending long enough, and are we allowed to switch away
  429. // from the active action?
  430. if ((now - mPendingActivationTime) > mPendingInstruction.m_ActivateAfter
  431. && ((now - mActivationTime) > mActiveInstruction.m_MinDuration
  432. || mPendingInstruction.m_VirtualCamera.Priority
  433. > mActiveInstruction.m_VirtualCamera.Priority))
  434. {
  435. // Yes, activate it now
  436. mActiveInstruction = mPendingInstruction;
  437. mActivationTime = now;
  438. mPendingActivationTime = 0;
  439. }
  440. return mActiveInstruction.m_VirtualCamera;
  441. }
  442. }
  443. }
  444. // Neither active nor pending.
  445. mPendingActivationTime = 0; // cancel the pending, if any
  446. if (!mInstructionDictionary.ContainsKey(hash))
  447. {
  448. // No defaults set, we just ignore this state
  449. if (mActivationTime != 0)
  450. return mActiveInstruction.m_VirtualCamera;
  451. return defaultCam;
  452. }
  453. // Can we activate it now?
  454. Instruction newInstr = m_Instructions[mInstructionDictionary[hash]];
  455. if (newInstr.m_VirtualCamera == null)
  456. newInstr.m_VirtualCamera = defaultCam;
  457. if (PreviousStateIsValid && mActivationTime > 0)
  458. {
  459. if (newInstr.m_ActivateAfter > 0
  460. || ((now - mActivationTime) < mActiveInstruction.m_MinDuration
  461. && newInstr.m_VirtualCamera.Priority
  462. <= mActiveInstruction.m_VirtualCamera.Priority))
  463. {
  464. // Too early - make it pending
  465. mPendingInstruction = newInstr;
  466. mPendingActivationTime = now;
  467. if (mActivationTime != 0)
  468. return mActiveInstruction.m_VirtualCamera;
  469. return defaultCam;
  470. }
  471. }
  472. // Activate now
  473. mActiveInstruction = newInstr;
  474. mActivationTime = now;
  475. return mActiveInstruction.m_VirtualCamera;
  476. }
  477. int GetClipHash(int hash, List<AnimatorClipInfo> clips)
  478. {
  479. // Find the strongest-weighted animation clip substate
  480. int bestClip = -1;
  481. for (int i = 0; i < clips.Count; ++i)
  482. if (bestClip < 0 || clips[i].weight > clips[bestClip].weight)
  483. bestClip = i;
  484. // Use its hash
  485. if (bestClip >= 0 && clips[bestClip].weight > 0)
  486. hash = LookupFakeHash(hash, clips[bestClip].clip);
  487. return hash;
  488. }
  489. private CinemachineBlendDefinition LookupBlend(
  490. ICinemachineCamera fromKey, ICinemachineCamera toKey)
  491. {
  492. // Get the blend curve that's most appropriate for these cameras
  493. CinemachineBlendDefinition blend = m_DefaultBlend;
  494. if (m_CustomBlends != null)
  495. {
  496. string fromCameraName = (fromKey != null) ? fromKey.Name : string.Empty;
  497. string toCameraName = (toKey != null) ? toKey.Name : string.Empty;
  498. blend = m_CustomBlends.GetBlendForVirtualCameras(
  499. fromCameraName, toCameraName, blend);
  500. }
  501. if (CinemachineCore.GetBlendOverride != null)
  502. blend = CinemachineCore.GetBlendOverride(fromKey, toKey, blend, this);
  503. return blend;
  504. }
  505. }
  506. #endif
  507. }