JP / EN

Moving Characters with TypeScript in UE5


Introduction

Hi, I’m Mizuame, also an Unreal Engine engineer at Santa Sunrise Inc.. (This article is Powered By Santa Sunrise Inc.) Today we’ll move characters not with Blueprints or C++, but with TypeScript.

LLMs are advancing, and Unity C# gets heavy AI assistance. But UE’s Blueprints are GUI-based, making it unreasonable to code with even multimodal LLMs. However, my experience having LLMs write low-level languages like Rust and C++ is still rough.

So, a scripting language that’s code-based is needed for UE (right?)

That’s where TypeScript comes in.

Using PureTS

https://github.com/Tencent/puerts

A plugin by Tencent that lets you write Unity/Unreal in TypeScript.

Frequently updated, supporting UE4.22 through latest 5.5.

What’s Happening?

Some of this I don’t fully understand. Sorry if wrong.

C++ doesn’t have reflection, but Unreal Engine uses:

UCLASS() USTRUCT() UENUM()

meta info to generate reflection.

UObject::ProcessEvent

And call arbitrary functions.

Reference: https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/CoreUObject/UObject/UObject/ProcessEvent

https://qiita.com/unknown_ds/items/30ea13c9d50f2c2de6ff

PureTS generates TypeScript type definitions and provides binding layer with V8/QuickJS/Node.js.

Performance concerns for reflection-based binding, but:

https://github.com/Tencent/puerts/tree/master/unreal

It’s sufficiently practical according to their benchmarks.

Using It

Get the appropriate version from releases.

For UE5.2, Unreal_v1.0.5 worked. https://github.com/Tencent/puerts/releases/tag/Unreal_v1.0.5

Place like normal Plugin. C++ project conversion required.

Create Type Definition Files

Puerts.Gen

Run the command or:

Press the editor button,

Plugins\Puerts\Typing\ue

and

"ue.d.ts"
"puerts.d.ts"

These two type definition files should be exported.

File Placement

MyProject/
├── Config/
├── Content/
├── Source/
├── TypeScript/
└── MyProject.uproject

Create TypeScript folder at project root, place the two files. Verify types are recognized in your code editor.

Mode Change

In

Plugins\Puerts

run:

node enable_puerts_module.js

Verify

tsconfig.json

was generated at root.

Configuration Change

Add to tsconfig.json:

"skipLibCheck": true,

Write Code

Write in the Typing folder. Transpile with tsc; js goes to

Content\JavaScript

with the same name. At this point:

  1. Starts with import * as UE from 'ue';
  2. File name matches class name
  3. Ends with export default ClassName;

Without these three, it won’t be recognized in the editor.

import * as UE from 'ue';
class TS_Character extends UE.Character {
    SpringArm: UE.SpringArmComponent;
    Camera: UE.CameraComponent;

    WalkSpeed: number = 600.0;
    MouseSensitivity: number = 1.0;

    Constructor() {
        // Character movement settings
        this.bUseControllerRotationPitch = false;
        this.bUseControllerRotationYaw = false;
        this.bUseControllerRotationRoll = false;

        this.CharacterMovement.bOrientRotationToMovement = true;
        this.CharacterMovement.RotationRate = new UE.Rotator(0.0, 540.0, 0.0);
        this.CharacterMovement.MaxWalkSpeed = this.WalkSpeed;

        this.SpringArm = this.CreateDefaultSubobject(
            "SpringArm",
            UE.SpringArmComponent.StaticClass(),
            UE.SpringArmComponent.StaticClass(),
            true,
            false
        ) as UE.SpringArmComponent;
        this.SpringArm.SetupAttachment(this.RootComponent, "");
        this.SpringArm.TargetArmLength = 300.0;
        this.SpringArm.bUsePawnControlRotation = true;

        // Camera setup
        this.Camera = this.CreateDefaultSubobject(
            "Camera",
            UE.CameraComponent.StaticClass(),
            UE.CameraComponent.StaticClass(),
            true,
            false
        ) as UE.CameraComponent;
        this.Camera.SetupAttachment(this.SpringArm, "");
        this.Camera.bUsePawnControlRotation = false;
    }


    // Called when game starts
    ReceiveBeginPlay(): void {
        super.ReceiveBeginPlay();

    }

    MoveForward(Value: number): void {
        if (Value != 0.0 && this.Controller) {
            const Rotation = this.Controller.GetControlRotation();
            const YawRotation = new UE.Rotator(0, Rotation.Yaw, 0);

            const Direction = UE.KismetMathLibrary.GetForwardVector(YawRotation);

            this.AddMovementInput(Direction, Value);
        }
    }

    MoveRight(Value: number): void {
        if (Value != 0.0 && this.Controller) {
            const Rotation = this.Controller.GetControlRotation();
            const YawRotation = new UE.Rotator(0, Rotation.Yaw, 0);

            const Direction = UE.KismetMathLibrary.GetRightVector(YawRotation);

            this.AddMovementInput(Direction, Value);
        }
    }


}

export default TS_Character;

Write using syntax highlighting and LSP.

LLMs help somewhat but often lie, so fixes are needed.

Execution

After tsc transpile, check if recognized in editor.

If successful, it should be recognized.

Result

https://x.com/mizuameisgod/status/1907809617851343291

Summary

How was it?

While writing Unreal C++ with LLMs is tough, TypeScript works reasonably well, so feeding in domain knowledge (context window, fine-tuning) could be practical.

But currently, users still need TypeScript, Unreal, and some C++ knowledge.

Back to list