UE5相机系统初探(二)
上一章节我们实现了一个基本的第三人称相机。这一节我们为相机添加一些功能。
Lag
Spring Arm Component有一组关于Lag的参数,它表示相机的跟随延迟,这个是非常非常使用的功能,在Spring Arm Component中我们可以分别控制相机的位移延迟,以及旋转的延迟。位移的延迟可以设置一个最大的延后距离,这样可以保证相机不会离玩家太远导致玩家跑出了屏幕外。
如上设置后,来看下玩家移动时的延迟效果:
再把Camera Rotation Lag Speed
调到1,观察下旋转的延迟(容易引起晕眩):
location lag背后的逻辑其实很简单,就是一个线性插值,这个插值有两种方案,一种是直接根据delta time和speed插值,还有一种是使用一个小的步长,在delta time区间内不断更新插值目标,来调整相机的位置,这种表现上更丝滑,当然计算开销也会更高。
if (bUseCameraLagSubstepping && DeltaTime > CameraLagMaxTimeStep && CameraLagSpeed > 0.f)
{
const FVector ArmMovementStep = (DesiredLoc - PreviousDesiredLoc) * (1.f / DeltaTime);
FVector LerpTarget = PreviousDesiredLoc;
float RemainingTime = DeltaTime;
while (RemainingTime > UE_KINDA_SMALL_NUMBER)
{
const float LerpAmount = FMath::Min(CameraLagMaxTimeStep, RemainingTime);
LerpTarget += ArmMovementStep * LerpAmount;
RemainingTime -= LerpAmount;
DesiredLoc = FMath::VInterpTo(PreviousDesiredLoc, LerpTarget, LerpAmount, CameraLagSpeed);
PreviousDesiredLoc = DesiredLoc;
}
}
else
{
DesiredLoc = FMath::VInterpTo(PreviousDesiredLoc, DesiredLoc, DeltaTime, CameraLagSpeed);
}
rotation lag也是同理,只不过换成了球形插值。
Shake
目前UE提供的camera shake有两种蓝图,一是DefaultCameraShakeBase
,另一种是LegacyCameraShake
,它们都继承自基类UCameraShakeBase
。
我们希望在角色跑步时,引入camera shake,来加强跑步的观感。同时,camera shake希望是可配置的,也就是说它可以来自不同的蓝图,但都要继承自UCameraShakeBase
。那么需要在类里定义变量:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera")
TSubclassOf<UCameraShakeBase> SprintShake;
然后,加入跑步时触发shake和停止shake的逻辑:
void ACF_Character::SprintStart()
{
GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
if (SprintShake && GetVelocity().Length() != 0)
{
CurrentSprintShake = GetWorld()->GetFirstPlayerController()->PlayerCameraManager->StartCameraShake(SprintShake, 1.f);
}
}
void ACF_Character::SprintEnd()
{
GetCharacterMovement()->MaxWalkSpeed = DefaultSpeed;
if (SprintShake)
{
GetWorld()->GetFirstPlayerController()->PlayerCameraManager->StopCameraShake(CurrentSprintShake);
CurrentSprintShake = nullptr;
}
}
回到之前创建的camera shake蓝图,我们选择一个shake pattern进行配置:
来看看效果:
StartCameraShake
背后会先从camera shake的缓存池中找可用的instance,如果找不到再去创建shake class对应的instance,然后播放该shake。
UCameraShakeBase* NewInst = ReclaimShakeFromExpiredPool(ShakeClass);
// No old shakes, create a new one
if (NewInst == nullptr)
{
NewInst = NewObject<UCameraShakeBase>(this, ShakeClass);
}
if (NewInst)
{
// Custom initialization if necessary.
if (bIsCustomInitialized)
{
Params.Initializer.Execute(NewInst);
}
// Initialize new shake and add it to the list of active shakes
FCameraShakeBaseStartParams StartParams;
StartParams.CameraManager = CameraOwner;
StartParams.Scale = Scale;
StartParams.PlaySpace = Params.PlaySpace;
StartParams.UserPlaySpaceRot = Params.UserPlaySpaceRot;
StartParams.DurationOverride = Params.DurationOverride;
NewInst->StartShake(StartParams);
}
StopCameraShake
则会停止该shake,并将其加入到缓存池中。
for (int32 i = 0; i < ActiveShakes.Num(); ++i)
{
FActiveCameraShakeInfo& ShakeInfo = ActiveShakes[i];
if (ShakeInfo.ShakeInstance == ShakeInst)
{
ShakeInst->StopShake(bImmediately);
if (bImmediately)
{
ShakeInst->TeardownShake();
SaveShakeInExpiredPoolIfPossible(ShakeInfo);
ActiveShakes.RemoveAt(i, 1);
UE_LOG(LogCameraShake, Verbose, TEXT("UCameraModifier_CameraShake::RemoveCameraShake %s"), *GetNameSafe(ShakeInfo.ShakeInstance));
}
break;
}
}
实际start以及stop的表现逻辑,是交给UCameraShakePattern
这个类来实现的,不同的表现效果都继承自这个类,比如DefaultCameraShakeBase
使用的是UPerlinNoiseCameraShakePattern
。
此外,我们还想实现当玩家起跳落地时,相机也会随之震动的效果。但是我们希望,相机离玩家越近时,震动效果越强,离玩家越远,震动效果越弱。这时可借助PlayWorldCameraShake
接口实现:
void ACF_Character::Landed(const FHitResult& Hit)
{
Super::Landed(Hit);
UGameplayStatics::PlayWorldCameraShake(this, LandedShake, GetActorLocation(), 600.f, 2000.f);
}
代码中PlayWorldCameraShake
的后三个参数分别表示播放camera shake的位置,效果开始衰减的起始距离,完全衰减的距离。来看下相机和玩家不同距离下的效果:
衰减背后的逻辑也很简单,看代码一目了然,就不赘述了。
float APlayerCameraManager::CalcRadialShakeScale(APlayerCameraManager* Camera, FVector Epicenter, float InnerRadius, float OuterRadius, float Falloff)
{
// using camera location so stuff like spectator cameras get shakes applied sensibly as well
// need to ensure server has reasonably accurate camera position
FVector POVLoc = Camera->GetCameraLocation();
if (InnerRadius < OuterRadius)
{
float DistPct = ((Epicenter - POVLoc).Size() - InnerRadius) / (OuterRadius - InnerRadius);
DistPct = 1.f - FMath::Clamp(DistPct, 0.f, 1.f);
return FMath::Pow(DistPct, Falloff);
}
else
{
// ignore OuterRadius and do a cliff falloff at InnerRadius
return ((Epicenter - POVLoc).SizeSquared() < FMath::Square(InnerRadius)) ? 1.f : 0.f;
}
}
camera actor
有时候我们希望使用多个相机,当触发某些条件时,视角可以切换到不同的相机。UE提供了CameraActor
来帮助做这件事情。在场景中创建一个CameraActor,可以发现它其实就是一个预设好的绑定了Camera Component的普通Actor而已: