doc/unreal/en/engine_call_script.md
Useful when Unreal Engine needs to execute certain functionality inside of TypeScript.
TypeScript logic can be executed from within UObjects by binding to DYNAMIC_DELEGATE and DYNAMIC_MULTICAST_DELEGATE.
Example use-cases include:
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "DemoActor.generated.h"
DECLARE_DYNAMIC_DELEGATE(FBasicNotify);
DECLARE_DYNAMIC_DELEGATE_OneParam(FNotifyWithRefString, FString&, InStringRef);
DECLARE_DYNAMIC_DELEGATE_RetVal_OneParam(FString, FNotifyWithStringRet, FString, InString);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMulticastNotifyWithInt, int32, InInt);
UCLASS()
class ADemoActor : public AActor
{
GENERATED_BODY()
public:
virtual void BeginPlay() override {
// DECLARE_DYNAMIC_DELEGATE
BasicNotify.ExecuteIfBound();
// DECLARE_DYNAMIC_DELEGATE_OneParam
if (NotifyWithRefString.IsBound())
{
FString StringReference = "Example String...";
NotifyWithRefString.Execute(StringReference);
}
// DECLARE_DYNAMIC_DELEGATE_RetVal_OneParam
if (NotifyWithStringRet.IsBound())
{
FString ReturnedString = NotifyWithStringRet.Execute("Example string");
UE_LOG(LogTemp, Warning, TEXT("ReturnedString: %s"), *ReturnedString); // "Example string" + " with some extra characters"
}
// DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam
MulticastNotifyWithInt.Broadcast(1337);
}
UFUNCTION()
static void PassJsFunctionAsDelegate(FBasicNotify Callback)
{
Callback.ExecuteIfBound();
}
public:
UPROPERTY()
FBasicNotify BasicNotify;
UPROPERTY()
FNotifyWithRefString NotifyWithRefString;
UPROPERTY()
FNotifyWithStringRet NotifyWithStringRet;
UPROPERTY()
FMulticastNotifyWithInt MulticastNotifyWithInt;
};
Note: UPROPERTY() exposes the delegate variable to puerts
// DECLARE_DYNAMIC_DELEGATE
function OnNotifyBasic(): void {
console.warn("BasicNotify Callback Executed.");
}
DemoActor.BasicNotify.Bind(OnNotifyBasic);
// DECLARE_DYNAMIC_DELEGATE_OneParam
import {$unref, $set} from 'puerts';
DemoActor.NotifyWithRefString.Bind((InStringRef) => {
console.warn("NotifyWithRefString: " + $unref(InStringRef)); // Print the value
$set(InStringRef, "Some new string!"); // Update the value by reference
});
// DECLARE_DYNAMIC_DELEGATE_RetVal_OneParam
DemoActor.NotifyWithStringRet.Bind((InString) => {
return InString + " with some extra characters.";
});
// DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam
function OnMulticast(InInt: number): void {
console.warn("Multicast Callback 1: " + InInt);
}
DemoActor.MulticastNotifyWithInt.Add(OnMulticast);
DemoActor.MulticastNotifyWithInt.Add((InInt: number) => {
console.warn("Multicast Callback 2: " + InInt);
});
Instead of directly binding to DYNAMIC_DELEGATE inside of a UObject, new delegates can be constructed from within TypeScript.
Example use-cases include:
// Delegate handle owned by UObject
import {toDelegate} from 'puerts';
function PrintHelloWorld() : void {
console.warn("Hello World!");
}
const NewDelegate = toDelegate(DemoActor, PrintHelloWorld); // toDelegate(owOwnerner: UE.Object, Func:Function)
DemoActor.PassJsFunctionAsDelegate(NewDelegate);
// Delegate handle with no owner (Manual Cleanup)
import {toManualReleaseDelegate, releaseManualReleaseDelegate} from 'puerts';
function PrintHelloWorld() : void {
console.warn("Hello World!");
}
const NewManualReleaseDelegate = toManualReleaseDelegate(PrintHelloWorld); // toManualReleaseDelegate(Func:Function)
DemoActor.PassJsFunctionAsDelegate(NewManualReleaseDelegate);
releaseManualReleaseDelegate(PrintHelloWorld); // Release to prevent memory leak
Aside from delegates, JsObjects can be used within C++ to execute functions, access variables, e.t.c
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "JsObject.h"
#include "DemoActor.generated.h"
UCLASS()
class ADemoActor : public AActor
{
GENERATED_BODY()
public:
UFUNCTION()
static void CalculateAge(FJsObject InPerson)
{
const int BirthYear = InPerson.Get<int>("BirthYear");
InPerson.Set<int>("Age", 2025 - BirthYear);
}
UFUNCTION()
static void ExecuteJsFunctionObject(FJsObject InJsFunctionObject)
{
UE_LOG(LogTemp, Warning, TEXT("ADemoActor:ExecuteJsFunctionObject() = %i"), InJsFunctionObject.Func<int>());
}
};
import * as UE from 'ue'
let John = {BirthYear:1999, Age: -1};
UE.DemoActor.CalculateAge(John);
console.warn("John is aged " + John.Age);
UE.DemoActor.ExecuteJsFunctionObject(() => {
return 1337;
});
Inside of a standard C++ environment, std::function is the preferred method for passing around function pointers. Puerts supports this as well.
static void PassJsFunctionWithStd(std::function<int(int, int)> InFunction)
{
int ReturnValue = InFunction(88, 99);
UE_LOG(LogTemp, Warning, TEXT("ADemoActor:PassJsFunctionWithStd() | 89 + 99 = %i"), ReturnValue);
}
import * as UE from 'ue'
UE.DemoActor.PassJsFunctionWithStd((InA: number, InB: number) => {
return InA + InB;
});