This commit is contained in:
2025-08-02 12:09:34 +08:00
commit e70b01cdca
2785 changed files with 575579 additions and 0 deletions

View File

@@ -0,0 +1,113 @@
# ScriptConstant
- **功能描述:** 把一个静态函数的返回值包装成为一个常量值。
- **使用位置:** UFUNCTION
- **引擎模块:** Script
- **元数据类型:** string="abc"
- **关联项:** [ScriptConstantHost](ScriptConstantHost.md)
- **常用程度:** ★★★
把一个静态函数的返回值包装成为一个常量值。
- 函数的名字即为常量的默认名称但ScriptConstant也可以额外提供一个自定义名称。
- 常量作用域默认存在于该静态函数的外部类中但也可以通过ScriptConstantHost来指定到另外一个类型中。
## 测试代码:
```cpp
USTRUCT(BlueprintType)
struct INSIDER_API FMyPythonConstantStruct
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString MyString;
};
UCLASS(Blueprintable, BlueprintType)
class INSIDER_API UMyPython_ConstantOwner :public UObject
{
GENERATED_BODY()
public:
};
UCLASS(Blueprintable, BlueprintType)
class INSIDER_API UMyPython_Constant_Test :public UObject
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintPure, meta = (ScriptConstant))
static int32 MyIntConst() { return 123; }
UFUNCTION(BlueprintPure, meta = (ScriptConstant = "MyOtherIntConst"))
static int32 MyIntConst2() { return 456; }
UFUNCTION(BlueprintPure, meta = (ScriptConstant))
static FMyPythonConstantStruct MyStructConst() { return FMyPythonConstantStruct{ TEXT("Hello") }; }
UFUNCTION(BlueprintPure, meta = (ScriptConstant = "MyOtherStructConst"))
static FMyPythonConstantStruct MyStructConst2() { return FMyPythonConstantStruct{ TEXT("World") }; }
public:
UFUNCTION(BlueprintPure, meta = (ScriptConstant="FirstString", ScriptConstantHost = "/Script/Insider.MyPython_ConstantOwner"))
static FString MyStringConst() { return TEXT("First"); }
****};
```
## 生成的Py代码
```cpp
class MyPython_Constant_Test(Object):
r"""
My Python Constant Test
**C++ Source:**
- **Module**: Insider
- **File**: MyPython_ScriptConstant.h
"""
MY_OTHER_STRUCT_CONST: MyPythonConstantStruct #: (MyPythonConstantStruct): My Struct Const 2
MY_STRUCT_CONST: MyPythonConstantStruct #: (MyPythonConstantStruct): My Struct Const
MY_OTHER_INT_CONST: int #: (int32): My Int Const 2
MY_INT_CONST: int #: (int32): My Int Const
class MyPython_ConstantOwner(Object):
r"""
**My Python Constant Owner
**C++ Source:**
- **Module**: Insider
- **File**: MyPython_ScriptConstant.h
"""
FIRST_STRING: str #: (str): My String Const
```
## 运行的结果:
可见在类中生成了相应的常量。而MyStringConst因为指定了ScriptConstantHost 而生成在别的类中。
```cpp
LogPython: print(unreal.MyPython_Constant_Test.MY_INT_CONST)
LogPython: 123
LogPython: print(unreal.MyPython_Constant_Test.MY_OTHER_INT_CONST)
LogPython: 456
LogPython: print(unreal.MyPython_Constant_Test.MY_OTHER_STRUCT_CONST)
LogPython: <Struct 'MyPythonConstantStruct' (0x00000A0FC4051F00) {my_string: "World"}>
LogPython: print(unreal.MyPython_Constant_Test.MY_STRUCT_CONST)
LogPython: <Struct 'MyPythonConstantStruct' (0x00000A0FC4051EA0) {my_string: "Hello"}>
LogPython: print(unreal.MyPython_ConstantOwner.FIRST_STRING)
LogPython: First
```
## 原理:
生成的逻辑在这个GenerateWrappedConstant 函数里。
```cpp
auto GenerateWrappedConstant = [this, &GeneratedWrappedType, &OutGeneratedWrappedTypeReferences, &OutDirtyModules](const UFunction* InFunc)
{}
```

View File

@@ -0,0 +1,17 @@
# ScriptConstantHost
- **功能描述:** 在ScriptConstant的基础上指定常量生成的所在类型。
- **使用位置:** UFUNCTION
- **引擎模块:** Script
- **元数据类型:** string="abc"
- **关联项:** [ScriptConstant](ScriptConstant.md)
- **常用程度:** ★
在ScriptConstant的基础上指定常量生成的所在类型。
测试代码见ScriptConstant。ScriptConstantHost指定的字符串应该是个对象路径。
```cpp
UFUNCTION(BlueprintPure, meta = (ScriptConstant="FirstString", ScriptConstantHost = "/Script/Insider.MyPython_ConstantOwner"))
static FString MyStringConst() { return TEXT("First"); }
```

View File

@@ -0,0 +1,9 @@
# ScriptDefaultBreak
- **使用位置:** USTRUCT
- **引擎模块:** Script
- **元数据类型:** bool
- **关联项:** [ScriptDefaultMake](ScriptDefaultMake.md)
- **常用程度:** ★
见ScriptDefaultMake的原理和测试代码。

View File

