找回密码
 立即注册
查看: 307|回复: 0

UE4自定义UScriptStruct热更新后,同步更新蓝图节点

[复制链接]
发表于 2022-9-1 09:03 | 显示全部楼层 |阅读模式
UE4中的结构体有两种,其一是 C++ 中定义,不可热更新的 UStruct,其二是脚本中定义,可热更新的 UScriptStruct。
UE4蓝图中自带的结构体是 UUserDefinedStruct,它是 UScriptStruct 的子类。该结构体可在编辑器中定义,在蓝图,以及脚本中使用。
但如果想在脚本中定义结构体,在蓝图中使用,情况就不一样了。因为 UE4 本身并不支持这种操作,不过我们可以参照 UUserDefinedStruct 的做法,实现这一点。
GitHub 上已经有人做出了插件,实现了这个功能:GitHub - 20tab/UnrealEnginePython: Embed Python in Unreal Engine 4
插件使用效果如下:
import unreal_engine as ue

@ustruct()
class TestScriptStruct:
        TestBool: bool = ue.uproperty(BlueprintReadWrite = 1, EditAnywhere = 1)
        TestInt: int = ue.uproperty(BlueprintReadWrite = 1, EditAnywhere = 1)
        TestFloat: float = ue.uproperty(BlueprintReadWrite = 1, EditAnywhere = 1)
        TestStr: str = ue.uproperty(BlueprintReadWrite = 1, EditAnywhere = 1)
        TestVector: ue.FVector = ue.uproperty(BlueprintReadWrite = 1, EditAnywhere = 1)
        TestObject: Object = ue.uproperty(BlueprintReadWrite = 1, EditAnywhere = 1)

但是该插件有个问题,如果脚本中定义的结构体在蓝图中也有使用,那么在我们修改结构体后,由于反射信息发生了变化,而蓝图节点没有重新构造,所以在执行与该结构体相关的蓝图节点时,会崩溃。
为了解决崩溃的问题(本质是 UScriptStruct 热更新的问题),在 UScriptStruct 更新完后,把引用该结构体的蓝图节点重新构造即可。引擎自身对 UUserDefinedStruct 蓝图节点热更新的代码如下(该段代码在引擎源文件 UserDefinedStructureCompilerUtils.cpp 中可找到):
void FUserDefinedStructureCompilerUtils::CompileStruct(class UUserDefinedStruct* Struct, class FCompilerResultsLog& MessageLog, bool bForceRecompile)
{
        if (FStructureEditorUtils::UserDefinedStructEnabled() && Struct)
        {
                TArray<UUserDefinedStruct*> ChangedStructs;
                if (FUserDefinedStructureCompilerInner::ShouldBeCompiled(Struct) || bForceRecompile)
                {
                        ChangedStructs.Add(Struct);
                }

                TMap<UBlueprint*, FUserDefinedStructureCompilerInner::FBlueprintUserStructData> BlueprintsToRecompile;
                for (int32 StructIdx = 0; StructIdx < ChangedStructs.Num(); ++StructIdx)
                {
                        UUserDefinedStruct* ChangedStruct = ChangedStructs[StructIdx];
                        if (ChangedStruct)
                        {
                                FStructureEditorUtils::BroadcastPreChange(ChangedStruct);
                                FUserDefinedStructureCompilerInner::ReplaceStructWithTempDuplicate(ChangedStruct, BlueprintsToRecompile, ChangedStructs);
                                ChangedStruct->Status = EUserDefinedStructureStatus::UDSS_Dirty;
                        }
                }

                // COMPILE IN PROPER ORDER
                FUserDefinedStructureCompilerInner::BuildDependencyMapAndCompile(ChangedStructs, MessageLog);

                // UPDATE ALL THINGS DEPENDENT ON COMPILED STRUCTURES
                TSet<UBlueprint*> BlueprintsThatHaveBeenRecompiled;
                for (TObjectIterator<UK2Node> It(RF_Transient | RF_ClassDefaultObject, /** bIncludeDerivedClasses */ true, /** InternalExcludeFlags */ EInternalObjectFlags::PendingKill); It && ChangedStructs.Num(); ++It)
                {
                        bool bReconstruct = false;

                        UK2Node* Node = *It;

                        if (Node && !Node->HasAnyFlags(RF_Transient) && !Node->IsPendingKill())
                        {
                                // If this is a struct operation node operation on the changed struct we must reconstruct
                                if (UK2Node_StructOperation* StructOpNode = Cast<UK2Node_StructOperation>(Node))
                                {
                                        UUserDefinedStruct* StructInNode = Cast<UUserDefinedStruct>(StructOpNode->StructType);
                                        if (StructInNode && ChangedStructs.Contains(StructInNode))
                                        {
                                                bReconstruct = true;
                                        }
                                }
                                if (!bReconstruct)
                                {
                                        // Look through the nodes pins and if any of them are split and the type of the split pin is a user defined struct we need to reconstruct
                                        for (UEdGraphPin* Pin : Node->Pins)
                                        {
                                                if (Pin->SubPins.Num() > 0)
                                                {
                                                        UUserDefinedStruct* StructType = Cast<UUserDefinedStruct>(Pin->PinType.PinSubCategoryObject.Get());
                                                        if (StructType && ChangedStructs.Contains(StructType))
                                                        {
                                                                bReconstruct = true;
                                                                break;
                                                        }
                                                }

                                        }
                                }
                        }

                        if (bReconstruct)
                        {
                                if (Node->HasValidBlueprint())
                                {
                                        UBlueprint* FoundBlueprint = Node->GetBlueprint();
                                        // The blueprint skeleton needs to be updated before we reconstruct the node
                                        // or else we may have member references that point to the old skeleton
                                        if (!BlueprintsThatHaveBeenRecompiled.Contains(FoundBlueprint))
                                        {
                                                BlueprintsThatHaveBeenRecompiled.Add(FoundBlueprint);
                                                BlueprintsToRecompile.Remove(FoundBlueprint);

                                                // Reapply CDO data

                                                FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(FoundBlueprint);
                                        }
                                        Node->ReconstructNode();
                                }
                        }
                }

                for (TPair<UBlueprint*, FUserDefinedStructureCompilerInner::FBlueprintUserStructData>& Pair : BlueprintsToRecompile)
                {
                        FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Pair.Key);
                }

                for (UUserDefinedStruct* ChangedStruct : ChangedStructs)
                {
                        if (ChangedStruct)
                        {
                                FStructureEditorUtils::BroadcastPostChange(ChangedStruct);
                                ChangedStruct->MarkPackageDirty();
                        }
                }
        }
}
借鉴引擎的做法,稍作修改,我们自定义的 UScriptStruct 也可实现蓝图节点的更新,如此便能解决崩溃的问题。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-10 05:33 , Processed in 0.092086 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表