Object的观察工具之前已经有两篇了。但是第一篇的代码有些冗杂,很多方面都可以简化。最近的一篇的代码比较简洁优雅,但它是直接输出了所有Object的信息,而Object数目非常多,在两万多个Object中找到想要观察的目标是较为困难的。这让我想要做一个工具(或者说建立一个机制)能按照自定义的“规则”筛选出想要观察的Object,并且最好是能用蓝图表示出这个“规则”。
将上一篇输出Object基础信息的逻辑封装为一个Object(为了借用它的Detail面板界面)。并且包含“过滤函数”,而这个函数要暴露给蓝图实现。最后使用UMG界面用于显示这个Object的细节面板,以方便操控。
之所以要创建一个UObject类原因是:
最终的代码如下:
ObjectPrinter.h:
#pragma once
#include "UObject/Object.h"
#include "ObjectPrinter.generated.h"
UCLASS(Blueprintable)
class UObjectPrinter : public UObject
{
GENERATED_UCLASS_BODY()
public:
//将符合条件的Object打印到输出窗口中!(会呈现在细节面板中成为一个按钮)
UFUNCTION(CallInEditor)
void PrintObjects();
//判断这个对象是否应该被打印(此函数期望被子类蓝图重载而实现自定义的逻辑)
UFUNCTION(BlueprintImplementableEvent)
bool Filter(UObject* Obj);
//强制执行一次GC(会呈现在细节面板中成为一个按钮。可以手动触发,来排除临时物体干扰观察)
UFUNCTION(CallInEditor)
void ForceGC();
};
ObjectPrinter.cpp:
#include"ObjectPrinter.h"
DEFINE_LOG_CATEGORY_STATIC(LogYKS, Log, All);
FString RecursiveGetOuterText(UObject* Object, FString Result)
{
if (!Object->GetOuter())
return Result;
else
{
Result += "->(" + Object->GetOuter()->GetFName().ToString() + ")";
return RecursiveGetOuterText(Object->GetOuter(), Result);
}
}
FString GetFlagText(EObjectFlags Flag)
{
FString Result;
if (Flag & RF_NoFlags)
Result += "+RF_NoFlags";
if (Flag & RF_Public)
Result += "+RF_Public";
if (Flag & RF_Standalone)
Result += "+RF_Standalone";
if (Flag & RF_MarkAsNative)
Result += "+RF_MarkAsNative";
if (Flag & RF_Transactional)
Result += "+RF_Transactional";
if (Flag & RF_ClassDefaultObject)
Result += "+RF_ClassDefaultObject";
if (Flag & RF_ArchetypeObject)
Result += "+RF_ArchetypeObject";
if (Flag & RF_Transient)
Result += "+RF_Transient";
if (Flag & RF_MarkAsRootSet)
Result += "+RF_MarkAsRootSet";
if (Flag & RF_TagGarbageTemp)
Result += "+RF_TagGarbageTemp";
if (Flag & RF_NeedInitialization)
Result += "+RF_NeedInitialization";
if (Flag & RF_NeedLoad)
Result += "+RF_NeedLoad";
if (Flag & RF_KeepForCooker)
Result += "+RF_KeepForCooker";
if (Flag & RF_NeedPostLoad)
Result += "+RF_NeedPostLoad";
if (Flag & RF_NeedPostLoadSubobjects)
Result += "+RF_NeedPostLoadSubobjects";
if (Flag & RF_NewerVersionExists)
Result += "+RF_NewerVersionExists";
if (Flag & RF_BeginDestroyed)
Result += "+RF_BeginDestroyed";
if (Flag & RF_FinishDestroyed)
Result += "+RF_FinishDestroyed";
if (Flag & RF_BeingRegenerated)
Result += "+RF_BeingRegenerated";
if (Flag & RF_DefaultSubObject)
Result += "+RF_DefaultSubObject";
if (Flag & RF_WasLoaded)
Result += "+RF_WasLoaded";
if (Flag & RF_TextExportTransient)
Result += "+RF_TextExportTransient";
if (Flag & RF_LoadCompleted)
Result += "+RF_LoadCompleted";
if (Flag & RF_InheritableComponentTemplate)
Result += "+RF_InheritableComponentTemplate";
if (Flag & RF_DuplicateTransient)
Result += "+RF_DuplicateTransient";
if (Flag & RF_StrongRefOnFrame)
Result += "+RF_StrongRefOnFrame";
if (Flag & RF_NonPIEDuplicateTransient)
Result += "+RF_NonPIEDuplicateTransient";
if (Flag & RF_Dynamic)
Result += "+RF_Dynamic";
if (Flag & RF_WillBeLoaded)
Result += "+RF_WillBeLoaded";
if (Flag & RF_HasExternalPackage)
Result += "+RF_HasExternalPackage";
return Result;
}
UObjectPrinter::UObjectPrinter(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{}
void UObjectPrinter::PrintObjects()
{
for (int i = 0; i < GUObjectArray.GetObjectArrayNum(); i++)
{
FUObjectItem* ObjectElement = GUObjectArray.IndexToObject(i);
if (ObjectElement->Object)
{
UObject* Object = (UObject*)ObjectElement->Object;
if (!Filter(Object))//过滤
continue;
UE_LOG(LogYKS, Warning, TEXT("[%d], <%s>, %s, %s, %s")
, Object->GetUniqueID() //InternalIndex
, *(Object->GetClass()->GetFName().ToString()) //类
, *(Object->GetFName().ToString()) //名字
, *RecursiveGetOuterText(Object, "") //Outer链
, *GetFlagText(Object->GetFlags()) //具有的Flag
);
}
}
}
void UObjectPrinter::ForceGC()
{
CollectGarbage(EObjectFlags::RF_NoFlags);
}
基本上就是对上一篇代码的封装。只是在打印的时候还会通过Filter
函数过滤一下。(另外还加了一个强制GC的函数,可以随时调用,用于排除一些临时的Object干扰观察)
进入编辑器,以刚才在Cpp中定义的ObjectPrinter
为基类创建蓝图:
作为测试,我将以 “Object类型” 作为过滤条件,因此我将蓝图命名为ObjectPrinter_ClassType
。
打开这个蓝图,创建一个变量名为ClassType
。将它类型设置为 Soft Class Path用于表示用哪个类型过滤:
然后,就可以重载Filter
函数了:
Filter函数连接如下:
连接节点需要注意的是,Soft Class Path 并不能直接转换为Class,需要两次装换:
为了能显示细节面板,我选择创建一个 Editor Utility Widget:
打开这个蓝图,创建一个细节面板(DetailsView)控件:(勾选 “Size To Content” 以便界面可以显示完全)
然后,在构造函数中,以自己为Outer创建一个ObjectPrinter_ClassType
(上一步用蓝图定义的对象),并将细节面板显示的对象设置为它:
编译保存后,就可以右键它来显示界面了:
测试使用:
后续如果想要用其他自定义的逻辑来筛选Object,就可以创建新的蓝图并重载过滤函数,然后将窗口中细节面板的对象设置为新蓝图。