@@ -0,0 +1,173 @@
# ScriptDefaultMake
- **功能描述:** 禁用结构上的HasNativeMake在脚本里构造的时候不调用C++里的NativeMake函数而采用脚本内建的默认初始化方式。
- **使用位置:** USTRUCT
- **引擎模块:** Script
- **元数据类型:** bool
- **关联项:** [ScriptDefaultBreak](ScriptDefaultBreak.md)
- **常用程度:** ★
禁用结构上的HasNativeMake在脚本里构造的时候不调用C++里的NativeMake函数而采用脚本内建的默认初始化方式。
ScriptDefaultBreak也是同理。
## 测试代码:
```cpp
USTRUCT(BlueprintType, meta = (ScriptDefaultMake, ScriptDefaultBreak,HasNativeMake = "/Script/Insider.MyPython_MakeBreak_Test.MyNativeMake", HasNativeBreak = "/Script/Insider.MyPython_MakeBreak_Test.MyNativeBreak"))
struct INSIDER_API FMyPythonMBStructNative
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditAnywhere)
int32 MyInt = 0;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString MyString;
};
UCLASS(Blueprintable, BlueprintType)
class INSIDER_API UMyPython_MakeBreak_Test :public UObject
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintPure, meta = ())
static FMyPythonMBStructNative MyNativeMake(int32 InInt) { return FMyPythonMBStructNative{ InInt,TEXT("Hello") }; }
UFUNCTION(BlueprintPure, meta = ())
static void MyNativeBreak(const FMyPythonMBStructNative& InStruct, int& outInt) { outInt = InStruct.MyInt + 123; }
};
```
## 生成的py代码
无论有没有加ScriptDefaultMake, ScriptDefaultBreakMyPythonMBStructNative生成的py代码其实是一样的。不同点在于构成和to_tuple时候的结果不同。
```cpp
class MyPythonMBStructNative(StructBase):
r"""
My Python MBStruct Native
**C++ Source:**
- **Module**: Insider
- **File**: MyPython_ScriptMakeBreak.h
**Editor Properties:** (see get_editor_property/set_editor_property)
- ``my_int`` (int32): [Read-Write]
- ``my_string`` (str): [Read-Write]
"""
def __init__(self, int: int = 0) -> None:
...
@property
def my_int(self) -> int:
r"""
(int32): [Read-Write]
"""
...
@my_int.setter
def my_int(self, value: int) -> None:
...
@property
def my_string(self) -> str:
r"""
(str): [Read-Write]
"""
...
@my_string.setter
def my_string(self, value: str) -> None:
...
```
## 运行的结果:
- 第二段是加了ScriptDefaultMake, ScriptDefaultBreak后的效果。我故意在C++的Make和Break函数里做了一些不一样可以观察到调用到C++里的函数。
- 第一段是在代码里加上ScriptDefaultMake, ScriptDefaultBreak后保留HasNativeMakeHasNativeBreak调用的结果可见C++里的Make/Break函数就没有再被调用到了。
```cpp
LogPython: b=unreal.MyPythonMBStructNative()
LogPython: print(b)
LogPython: <Struct 'MyPythonMBStructNative' (0x0000085F2EE9E680) {my_int: 0, my_string: "Hello"}>
LogPython: print(b.to_tuple())
LogPython: (123,)
LogPython: b=unreal.MyPythonMBStructNative()
LogPython: print(b)
LogPython: <Struct 'MyPythonMBStructNative' (0x000005E6C3AAFDC0) {my_int: 0, my_string: ""}>
LogPython: print(b.to_tuple())
LogPython: (0, '')
```
## 原理:
在FindMakeBreakFunction函数里如果发现有ScriptDefaultMake或ScriptDefaultBreak标记就不去使用C++里由HasNativeMakeHasNativeBreak指定的函数。
另外py里的结构初始化会调用到默认的init或者结构的make函数而to_tuple就相当于break的作用会调用到默认的每个属性to_tuple或者是结构的自定义break函数。
```cpp
const FName ScriptDefaultMakeMetaDataKey = TEXT("ScriptDefaultMake");
const FName ScriptDefaultBreakMetaDataKey = TEXT("ScriptDefaultBreak");
namespace UE::Python
{
/**
* Finds the UFunction corresponding to the name specified by 'HasNativeMake' or 'HasNativeBreak' meta data key.
* @param The structure to inspect for the 'HasNativeMake' or 'HasNativeBreak' meta data keys.
* @param InNativeMetaDataKey The native meta data key name. Can only be 'HasNativeMake' or 'HasNativeBreak'.
* @param InScriptDefaultMetaDataKey The script default meta data key name. Can only be 'ScriptDefaultMake' or 'ScriptDefaultBreak'.
* @param NotFoundFn Function invoked if the structure specifies as Make or Break function, but the function couldn't be found.
* @return The function, if the struct has the meta key and if the function was found. Null otherwise.
*/
template<typename NotFoundFuncT>
UFunction* FindMakeBreakFunction(const UScriptStruct* InStruct, const FName& InNativeMetaDataKey, const FName& InScriptDefaultMetaDataKey, const NotFoundFuncT& NotFoundFn)
{
check(InNativeMetaDataKey == PyGenUtil::HasNativeMakeMetaDataKey || InNativeMetaDataKey == PyGenUtil::HasNativeBreakMetaDataKey);
check(InScriptDefaultMetaDataKey == PyGenUtil::ScriptDefaultMakeMetaDataKey || InScriptDefaultMetaDataKey == PyGenUtil::ScriptDefaultBreakMetaDataKey);
UFunction* MakeBreakFunc = nullptr;
if (!InStruct->HasMetaData(InScriptDefaultMetaDataKey)) // <--- 有了default, 会直接返回null
{
const FString MakeBreakFunctionName = InStruct->GetMetaData(InNativeMetaDataKey);
if (!MakeBreakFunctionName.IsEmpty())
{
// Find the function.
MakeBreakFunc = FindObject<UFunction>(/*Outer*/nullptr, *MakeBreakFunctionName, /*ExactClass*/true);
if (!MakeBreakFunc)
{
NotFoundFn(MakeBreakFunctionName);
}
}
}
return MakeBreakFunc;
}
}
struct FFuncs
{
static int Init(FPyWrapperStruct* InSelf, PyObject* InArgs, PyObject* InKwds)
{
const int SuperResult = PyWrapperStructType.tp_init((PyObject*)InSelf, InArgs, InKwds);
if (SuperResult != 0)
{
return SuperResult;
}
return FPyWrapperStruct::MakeStruct(InSelf, InArgs, InKwds);
}
};
GeneratedWrappedType->PyType.tp_init = (initproc)&FFuncs::Init;
// python wrapper 给每个类型都映射了 to_tuple 函数,会调用类型的 break 函数转换为 tuple
static PyObject* ToTuple(FPyWrapperStruct* InSelf)
{
return FPyWrapperStruct::BreakStruct(InSelf);
}
....
{ "to_tuple", PyCFunctionCast(&FMethods::ToTuple), METH_NOARGS, "to_tuple(self) -> Tuple[object, ...] -- break this Unreal struct into a tuple of its properties" },
```

