UE_Angelscript 绑定篇——手工绑定 1 - 基础理解#

前言#

UE-Angelscript 脚本本身已经提供了大量的常用代码绑定,除了极少的情况外,脚本里能用的功能是大于蓝图的。

为了想要在 AS 里少写点 C++,让书写的体验更接近 C++的代码,这就需要学习如何将 C++的内容绑定到 AS 中,这个在官方文档里是几乎不提的。

本系列文章的绑定篇里,之前介绍过了绑定的综述 UE_Angelscript 绑定篇——综述:暴露更多内容给 AS 和蓝图,建议也要看看,知道整体上都有什么办法能做绑定。

这个综述里提到,绑定代码有三种方式:手工绑定、Mixin、继承包装转换。 其中,手工绑定 最为强大,但是也最复杂,本文讲的主要也是这个。一旦理解了 手工绑定,那么 Mixin 以及 继承包装转换 就不在话下了。

手工绑定 相对于 Mixin ,可以说在功能上是包含关系,也就是说,Mixin 能做的,手工绑定 都可以(也许有极特殊情况,但我没遇到过)。

手工绑定 相对于 继承包装转换,除了不能暴露出 BlueprintEvent 和 类型的 非共有成员或函数 外,其余功能也是包含关系。而本系列文章的认知篇提到过,AS 里的声明的 UClass 类型,凡是经常用的,都建议搞一个 C++ 的中间层,除了是因为方便加功能外,还有一个重要原因就是——手工绑定不能完全覆盖住 继承包装转换 的功能。

本系列文章还有一些绑定相关的解决方案、避坑操作,读者可以自行查看,一般是遇到问题时就着问题来找答案。

手工绑定的理解#

手工绑定本质上是脏活#

本系列文章在此篇之前没有系统地介绍手工绑定的具体操作,为什么?因为这是门手艺活、脏活。这容易产生一个问题——如果理解错误,可能会导致大量的脏活得重做或者重构!

现在经过一段相当长时间的实践,以及对脚本源码理解更深一点点之后,个人认为经验算是足够丰富了,此篇相关的内容就可以发表出来了,但仍不能保证是精准无误的,读者需要自己分析判断以及实践。

免责声明——本文不是官方文档,是技术分享,仅供参考,不保证完全正确。

好了,免责声明完成后,这就简单提一提一个事——有时候你会发现 AS 有一些函数并没有文档说明,而蓝图和 C++的对应 API 是有的,这种情况大概率是绑定的时候懒得写了。

为什么懒得写呢,因为都是拷贝的操作,有多少绑定就有多少拷贝,总而言之,绑定代码一旦熟悉了套路后,剩下的就是脏活了,虽然有大量的宏工具来辅助,但还是脏活。

手工绑定也是一门手艺#

这里就不得不提官方对 UClass 和 UStruct 类型的绑定了,这些类型数量极其庞大,官方写了批量绑定的代码,这其实是手艺!

另外关于模板的绑定,也是相当的复杂,是脏活没错,但这不是简单的脏活,而是需要精巧的设计。什么 TArray<> TMap<>等,处理起来并不简单。

凡是手艺活,就需要一些特定的知识,除了 UE 引擎相关的,还需要不少对 C++ 底层知识的理解,特别是内存的操作,另外,Angelscript 这个脚本本身,也是要有点理解的。

如果学习这个手艺呢?

仅就实操而言,UE-Angelscript 这插件已经提供了很多范例,照猫画虎,就可以学到很多很多了。如果碰到了什么不会绑定的,就在插件源码里找找类似的,也许看看就豁然开朗。

Angelscript 的绑定基础知识#

UE-Angelscript 即使是魔改了的 Angelscript,但它仍然是 Angelscript,所以 Angelscript 文档是理解 UE-Angelscript 的出发点。

Angelscript 官方文档链接如下: https://www.angelcode.com/angelscript/sdk/docs/manual/index.html

