【Unity】使Cinemachine立即完成Damp、当前Blend和即将发生的Blend和Damp
游戏中很多时候都会动态生成玩家角色,这时会希望相机立即定位到刚刚生成的玩家角色,不要有从远处将镜头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
{
public void ManualUpdate(float deltaTime)
{
if (m_BlendUpdateMethod != BrainUpdateMethod.FixedUpdate)
UpdateFrame0(deltaTime);
ComputeCurrentBlend(ref mCurrentLiveCameras, 0);
if (m_UpdateMethod == UpdateMethod.FixedUpdate)
{
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)
{
UpdateTracker.OnUpdate(UpdateTracker.UpdateClock.Late);
filter = CinemachineCore.UpdateFilter.SmartLate;
}
UpdateVirtualCameras(filter, deltaTime);
}
if (m_BlendUpdateMethod != BrainUpdateMethod.FixedUpdate)
ProcessActiveCamera(deltaTime);
}
public void CompleteCurrentBlend(bool completeDamp = true)
{
if (mFrameStack.Count == 0)
{
return;
}
mFrameStack[0].blend.Duration = 0;
ManualUpdate();
var deltaTime = -1;
CinemachineCore.UpdateFilter filter = CinemachineCore.UpdateFilter.Late;
if (m_UpdateMethod == UpdateMethod.SmartUpdate)
{
UpdateTracker.OnUpdate(UpdateTracker.UpdateClock.Late);
filter = CinemachineCore.UpdateFilter.SmartLate;
}
UpdateVirtualCameras(filter, deltaTime);
if (m_BlendUpdateMethod != BrainUpdateMethod.FixedUpdate)
ProcessActiveCamera(deltaTime);
if (completeDamp && ActiveVirtualCamera is CinemachineVirtualCameraBase vcam)
{
vcam.PreviousStateIsValid = false;
ManualUpdate();
}
}
public void CompleteIncomingBlend(CinemachineVirtualCameraBase appointCamera = null, bool completeDamp = true)
{
if (mFrameStack.Count == 0)
{
return;
}
if (appointCamera)
{
appointCamera.MoveToTopOfPrioritySubqueue();
}
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;
}
}
if (topCam == null || (appointCamera && appointCamera != topCam))
{
return;
}
topCam.MoveToTopOfPrioritySubqueue();
UpdateFrame0(0);
CompleteCurrentBlend(completeDamp);
}
}
public static class CinemachineCameraExtension
{
public static void CompleteDamps(this ICinemachineCamera cmCam)
{
if (cmCam is CinemachineVirtualCameraBase vcam)
{
vcam.PreviousStateIsValid = false;
}
}
}
}
|