前言:对Slomo的来源进行一次简单分析。
Slomo的作用就是设置把值传给SetTimeDilation:
//UE_4.26\Engine\Source\Runtime\Engine\Private\CheatManager.cpp
void UCheatManager::Slomo(float NewTimeDilation)
{
GetOuterAPlayerController()->GetWorldSettings()->SetTimeDilation(NewTimeDilation);
}
SetTimeDilation作用就是把值钳制一次,保证输入的值在默认最大最小之间,
//UE_4.26\Engine\Source\Runtime\Engine\Private\WorldSettings.cpp
float AWorldSettings::SetTimeDilation(float NewTimeDilation)
{
TimeDilation = FMath::Clamp(NewTimeDilation, MinGlobalTimeDilation, MaxGlobalTimeDilation);
return TimeDilation;
}
最大值和最小值的默认设置在UE_4.26\Engine\Config\BaseGame.ini
MinUndilatedFrameTime=0.0005 ; 2000 fps
MaxUndilatedFrameTime=0.4 ; 2.5 fps
MinGlobalTimeDilation=0.0001
MaxGlobalTimeDilation=20.0
注意其中除了GlobalTimeDilation,还有UndilatedFrameTime,在后面会使用到。
而SetTimeDilation在SetGlobalTimeDilation中有被调用,准确来说不是调用,而是又做了一次判断来确定输入的slomo是否符合MinGlobalTimeDilation~MaxGlobalTimeDilation:
//UE_4.26\Engine\Source\Runtime\Engine\Private\GameplayStatics.cpp
void UGameplayStatics::SetGlobalTimeDilation(const UObject* WorldContextObject, float TimeDilation)
{
UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
if (World != nullptr)
{
AWorldSettings* const WorldSettings = World->GetWorldSettings();
if (WorldSettings != nullptr)
{
float const ActualTimeDilation = WorldSettings->SetTimeDilation(TimeDilation);
if (TimeDilation != ActualTimeDilation)
{
UE_LOG(LogBlueprintUserMessages, Warning, TEXT("Time Dilation must be between %f and %f. Clamped value to that range."), WorldSettings->MinGlobalTimeDilation, WorldSettings->MaxGlobalTimeDilation);
}
}
}
}
此时SetTimeDilation返回的TimeDilation在WorldSettings中已经被改变,在GetEffectiveTimeDilation中正式调用,得到返回值是综合考虑了Slomo,Matinee和DemoPlay的速度,也就是最终有效膨胀时间,
//UE_4.26\Engine\Source\Runtime\Engine\Classes\GameFramework\WorldSettings.h
virtual float GetEffectiveTimeDilation() const
{
return TimeDilation * MatineeTimeDilation * DemoPlayTimeDilation;
}
在这里顺便发现
了actor中的自定义膨胀时间确实是在这个最终有效膨胀时间的基础上进行的乘法运算
//UE_4.26\Engine\Source\Runtime\Engine\Private\Actor.cpp
float AActor::GetActorTimeDilation() const
{
// get actor custom time dilation
// if you do slomo, that changes WorldSettings->TimeDilation
// So multiply to get final TimeDilation
return CustomTimeDilation * GetWorldSettings()->GetEffectiveTimeDilation();
}
float AActor::GetActorTimeDilation(const UWorld& ActorWorld) const
{
checkSlow(&ActorWorld == GetWorld());
return CustomTimeDilation * ActorWorld.GetWorldSettings()->GetEffectiveTimeDilation();
}
继续主线任务,
GetEffectiveTimeDilation是被FixupDeltaSeconds使用:
FixupDeltaSeconds作用与setTimeDilation有点相似,也是把扩张的速度钳制在一定范围,但是此处的扩张速度是Slomo和过场动画速度(MatineeTimeDilation )以及DemoPlay(DemoPlayTimeDilation )共同决定,而且钳制的范围也变成了UndilatedFrameTime,默认帧时间,0.005秒~0.4秒。
//UE_4.26\Engine\Source\Runtime\Engine\Private\WorldSettings.cpp
float AWorldSettings::FixupDeltaSeconds(float DeltaSeconds, float RealDeltaSeconds)
{
// DeltaSeconds is assumed to be fully dilated at this time, so we will dilate the clamp range as well
//TimeDilation * MatineeTimeDilation * DemoPlayTimeDilation
float const Dilation = GetEffectiveTimeDilation();
float const MinFrameTime = MinUndilatedFrameTime * Dilation;
float const MaxFrameTime = MaxUndilatedFrameTime * Dilation;
// clamp frame time according to desired limits
return FMath::Clamp(DeltaSeconds, MinFrameTime, MaxFrameTime);
}
在全部解决方案中,只有四个地方有FixupDeltaSeconds这个函数,分别是关卡每一帧调用,重播系统调用,时间轴调用,以及世界设置中的函数定义,所以我们主要看关卡每一帧调用:
//UE_4.26\Engine\Source\Runtime\Engine\Private\LevelTick.cpp
// Save off actual delta
float RealDeltaSeconds = DeltaSeconds;
// apply time multipliers
//GetEffectiveTimeDilation()==TimeDilation * MatineeTimeDilation * DemoPlayTimeDilation
DeltaSeconds *= Info->GetEffectiveTimeDilation();
// Handle clamping of time to an acceptable value
const float GameDeltaSeconds = Info->FixupDeltaSeconds(DeltaSeconds, RealDeltaSeconds);
check(GameDeltaSeconds >= 0.0f);
DeltaSeconds = GameDeltaSeconds;
DeltaTimeSeconds = DeltaSeconds;
UnpausedTimeSeconds += DeltaSeconds;
我们看Tick里的这一部分,先保存当前膨胀秒数为RealDeltaSeconds,然后读取我们定义的Slomo速度TimeDilation过场动画速度DemoPlay运行速度,设为当前膨胀秒数,然后定义一个GameDeltaSeconds,用来保存已经被钳制过的速度,然后再赋值给当前膨胀秒数DeltaSeconds、RealDeltaSeconds,到这里就完成一次Slomo。
另外,在GameState中有个默认计时器,也用到了GetEffectiveTimeDilation,没有FixupDeltaSeconds,猜测默认计时器也是受到Slomo影响的,不受默认帧时间影响。
//UE_4.26\Engine\Source\Runtime\Engine\Private\GameState.cpp
void AGameState::DefaultTimer()
{
if (IsMatchInProgress())
{
++ElapsedTime;
if (GetNetMode() != NM_DedicatedServer)
{
OnRep_ElapsedTime();
}
}
GetWorldTimerManager().SetTimer(TimerHandle_DefaultTimer, this, &AGameState::DefaultTimer, GetWorldSettings()->GetEffectiveTimeDilation() / GetWorldSettings()->DemoPlayTimeDilation, true);
}