Template-based static binding
Below is a translated version of the original docs by Incanta Games. The translation is mainly done with Google Translate, but then modified by hand to try to make sense of what Google Translate is missing.
Overview
This document describes the ordinary C++ class/struct, Or UCLASS, unchecked the properties of UPPROPERTY, UFUNCTION, and calls.
This document also applies to non-UE environments (such as servers, unity, etc.), C++ class/struct access.
Support features:
- Constructor
- Static function
- Member variables
- Member function
- Constructor / static function / member function supports overload
- Support inheritance
- Generate TypeScript declaration
- The UE type is not marked
UPROPERTY
. After theUFUNCTION
member declares, it will seamlessly appear in the original category. - Support JS object mapping to C++
JSObject
,JSObject
can get/set the properties, call the JS function. - Support JS function mapping to
std::function
- Support custom converter
Important Notes
If you want to use this feature outside of the JsEnv
module itself, such as another game module, you'll need to do the following:
- Use dynamic library version
V8
libraries, switching methods:- To the Puerts official website download and Puerts supporting
V8
library, and unzip it toPlugins/Puerts/ThirdParty/
(or respective folder) - Find the
JsEnv.Build.cs
file and changeUseNewV8
totrue
- To the Puerts official website download and Puerts supporting
- In the module's
*.Build.cs
- Add a dependency to the
JsEnv
module
- Add a dependency to the
Examples
Hello World
With a simplest common C++ class as an example
// Calc.h
class Calc
{
public:
static int32_t Add(int32_t a, int32_t b)
{
return a + b;
}
};
We declare as follows
#include "Calc.h"
#include "Binding.hpp"
// A macro defined in Binding.hpp which
// creates a converter (either V8 or Pesapi) for your class
// which helps build the translation layer
UsingCppType(Calc);
struct AutoRegisterForCPP
{
AutoRegisterForCPP()
{
puerts::DefineClass<Calc>()
.Function("Add", MakeFunction(&Calc::Add)) // There's also `.Method(...)` and `.Property(...)`
.Register();
}
};
// Completes the automatic registration with puerts. When this calls
// when the module is loaded, it will call the constructor defined above,
// executing the registration with puerts
AutoRegisterForCPP _AutoRegisterForCPP__;
Compile the C++, open the Unreal editor, and generate TypeScript definitions (either with the button or command) to call in TypeScript. Then in TypeScript you can do:
import * as cpp from 'cpp'
let Calc = cpp.Calc;
// static function
console.log(Calc.Add(12, 34));
Static function declaration
The presentation is a static function, .Function(Name, <function reference>)
. To register a static function, there are several ways to reference:
- The function is not overloaded:
MakeFunction(&Calc::Add)
- The function is not overloaded, but you want to verify the parameters:
MakeCheckFunction(&Calc::Add)
- The function is overloaded, but you just want to choose one of them:
SelectFunction(float (*)(float, float), &Calc::Add)
- The function is overloaded, and you want multiple overloads available:
CombineOverloads(
MakeOverload(void(*)(), &TestClass::Overload),
MakeOverload(void(*)(int32_t), &TestClass::Overload),
MakeOverload(void(*)(int32_t, int32_t), &TestClass::Overload),
MakeOverload(void(*)(std::string, int32_t), &TestClass::Overload)
)
Member variables
class TestClass
{
public:
int32_t X;
int32_t Y;
};
Statement
puerts::DefineClass<TestClass>()
.Property("X", MakeProperty(&TestClass::X))
.Property("Y", MakeProperty(&TestClass::Y))
.Register();
Constructor
class TestClass
{
public:
TestClass();
TestClass(int32_t InX, int32_t InY);
};
Statement
puerts::DefineClass<TestClass>()
.Constructor(CombineConstructors(
MakeConstructor(TestClass, int32_t, int32_t),
MakeConstructor(TestClass)
))
.Register();
If there is only one constructor, you can simplify to
puerts::DefineClass<AdvanceTestClass>()
.Constructor<int>() // if only one Constructor
.Register();
Member function
class TestClass
{
public:
int32_t OverloadMethod();
int32_t OverloadMethod(int32_t a);
uint32_t OverloadMethod(uint32_t a);
int64_t OverloadMethod(int64_t a);
TestClass *GetSelf();
};
Statement
puerts::DefineClass<TestClass>()
.Method("OverloadMethod", CombineOverloads(
MakeOverload(int32_t(TestClass::*)(), &TestClass::OverloadMethod),
MakeOverload(int32_t(TestClass::*)(int32_t), &TestClass::OverloadMethod),
MakeOverload(uint32_t(TestClass::*)(uint32_t), &TestClass::OverloadMethod),
MakeOverload(int64_t(TestClass::*)(int64_t), &TestClass::OverloadMethod)
))
.Method("GetSelf", MakeFunction(&TestClass::GetSelf))
.Register();
Inheritance
class BaseClass
{
public:
void Foo(int p);
};
class TestClass : public BaseClass
{
public:
};
Statement
puerts::DefineClass<BaseClass>()
.Method("Foo", MakeFunction(&BaseClass::Foo))
.Register();
puerts::DefineClass<TestClass>()
.Extends<BaseClass>()
.Register();
JS object is mapped to JsObject and get/set the JS object properties
#include "JsObject.h"
class AdvanceTestClass
{
public:
AdvanceTestClass(int A);
void JsObjectTest(FJsObject Object);
};
void AdvanceTestClass::JsObjectTest(FJsObject Object)
{
auto P = Object.Get<int>("p");
UE_LOG(LogTemp, Warning, TEXT("AdvanceTestClass::JsObjectTest({p:%d})"), P);
Object.Set<std::string>("q", "john");
}
Use in TypeScript
import * as cpp from 'cpp'
// js object
let obj = new cpp.AdvanceTestClass(100);
let j:any = {p:100};
obj.JsObjectTest(j);
console.log(j.q);
JS function mapping JsObject and callback
// class decl ...
void AdvanceTestClass::CallJsObjectTest(FJsObject Object)
{
auto Ret = Object.Func<float>(1024, "che");
UE_LOG(LogTemp, Warning, TEXT("AdvanceTestClass::CallJsObjectTest Callback Ret %f"), Ret);
}
Use in TypeScript
let obj = new cpp.AdvanceTestClass(100);
obj.CallJsObjectTest((i, str) => {
console.log(i, str);
return 1.01;
})
JS function mapping to std::function
//class decl ...
void AdvanceTestClass::StdFunctionTest(std::function<int(int, int)> Func)
{
int Ret = Func(88, 99);
UE_LOG(LogTemp, Warning, TEXT("AdvanceTestClass::StdFunctionTest Callback Ret %d"), Ret);
}
Use in TypeScript
let obj = new cpp.AdvanceTestClass(100);
obj.StdFunctionTest((x:number, y:number) => {
console.log('x=' + x + ",y=" + y);
return x + y;
})
Supplement Engine Classes
This is if you want to supplement existing classes like UObject
which has method like CreateDefaultSubobject
, GetName
, GetOuter
, GetClass
, GetWorld
which don't have the UFUNCTION
modifier.
To achieve this, add the C++:
#include "CoreMinimal.h"
#include "Binding.hpp"
#include "UEDataBinding.hpp"
UsingUClass(UObject)
UsingUClass(UWorld) // for return type
UsingUClass(UClass)
UsingUClass(USceneComponent)
puerts::DefineClass<UObject>()
#if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION >= 23
.Method("CreateDefaultSubobject", SelectFunction(UObject* (UObject::*)(FName, UClass*, UClass*, bool , bool), &UObject::CreateDefaultSubobject))
#else
.Method("CreateDefaultSubobject", SelectFunction(UObject* (UObject::*)(FName, UClass*, UClass*, bool, bool, bool), &UObject::CreateDefaultSubobject))
#endif
.Method("GetName", SelectFunction(FString (UObjectBaseUtility::*)() const, &UObjectBaseUtility::GetName))
.Method("GetOuter", MakeFunction(&UObject::GetOuter))
.Method("GetClass", MakeFunction(&UObject::GetClass))
.Method("GetWorld", MakeFunction(&UObject::GetWorld))
.Register();
Note: Ordinary C++ class is different (i.e. not a descendent of UObject
/UClass
), like specified earlier in this file. If you're modifying something that is a descendent of UClass
, you meed to use the UsingUClass
macro as shown above. Similarly if you're supplementing a UStruct
, you need to use UsingUStruct
.
After regenerating ue.d.ts
, it can be seen that the above methods have been added to UE.Object
's type declaration:
class Object {
constructor(Outer?: Object, Name?: string, ObjectFlags?: number);
ExecuteUbergraph(EntryPoint: number): void;
CreateDefaultSubobject(p0: string, p1: $Nullable<Class>, p2: $Nullable<Class>, p3: boolean, p4: boolean) : Object;
GetName() : string;
GetOuter() : Object;
GetClass() : Class;
GetWorld() : World;
static StaticClass(): Class;
static Find(OrigInName: string, Outer?: Object): Object;
static Load(InName: string): Object;
}
Subsequent use of the above method can be used directly on the Object
object.