View File

@@ -0,0 +1,170 @@
# ScriptMethod
- **功能描述:** 把静态函数导出变成第一个参数的成员函数。
- **使用位置:** UFUNCTION
- **引擎模块:** Script
- **元数据类型:** string="a;b;c"
- **限制类型:** static function
- **关联项:** [ScriptMethodMutable](ScriptMethodMutable.md), [ScriptMethodSelfReturn](ScriptMethodSelfReturn.md)
- **常用程度:** ★★★
把静态函数导出变成第一个参数的成员函数。
- 把func(AB)变成A.func(B)这样就可以给A对象添加成员函数方法。有点像C#里的扩展方法
- 也可以直接再提供一个名字来改变包装后的成员函数的名称。注意与ScriptName区分ScriptName改变的是本身导出到脚本的名字而ScriptMethod改变的是结果成员函数的名字。把func(AB)改成A.OtherFunc(B)。
## 测试代码:
```cpp
UCLASS(Blueprintable, BlueprintType)
class INSIDER_API UMyPython_ScriptMethod :public UObject
{
GENERATED_BODY()
public:
};
USTRUCT(BlueprintType)
struct INSIDER_API FMyPythonStruct_ScriptMethod
{
GENERATED_BODY()
};
UCLASS(Blueprintable, BlueprintType)
class INSIDER_API UMyPython_ScriptMethod_Test :public UObject
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, meta = (ScriptMethod))
static void MyFuncOnObject(UMyPython_ScriptMethod* obj, FString val);
UFUNCTION(BlueprintCallable, meta = (ScriptMethod = "MySuperFuncOnObject;MyOtherFuncOnObject"))
static void MyFuncOnObject2(UMyPython_ScriptMethod* obj, FString val);
public:
UFUNCTION(BlueprintCallable, meta = (ScriptMethod))
static void MyFuncOnStruct(const FMyPythonStruct_ScriptMethod& myStruct, FString val);;
};
```
## 测试效果:
可见在MyPythonStruct_ScriptMethod里增加了my_func_on_struct的方法而MyPython_ScriptMethod里增加了my_func_on_object的方法。因此如果在py里你就可以把这两个函数当作成员函数一样调用。
另外MyFuncOnObject2上面设置了两个ScriptMethod 别称也可以在MyPython_ScriptMethod里见到。
```cpp
class MyPythonStruct_ScriptMethod(StructBase):
r"""
My Python Struct Script Method
**C++ Source:**
- **Module**: Insider
- **File**: MyPython_ScriptMethod.h
"""
def __init__(self) -> None:
...
def my_func_on_struct(self, val: str) -> None:
r"""
x.my_func_on_struct(val) -> None
My Func on Struct
Args:
val (str):
"""
...
class MyPython_ScriptMethod(Object):
r"""
My Python Script Method
**C++ Source:**
- **Module**: Insider
- **File**: MyPython_ScriptMethod.h
"""
def my_super_func_on_object(self, val: str) -> None:
r"""
x.my_super_func_on_object(val) -> None
My Func on Object 2
Args:
val (str):
"""
...
def my_other_func_on_object(self, val: str) -> None:
r"""
deprecated: 'my_other_func_on_object' was renamed to 'my_super_func_on_object'.
"""
...
def my_func_on_object(self, val: str) -> None:
r"""
x.my_func_on_object(val) -> None
My Func on Object
Args:
val (str):
"""
...
class MyPython_ScriptMethod_Test(Object):
r"""
My Python Script Method Test
**C++ Source:**
- **Module**: Insider
- **File**: MyPython_ScriptMethod.h
"""
@classmethod
def my_func_on_struct(cls, my_struct: MyPythonStruct_ScriptMethod, val: str) -> None:
r"""
X.my_func_on_struct(my_struct, val) -> None
My Func on Struct
Args:
my_struct (MyPythonStruct_ScriptMethod):
val (str):
"""
...
@classmethod
def my_func_on_object2(cls, obj: MyPython_ScriptMethod, val: str) -> None:
r"""
X.my_func_on_object2(obj, val) -> None
My Func on Object 2
Args:
obj (MyPython_ScriptMethod):
val (str):
"""
...
@classmethod
def my_func_on_object(cls, obj: MyPython_ScriptMethod, val: str) -> None:
r"""
X.my_func_on_object(obj, val) -> None
My Func on Object
Args:
obj (MyPython_ScriptMethod):
val (str):
"""
...
```
## 原理:
在GenerateWrappedDynamicMethod中有详细的如何把静态函数包装成成员函数的过程。感兴趣的可以去细看。
```cpp
PyTypeObject* FPyWrapperTypeRegistry::GenerateWrappedClassType(const UClass* InClass, FGeneratedWrappedTypeReferences& OutGeneratedWrappedTypeReferences, TSet<FName>& OutDirtyModules, const EPyTypeGenerationFlags InGenerationFlags)
{
// Should this function also be hoisted as a struct method or operator?
if (InFunc->HasMetaData(PyGenUtil::ScriptMethodMetaDataKey))
{
GenerateWrappedDynamicMethod(InFunc, GeneratedWrappedMethodCopy);
}
}
```

View File

