前言
使用LevelSequence制作过场动画时如果包含对话又希望使用项目中已有的对话系统来展示UI字幕等效果时,就需要为LevelSequence扩展我们自己的Track等类型,本文就以展示对话字幕这个场景分析下LevelSequence涉及的几个相关类型
框架分析
MovieSceneTrack
首先我们需要实现一个Track作为字幕轨道就像SkeletonMeshTrack和TransformTrack一样,我们需要继承UMovieSceneTrack,Track并不做实际的工作,我们只需要指定Track的名字以及它能支持的Section类型
virtual bool SupportsType(TSubclassOf<UMovieSceneSection> SectionClass) const override;
virtual FName GetTrackName() const override;
#if WITH_EDITORONLY_DATA
virtual FText GetDisplayName() const override;
#endif
然后最重要的就是我们要实现创建我们自己的Section和EvalTemplate的接口,创建自己的类返回就可以了
virtual UMovieSceneSection* CreateNewSection() override;
virtual FMovieSceneEvalTemplatePtr CreateTemplateForSection(const UMovieSceneSection& InSection) const override;
在创建完Track后就能在Sequencer编辑器的添加轨道中看到我们自己的轨道了。UMovieSceneTrack在编译后会成为FMovieSceneEvaluationTrack用于Runtime的Evaluate
MovieSceneSection
Section就是你在轨道上添加的每个段,简单来说就是一个数据载体,我们需要在自己的Section中通过UPROPERTY将对应的字幕属性(Text或者DataTableRowHandle)暴露给Sequencer编辑器,在运行时访问这些属性进行Evaluate
UCLASS(MinimalAPI)
class UMovieSceneDialogueSection : public UMovieSceneSection
{
private:
UPROPERTY(EditAnywhere, Category = Dialogue)
bool bHideDialogueUI = false;
UPROPERTY(EditAnywhere, Category = Dialogue)
FDataTableRowHandle DialogueRowHandle;
};
MovieSceneEvalTemplate
EvalTemplate是每个Track在编译时创建出的东西,包含在FMovieSceneEvaluationTrack中,每个EvalTemplate对应一个section(通过前面Track实现的CreateTemplateForSection),所以一个Track可能包含多个EvalTemplate。
EvalTemplate指定了Section的执行方式,由于创建EvalTemplate的函数是我们自己在Track中实现的,所以我们在创建时就可以把Section中配置好的属性给到EvalTemplate
FMovieSceneDialogueTemplate::FMovieSceneDialogueTemplate(const UMovieSceneDialogueSection* InSection)
: Section(InSection)
{
UMovieSceneDialogueSection* SectionPtr = const_cast<UMovieSceneDialogueSection*>(InSection);
if (SectionPtr == nullptr)
{
return;
}
bHideDialogueUI = SectionPtr->GetShouldHideDialogueUI();
DialogueRowHandle = SectionPtr->GetDialogueinfoRow();
}
然后我们需要实现几个函数,Setup和Teardown分别是在Section的开始和结束时调用,Evaluate则是每次Tick的执行函数
virtual void Evaluate(const FMovieSceneEvaluationOperand& Operand, const FMovieSceneContext& Context, const FPersistentEvaluationData& PersistentData, FMovieSceneExecutionTokens& ExecutionTokens) const override;
virtual UScriptStruct& GetScriptStructImpl() const override { return *StaticStruct(); }
virtual void Setup(FPersistentEvaluationData& PersistentData, IMovieScenePlayer& Player) const override;
virtual void TearDown(FPersistentEvaluationData& PersistentData, IMovieScenePlayer& Player) const override;
virtual void SetupOverrides() override { EnableOverrides(RequiresSetupFlag | RequiresTearDownFlag); }
在Setup和Teardown中我们需要向PersistentData中放入我们自己定义好的Data,供后续Token使用
struct FDialogueEvaluationData : IPersistentEvaluationData
{
bool bHideDialogueUI = false;
FDataTableRowHandle DialogueRowHandle;
};
struct FDialogueExecutionToken : IMovieSceneExecutionToken
{
virtual void Execute(const FMovieSceneContext& Context, const FMovieSceneEvaluationOperand& Operand,
FPersistentEvaluationData& PersistentData, IMovieScenePlayer& Player) override
{
auto persistentData = PersistentData.GetSectionData<FDialogueEvaluationData>();
//Do Logic Here
}
};
在Evaluate中只需要将我们自己的Token加入TokenStack中即可
void FMovieSceneDialogueTemplate::Evaluate(const FMovieSceneEvaluationOperand& Operand, const FMovieSceneContext& Context,
const FPersistentEvaluationData& PersistentData, FMovieSceneExecutionTokens& ExecutionTokens) const
{
ExecutionTokens.Add(FDialogueExecutionToken());
}
那么这里其实就有一个问题,我们为什么不直接在Evaluate中执行逻辑使用EvalTemplate中的成员变量,而要把数据先放到EvaluationData中再通过Token来执行逻辑呢?
区别在于Evaluate本身不一定执行在GameThread上,为了效率可以将一些耗时性的计算工作放在Evaluate中;而Token的Execute是一定执行在GameThread上的,由于我们要通过事件等方式驱动我们的UI显示对话文本,所以我们的逻辑需要放在Token中,数据自然也要放到EvaluationData中。
总结
Track、Section、EvalTemplate、EvaluationData和ExecutionToken就是我们在扩展轨道时需要接触的几个概念,我们不仅可以显示对话文本,如果我们有对话选项等系统的话也可以按照相同的方式与Sequence结合起来,更好的将Sequence融入到游戏中
关于更加详细的源码分析可以参考其他大佬的文章
UE4 LevelSequence源码剖析(一)
UE4 LevelSequence源码剖析(二)
UE4 LevelSequence源码剖析(三)