【UE4】Slomo 顿帧
本文以 UE4 自带的 ActionRPG 为例,记录 UE4 的慢动作(顿帧 slomo)的效果及实现,并且记录 slomo 状态下如何调整 Timer 使得计时器时间同样变化一、Global Slomo在启动 UE4 编辑器后,点击 ` 按钮,开启调试命令行,输入slomo 0.1,即可实现慢动作效果(顿帧效果),慢放速度是 0.1 倍(如下图所示)。
在动画条上,也可以配置Anim Notify Slomo来达到调整世界速度的效果(如下图所示)
在 ActionRPG 中,将斧头普攻上配置 AnimNotifySlomo,并设置为 0.1 后,可以看到效果如下所示,在进行普攻并触发 Notify 之后,整个世界 的速度都变慢了(火焰、主角自己、怪物)。
不管从蓝图,还是从 C++,最终都是调用Engine\Classes\Kismet\GameplayStatics.h中的方法:
/**
* Sets the global time dilation.
* @param TimeDilation value to set the global time dilation to
*/
UFUNCTION(BlueprintCallable, Category="Utilities|Time", meta=(WorldContext="WorldContextObject") )
static void SetGlobalTimeDilation(const UObject* WorldContextObject, float TimeDilation);
UE4 管这个设置时间速度的方法叫 SetGlobalTimeDilation,即设置 世界时间膨胀。这个函数内调用AWorldSettings::SetTimeDilation达到调整时间速度效果:
float AWorldSettings::SetTimeDilation(float NewTimeDilation)
{
TimeDilation = FMath::Clamp(NewTimeDilation, MinGlobalTimeDilation, MaxGlobalTimeDilation);
return TimeDilation;
}
这个TimeDilation会调整世界 Tick 的 DeltaSeconds,所以可以让世界变慢。改变DeltaSeconds的函数是:
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
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);
}
二、Custom Slomo
但是有的时候,我们不想让世界变慢,只想让特点事物变慢,比如只让我自己变慢,或者让我和我打击的目标变慢,那就不能用 Global 的 TimeDilation 来调整,而是每个 Actor 自己的时间。
UE4 对每个 Actor 都存了一个自己的时间膨胀速度:
/** Allow each actor to run at a different time speed.
The DeltaTime for a frame is multiplied by the global TimeDilation (in WorldSettings)
and this CustomTimeDilation for this actor's tick.*/
UPROPERTY(BlueprintReadWrite, AdvancedDisplay, Category=Actor)
float CustomTimeDilation;
也就是说对于每个Actor,它的时间膨胀速度是由 Global Dilation 乘上自己的 Custom Dilation 得到的。
如果只想让某个 Actor 变慢的话,Global 的 Time Dilation 不变,调整每个 Actor 自己的 CustomTimeDilation 可以达到同样的效果,如下所示(只有主角自己"变慢了",火焰和怪物都是正常速度):
三、Slomo 对 Timer 的影响
但是 UE4 里的计时器,即 TimerManager 里的 SetTimer 设置定时多久后触发的函数,是按照时间的 DeltaSeconds 来计算的,也正常情况下,SetTimer 3 秒后触发的 Delegate,会在从开始计时起,World 的 Tick 里的 DeltaTime 累计超过 3 的时候触发。
如果设置了世界慢放,即调整了 World 的 TimeDilation,那么 World 的 Tick 的 DeltaTime 每次就变少了,比如正常是 0.01667(60帧每秒),那么 slomo 0.1 之后的 DeltaTime 就是 0.001667。
所以用 World 里(GameInstance里)的 TimerManager 设置的 Timer,就会按照世界的速度来计算定时。但是如果只改变了自己的 CustomTimeDilation,World 里的TimerManager 设置的 Timer 还是按照正常的速度进行 TIck,就会比角色快很多。
解决这个问题有两种方法:
[*]改变 CustomTimeDilation 时候重新绑定 Timer
[*]Actor 身上创建一个自己的 TimerManager
3.1 改变 CustomTimeDilation 时候重新绑定 Timer
比如在 SetTimer 时候这样调用:
GetWorld()->GetTimerManager().SetTimer(
MyTimerHandle,
FTimerDelegate::CreateUObject(this, &AMyPlayer::DoPrint, "abc"),
3.0,
false);
那么在 Slomo 改变的 CallBack 里可以调用下边函数(传入设置的 SlomoValue)重新绑定:
const float TimeRemain = GetWorld()->GetTimerManager().GetTimerRemaining(ResetNextAbilityTimerHandle);
if (TimeRemain > 0.f)
{
GetWorld()->GetTimerManager().SetTimer(
MyTimerHandle,
FTimerDelegate::CreateUObject(this, &AMyPlayer::DoPrint, "abc"),
TimeRemain / SlomoValue,
false);
}
即,如果这个 Timer 还剩 1.3 秒结束并触发绑定的 Delegate,那么重新绑定这个 Timer,事件是 1.3除以Slomo 的值,比如slomo 0.1,那么就是 1.3 / 0.1 = 13,即 13 秒后触发。
SetTimer 的注释里有写道:
/**
* Sets a timer to call the given native function at a set interval.
* If a timer is already set for this handle, it will replace the current timer.
*/3.2 Actor 身上创建一个自己的 TimerManager
但是上述方法需要把每个绑定的事件都这么触发,且之后每次新添加 Timer 都需要考虑这个问题,所以很麻烦。
因为 UE4 的整个世界可以认为都是建立在 Tick 上的,所以在 Actor 身上添加一个自己的 TimerManager 然后在自己的 Tick 里调用 TimerManager 的 Tick 即可。(这一步很重要,因为 TimerManager 不会自己计算时间,World 的 Global TimerManager 也是在 Engine 的 Tick 里做的自己的 Tick,并且用世界的 DeltaSeconds,作为自己的 DeltaSeconds):
void UEditorEngine::Tick( float DeltaSeconds, bool bIdleMode )
{
// ...
// Update the timer manager
TimerManager->Tick(DeltaSeconds);
// ...
}
在自己的 Character 的构造函数中创建一个 TimerManager:
AMyCharacter::AMyCharacter(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
// ...
CharacterTimerManager = MakeUnique<FTimerManager>();
}
就可以直接在自己的 Character 的 Tick 里 Tick 自己的 TimerManager了:
// 头文件中
inline FTimerManager& GetCharacterTimerManager() const
{
return *CharacterTimerManager.Get();
}
在角色的 Tick 中进行自己的 TimerManager 的 Tick:
void AMyCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
GetCharacterTimerManager().Tick(DeltaTime);
}
原来的GetWorld()->GetTimerManager().SetTimer(...);直接改成GetCharacterTimerManager().SetTimer(...);就可以了。这样自己身上的 Timer 会根据自己的 slomo 来计算事时间,该快快,该慢慢。
[*]个人博客:
你这个 是用的系统自带的ActionRPG,我用他自带的资源实现了 同步 网络化。。。 可以互相交流学习下~
页:
[1]