@@ -0,0 +1,181 @@
# ScriptMethodMutable
- **功能描述:** 把ScriptMethod的第一个const结构参数在调用上改成引用参数函数内修改的值会保存下来。
- **使用位置:** UFUNCTION
- **引擎模块:** Script
- **元数据类型:** bool
- **限制类型:** 第一个参数是结构类型
- **关联项:** [ScriptMethod](ScriptMethod.md)
- **常用程度:** ★★
把ScriptMethod的第一个const结构参数在调用上改成引用参数函数内修改的值会保存下来。
- 在const参数上如果想改变值依然要标记c++里的mutable。
- 虽然py生成的代码一模一样但实际调用上ScriptMethodMutable会真正改变参数的值而没有ScriptMethodMutable的函数并不会改变参数的原始值。
- ScriptMethodMutable和UPARAM(ref) 在调用效果上都可以改变参数的值。但区别是UPARAM(ref) 生成的py代码会返回第一个参数作为返回值。
```cpp
USTRUCT(BlueprintType)
struct INSIDER_API FMyPythonStruct_ScriptMethod
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditAnywhere)
mutable FString MyString;
};
UCLASS(Blueprintable, BlueprintType)
class INSIDER_API UMyPython_ScriptMethod_Test :public UObject
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, meta = (ScriptMethod))
static void SetStringOnStruct(const FMyPythonStruct_ScriptMethod& myStruct, FString val);
UFUNCTION(BlueprintCallable, meta = (ScriptMethod, ScriptMethodMutable))
static void SetStringOnStructMutable(const FMyPythonStruct_ScriptMethod& myStruct, FString val);
UFUNCTION(BlueprintCallable, meta = (ScriptMethod, ScriptMethodMutable))
static void SetStringOnStructViaRef(UPARAM(ref) FMyPythonStruct_ScriptMethod& myStruct, FString val);
};
```
## 测试效果:
看py里生成的代码是一致的如果用UPARAM(ref)则在MyPython_ScriptMethod_Test里面生成的my_func_on_struct_via_ref会返回结构MyPythonStruct_ScriptMethod来达成引用的效果。
然而my_func_on_struct_mutable返回的是None同不加ScriptMethodMutable的my_func_on_struct并没有区别。但是实际上在真正调用的时候会真正有区别。
```cpp
class MyPythonStruct_ScriptMethod(StructBase):
r"""
My Python Struct Script Method
**C++ Source:**
- **Module**: Insider
- **File**: MyPython_ScriptMethod.h
"""
def __init__(self) -> None:
...
def my_func_on_struct_via_ref(self, val: str) -> None:
r"""
x.my_func_on_struct_via_ref(val) -> None
My Func on Struct Via Ref
Args:
val (str):
"""
...
def my_func_on_struct_mutable(self, val: str) -> None:
r"""
x.my_func_on_struct_mutable(val) -> None
My Func on Struct Mutable
Args:
val (str):
"""
...
def my_func_on_struct(self, val: str) -> None:
r"""
x.my_func_on_struct(val) -> None
My Func on Struct
Args:
val (str):
"""
...
class MyPython_ScriptMethod_Test(Object):
r"""
My Python Script Method Test
**C++ Source:**
- **Module**: Insider
- **File**: MyPython_ScriptMethod.h
"""
@classmethod
def my_func_on_struct_via_ref(cls, my_struct: MyPythonStruct_ScriptMethod, val: str) -> MyPythonStruct_ScriptMethod:
r"""
X.my_func_on_struct_via_ref(my_struct, val) -> MyPythonStruct_ScriptMethod
My Func on Struct Via Ref
Args:
my_struct (MyPythonStruct_ScriptMethod):
val (str):
Returns:
MyPythonStruct_ScriptMethod:
my_struct (MyPythonStruct_ScriptMethod):
"""
...
@classmethod
def my_func_on_struct_mutable(cls, my_struct: MyPythonStruct_ScriptMethod, val: str) -> None:
r"""
X.my_func_on_struct_mutable(my_struct, val) -> None
My Func on Struct Mutable
Args:
my_struct (MyPythonStruct_ScriptMethod):
val (str):
"""
...
@classmethod
def my_func_on_struct(cls, my_struct: MyPythonStruct_ScriptMethod, val: str) -> None:
r"""
X.my_func_on_struct(my_struct, val) -> None
My Func on Struct
Args:
my_struct (MyPythonStruct_ScriptMethod):
val (str):
"""
...
```
在UE Python控制台里调用的记录分析调用顺序
- 一开始调用set_string_on_struct_mutable再print(a)可以打印出Hello说明值真正的设置到了a结构里。
- 再尝试set_string_on_struct再print(a)无法打印出FFF说明值并没有设置到a结构里。说明py在调用的时候很可能构造了一个临时值来当作调对象调用完成的新值并没有设置到a对象上。
- 再尝试set_string_on_struct_via_ref再print(a)可以打印出First说明用UPARAM(ref)可以也达成改变参数的效果。
```cpp
LogPython: a=unreal.MyPythonStruct_ScriptMethod()
LogPython: print(a)
LogPython: <Struct 'MyPythonStruct_ScriptMethod' (0x0000092D08CD6ED0) {my_string: ""}>
LogPython: a.set_string_on_struct_mutable("Hello")
LogBlueprintUserMessages: [None] UMyPython_ScriptMethod_Test::SetStringOnStructMutable
LogPython: print(a)
LogPython: <Struct 'MyPythonStruct_ScriptMethod' (0x0000092D08CD6ED0) {my_string: "Hello"}>
LogPython: a.set_string_on_struct("FFF")
LogBlueprintUserMessages: [None] UMyPython_ScriptMethod_Test::SetStringOnStruct
LogPython: print(a)
LogPython: <Struct 'MyPythonStruct_ScriptMethod' (0x0000092D08CD6ED0) {my_string: "Hello"}>
LogPython: a.set_string_on_struct_via_ref("First")
LogBlueprintUserMessages: [None] UMyPython_ScriptMethod_Test::SetStringOnStructViaRef
LogPython: print(a)
LogPython: <Struct 'MyPythonStruct_ScriptMethod' (0x0000092D08CD6ED0) {my_string: "First"}>
```
## 原理:
判断如果有ScriptMethodMutable会设置SelfReturn从而再最后把函数调用中的临时值复制给原本的参数值达成可变引用调用的效果。
```cpp
// The function may have been flagged as mutable, in which case we always consider it to need a 'self' return
if (!GeneratedWrappedDynamicMethod.SelfReturn.ParamProp && InFunc->HasMetaData(PyGenUtil::ScriptMethodMutableMetaDataKey))
{
if (!SelfParam.ParamProp->IsA<FStructProperty>())
{
REPORT_PYTHON_GENERATION_ISSUE(Error, TEXT("Function '%s.%s' is marked as 'ScriptMethodMutable' but the 'self' argument is not a struct."), *InFunc->GetOwnerClass()->GetName(), *InFunc->GetName());
return;
}
GeneratedWrappedDynamicMethod.SelfReturn = SelfParam;
}
```

