20 KiB
title, date, excerpt, tags, rating
title | date | excerpt | tags | rating |
---|---|---|---|---|
Ue4 c++ UProperty反射 PostEditChangeProperty | 2022-12-09 13:40:28 | UObject | ⭐⭐⭐ |
反射系统
https://ikrima.dev/ue4guide/engine-programming/uobject-reflection/uobject-reflection/
Property类型判断
- UStructProperty:结构体
- UMapProperty:TMap
- UArrayProperty:TArray 属性判断是否是数组或是Map
FProperty* PropertyThatChanged = PropertyChangedEvent.Property;
if ( PropertyThatChanged != nullptr )
{
if (PropertyThatChanged->IsA(FArrayProperty::StaticClass()))
{
FArrayProperty* ArrayProp = CastField<FArrayProperty>(PropertyThatChanged);
FProperty* InnerProp = ArrayProp->Inner;
if (InnerProp->IsA(FStructProperty::StaticClass()))
{
const FToonRampData* ToonRampData = ArrayProp->ContainerPtrToValuePtr<FToonRampData>(InnerProp, 0);
if(ToonRampData)
{
}
}
}else if(PropertyThatChanged->IsA(FMapProperty::StaticClass()))
{
FArrayProperty* ArrayProp = CastField<FArrayProperty>(PropertyThatChanged);
FProperty* InnerProp = ArrayProp->Inner;
if (InnerProp->IsA(FStructProperty::StaticClass()))
{
const FToonRampData* ToonRampData = ArrayProp->ContainerPtrToValuePtr<FToonRampData>(InnerProp, 0);
if(ToonRampData)
{
}
}
}
}
属性变化回调函数
virtual void EditorApplyTranslation(const FVector& DeltaTranslation, bool bAltDown, bool bShiftDown, bool bCtrlDown) override;
virtual void EditorApplyRotation(const FRotator& DeltaRotation, bool bAltDown, bool bShiftDown, bool bCtrlDown) override;
virtual void EditorApplyScale(const FVector& DeltaScale, const FVector* PivotLocation, bool bAltDown, bool bShiftDown, bool bCtrlDown) override;
virtual void PostEditMove(bool bFinished) override;
virtual void PostEditComponentMove(bool bFinished) override;
没有Struct()却可以在蓝图找到类型问题笔记
Traits
大概率是这个
template<>
struct TStructOpsTypeTraits<FBox2D> : public TStructOpsTypeTraitsBase2<FBox2D>
{
enum
{
WithIdenticalViaEquality = true,
WithNoInitConstructor = true,
WithZeroConstructor = true,
};
};
IMPLEMENT_STRUCT(Box2D);
BlueprintCompilerCppBackendValueHelper.cpp
bool FEmitDefaultValueHelper::SpecialStructureConstructor(const UStruct* Struct, const uint8* ValuePtr, /*out*/ FString* OutResult)
{
...
if (TBaseStructure<FBox2D>::Get() == Struct)
{
if (OutResult)
{
const FBox2D* Box2D = reinterpret_cast<const FBox2D*>(ValuePtr);
*OutResult = FString::Printf(TEXT("CreateFBox2D(FVector2D(%s, %s), FVector2D(%s, %s), %s)")
, *FEmitHelper::FloatToString(Box2D->Min.X)
, *FEmitHelper::FloatToString(Box2D->Min.Y)
, *FEmitHelper::FloatToString(Box2D->Max.X)
, *FEmitHelper::FloatToString(Box2D->Max.Y)
, Box2D->bIsValid ? TEXT("true") : TEXT("false"));
}
return true;
}
...
}
struct FStructAccessHelper_StaticData
{
TMap<const UScriptStruct*, FString> BaseStructureAccessorsMap;
TMap<const UScriptStruct*, bool> SupportsDirectNativeAccessMap;
TArray<FSoftClassPath> NoExportTypesWithDirectNativeFieldAccess;
static FStructAccessHelper_StaticData& Get()
{
static FStructAccessHelper_StaticData StaticInstance;
return StaticInstance;
}
private:
FStructAccessHelper_StaticData()
{
// These are declared in Class.h; it's more efficient to access these native struct types at runtime using the specialized template functions, so we list them here.
MAP_BASE_STRUCTURE_ACCESS(TBaseStructure<FRotator>::Get());
MAP_BASE_STRUCTURE_ACCESS(TBaseStructure<FTransform>::Get());
MAP_BASE_STRUCTURE_ACCESS(TBaseStructure<FLinearColor>::Get());
MAP_BASE_STRUCTURE_ACCESS(TBaseStructure<FColor>::Get());
MAP_BASE_STRUCTURE_ACCESS(TBaseStructure<FVector>::Get());
MAP_BASE_STRUCTURE_ACCESS(TBaseStructure<FVector2D>::Get());
MAP_BASE_STRUCTURE_ACCESS(TBaseStructure<FRandomStream>::Get());
MAP_BASE_STRUCTURE_ACCESS(TBaseStructure<FGuid>::Get());
MAP_BASE_STRUCTURE_ACCESS(TBaseStructure<FTransform>::Get());
MAP_BASE_STRUCTURE_ACCESS(TBaseStructure<FBox2D>::Get());
MAP_BASE_STRUCTURE_ACCESS(TBaseStructure<FFallbackStruct>::Get());
MAP_BASE_STRUCTURE_ACCESS(TBaseStructure<FFloatRangeBound>::Get());
MAP_BASE_STRUCTURE_ACCESS(TBaseStructure<FFloatRange>::Get());
MAP_BASE_STRUCTURE_ACCESS(TBaseStructure<FInt32RangeBound>::Get());
MAP_BASE_STRUCTURE_ACCESS(TBaseStructure<FInt32Range>::Get());
MAP_BASE_STRUCTURE_ACCESS(TBaseStructure<FFloatInterval>::Get());
MAP_BASE_STRUCTURE_ACCESS(TBaseStructure<FInt32Interval>::Get());
MAP_BASE_STRUCTURE_ACCESS(TBaseStructure<FFrameNumber>::Get());
MAP_BASE_STRUCTURE_ACCESS(TBaseStructure<FFrameTime>::Get());
{
// Cache the known set of noexport types that are known to be compatible with emitting native code to access fields directly.
TArray<FString> Paths;
GConfig->GetArray(TEXT("BlueprintNativizationSettings"), TEXT("NoExportTypesWithDirectNativeFieldAccess"), Paths, GEditorIni);
for (FString& Path : Paths)
{
NoExportTypesWithDirectNativeFieldAccess.Add(FSoftClassPath(Path));
}
}
}
}
大钊的文章中有相似的代码
https://zhuanlan.zhihu.com/p/26019216
Struct的收集 对于Struct,我们先来看上篇里生成的代码:
static FCompiledInDeferStruct Z_CompiledInDeferStruct_UScriptStruct_FMyStruct(FMyStruct::StaticStruct, TEXT("/Script/Hello"), TEXT("MyStruct"), false, nullptr, nullptr); //延迟注册
static struct FScriptStruct_Hello_StaticRegisterNativesFMyStruct
{
FScriptStruct_Hello_StaticRegisterNativesFMyStruct()
{
UScriptStruct::DeferCppStructOps(FName(TEXT("MyStruct")),new UScriptStruct::TCppStructOps<FMyStruct>);
}
} ScriptStruct_Hello_StaticRegisterNativesFMyStruct;
https://zhuanlan.zhihu.com/p/59553490
ICppStructOps的作用
很多朋友在看源码的时候,可能会对UScriptStruct里定义的ICppStructOps类以及模板子类TCppStructOps<CPPSTRUCT>
感到疑惑。其实它们是C++的一种常见的架构模式,用一个虚函数基类定义一些公共操作,再用一个具体模板子类来实现,从而既可以保存类型,又可以有公共操作接口。
针对于UE4这里来说,ICppStructOps就定义了这个结构的一些公共操作。而探测这个C++结构的一些特性就交给了TCppStructOps<CPPSTRUCT>
类里的TStructOpsTypeTraits<CPPSTRUCT>
。一些C++结构的信息不能通过模板探测出来的,就需要我们手动标记提供了,所以具体的代码是:
template <class CPPSTRUCT>
struct TStructOpsTypeTraitsBase2
{
enum
{
WithZeroConstructor = false, // 0构造,内存清零后就可以了,说明这个结构的默认值就是0
WithNoInitConstructor = false, // 有个ForceInit的参数的构造,用来专门构造出0值结构来
WithNoDestructor = false, // 是否没有结构有自定义的析构函数, 如果没有析构的话,DestroyStruct里面就可以省略调用析构函数了。默认是有的。结构如果是pod类型,则肯定没有析构。
WithCopy = !TIsPODType<CPPSTRUCT>::Value, // 是否结构有自定义的=赋值函数。如果没有的话,在CopyScriptStruct的时候就只需要拷贝内存就可以了
WithIdenticalViaEquality = false, // 用==来比较结构
WithIdentical = false, // 有一个自定义的Identical函数来专门用来比较,和WithIdenticalViaEquality互斥
WithExportTextItem = false, // 有一个ExportTextItem函数来把结构值导出为字符串
WithImportTextItem = false, // 有一个ImportTextItem函数把字符串导进结构值
WithAddStructReferencedObjects = false, // 有一个AddStructReferencedObjects函数用来添加结构额外的引用对象
WithSerializer = false, // 有一个Serialize函数用来序列化
WithStructuredSerializer = false, // 有一个结构结构Serialize函数用来序列化
WithPostSerialize = false, // 有一个PostSerialize回调用来在序列化后调用
WithNetSerializer = false, // 有一个NetSerialize函数用来在网络复制中序列化
WithNetDeltaSerializer = false, // 有一个NetDeltaSerialize函数用来在之前NetSerialize的基础上只序列化出差异来,一般用在TArray属性上进行优化
WithSerializeFromMismatchedTag = false, // 有一个SerializeFromMismatchedTag函数用来处理属性tag未匹配到的属性值,一般是在结构进行升级后,但值还是原来的值,这个时候用来把旧值升级到新结构时使用
WithStructuredSerializeFromMismatchedTag = false, // SerializeFromMismatchedTag的结构版本
WithPostScriptConstruct = false,// 有一个PostScriptConstruct函数用在蓝图构造脚本后调用
WithNetSharedSerialization = false, // 指明结构的NetSerialize函数不需要用到UPackageMap
};
};
template<class CPPSTRUCT>
struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2<CPPSTRUCT>
{
};
举个小例子,假如你看到编辑器里某个属性,想在C++里去修改它的值,结果发现它不是public的,甚至有可能连头文件都是private的,这个时候如果对类型系统结构理解不深的人可能就放弃了,但懂的人就知道可以通过这个对象遍历UProperty来查找到这个属性从而修改它。 还有一个例子是如果你做了一个插件,调用了引擎编辑器本身的Details面板属性,但又想隐藏其中的一些字段,这个时候如果不修改引擎往往是难以办到的,但是如果知道了属性面板里的属性其实也都是一个个UProperty来的,这样你就可以通过对象路径获得这个属性,然后开启关闭它的某些Flags来达成效果。这也算是一种常规的Hack方式。
《InsideUE4》UObject(十三)类型系统-反射实战
https://zhuanlan.zhihu.com/p/61042237
获取类型对象
如果想获取到程序里定义的所有的class,方便的方法是:
TArray<UObject*> result;
GetObjectsOfClass(UClass::StaticClass(), result); //获取所有的class和interface
GetObjectsOfClass(UEnum::StaticClass(), result); //获取所有的enum
GetObjectsOfClass(UScriptStruct::StaticClass(), result); //获取所有的struct
GetObjectsOfClass是UE4已经写好的一个很方便的方法,可以获取到属于某个UClass下面的所有对象。因此如果用UClass::StaticClass()本身,就可以获得程序里定义的所有class。值得注意的是,UE4里的接口是有一个配套的UInterface对象来存储元数据信息,它的类型也是用UClass表示的,所以也会获得interface。根据前文,enum会生成UEnum,struct会生成UScriptStruct,所以把参数换成UEnum::StaticClass()就可以获得所有的UEnum对象了,UScriptStruct::StaticClass()就是所有的UScriptStruct了,最后就可以根据这些类型对象来反射获取类型信息了。 而如果要精确的根据一个名字来查找某个类型对象,就可以用UE4里另一个方法:
template< class T >
inline T* FindObject( UObject* Outer, const TCHAR* Name, bool ExactClass=false )
{
return (T*)StaticFindObject( T::StaticClass(), Outer, Name, ExactClass );
}
UClass* classObj=FindObject<UClass>(ANY_PACKAGE,"MyClass"); //获得表示MyClass的UClass*
遍历字段
在获取到了一个类型对象后,就可以用各种方式去遍历查找内部的字段了。为此,UE4提供了一个方便的迭代器TFieldIterator<T>
,可以通过它筛选遍历字段。
const UStruct* structClass; //任何复合类型都可以
//遍历属性
for (TFieldIterator<UProperty> i(structClass); i; ++i)
{
UProperty* prop=*i;
}
//遍历函数
for (TFieldIterator<UFunction> i(structClass); i; ++i)
{
UFunction* func=*i;
//遍历函数的参数
for (TFieldIterator<UProperty> i(func); i; ++i)
{
UProperty* param=*i;
if( param->PropertyFlags & CPF_ReturnParm ) //这是返回值
{
}
}
}
//遍历接口
const UClass* classObj; //只有UClass才有接口
for (const FImplementedInterface& ii : classObj->Interfaces)
{
UClass* interfaceClass = ii.Class;
}
//遍历枚举
const UEnum* enumClass;
for (int i = 0; i < enumClass->NumEnums(); ++i)
{
FName name = enumClass->GetNameByIndex(i);
int value = enumClass->GetValueByIndex(i);
}
//遍历元数据
#if WITH_METADATA
const UObject* obj;//可以是任何对象,但一般是UField才有值
UMetaData* metaData = obj->GetOutermost()->GetMetaData();
TMap<FName, FString>* keyValues = metaData->GetMapForObject(obj);
if (keyValues != nullptr&&keyValues->Num() > 0)
{
for (const auto& i : *keyValues)
{
FName key=i.Key;
FString value=i.Value;
}
}
#endif
//查找属性
UProperty* UStruct::FindPropertyByName(FName InName) const
{
for (UProperty* Property = PropertyLink; Property != NULL; Property = Property->PropertyLinkNext)
{
if (Property->GetFName() == InName)
{
return Property;
}
}
return NULL;
}
//查找函数
UFunction* UClass::FindFunctionByName(FName InName, EIncludeSuperFlag::Type IncludeSuper) const;
查看继承
//得到类型对象后,也可以遍历查看它的继承关系。 遍历继承链条:
const UStruct* structClass; //结构和类
TArray<FString> classNames;
classNames.Add(structClass->GetName());
UStruct* superClass = structClass->GetSuperStruct();
while (superClass)
{
classNames.Add(superClass->GetName());
superClass = superClass->GetSuperStruct();
}
FString str= FString::Join(classNames, TEXT("->")); //会输出MyClass->UObject
//那反过来,如果想获得一个类下面的所有子类,可以这样:
const UClass* classObj; //结构和类
TArray<UClass*> result;
GetDerivedClasses(classObj, result, false);
//函数原型是
void GetDerivedClasses(UClass* ClassToLookFor, TArray<UClass *>& Results, bool bRecursive);
//那么怎么获取实现了某个接口的所有子类呢?
TArray<UObject*> result;
GetObjectsOfClass(UClass::StaticClass(), result);
TArray<UClass*> classes;
for (UObject* obj : result)
{
UClass* classObj = Cast<UClass>(obj);
if (classObj->ImplementsInterface(interfaceClass))//判断实现了某个接口
{
classes.Add(classObj);
}
}
获取设置属性值
template<typename ValueType>
ValueType* UProperty::ContainerPtrToValuePtr(void* ContainerPtr, int32 ArrayIndex = 0) const
{
return (ValueType*)ContainerVoidPtrToValuePtrInternal(ContainerPtr, ArrayIndex);
}
template<typename ValueType>
ValueType* UProperty::ContainerPtrToValuePtr(UObject* ContainerPtr, int32 ArrayIndex = 0) const
{
return (ValueType*)ContainerVoidPtrToValuePtrInternal(ContainerPtr, ArrayIndex);
}
void* UProperty::ContainerVoidPtrToValuePtrInternal(void* ContainerPtr, int32 ArrayIndex) const
{
//check...
return (uint8*)ContainerPtr + Offset_Internal + ElementSize * ArrayIndex;
}
void* UProperty::ContainerUObjectPtrToValuePtrInternal(UObject* ContainerPtr, int32 ArrayIndex) const
{
//check...
return (uint8*)ContainerPtr + Offset_Internal + ElementSize * ArrayIndex;
}
//获取对象或结构里的属性值地址,需要自己转换成具体类型
void* propertyValuePtr = property->ContainerPtrToValuePtr<void*>(object);
//包含对象引用的属性可以获得对象
UObject* subObject = objectProperty->GetObjectPropertyValue_InContainer(object);
//也因为获取到的是存放属性值的指针地址,所以其实也就可以*propertyValuePtr=xxx;方便的设置值了。当然如果是从字符串导入设置进去,UE4也提供了两个方法来导出导入:
//导出值
virtual void ExportTextItem( FString& ValueStr, const void* PropertyValue, const void* DefaultValue, UObject* Parent, int32 PortFlags, UObject* ExportRootScope = NULL ) const;
//使用
FString outPropertyValueString;
property->ExportTextItem(outPropertyValueString, property->ContainerPtrToValuePtr<void*>(object), nullptr, (UObject*)object, PPF_None);
//导入值
const TCHAR* UProperty::ImportText( const TCHAR* Buffer, void* Data, int32 PortFlags, UObject* OwnerObject, FOutputDevice* ErrorText = (FOutputDevice*)GWarn ) const;
//使用
FString valueStr;
prop->ImportText(*valueStr, prop->ContainerPtrToValuePtr<void*>(obj), PPF_None, obj);
反射调用函数
//方法原型
int32 UMyClass::Func(float param1);
UFUNCTION(BlueprintCallable)
int32 InvokeFunction(UObject* obj, FName functionName,float param1)
{
struct MyClass_Func_Parms //定义一个结构用来包装参数和返回值,就像在gen.cpp里那样
{
float param1;
int32 ReturnValue;
};
UFunction* func = obj->FindFunctionChecked(functionName);
MyClass_Func_Parms params;
params.param1=param1;
obj->ProcessEvent(func, ¶ms);
return params.ReturnValue;
}
//使用
int r=InvokeFunction(obj,"Func",123.f);
ProcessEvent也是UE4里事先定义好的非常方便的函数,内部会自动的处理蓝图VM的问题。当然,更底层的方法也可以是:
//调用1
obj->ProcessEvent(func, ¶ms);
//调用2
FFrame frame(nullptr, func, ¶ms, nullptr, func->Children);
obj->CallFunction(frame, ¶ms + func->ReturnValueOffset, func);
//调用3
FFrame frame(nullptr, func, ¶ms, nullptr, func->Children);
func->Invoke(obj, frame, ¶ms + func->ReturnValueOffset);
运行时修改类型
让我们继续扩宽一下思路,之前已经详细讲解过了各大类型对象的构造过程,最后常常都是到UE4CodeGen_Private里的调用。既然我们已经知道了它运行的逻辑,那我们也可以仿照着来啊!我们也可以在常规的类型系统注册流程执行完之后,在游戏运行的半途过程中,动态的去修改类型甚至注册类型,因为说到底UE4编辑器也就是一个特殊点的游戏而已啊!这种方式有点类似C#的emit的方式,用代码去生成代码然后再编译。这些方式理论上都是可以通的,我来提供一些思路用法,有兴趣的朋友可以自己去实现下,代码贴出来就太长了。
- 修改UField的MetaData信息,其实可以改变字段在编辑器中的显示信息。MetaData里有哪些字段,可以在ObjectMacros.h中自己查看。
- 动态修改UField的相应的各种Flags数据,比如PropertyFlags,StructFlags,ClassFlags等,可以达成在编辑器里动态改变其显示行为的效果。
- 动态添加删除UEnum对象里面的Names字段,就可以动态给enum添加删除枚举项了。
- 动态地给结构或类添加反射属性字段,就可以在蓝图内创建具有不定字段的结构了。当然前提是在结构里预留好属性存放的内存,这样UProperty的Offset才有值可指向。这么做现在想来好像也不知道能用来干嘛。
- 同属性一样,其实参照对了流程,也可以动态的给蓝图里暴露函数。有时候这可以达成某种加密保护的奇效。
- 可以动态的注册新结构,动态的构造出来相应的UScriptStruct其实就可以了。
- 动态注册新类其实也是可以的,只不过UClass的构造稍微要麻烦点,不过也没麻烦到哪去,有需求了就自然能照着源码里的流程自己实现一个流程出来。
- 再甚至,其实某种程度上的用代码动态创建蓝图节点,填充蓝图VM指令其实也是可行的。只不过想了想好像一般用不着上这种大手术。