在Unreal中使用LatentAction的几个步骤:
- 继承FPendingLatentAction,派生出子类,构造函数传入需要LatentAction涉及到的参数。
- 覆写UpdateOperation函数,实现每帧调用的逻辑。
- UpdateOperation内部判断当前条件,如果需要结束,则调用FLatentResponse::FinishAndTriggerIf或者FLatentResponse::DoneIf。
我们以Delay一定帧,并每帧返回当前剩余帧数来说明使用过程。
1、我们需要Delay函数能够有不同的输出节点,需要使用ExpandEnumAsExecs,首先定义输出节点的枚举类型
UENUM(BlueprintType)
enum class LatentExec :uint8
{
DelayExe,
Complete,
};
2、添加FPendingLatentAction子类
class FRpgDelayForFramesLatentAction : public FPendingLatentAction
{
public:
FRpgDelayForFramesLatentAction(const FLatentActionInfo& LatentInfo, int32 NumFrames,LatentExec& exec,int& CurFrame) : ExecutionFunction(LatentInfo.ExecutionFunction)
, OutputLink(LatentInfo.Linkage)
, CallbackTarget(LatentInfo.CallbackTarget)
, FramesRemaining(NumFrames)
, exec(exec)
, CurFrame(CurFrame)
{
}
virtual ~FRpgDelayForFramesLatentAction() {};
virtual void UpdateOperation(FLatentResponse& Response) override
{
--FramesRemaining;
if (FramesRemaining == 0)
{
exec = LatentExec::Complete;
CurFrame = FramesRemaining;
Response.FinishAndTriggerIf(true, ExecutionFunction, OutputLink, CallbackTarget);
}
else
{
exec = LatentExec::DelayExe;
CurFrame = FramesRemaining;
Response.TriggerLink(ExecutionFunction, OutputLink, CallbackTarget);
}
}
#if WITH_EDITOR
FString GetDescription() const
{
return FString::Printf(TEXT("Delay (%d frames remaining)"), FramesRemaining);
}
#endif
private:
FName ExecutionFunction;
int32 OutputLink;
FWeakObjectPtr CallbackTarget;
int32 FramesRemaining;
LatentExec& exec;
int& CurFrame;
};
构造函数一般用来发起本次LatentAction,我们这里函数体没有代码,只需要记录一个延迟帧数即可。
UpdateOperation中更新剩余帧数,并在一定条件后触发不同的后续分支。
这里需要稍微注意一个TriggerLink和FinishAndTriggerLink两个函数。前者单纯地用来触发后续分支,后者判断条件满足后触发后续分支本结束本次LatentAction。
3、添加蓝图可用的Delay函数,这个可以加在任何一个BlueprintFunctionLibrary中
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "exec", Latent, LatentInfo = "LatentInfo"))
static void DelayFramesLatent(int Frames, LatentExec& exec, FLatentActionInfo LatentInfo,int& CurFrame)
{
if (UWorld* World = GEngine->GetWorldFromContextObject(URpgGameInstance::GetInstance()->GetWorld(), EGetWorldErrorMode::LogAndReturnNull))
{
FLatentActionManager& LatentActionManager = World->GetLatentActionManager();
if (LatentActionManager.FindExistingAction<FRpgDelayForFramesLatentAction>(LatentInfo.CallbackTarget, LatentInfo.UUID) == nullptr)
{
LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, new FRpgDelayForFramesLatentAction(LatentInfo, Frames, exec, CurFrame));
}
}
}
4、可以在蓝图中使用了
这个例子中需要注意的几个点:
- 覆写FPendingLatentAction::UpdateOperation时要记住,这个函数是每帧执行一次的,如果函数的任务过重,会使主线程卡顿。
- LatentAction必须要两部分结合使用,即继承自FPendingLatentAction的子类,和带有Latent标识的C++函数。
- 多分支的原理在于ExpandEnumAsExecs,并且LatentAction内部的枚举类型是引用,这使得它可以控制函数的ExpandEnumAsExecs参数,进而控制分支出口。同理用来控制当前帧的输出参数CurFrame。
- GetDescription会在节点上显示文本,用来在编辑器中辅助调试。
以上就是使用LatentAction的基本步骤,所以LatentAction能解决的事情是:每帧轮询(即执行某些轻度任务),在条件达成时,执行后续的蓝图节点。
LatentAction不能解决的事情是:不能异步加载资源,这个必须要记住!
一些同学看见LatentAction有异步的效果,认为可以使用它来解决资源的异步加载,但正如我们之前所说的,LatentAction的原理在于每一帧都进行条件轮询,条件完成再执行后续节点,它的UpdateOperation的调用是在主线程做的,并且每帧都会执行(直到条件满足)。用它来加载资源,和直接阻塞加载LoadSynchronous基本没有区别!所以,不要用它来异步加载资源!