UE – LevelSequence C++事件添加 完全踩坑记录

内容纲要

file
此图是UE 5.1 Sequence 的 Section 与对应的 Channel 记录, 也许后续会对此图进行更改,但目前5.1是这么个结构。
UE 在创建 UMovieSceneSection 子类的时候,并没有将 UMovieSceneEventSectionBase 与 UMovieSceneEventSection 置于同一条继承下

UMovieSceneEventSection 中 Channel 使用的 FEventPayload 可以很方便的利用内置的函数修改参数。

// 定义Struct类型,此处可以替换成自己的结构体。
FStruct Struct;
// 定义Payload。
FEventPayload Payload;
// 此处可以填入执行事件的名字,但我并未对此进行测试。
Payload.EventName = FName(SequencerQuickBindingResult.EventEndpoint.GetFullName());
// 将 Payload 的参数结构重新绑定至新的结构体上。
Payload.Parameters.Reassign(Struct.StaticStruct());
// 将对应结构体的实例化对象以 uint8* 的形式调用函数将其覆写,上面必须执行,不然结构不一致会出现乱码。
Payload.Parameters.OverwriteWith(reinterpret_cast<uint8*>(&Struct));
// 最后直接在 UMovieSceneSection 轨道下,对 FMovieSceneEventSectionData 进行 FEventPayload 操作
// 与图中结构是一一对应的。
EventSection->GetChannelProxy().GetChannels<FMovieSceneEventSectionData>()[0]
    ->GetData().UpdateOrAddKey(CrossRange.Time.FrameNumber, Payload);

但在此处,我将着重描述 UMovieSceneEventSectionBase 的使用操作。
UMovieSceneEventSectionBase 被两个常用子类所继承。
其一是 UMovieSceneEventTriggerSection
另一个是 UMovieSceneEventRepeaterSection
这两个同时对应了 Sequence 中的 Trigger 和 Repeater 两个轨道。
如图所示,如果想在 UMovieSceneEventSectionBase 的 Channel 中添加 Data。
那么需要使用 FMovieSceneEvent 类才可以。

但是在 UE 的设计中 FMovieSceneEvent 的参数是由成员变量 PayloadVariables 记录的
而此变量的结构如下:

TMap<FName, FMovieSceneEventPayloadVariable> PayloadVariables;

可以看到,FName代表了参数的名字,而右侧 FMovieSceneEventPayloadVariable
所储存的就是参数的类型与数据了。

USTRUCT(BlueprintType)
struct FMovieSceneEventPayloadVariable
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, Category="Sequencer|Event")
    FString Value;
};

而储存对应类型与数据的类里,居然只有一个FString变量?!怎会如此。
但是不要慌张,我们可以先来看一些源码的架构。
file
UE 在构建右侧面板时(也就是说右键关键帧所弹出的面板)时,会通过 MovieSceneEventCustomiyation.cpp
去执行这些。

const FMovieSceneEventPayloadVariable* PayloadVariable = 
    EntryPoint->PayloadVariables.Find(Field->GetFName());
if (PayloadVariable)
    {
        AllValidNames.Add(Field->GetFName());
        // We have an override for this variable
        const bool bImportSuccess = FBlueprintEditorUtils::PropertyValueFromString(
        Field, PayloadVariable->Value, StructData->GetStructMemory());
        if (!bImportSuccess)
            {
                // @todo: error
            }
    }

可以清晰地看到,在构建面板时这里通过了 FBlueprintEditorUtils::PropertyValueFromString 函数
成功将 FMovieSceneEventPayloadVariable 中唯一的 FString 解析为了数据可以写入参数。
如此一来只需要照猫画虎:

FString StructValue;
const uint8* Container = reinterpret_cast<const uint8*>(this);
// 需要在对应的 Actor 中, 拥有打了 UPROPERTY 宏的 Struct。
const FProperty* Property = FindFProperty<FStructProperty>(
    AActor::StaticClass(), 
    GET_MEMBER_NAME_CHECKED(AActor, Struct));
FBlueprintEditorUtils::PropertyValueToString(Property, Container, StructValue);
FMovieSceneEventPayloadVariable MovieSceneEventPayloadVariable;
MovieSceneEventPayloadVariable.Value = StructValue;
MovieSceneEvent.PayloadVariables[FName("Struct")] = MovieSceneEventPayloadVariable;
MovieSceneSection->GetChannelProxy().GetChannels<FMovieSceneEventChannel>()[0]
    ->GetData().UpdateOrAddKey(Time, MovieSceneEvent);

直接利用 FBlueprintEditorUtils::PropertyValueToString(); 将结构体变成 FString。
这样就可以作为参数传入了。


题外话捏:
如果想自动在 Sequence 的 Director 中添加事件+函数绑定。
需要使用

FSequencerQuickBindingResult SequencerQuickBindingResult = 
    USequencerToolsFunctionLibrary::CreateQuickBinding(
    LevelSequence, this, "this中拥有的函数名", true);

这样就在 Director 中创建绑定了。

FMovieSceneEvent不能直接创建,需要从上面的函数中得到 FSequencerQuickBindingResult
然后直接使用

FMovieSceneEvent MovieSceneEvent = USequencerToolsFunctionLibrary::CreateEvent(ModifySequence, EventSection, SequencerQuickBindingResult, SequencerQuickBindingResult.PayloadNames);

否则无法给事件对应的绑定。

Related Posts

发表回复

同步Schの秘密防空洞