【Unity】使Cinemachine立即完成Damp、当前Blend和即将发生的Blend
2022.11.13补充:
Unity官方回复说新版Cinemachine已经内置此功能:
https://forum.unity.com/threads/how-to-make-cinemachine-complete-current-blending-immediately.1131133/#post-85810392022.11.18补充:
论坛上有人对下面的方法给出了一些补充建议:
https://forum.unity.com/threads/how-to-make-cinemachine-complete-current-blending-immediately.1131133/#post-8567951
游戏中很多时候都会动态生成玩家角色,这时会希望相机立即定位到刚刚生成的玩家角色,不要有从远处将镜头Blend过来或者Damp的过程,但Cinemachine直到2.8.0版本都没有提供立即完成Blend的方法。这里对Cinemachine的源码做了些微小的改动,使其能够立即完成Damp、当前Blend和即将发生的Blend。
目前版本的Cinemachine已经含有使相机立即完成Damp的功能,只需要将 cinemachineVirtualCameraBase.PreviousStateIsValid
设为 false
即可让虚拟相机在下一帧立即完成Damp。但将Damp延迟到下一帧完成,有时可能导致画面跳变,因此下面的完成Blend的代码对其进行了整合,可以使Damp和Blend都在调用方法的当帧完成。
首先需要将Cinemachine的源码从 Library
文件夹移到 Packages
文件夹中,然后将 CinemachineBrain
类改为 partial
类型,之后的修改都不会再动到Cinemachine的核心源码。
接下来新建一个 partial
的 CinemachineBrain
类,在其中填充完成Blend和Damp的代码。下面贴出的源代码中已经包含了主要步骤的注释,因此这里不再描述细节,只说一些使用限制:下面的代码是基于Cinemachine 2.6.5版本写的,依赖了从这个版本才加入的 ManualUpdate()
方法,如果是低版本的Cinemachine,将 ManualUpdate()
方法换成 LateUpdate()
应该也可以生效(我没有测试过低版本)。
源代码:
namespace Cinemachine
{
public partial class CinemachineBrain
{
/// <summary>
/// Call this method explicitly from an external script to update the virtual cameras
/// and position the main camera, if the UpdateMode is set to ManualUpdate.
/// For other update modes, this method is called automatically, and should not be
/// called from elsewhere.
/// </summary>
public void ManualUpdate(float deltaTime)
{
if (m_BlendUpdateMethod != BrainUpdateMethod.FixedUpdate)
UpdateFrame0(deltaTime);
ComputeCurrentBlend(ref mCurrentLiveCameras, 0);
if (m_UpdateMethod == UpdateMethod.FixedUpdate)
{
// Special handling for fixed update: cameras that have been enabled
// since the last physics frame must be updated now
if (m_BlendUpdateMethod != BrainUpdateMethod.FixedUpdate)
{
CinemachineCore.Instance.CurrentUpdateFilter = CinemachineCore.UpdateFilter.Fixed;
if (SoloCamera == null)
mCurrentLiveCameras.UpdateCameraState(
DefaultWorldUp, GetEffectiveDeltaTime(true));
}
}
else
{
CinemachineCore.UpdateFilter filter = CinemachineCore.UpdateFilter.Late;
if (m_UpdateMethod == UpdateMethod.SmartUpdate)
{
// Track the targets
UpdateTracker.OnUpdate(UpdateTracker.UpdateClock.Late);
filter = CinemachineCore.UpdateFilter.SmartLate;
}
UpdateVirtualCameras(filter, deltaTime);
}
// Choose the active vcam and apply it to the Unity camera
if (m_BlendUpdateMethod != BrainUpdateMethod.FixedUpdate)
ProcessActiveCamera(deltaTime);
}
/// <summary>
/// Complete current active blend immediately.
/// </summary>
/// <param name="completeDamp">If true, will also complete aim and body damp.</param>
public void CompleteCurrentBlend(bool completeDamp = true)
{
if (mFrameStack.Count == 0)
{
return;
}
// Complete current blend
mFrameStack[0].blend.Duration = 0;
ManualUpdate();
// Use -1 to prevent dolly camera finds closest point
var deltaTime = -1;
CinemachineCore.UpdateFilter filter = CinemachineCore.UpdateFilter.Late;
if (m_UpdateMethod == UpdateMethod.SmartUpdate)
{
// Track the targets
UpdateTracker.OnUpdate(UpdateTracker.UpdateClock.Late);
filter = CinemachineCore.UpdateFilter.SmartLate;
}
UpdateVirtualCameras(filter, deltaTime);
// Choose the active vcam and apply it to the Unity camera
if (m_BlendUpdateMethod != BrainUpdateMethod.FixedUpdate)
ProcessActiveCamera(deltaTime);
// Complete damp
if (completeDamp && ActiveVirtualCamera is CinemachineVirtualCameraBase vcam)
{
vcam.PreviousStateIsValid = false;
ManualUpdate();
}
}
/// <summary>
/// Complete incoming active blend immediately if the incoming blend is between camA and camB or contains camA or camB.
/// </summary>
/// <param name="appointCamera">If not null, will only take effect when top priority camera equals appointCamera.</param>
/// <param name="completeDamp">If true, will also complete aim and body damp.</param>
public void CompleteIncomingBlend(CinemachineVirtualCameraBase appointCamera = null, bool completeDamp = true)
{
if (mFrameStack.Count == 0)
{
return;
}
if (appointCamera)
{
appointCamera.MoveToTopOfPrioritySubqueue();
}
// Find new active camera
var topCam = ActiveVirtualCamera as CinemachineVirtualCameraBase;
int numCameras = CinemachineCore.Instance.VirtualCameraCount;
for (int i = 0; i < numCameras; ++i)
{
var cam = CinemachineCore.Instance.GetVirtualCamera(i);
if (cam.gameObject.scene != gameObject.scene)
{
continue;
}
if (!topCam || topCam.gameObject.scene != gameObject.scene || topCam.Priority < cam.Priority)
{
topCam = cam;
}
// When call MoveToTopOfPrioritySubqueue() and ManualUpdate(),
// cameras in mActiveCameras may out of order, so do not break
//if (topCam.Priority >= cam.Priority)
//{
// break;
//}
}
if (topCam == null || (appointCamera && appointCamera != topCam))
{
return;
}
// Trigger virtual cameras priority queen update
topCam.MoveToTopOfPrioritySubqueue();
// Update frame stack
UpdateFrame0(0);
// Complete blend
CompleteCurrentBlend(completeDamp);
}
}
public static class CinemachineCameraExtension
{
/// <summary>
/// Complete body and aim damp.
/// </summary>
/// <param name="cmCam"></param>
public static void CompleteDamps(this ICinemachineCamera cmCam)
{
if (cmCam is CinemachineVirtualCameraBase vcam)
{
vcam.PreviousStateIsValid = false;
}
}
}
}