UE_Angelscript — Manual binding 1: the basics#

Preface#

UE-Angelscript already supplies many common bindings. With very few exceptions, scripts can do more than Blueprints.

To write less C++ and get a more C++-like experience in AS, we need to learn how to bind C++ functionality. The official documentation barely covers this.

The binding series previously introduced an overview, UE_Angelscript — Binding overview: expose more to AS and Blueprints. Read it too, to see the available approaches.

That overview describes three: manual binding, Mixin, and inheritance/wrapping/conversion. Manual binding is the most powerful and complex, and is the focus here. Understand it and the others become much easier.

In terms of functionality, manual binding contains Mixin: it can do whatever a Mixin can do, apart from perhaps unusual exceptions I have not encountered.

Compared with inheritance/wrapping/conversion, it covers everything except exposing BlueprintEvent and non-public members or methods. The concepts articles recommend a C++ middle layer for frequently used AS UClasses, partly for easy extension and partly because manual binding cannot fully replace inheritance and wrapping.

There are other binding solutions and pitfalls in this series. Look them up when you encounter a problem.

Understanding manual binding#

Manual binding is dirty work#

Why has this series not systematically explained the practical steps before now? Because this is both craft and dirty work. Misunderstanding it can mean redoing or refactoring a lot of that work!

After a long period of practice and a little deeper understanding of the script source, I feel I have enough experience to publish this. It is still not guaranteed precise; readers should analyze, judge and try it themselves.

Disclaimer: this is technical sharing, not official documentation. It is for reference and not guaranteed completely correct.

With the disclaimer out of the way, one small point: you may find AS functions without documentation although their Blueprint and C++ APIs are documented. Most likely the binding author could not be bothered to copy it.

Why not? Every binding means another copy. Once the patterns are familiar, the rest is dirty work. Plenty of macro tools help, but it is still dirty work.

Manual binding is also a craft#

Official UClass and UStruct bindings deserve mention. There are enormous numbers of these, and the maintainers wrote batch-registration code. That takes craft!

Template bindings are complicated too. Dirty work, yes, but not simple dirty work: TArray<> and TMap<> require careful design.

A craft needs knowledge. Beyond UE itself, you need low-level C++, especially memory operations, and some understanding of Angelscript.

How do you learn it?

For practical work, the plugin supplies many examples. Imitating them teaches a lot. When unsure how to bind something, look for a similar binding in the plugin source. It may suddenly click.

Angelscript binding foundations#

Heavily modified as UE-Angelscript is, it is still Angelscript. The language documentation is the starting point.

Official documentation: https://www.angelcode.com/angelscript/sdk/docs/manual/index.html

It covers the language thoroughly with detailed examples. But UE-Angelscript uses an older, modified version, so treat it as reference.

This article extracts a few important binding concepts.

Bound types: values and handles#

In one sentence, UObject and its subclasses are handles; the rest are values.

A value represents itself, so copying duplicates the value. A handle refers to another concrete object; copying the handle does not copy its target.

Handles correspond to pointers. UObjects are often heavyweight. Handles or pointers keep operations lightweight and avoid costly accidental copies.

Key AS engine concepts#

This goes a little deeper; skip it unless needed. It is useful for generic binding.

AS script engine#

class asCScriptEngine* Engine is the engine object, accessible through FAngelscriptManager:

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

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

Names starting with asC are concrete objects; asI names are interfaces. Angelscript uses this even when an interface has only one implementation, so commonly used interfaces and concrete types can be cast between.

AS modules#

UE-Angelscript uses one AS module. Angelscript supports multiple modules if the host wants them; UE-Angelscript chooses single-module mode.

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

This API returns nullptr if nothing is found.

With a single module, declared AS types ultimately use this module form:

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

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

AS classes live under /Script/Angelscript. Omit A, U and F class-name prefixes. Enums are the exception and require their full names.

When Engine and Module offer similar APIs, Module generally gives a more precise result. Global AS functions, for example, belong to the Module namespace.

AS type information#

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

asCTypeInfo describes every usable AS type, whether bound or script-declared. It makes information such as GetName() easy to obtain.

Its typeId member is often useful in generic binding. Unlike C++’s identifiers, this is Angelscript’s independent ID system, with a unique ID per type.

Another key member is plainUserData, a pointer for host-defined data. UE-Angelscript stores reflection-type information there: UObject types, for example, store a UClass*.

Not every plainUserData contains reflection data. UENUM handling is not uniform as of UE 5.5: a C++ UENUM stores UEnum* in asCTypeInfo, while an AS-declared enum does not.

Wrapping registration methods: FAngelscriptBinds::Bind#