这个文档对 Angelscript 本身做了各方各面的介绍,有详细的示例,不过需要注意,因为 UE-Angelscript 使用的不是最新版本,同时做了一些魔改,所以此文档只能是参考。

本文重点关注的是绑定方面的内容,摘取部分重要的概念来说明。

绑定类型——值与句柄#

总之一句话,UE-Angelscript 里的 UObject 及其子类,都是对象句柄类型;除此之外,都是值类型。

值类型和对象句柄的核心区别就是,值类型指代自身,拷贝的话,就是将自己复刻一个出来,而对象句柄用来指代其他具体的对象,拷贝的话,仅仅是拷贝自身而已,不拷贝其指代的对象。

为什么存在对象句柄,这是要和指针对应。UObject 这种类型很多都是重量级的,用对象句柄或者指针来操作,会轻量化很多,而且可以防止不小心的拷贝带来的性能问题。

AS 引擎一些关键内容#

这部分内容稍微深一点,不需要的时候可以跳过。但要了解泛型绑定,这是最好要了解的。

AS 脚本引擎#

class asCScriptEngine* Engine 是 AS 引擎对象,在 FAngelscriptManager 里可以获取到

/* Internal script data. */
class asCScriptEngine* Engine = nullptr;

asIScriptEngine* GetScriptEngine() const
{
    return (asIScriptEngine*)Engine;
}

asC 开头的,都是对象本身,asI 开头的,是接口, Angelscript 喜欢这么写,即使接口只有一个子类,所以,常用的那些接口和对象本身类型都是可以互相强转的。

AS 模块#

UE-Angelscript 里,我们只用一个 AS 模块。Angelscript 支持多模块,就看使用者是否使用了,UE-Angelscript 选择的是单模块模式。

const asCModule* Module = static_cast<asCModule*>(Engine->GetModuleByIndex(0));

这个 API 如果获取不到会返回 nullptr。

因为是单模块的设计,所以,我们在 AS 里声明的类型,最终都是一下的模块形式:

auto ObjPath = f"/Script/Angelscript.{ParentNameStr}";

UClass ParentClass = Cast<UClass>(FindObject(ObjPath));

AS 声明的类都在 /Script/Angelscript 里。 注意,类的开头的 A U F 等不可以有,枚举除外,枚举则是必须完整。

Engine 和 Module 如有类似的 API,一般 Module 获取到的比较精准。比如 AS 里定义的全局函数和全局函数这种,都是在 Module 的命名空间里。

AS 类型信息#

asCTypeInfo* Info = (asCTypeInfo*)Engine->GetTypeInfoById(TypeId);

asCTypeInfo 有 AS 能用的类型的所有类型信息,不管是绑定过来的还是自身声明的类型,都有一个对应的 asCTypeInfo ,我们可以方便地获取到很多类型信息,比如 GetName() 就轻松地获取到了类型名。

asCTypeInfo 有一个成员,我们在做泛型绑定的时候经常用到,就是 typeId,这和 C++ 的不一样,是 Angelscript  独立的一套  ID 体系,每个类型对应唯一的一个。

asCTypeInfo 还有一个关键的成员 plainUserData,这是给用户自定义数据的指针,UE-Angelscript 用它来存放了某类型相关的反射信息的类型,比如 UObject 及其子类,就会在plainUserData存 UClass*。

当然并不是所有 plainUserData 都存有反射信息的内容,看具体情况了,比如 UE-Angelscript 里对 UENUM 的处理就没有统一(截至到 UE5.5 版本),C++ 里声明的 UENUM 在对应的 asCTypeInfo 里存了 UEnum* ,而 AS 声明的,就没有。

绑定方法的封装——FAngelscriptBinds::Bind#

原理简析#

首先,对 C++ 与 AS 的交互,用一个全局单例来管理,即

FAngelscriptManager::Get()

在哪里都经常看到它。不过作为大总管,绑定的事情虽然涉及不少,但是核心的绑定代码还是交给专门的小弟来处理,即

struct ANGELSCRIPTCODE_API FAngelscriptBinds