View File

@@ -0,0 +1,169 @@
# ScriptMethodSelfReturn
- **功能描述:** 在ScriptMethod的情况下指定把这个函数的返回值要去覆盖该函数的第一个参数。
- **使用位置:** UFUNCTION
- **引擎模块:** Script
- **元数据类型:** bool
- **关联项:** [ScriptMethod](ScriptMethod.md)
- **常用程度:** ★★
在ScriptMethod的情况下指定把这个函数的返回值要去覆盖该函数的第一个参数。
这种情况下,原本的函数就没有返回值返回了。效果上形如:
```cpp
C Func(A,B) -> void A::Func2(B)
调用的时候:
C=A.Func(B) ->
void A::Func2(B)
{
A=A.Func(B)
}
```
## 测试代码:
注意因为AppendStringOnStructViaRef参数是引用参数所以为了结果应用到myStruct在函数体内就不需要创建临时值可以直接在myStruct上面修改。如果也用临时值的话myStruct就无法得到修改也就失去了ref参数的意义。
```cpp
public:
UFUNCTION(BlueprintCallable, meta = (ScriptMethod))
static FMyPythonStruct_ScriptMethod AppendStringOnStruct(const FMyPythonStruct_ScriptMethod& myStruct, FString val)
{
FMyPythonStruct_ScriptMethod Result = myStruct;
Result.MyString += val;
return Result;
}
UFUNCTION(BlueprintCallable, meta = (ScriptMethod,ScriptMethodSelfReturn))
static FMyPythonStruct_ScriptMethod AppendStringOnStructReturn(const FMyPythonStruct_ScriptMethod& myStruct, FString val)
{
FMyPythonStruct_ScriptMethod Result = myStruct;
Result.MyString += val;
return Result;
}
UFUNCTION(BlueprintCallable, meta = (ScriptMethod, ScriptMethodMutable))
static FMyPythonStruct_ScriptMethod AppendStringOnStructViaRef(UPARAM(ref) FMyPythonStruct_ScriptMethod& myStruct, FString val)
{
myStruct.MyString += val;
return myStruct;
}
//LogPython: Error: Function 'MyPython_ScriptMethod_Test.AppendStringOnStructViaRefReturn' is marked as 'ScriptMethodSelfReturn' but the 'self' argument is also marked as UPARAM(ref). This is not allowed.
//UFUNCTION(BlueprintCallable, meta = (ScriptMethod, ScriptMethodMutable,ScriptMethodSelfReturn))
//static FMyPythonStruct_ScriptMethod AppendStringOnStructViaRefReturn(UPARAM(ref) FMyPythonStruct_ScriptMethod& myStruct, FString val);
```
## 生成的py代码
可见append_string_on_struct_return是没有返回值了。而append_string_on_struct有返回值。append_string_on_struct_via_ref也有返回值。
```cpp
class MyPythonStruct_ScriptMethod(StructBase):
def append_string_on_struct_return(self, val: str) -> None:
r"""
x.append_string_on_struct_return(val) -> None
Append String on Struct Return
Args:
val (str):
Returns:
MyPythonStruct_ScriptMethod:
"""
...
def append_string_on_struct(self, val: str) -> MyPythonStruct_ScriptMethod:
r"""
x.append_string_on_struct(val) -> MyPythonStruct_ScriptMethod
Append String on Struct
Args:
val (str):
Returns:
MyPythonStruct_ScriptMethod:
"""
...
def append_string_on_struct_via_ref(self, val: str) -> MyPythonStruct_ScriptMethod:
r"""
x.append_string_on_struct_via_ref(val) -> MyPythonStruct_ScriptMethod
Append String on Struct Via Ref
Args:
val (str):
Returns:
MyPythonStruct_ScriptMethod:
"""
...
```
测试代码:观察运行的结果以及对象的内存地址。
- 可以看出append_string_on_struct是有返回值的但是改变的结果没有应用到参数a上。
- append_string_on_struct_return可以应用到参数a上但是没有返回值。
- append_string_on_struct_via_ref可以应用到参数a上同时也有返回值。但是注意返回值和a其实并不是同一个对象因为内存地址不同。
- 但是注意 ScriptMethodSelfReturn和UPARAM(ref)不能混用,否则会报错: LogPython: Error: Function 'MyPython_ScriptMethod_Test.AppendStringOnStructViaRefReturn' is marked as 'ScriptMethodSelfReturn' but the 'self' argument is also marked as UPARAM(ref). This is not allowed.
```cpp
LogPython: a=unreal.MyPythonStruct_ScriptMethod()
LogPython: print(a)
LogPython: <Struct 'MyPythonStruct_ScriptMethod' (0x000008DEBAB08ED0) {my_string: ""}>
LogPython: b=a.append_string_on_struct("Hello")
LogPython: print(b)
LogPython: <Struct 'MyPythonStruct_ScriptMethod' (0x000008DEBAB04010) {my_string: "Hello"}>
LogPython: print(a)
LogPython: <Struct 'MyPythonStruct_ScriptMethod' (0x000008DEBAB08ED0) {my_string: ""}>
LogPython: c=a.append_string_on_struct_return("Hello")
LogPython: print(c)
LogPython: None
LogPython: print(a)
LogPython: <Struct 'MyPythonStruct_ScriptMethod' (0x000008DEBAB08ED0) {my_string: "Hello"}>
LogPython: d=a.append_string_on_struct_via_ref("World")
LogPython: print(d)
LogPython: <Struct 'MyPythonStruct_ScriptMethod' (0x000008DEBAB06110) {my_string: "HelloWorld"}>
LogPython: print(a)
LogPython: <Struct 'MyPythonStruct_ScriptMethod' (0x000008DEBAB08ED0) {my_string: "HelloWorld"}>
```
## 原理:
把输出参数的第一个当作返回参数。输出参数其实就是函数里的返回值。SelfReturn的意思是这个值之后要去覆盖掉调用对象的值也就是发生调用的对象。
```cpp
// The function may also have been flagged as having a 'self' return
if (InFunc->HasMetaData(PyGenUtil::ScriptMethodSelfReturnMetaDataKey))
{
if (GeneratedWrappedDynamicMethod.SelfReturn.ParamProp)
{
REPORT_PYTHON_GENERATION_ISSUE(Error, TEXT("Function '%s.%s' is marked as 'ScriptMethodSelfReturn' but the 'self' argument is also marked as UPARAM(ref). This is not allowed."), *InFunc->GetOwnerClass()->GetName(), *InFunc->GetName());
return;
}
else if (GeneratedWrappedDynamicMethod.MethodFunc.OutputParams.Num() == 0 || !GeneratedWrappedDynamicMethod.MethodFunc.OutputParams[0].ParamProp->HasAnyPropertyFlags(CPF_ReturnParm))
{
REPORT_PYTHON_GENERATION_ISSUE(Error, TEXT("Function '%s.%s' is marked as 'ScriptMethodSelfReturn' but has no return value."), *InFunc->GetOwnerClass()->GetName(), *InFunc->GetName());
return;
}
else if (!SelfParam.ParamProp->IsA<FStructProperty>())
{
REPORT_PYTHON_GENERATION_ISSUE(Error, TEXT("Function '%s.%s' is marked as 'ScriptMethodSelfReturn' but the 'self' argument is not a struct."), *InFunc->GetOwnerClass()->GetName(), *InFunc->GetName());
return;
}
else if (!GeneratedWrappedDynamicMethod.MethodFunc.OutputParams[0].ParamProp->IsA<FStructProperty>())
{
REPORT_PYTHON_GENERATION_ISSUE(Error, TEXT("Function '%s.%s' is marked as 'ScriptMethodSelfReturn' but the return value is not a struct."), *InFunc->GetOwnerClass()->GetName(), *InFunc->GetName());
return;
}
else if (CastFieldChecked<const FStructProperty>(GeneratedWrappedDynamicMethod.MethodFunc.OutputParams[0].ParamProp)->Struct != CastFieldChecked<const FStructProperty>(SelfParam.ParamProp)->Struct)
{
REPORT_PYTHON_GENERATION_ISSUE(Error, TEXT("Function '%s.%s' is marked as 'ScriptMethodSelfReturn' but the return value is not the same type as the 'self' argument."), *InFunc->GetOwnerClass()->GetName(), *InFunc->GetName());
return;
}
else
{
GeneratedWrappedDynamicMethod.SelfReturn = MoveTemp(GeneratedWrappedDynamicMethod.MethodFunc.OutputParams[0]);
GeneratedWrappedDynamicMethod.MethodFunc.OutputParams.RemoveAt(0, 1, EAllowShrinking::No);
}
}
```