A short explanation#

A global singleton manages C++/AS interaction:

FAngelscriptManager::Get()

You see it everywhere. But the busy manager delegates core registration work to a helper:

struct ANGELSCRIPTCODE_API FAngelscriptBinds

FAngelscriptBinds then has its own errand-runner to pass registration information to FAngelscriptManager:

FAngelscriptBinds::Bind

This type is purely a courier, registering content through its constructor:

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);
    }
};

I mention the courier because binding code interacts with it constantly. Write bindings and you will meet it every day!

It passes our registration code to the GetBindArray() singleton, which eventually reports to FAngelscriptManager. The manager already has enough to do without directly handling every registration.

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

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

The source walkthrough covers the surrounding flow. This section focuses on the part most closely tied to binding.

The conclusion: constructing an FAngelscriptBinds::Bind stores its parameters — ordering and the registration function — until FAngelscriptManager calls them at the appropriate time.

Binding order#

The first important constructor parameter is order. Omit it and it defaults to 0.

An Enum normally specifies the order instead of a literal number:

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

The plugin does not directly document these stages, but existing bindings suggest: Early normally declares types and constructors with no arguments, same-type arguments or primitive arguments. Normal is used less often. Late normally binds constructors, members and methods.

Order matters. Refer to an undeclared type and registration fails, normally with a log message identifying the binding.

Registration function body#

Here are common binding methods and important basic concepts.

The second FAngelscriptBinds::Bind parameter is a function pointer, usually written as a lambda.

Inside, declare an FAngelscriptBinds variable and operate on it:

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

ValueClass declares a value type at Early. ReferenceClass binds reference types as AS object handles and is used only for UObjects.

FAngelscriptType::Register registers an FAngelscriptType description for the newly declared type. One important use is telling the plugin how that type behaves as another type’s member.

FAngelscriptType is important enough for its own explanation below.

New plain-struct bindings should generally define a custom FAngelscriptType; without one, problems can include being unusable in TArray and similar containers.

SetTypeUserData sets host-defined type data. Here it puts UScriptStruct information into asCObjectType’s plainUserData for later use.

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

ExistingClass retrieves the FAngelscriptBinds for an already-declared type so more registrations can be added.

FAngelscriptBinds has many methods. The most common are:

Constructor
Property
Method

Constructor binds constructors, Property binds fields, and Method binds member functions.

All of this maps the same memory between C++ and Angelscript. A C++ object called Alice need not have that name in AS. It can be Bob, or even Bob<Charlie> in a generic binding.

Matching memory layouts allow surprising tricks. If C++ types M and N have identical layouts — perhaps I copied inaccessible M into N — N can map to an AS type named M without trouble.

Type descriptions: FAngelscriptType and Usage#

FAngelscriptType#

Its comment is below. Think of a structure describing an AS type and its interaction with other types.

/**
 * 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>

Distinguish these pairs:

FAngelscriptTypeUsage#

Its comment is below. It describes a type’s particular usage. TArrays differ in subtypes, counts and other details; UObjects differ in constness. This type records such differences.

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

It is often used to compare types in generic binding. It connects to FAngelscriptType and can be created directly from TypeId.

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

Why does that matter? It makes generic binding much more convenient: validate the type supplied by the user! Space does not permit exploring that further here.

The three steps of complete binding#

The plugin already handles USTRUCT, UCLASS and UENUM. Full custom binding mainly concerns plain structs, so assume an FTestStruct exists.


This article covers the foundations and the three-step process. When time permits, I will organize the practical details — the craftsmanship.

At present, UE C++ APIs generally map fairly easily to Angelscript, apart from lambda-like function pointers, templates and interfaces. Even raw pointers need not always become reference types. This correspondence feels comfortable when working in both C++ and Angelscript.


×

Contact

Feedback

Send bug reports and feedback on GitHub Issues.

Cooperation

For cooperation enquiries, email contact@cuviss.com.

×

About

Terms of Service

See the Community page for each plugin's license.

You may reference site content with attribution.

The software and documentation are provided "as is" without warranty.

Unreal Engine and other third-party projects and names are trademarks of their respective owners. soloAS is not affiliated with Epic Games or Hazelight.

Attribution and showcase requests are non-binding, as described on the home page.

Updated 2026-10-24

Privacy Policy

This site uses no analytics, cookies or tracking, and has no forms or third-party scripts.

The site is hosted on GitHub Pages. GitHub may process standard access logs under its Privacy Statement.

What you send by email is shared with us; what you send through GitHub Issues is shared with GitHub.

Updated 2026-10-24

Contents

Pages

On this page