UE_Angelscript 奇技淫巧#

这里列举一些有意思的内容,旨在为了更舒服地写逻辑,是的,没有这些奇技淫巧,我们依然可以生活,这些奇技淫巧是锦上添花,让生活更美好。

本文主要涉及:判空的流畅写法、Cast 的流畅写法、Mixin 的另类写法 ScriptMethod、DeterminesOutputType 的使用、通过反射修改任意 UPROPERTY 参数的介绍。

判空的流畅写法#

AS 里的 UObject 虽然没有指针符号,但是它本质上是一个指针罢了。对于指针,我们使用之前,一般判空,在 cpp 里,写起来很简单,比如

if (ThisObject*)

而 AS 里正常情况下,有两种常用写法,如下

if (ThisObject != nullptr)

if (IsValid(ThisObject))

其中第二种写法还附带了判断是否正在被垃圾回收的功能,防止你使用已经标记为垃圾回收的对象,实际上适用面会更广。

由上可以看出,AS 里的写法比较没那么简洁,AS 里不是不能实现隐式转换,而是 AS 故意为之,尽可能地减少隐式转换的情况。

判空如何才能更顺畅地写出来?用 mixin 功能,实现链式调用,那么就可以这么写了

if (ThisObject.IsValid_())

要实现这个十分简单,不需要 c++ 代码,只需要在 AS 里找个文件,加上

// Mixin version of IsValid(const UObject Object)
mixin bool IsValid_(const UObject Object)
{
	return IsValid(Object);
}

我这里加上一个下划线,是因为不想破坏 IsValid() 这个全局函数的正常写法(此时,这个 mixin 函数不再是类似成员函数了,因为可以接受 nullptr,末尾加上个下划线,算是作为区分)。

这种链式调用的写法,会更加的顺畅,同时不用考虑和 nullptr 的比对,将 AS 里指针的概念进一步弱化。

补充,IsValid 会涉及到 GC , 如果不需要判断是否在 GC 中,或者想要极致的性能,那么可以直接判是不是等于 nullptr.

写法参考(此时,这个 mixin 函数不再是类似成员函数了,因为可以接受 nullptr,末尾加上个下划线,算是作为区分):

mixin bool IsNull_(UObject Obj)
{
	return Obj==nullptr;
}

Cast 的流畅写法#

这里也是用到了 Mixin ,不过需要用到 cpp 的。

正常情况下,我们写 Cast , 是和 c++一样的

auto A = Cast<AActor>(ThisObject);

而我们想流畅书写,实际上舒服地写法是类似这样的

auto A = ThisObject.CastTo(AActor);

这可以实现,用到了 UE 的反射系统,实现起来也简单的 需要给 UObject 写两个 Mixin 函数,用到了一个十分有用的标识符 DeterminesOutputType,这个标识符会自动根据反射的类型信息来推断应该返回的类型。

// Cast to Typed UObject, if false , return nullptr
UFUNCTION(ScriptCallable,  Meta = (DeterminesOutputType = "TypedClass"))
static UObject* CastTo_(UObject* Object, const TSubclassOf<UObject>& TypedClass)
{
    if (Object->IsA(TypedClass))
       return Object;
    return nullptr;
}

// Cast to Typed UObject, if false , will check false
UFUNCTION(ScriptCallable, Meta = (DeterminesOutputType = "TypedClass"))
static UObject* CastToChecked(UObject* Object, const TSubclassOf<UObject>& TypedClass)
{
    check(Object->IsA(TypedClass));
    return Object;
}

这里增加多一个 check 版本,是为了有时候确保 Cast 成功的。

值得一提的是,Mixin 的函数,类似于给类写一个新的成员函数,一般情况下,不必对 this 指针进行判空,在上面的函数里,也就是不需要对 UObject 判空。这也是 AS 各类绑定库的通用写法。

补充,如果需要让这个 Cast 可以接受 nullptr 作为参数,可以像下方这样(最新版本已经不允许 C++ 的 Mixin 对象为空,所以,这个不能用了。)

写法参考(此时,这个 mixin 函数不再是类似成员函数了,因为可以接受 nullptr,末尾加上个下划线,算是作为区分):

/**
 * Cast to Typed UObject, if false , return nullptr.
 * Can be used by any UObject even nullptr.
 */
UFUNCTION(ScriptCallable,  Meta = (DeterminesOutputType = "TypedClass"))
static UObject* CastTo_(UObject* Object, const TSubclassOf<UObject>& TypedClass)
{
    if (!Object) return nullptr;
    if (Object->IsA(TypedClass))
       return Object;
    return nullptr;
}

判空与 Cast 的流畅组合写法#

可以对比一下

// 原始的写法
if (IsValid(Cast<AActor>(ThisObject)))

if (Cast<AActor>(ThisObject) != nullptr)