View File

@@ -0,0 +1,149 @@
# ScriptName
- **功能描述:** 在导出到脚本里时使用的名字
- **使用位置:** Any
- **引擎模块:** Script
- **元数据类型:** string="abc"
- **常用程度:** ★★★
指定导出到脚本中的名字。
- 可以使用在UCLASSUSTRUCTUENUMUFUNCTIONUPROPERTY上使用改变其导出到脚本的名字。
- 如果没有使用ScriptName自定义名字则导出的名字未默认的python化的名字。如MyFunc()变成my_func()。
在测试Python的时候记得打开python插件。
可在\UnrealEngine\Engine\Plugins\Experimental\PythonScriptPlugin\Source\PythonScriptPlugin\Private\PyTest.h里见到大量写好的测试用例。
## 测试代码:
```cpp
UCLASS(Blueprintable, BlueprintType)
class INSIDER_API UMyPythonTestLibary2 :public UBlueprintFunctionLibrary
{
GENERATED_BODY()
};
UCLASS(Blueprintable, BlueprintType,meta=(ScriptName="MyPythonLib"))
class INSIDER_API UMyPythonTestLibary :public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
//unreal.MyPythonLib.my_script_func_default()
UFUNCTION(BlueprintCallable,meta=())
static void MyScriptFuncDefault()
{
UInsiderSubsystem::Get().PrintStringEx(nullptr, TEXT("MyScriptFuncDefault"));
}
//unreal.MyPythonLib.my_script_func()
UFUNCTION(BlueprintCallable,meta=(ScriptName="MyScriptFunc"))
static void MyScriptFunc_ScriptName()
{
UInsiderSubsystem::Get().PrintStringEx(nullptr, TEXT("MyScriptFunc_ScriptName"));
}
};
```
## 测试效果:
开启编辑器后引擎会自动根据类型数据信息反射生成向相应的导出到py的胶水代码我们在C++中定义的类就可以在\Intermediate\PythonStub\[unreal.py](http://unreal.py/)里查看其导出的脚本代码。
如上的类在unreal.py生成的py代码如下
- 可见UMyPythonTestLibary2 没有加ScriptName就是默认的名字而UMyPythonTestLibary 的名字变成了MyPythonLib。
- MyScriptFuncDefault的导出脚本名字是my_script_func_default而MyScriptFunc_ScriptName因为写了ScriptName变成了MyScriptFunc
```cpp
class MyPythonTestLibary2(BlueprintFunctionLibrary):
r"""
My Python Test Libary 2
**C++ Source:**
- **Module**: Insider
- **File**: MyPythonTest.h
"""
...
class MyPythonLib(BlueprintFunctionLibrary):
r"""
My Python Test Libary
**C++ Source:**
- **Module**: Insider
- **File**: MyPython_Test.h
"""
@classmethod
def my_script_func_default(cls) -> None:
r"""
X.my_script_func_default() -> None
My Script Func Default
"""
...
@classmethod
def my_script_func(cls) -> None:
r"""
X.my_script_func() -> None
My Script Func Script Name
"""
...
```
## 原理:
在获取各个类型名字的时候会先判断ScriptName如果获得就使用该名字。否则在GetFieldPythonNameImpl里会对名字进行python化处理。
```cpp
\Engine\Plugins\Experimental\PythonScriptPlugin\Source\PythonScriptPlugin\Private\PyGenUtil.cpp
const FName ScriptNameMetaDataKey = TEXT("ScriptName");
FString GetClassPythonName(const UClass* InClass)
{
return GetFieldPythonNameImpl(InClass, ScriptNameMetaDataKey);
}
TArray<FString> GetDeprecatedClassPythonNames(const UClass* InClass)
{
return GetDeprecatedFieldPythonNamesImpl(InClass, ScriptNameMetaDataKey);
}
FString GetStructPythonName(const UScriptStruct* InStruct)
{
return GetFieldPythonNameImpl(InStruct, ScriptNameMetaDataKey);
}
TArray<FString> GetDeprecatedStructPythonNames(const UScriptStruct* InStruct)
{
return GetDeprecatedFieldPythonNamesImpl(InStruct, ScriptNameMetaDataKey);
}
FString GetEnumPythonName(const UEnum* InEnum)
{
return GetFieldPythonNameImpl(InEnum, ScriptNameMetaDataKey);
}
TArray<FString> GetDeprecatedEnumPythonNames(const UEnum* InEnum)
{
return GetDeprecatedFieldPythonNamesImpl(InEnum, ScriptNameMetaDataKey);
}
FString GetFieldPythonNameImpl(const FFieldVariant& InField, const FName InMetaDataKey)
{
FString FieldName;
// First see if we have a name override in the meta-data
if (GetFieldPythonNameFromMetaDataImpl(InField, InMetaDataKey, FieldName))
{
return FieldName;
}
//。。。
}
```

View File

@@ -0,0 +1,88 @@
# ScriptNoExport
- **功能描述:** 不导出该函数或属性到脚本。
- **使用位置:** UFUNCTION, UPROPERTY
- **引擎模块:** Script
- **元数据类型:** bool
- **常用程度:** ★★★
不导出该函数或属性到脚本。
## 测试代码:
```cpp
UCLASS(Blueprintable, BlueprintType, meta = (ScriptName = "MyPythonLib"))
class INSIDER_API UMyPythonTestLibary :public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable)
static void MyScriptFunc_None();
UFUNCTION(BlueprintCallable, meta = (ScriptNoExport))
static void MyScriptFunc_NoExport();
public:
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float MyFloat = 123.f;
UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ScriptNoExport))
float MyFloat_NoExport = 123.f;
};
```
## 测试效果py代码
可见默认的函数和属性都会导出到脚本里。而MyScriptFunc_NoExport和MyFloat_NoExport在py里并没有。
```cpp
class MyPythonLib(BlueprintFunctionLibrary):
r"""
My Python Test Libary
**C++ Source:**
- **Module**: Insider
- **File**: MyPythonTest.h
"""
@property
def my_float(self) -> float:
r"""
(float): [Read-Write]
"""
...
@my_float.setter
def my_float(self, value: float) -> None:
...
@classmethod
def my_script_func_none(cls) -> None:
r"""
X.my_script_func_none() -> None
My Script Func None
"""
...
```
## 原理:
根据这个ScriptNoExport来判断一个属性或函数是否导出。
```cpp
bool IsScriptExposedProperty(const FProperty* InProp)
{
return !InProp->HasMetaData(ScriptNoExportMetaDataKey)
&& InProp->HasAnyPropertyFlags(CPF_BlueprintVisible | CPF_BlueprintAssignable);
}
bool IsScriptExposedFunction(const UFunction* InFunc)
{
return !InFunc->HasMetaData(ScriptNoExportMetaDataKey)
&& InFunc->HasAnyFunctionFlags(FUNC_BlueprintCallable | FUNC_BlueprintEvent)
&& !InFunc->HasMetaData(BlueprintGetterMetaDataKey)
&& !InFunc->HasMetaData(BlueprintSetterMetaDataKey)
&& !InFunc->HasMetaData(BlueprintInternalUseOnlyMetaDataKey)
&& !InFunc->HasMetaData(CustomThunkMetaDataKey)
&& !InFunc->HasMetaData(NativeBreakFuncMetaDataKey)
&& !InFunc->HasMetaData(NativeMakeFuncMetaDataKey);
}
```

View File

@@ -0,0 +1,206 @@
# ScriptOperator
- **功能描述:** 把第一个参数为结构的静态函数包装成结构的运算符。
- **使用位置:** UFUNCTION
- **引擎模块:** Script
- **元数据类型:** string="a;b;c"
- **常用程度:** ★★★
把第一个参数为结构的静态函数包装成结构的运算符。
- 可以包含多个运算符。
不同的运算符需要匹配不同的函数签名。规则见如下:
- bool运算符bool
- bool FuncName(const FMyStruct& Value); //Value的类型可以是const FMyStruct&或者直接FMyStruct
- 一元运算符neg (取负)
- FMyStruct FuncName(const FMyStruct&);
- 比较运算符: (==, !=, <, <=, >, >=)
- bool FuncName(const FMyStruct, OtherType); //OtherType可以是其他任何类型
- 数学运算符: (+, -, *, /, %, &, |, ^, >>, <<)
- ReturnType FuncName(const FMyStruct&, OtherType); //ReturnType 和 OtherType可以是其他任何类型
- 数学赋值运算符:(+=, -=, *=, /=, %=, &=, |=, ^=, >>=, <<=)
- FMyStruct FuncName(const FMyStruct&, OtherType); //OtherType可以是其他任何类型
可见,如果想一个函数同时支持普通数学运算符和赋值运算符,函数签名可以是:
FMyStruct FuncName(const FMyStruct&, OtherType); //这里OtherType可以是任何类型也可以是FMyStruct
这个也常常一起配合ScriptMethod使用这样就可以在结构中一起提供一个运算成员函数这个函数的名字还可以通过ScriptMethod来自定义。
源码里也常见到和ScriptMethodSelfReturn使用的例子如+=运算符上。但其实ScriptMethodSelfReturn不是必须的在+=的时候,自然会把返回值应用到第一个参数上。
## 测试代码:
```cpp
USTRUCT(BlueprintType)
struct INSIDER_API FMyPythonMathStruct
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditAnywhere)
int32 Value = 0;
};
UCLASS(Blueprintable, BlueprintType)
class INSIDER_API UMyPython_Operator_Test :public UObject
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, meta = (ScriptMethod=HasValue,ScriptOperator = "bool"))
static bool IsValid(const FMyPythonMathStruct& InStruct) { return InStruct.Value != 0; }
UFUNCTION(BlueprintCallable, meta = (ScriptOperator = "neg"))
static FMyPythonMathStruct Neg(const FMyPythonMathStruct& InStruct) { return { -InStruct.Value }; }
UFUNCTION(BlueprintCallable, meta = (ScriptOperator = "=="))
static bool IsEqual(const FMyPythonMathStruct& A, const FMyPythonMathStruct& B) { return A.Value == B.Value; }
UFUNCTION(BlueprintCallable, meta = (ScriptOperator = "+;+="))
static FMyPythonMathStruct AddInt(FMyPythonMathStruct InStruct, const int32 InValue) { InStruct.Value += InValue; return InStruct; }
};
```
## 生成的py代码
可见在py里生成了__bool____eq____add____iadd____neg__的函数。同时IsValid加上了ScriptMethod就有了另一个has_value函数。
```cpp
class MyPythonMathStruct(StructBase):
r"""
My Python Math Struct
**C++ Source:**
- **Module**: Insider
- **File**: MyPython_ScriptOperator.h
**Editor Properties:** (see get_editor_property/set_editor_property)
- ``value`` (int32): [Read-Write]
"""
def __init__(self, value: int = 0) -> None:
...
@property
def value(self) -> int:
r"""
(int32): [Read-Write]
"""
...
@value.setter
def value(self, value: int) -> None:
...
def has_value(self) -> bool:
r"""
x.has_value() -> bool
Is Valid
Returns:
bool:
"""
...
def __bool__(self) -> bool:
r"""
Is Valid
"""
...
def __eq__(self, other: object) -> bool:
r"""
**Overloads:**
- ``MyPythonMathStruct`` Is Equal
"""
...
def __add__(self, other: MyPythonMathStruct) -> None:
r"""
**Overloads:**
- ``int32`` Add Int
"""
...
def __iadd__(self, other: MyPythonMathStruct) -> None:
r"""
**Overloads:**
- ``int32`` Add Int
"""
...
def __neg__(self) -> None:
r"""
Neg
"""
...
```
## 进行运行的测试:
可见确实支持了数学+=运算符和bool的比较。
```cpp
LogPython: a=unreal.MyPythonMathStruct(3)
LogPython: print(a)
LogPython: <Struct 'MyPythonMathStruct' (0x0000074C90D5DCF0) {value: 3}>
LogPython: print(not a)
LogPython: False
LogPython: a+=3
LogPython: print(a)
LogPython: <Struct 'MyPythonMathStruct' (0x0000074C90D5DCF0) {value: 6}>
LogPython: print(-a)
LogPython: <Struct 'MyPythonMathStruct' (0x0000074C90D5DCF0) {value: -6}>
```
## 原理:
具体的包装函数都在GenerateWrappedOperator 里,具体想了解的可细看这里。
```cpp
auto GenerateWrappedOperator = [this, &OutGeneratedWrappedTypeReferences, &OutDirtyModules](const UFunction* InFunc, const PyGenUtil::FGeneratedWrappedMethod& InTypeMethod)
{
// Only static functions can be hoisted onto other types
if (!InFunc->HasAnyFunctionFlags(FUNC_Static))
{
REPORT_PYTHON_GENERATION_ISSUE(Error, TEXT("Non-static function '%s.%s' is marked as 'ScriptOperator' but only static functions can be hoisted."), *InFunc->GetOwnerClass()->GetName(), *InFunc->GetName());
return;
}
// Get the list of operators to apply this function to
TArray<FString> ScriptOperators;
{
const FString& ScriptOperatorsStr = InFunc->GetMetaData(PyGenUtil::ScriptOperatorMetaDataKey);
ScriptOperatorsStr.ParseIntoArray(ScriptOperators, TEXT(";"));
}
// Go through and try and create a function for each operator, validating that the signature matches what the operator expects
for (const FString& ScriptOperator : ScriptOperators)
{
PyGenUtil::FGeneratedWrappedOperatorSignature OpSignature;
if (!PyGenUtil::FGeneratedWrappedOperatorSignature::StringToSignature(*ScriptOperator, OpSignature))
{
REPORT_PYTHON_GENERATION_ISSUE(Error, TEXT("Function '%s.%s' is marked as 'ScriptOperator' but uses an unknown operator type '%s'."), *InFunc->GetOwnerClass()->GetName(), *InFunc->GetName(), *ScriptOperator);
continue;
}
PyGenUtil::FGeneratedWrappedOperatorFunction OpFunc;
{
FString SignatureError;
if (!OpFunc.SetFunction(InTypeMethod.MethodFunc, OpSignature, &SignatureError))
{
REPORT_PYTHON_GENERATION_ISSUE(Error, TEXT("Function '%s.%s' is marked as 'ScriptOperator' but has an invalid signature for the '%s' operator: %s."), *InFunc->GetOwnerClass()->GetName(), *InFunc->GetName(), *ScriptOperator, *SignatureError);
continue;
}
}
// Ensure that we've generated a finalized Python type for this struct since we'll be adding this function as a operator on that type
const UScriptStruct* HostedStruct = CastFieldChecked<const FStructProperty>(OpFunc.SelfParam.ParamProp)->Struct;
if (GenerateWrappedStructType(HostedStruct, OutGeneratedWrappedTypeReferences, OutDirtyModules, EPyTypeGenerationFlags::ForceShouldExport))
{
// Find the wrapped type for the struct as that's what we'll actually add the operator to (via its meta-data)
TSharedPtr<PyGenUtil::FGeneratedWrappedStructType> HostedStructGeneratedWrappedType = StaticCastSharedPtr<PyGenUtil::FGeneratedWrappedStructType>(GeneratedWrappedTypes.FindRef(PyGenUtil::GetTypeRegistryName(HostedStruct)));
check(HostedStructGeneratedWrappedType.IsValid());
StaticCastSharedPtr<FPyWrapperStructMetaData>(HostedStructGeneratedWrappedType->MetaData)->OpStacks[(int32)OpSignature.OpType].Funcs.Add(MoveTemp(OpFunc));
}
}
};
```