FAngelscriptBinds 作为小弟,其又安排了一个小弟来作为跑腿的,让绑定信息可以传给大总管 FAngelscriptManager ,即

FAngelscriptBinds::Bind

这个类型完全就是跑腿作用,利用它的构造函数来注册绑定内容

struct ANGELSCRIPTCODE_API FBind
{
    FBind(int32 BindOrder, TFunction<void()> Function)
    {
       FAngelscriptBinds::RegisterBinds(BindOrder, Function);
    }

    FBind(EOrder BindOrder, TFunction<void()> Function)
    {
       FAngelscriptBinds::RegisterBinds((int32)BindOrder, Function);
    }

    FBind(TFunction<void()> Function)
    {
       FAngelscriptBinds::RegisterBinds(0, Function);
    }
};

提这个小弟的跑腿是因为,我们的绑定代码核心是与它打交道,只要你在写绑定代码,那真是得天天见面!

这个小弟的跑腿工作,只是把我们写的绑定代码转交给一个 GetBindArray() 这个全局单例,这个全局单例最终才是给大总管 FAngelscriptManager 交差的。毕竟大总管 FAngelscriptManager 日理万机,如果直接对接那么多绑定工作,还是不合适的。

static TArray<FBindFunction>& GetBindArray()
{
    static TArray<FBindFunction> BindArray;
    return BindArray;
}

void FAngelscriptBinds::RegisterBinds(int32 BindOrder, TFunction<void()> Function)
{
    GetBindArray().Add({BindOrder, Function});
}

这个小结的内容在源码篇里将上下文流程都讲过了,这里只是重点提了整个流程中与绑定最密切的部分。

这里得到一个结论——每构造一个FAngelscriptBinds::Bind 实例,就会将构造这个实例的参数(绑定顺序、绑定运行的函数)暂时存起来,然后等待 FAngelscriptManager 在合适的时机调用。

绑定顺序#

构造一个FAngelscriptBinds::Bind 实例,第一个重要参数就是绑定顺序。当你缺省不写时,是 0。

一般情况下,会用一个 Enum 来控制顺序而不是直接使用数字。

enum class EOrder : int32
{
    Early = -100,
    Normal = 0,
    Late = 100,
};

对于这几个阶段,插件源码里虽然没有直接的注释,但是通过查看各类绑定的代码,可以大概得到这么一个用法—— Early 阶段一般用于绑定声明,以及参数为空、参数为自身类型、参数为基础类型的构造函数。 Normal 阶段用于比较少。 Late 阶段一般用于绑定构造函数、成员、成员函数。

绑定顺序挺重要的,如果写错,使用了没有绑定过声明的类型,那么就会导致这个绑定报错,一般会在 Log 里报出来说某某绑定注册失败。

绑定函数体#

这里列举一些最常见的绑定函数以及重要概念,重在讲解基础知识。

构造一个FAngelscriptBinds::Bind 实例的第二个参数是一个函数指针,一般用匿名函数 Lambda 来写。

在函数体内部,一般是先声明一个 FAngelscriptBinds 变量,然后就对这个 FAngelscriptBinds 变量进行操作。

auto FQuat_ = FAngelscriptBinds::ValueClass<FQuat>("FQuat", Flags);
FAngelscriptType::Register(MakeShared<FQuatType>());
FQuat_.SetTypeUserData(TBaseStructure<FQuat>::Get());

这里的 ValueClass 是在 Early 阶段绑定声明,这里绑定方式顾名思义,是值类型绑定。(另外还有 ReferenceClass 用于引用类型绑定,可以绑定为 Angelscript 的对象句柄,只对 UObject 使用。)

FAngelscriptType::Register 函数是注册一个描述类型 FAngelscriptType 给刚刚声明的类型,这个描述类型的重要用途之一是 UE-Angelscript 插件用于描述这个类型作为其他类型的成员的时候有什么特性。

对于描述类型 FAngelscriptType ,是一个重要概念,后续会单独解释。