// 新的写法
if (ThisObject.CastTo(AActor).IsValid_())

很明显,新的写法更符合阅读的直觉,写起来与看起来都舒服一些。不是吗?(当然,如果你觉得不是,那一定是你更对!)

补充提一下,正常的代码千万不要这么写,如果只是判断类型,用 IsA() 函数,如果需要 CastTo 的值,应该在 if 之前用一个变量来承接。

Mixin 的另类写法 ScriptMethod#

如果用传统的 Mixin 写法,每对一个类增加功能,都必须要新建一个 UObject , 有时候觉得挺麻烦的,这时候就可以请 UE 原生的标识符出场了。

这个标识符本来设计是给 python 之类的脚本使用,有意思的是,AS 也能用,而且用起来很舒服。

这是英文的解释:

此处摘录的是 Unreal Engine 对该元数据作用的说明。

这里给出一个官方的示例,与 DataTable 相关的函数库。

mixin 写起来方便多了,但是这会导致 DataTable:: 的命名空间的写法不能再使用,如果之前 AS 代码有这种写法,那么需要修改了。

class UDataTableFunctionLibrary : public UBlueprintFunctionLibrary
{
    GENERATED_UCLASS_BODY()

	// Returns whether or not Table contains a row named RowName
  	UFUNCTION(BlueprintCallable, Category = "DataTable", meta=(ScriptMethod="DoesRowExist"))
 	static ENGINE_API bool DoesDataTableRowExist(const UDataTable* Table, FName RowName);

	UFUNCTION(BlueprintCallable, Category = "DataTable", meta=(ScriptMethod="GetRowNames"))
	static ENGINE_API void GetDataTableRowNames(const UDataTable* Table, TArray<FName>& OutRowNames);

ScriptMethod 之后也可以不加 = 号,这样就不会改变函数名。

经过以下的测试,对于 AS 来说,ScriptMethodMutable 没有什么作用,单独用不行,与 ScriptMethod 组合用,也没有让 const 的内容变可修改。内容可不可以修改,完全取决于第一个参数是否是 const。UPARAM(ref) 也和 ScriptMethodMutable 一样,没什么作用。

综上,ScriptMethod 可以当作 mixin 的单函数版本,可以在一个蓝图函数库里对多种类型进行 mixin 的,const 与否,完全取决于第一个参数是否有这个标签。

下方是一个测试函数的用例,用于测试以上内容的,需要的话,也可以用来验证一下是否正确。

UCLASS()
class YOUR_API UTestStructFunctionLibrary : public UBlueprintFunctionLibrary
{
    GENERATED_BODY()

    /// ScriptMethod
    UFUNCTION(ScriptCallable,  meta = (ScriptMethod))
    static void TestScriptMethod_Const(const FTestStruct& TestStruct, FName Name)
    {
       // do nothing
    };
    UFUNCTION(ScriptCallable,  meta = (ScriptMethod))
    static void TestScriptMethod(FTestStruct& TestStruct, FName Name)
    {
       // do nothing
    };
    /// ScriptMethodMutable, must use with ScriptMethod, only use with struct
    UFUNCTION(ScriptCallable,  meta = (ScriptMethod, ScriptMethodMutable))
    static void TestScriptMethodMutable_Const(const FTestStruct& TestStruct, FName Name)
    {
       // do nothing
    };
    UFUNCTION(ScriptCallable,  meta = (ScriptMethod, ScriptMethodMutable))
    static void TestScriptMethodMutable(FTestStruct& TestStruct, FName Name)
    {
       // do nothing
    };

    /// UPARAM, must use with ScriptMethod
UFUNCTION(ScriptCallable,  meta = (ScriptMethod))
    static void Test_UPARAM_Const(UPARAM(ref) const FTestStruct& TestStruct, FName Name)
    {
       // do nothing
    };
    UFUNCTION(ScriptCallable,  meta = (ScriptMethod))
    static void Test_UPARAM(UPARAM(ref) FTestStruct& TestStruct, FName Name)
    {
       // do nothing
    };

    /// UPARAM, must use with ScriptMethod
UFUNCTION(ScriptCallable,  meta = (ScriptMethod, ScriptMethodMutable))
    static void Test_UPARAM_Const2(UPARAM(ref) const FTestStruct& TestStruct, FName Name)
    {
       // do nothing
    };
    UFUNCTION(ScriptCallable,  meta = (ScriptMethod, ScriptMethodMutable))
    static void Test_UPARAM2(UPARAM(ref) FTestStruct& TestStruct, FName Name)
    {
       // do nothing
    };
};

Cpp 的 Meta 实现自动类型判断 :DeterminesOutputType 标识符#

这是 Lyra 项目里的一个函数,其会根据 FragmentClass 的类型自动给推断返回类型,蓝图里或 AS 里调用的时候无需 Cast。