凡是绑定的普通 struct 新类型,最好都要自定义一个FAngelscriptType,否则就可能会面临一些问题,比如默认情况下,无法放入 TArray 等容器中。

SetTypeUserData 则是配置用户自定义的类型信息,这里是将 UScriptStruct 类型信息注入了 asCObjectType 的 plainUserData,以供后续使用。

FAngelscriptBinds FQuat_ = FAngelscriptBinds::ExistingClass("FQuat");

ExistingClass 用于获取之前已经绑定声明的类型对应的 FAngelscriptBinds,从而对其进行其他绑定操作。

FAngelscriptBinds 有很多绑定方法,最常用的是

Constructor
Property
Method

Constructor 是用来绑定构造函数的,Property 是用来绑定成员的,Method 是用来绑定成员函数的。

所有这些绑定,本质上是同一份内存在 C++里和在 Angelscript 的映射,这同一份的内存,在 C++ 叫这么一个名称,比如张三,在 Angelscript 里未必叫张三,你可以指定叫做李四,甚至可以指定为李四<小白>(泛型绑定)。

有时候,你可以做一些神奇的操作,只要内存布局一致,比如 C++ 里的 M 和 N 类型的内存布局一致(这是因为我不方便访问 M ,从而复刻了一个 N),那么把 N 映射为 Angelscript 里叫做 M 的类型,也完全没有问题。

描述类型 FAngelscriptType 与 USage#

FAngelscriptType#

它的注释如下,可以理解为是一个描述 Angelscript 类型自身情况以及与其他类型如何沟通的结构体。

/**
 * FAngelscriptType holds all information necessary to
 * communicate types between their UClass/FProperty variants
 * and the methods angelscript needs to know about them.
 *
 * This will be subclassed by various Binds_*.cpp files
 * in order to implement specific type operations.
 */
struct ANGELSCRIPTCODE_API FAngelscriptType : TSharedFromThis<FAngelscriptType>

有这么一些相对的概念需要区分:

FAngelscriptTypeUsage#

它的注释如下,可以理解为每一个类型的具体使用情况。比如大家都是 TArray 类型,明显会根据 Array 里子类型、数量等信息而产生不同的使用情况。另比如大家都是 UObject 类型,也会有 const 与否的不同。这个类就是用来描述这些情况的。

/**
 * Describes a particular usage of an angelscript type,
 * including all qualifiers, wrappers, and other.
 */
struct ANGELSCRIPTCODE_API FAngelscriptTypeUsage

这个类型在绑定相关的内容中,用的比较多的是泛型绑定时的类型比对。因为它可以沟通 FAngelscriptType ,同时可以直接从 TypeId 生成。

// Match a type usage to an angelscript TypeId
static FAngelscriptTypeUsage FromTypeId(int32 FAngelscriptType);

直接从 TypeId 生成,意味着什么呢,意味着泛型绑定的极大便利,可以验证用户输入的类型是否正确!限于篇幅,此处就不深入泛型绑定的内容了。

全流程绑定三部曲#

因为 USTRUCT 、 UCLASS 、 UENUM 都被插件处理了,所以我们实际要全流程绑定的,主要是普通的结构体 plain struct ,所以这里就以 plain struct 为例。 假设你已经有了 FTestStruct 这么一个结构体。


本篇主要讲解了基础知识以及最后给出手工绑定的三部曲,如果有时间了再整理具体的绑定细节,这些内容都是所谓的手艺了。

现阶段,UE C++ 的 API 除了 Lambda 等函数指针、模板、接口以外,都可以比较容易地和 Angelscript 一一对应,包括裸指针,不一定非得绑定为引用类型。这种 API 的对应,对于 C++ 和 Angelscript 同时用的人来说,会比较舒适。


×

联系

问题反馈

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

合作

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

×

关于

服务条款

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

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

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

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

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

更新于 2026-10-24

隐私政策

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

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

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

更新于 2026-10-24

目录

页面列表

本页目录