	UFUNCTION(BlueprintCallable, meta=(DeterminesOutputType=FragmentClass))
	static const ULyraInventoryItemFragment* FindItemDefinitionFragment(TSubclassOf<ULyraInventoryItemDefinition> ItemDef, TSubclassOf<ULyraInventoryItemFragment> FragmentClass);

拓展,DeterminesOutputType 支持自动推断 TArray 里的值,还支持多参数推断,支持自动解析结构体内部的 Property 作为基础来推断。

使用示例,可以推断函数的返回类型、数量:

	// 函数返回类的自动推断,如果没有配合 DynamicOutputParam 参数, 默认推断返回值
	UFUNCTION(BlueprintCallable, Meta = (DeterminesOutputType = "TypedClass"))
	static TArray<AActor*> MyTestFunc(TSubclassOf<ATestActor> TypedClass);

	// 函数参数的类型推断,与 DynamicOutputParam 配合,支持同时推断多个参数
	UFUNCTION(BlueprintCallable, Meta = (DeterminesOutputType = "TypedClass", DynamicOutputParam = "OutTest,OutTest2"))
	static void MyTestFunc(TSubclassOf<ATestActor> TypedClass, TArray<AActor*>& OutTest, AActor*&  OutTest2);

使用示例,用于推断的基础类型:

	// TSubclassOf<> 是支持的,上方示例已经有

	// DeterminesOutputType 里还可以接受 UObject
	UFUNCTION(BlueprintCallable, Meta = (DeterminesOutputType = "Object"))
	static TArray<UObject*> MyTestFunc(ATestObject* Object);

	// DeterminesOutputType 支持将 struct 里的 property 作为推断的基础类型
	// 下方就是将 FTestStruct 里的AnyProperty 作为推断的基础
	UFUNCTION(BlueprintCallable, Meta = (DeterminesOutputType = "TestStruct_AnyProperty"))
	static TArray<AActor*> MyTestFunc(const FTestStruct& TestStruct);
};

这个 DeterminesOutputType 用好了,可以节省很多无意义的 Cast 代码,如果用在 TArray 上,那么会感觉世界更美好!

通过反射修改任意 UPROPERTY 参数#

AS 里会遇到一种情况是,某个参数暴露给 AS 了,但是它只给在构造的时候修改,不能在运行时修改。正常做法是在 cpp 里写 Mixin 函数,通过函数来操作。

但是这样总是显得麻烦,还有一种情况是这个参数在 cpp 里不是 public,那么 cpp 正常的访问也不行了。

在确保可靠的情况下,用反射就可以改这种参数了。可以这么说,大部分 Cpp 里的参数,只要进入了反射系统,都能被操作。

一个简单的使用情况如下:

这是测试用例。

/// Cpp 的测试用例
UCLASS(Blueprintable)
class ATestClass : public AActor
{
    GENERATED_BODY()
public:
    ATestClass(){ TestFloat= 666.f;};
private:
    UPROPERTY()
    float TestFloat;
};

这是包装了反射函数 SetPropertyFloat 和 GetPropertyFloat 后,在 AS 里的写法。

/// AS 里的测试
ATestClass TestClass11 = NewObject(nullptr, ATestClass );

float32 OutValue = 1.0f;
// 我将这个函数放在 Reflect 命名空间下,你随意
Reflect::SetPropertyFloat(TestClass11, n"TestFloat", 9.0f);
Reflect::GetPropertyFloat(TestClass11, n"TestFloat", OutValue);

// FUnitTest& T
T.AssertTrue(OutValue == 9.0f);

// 测试应该通过

具体可以参考这篇文章,魔法毕竟是魔法,使用需谨慎。 手搓 UE5 的反射魔法,是黑魔法?是白魔法?

小结#

本文主要围绕判空、Cast、Mixin 用法、ScriptMethod、DeterminesOutputType、反射修改参数等话题展开,所谓奇技淫巧,主要是将机械重复的工作交给机器处理,让编码更舒适。


×

联系

问题反馈

问题与反馈请提交至 GitHub Issues。

合作

合作事宜请发送邮件至 contact@cuviss.com。

×

关于

服务条款

各插件的许可证见社区贡献页。

网站内容可在注明来源后引用。

软件和文档按“现状”提供,不作任何保证。

Unreal Engine 等第三方项目及名称相关商标归各自所有者所有。soloAS 与 Epic Games、Hazelight 均无关联。

致谢和案例展示请求均不具约束力,详见首页说明。

更新于 2026-10-24

隐私政策

本网站不使用分析工具、Cookie 或跟踪,也没有数据表单或第三方脚本。

网站托管于 GitHub Pages。GitHub 可能按其隐私声明处理标准访问日志。

通过邮件发送的内容会提供给我们;通过 GitHub Issues 发送的内容会提供给 GitHub。

更新于 2026-10-24

目录

页面列表

本页目录