UE5でTypeScriptを用いてキャラクターを動かす


はじめに

こんにちは、Santa Sunrise Inc.でもUnrealEngineのエンジニア?をしているみずあめです。(この記事はPowerd By Santa Sunrise Inc.です ) 今回はBlueprintでなく、C++でもなく、TypeScriptでキャラクターを動かしてみたいと思います。

現在LLMが発達しており、UnityC#であればゴリゴリに支援を受けられます。 ですがUEのBlueprintはGUIベースであり、いくらマルチモーダルなLLMであろうと、これでコーディングさせるのは無理があります。 しかし、RustやC++などの低級な言語を書かせた感想としては、まだ厳しいものを感じます。

そう、つまりスクリプト言語、かつコードベースの言語がUEに求められているわけです(?)

ここで今回ご紹介するTypeScriptで動かそうと言う話になります。

PureTSを使用

https://github.com/Tencent/puerts

あのテンセントが開発している、Unity/UnrealをTypeScriptで書けるようにしてくれるプラグインです。

頻繁に更新されており、UE4.22辺りから最新の5.5までサポートされています。

何が起きているのか?

以下は自分も理解が出来てない部分が多々あります。間違ってたらごめんなさい。

まずC++自体にリフレクションというシステムは無いはずなのですが、UnrealEngineは

 UCLASS() USTRUCT() UENUM()

のメタ情報を付与することにより、リフレクションを生成できるようにしているみたいです。

UObject::ProcessEvent

そして任意の関数を呼び出せます。

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

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

PureTSではtsの型定義を生成してくれるのと、V8/QuickJS/Node.jsとのバインディングレイヤーを提供してくれます。

リフレクションを元にバインディングする場合、パフォーマンスが疑問になりますが、

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

十分実用的な感じらしいです。

使っていく

リリースから適切なバージョンを選んでください。

UE5.2の場合はUnreal_v1.0.5が動きました。 https://github.com/Tencent/puerts/releases/tag/Unreal_v1.0.5

通常のPluginのように配置します。 プロジェクトのC++化が必要です。

型定義ファイルを作成

Puerts.Gen

をコマンドで実行するか、

エディターのボタンを押すと、

Plugins\Puerts\Typing\ue

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

この2つの型定義ファイルがエクスポートされていると思います。

ファイルを設置

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

プロジェクトのルートにTypeScriptファイルを作り、上記2つのファイルを配置します。 任意のコードエディターで型が認識されているか確認します。

モード変更

Plugins\Puerts

node enable_puerts_module.js

コマンドを実行します。

ルートに

tsconfig.json

jsonファイルが生成されているか確認します。

設定変更

"skipLibCheck": true,

をtsconfig.jsonに加筆します。

コードを書く

先ほど作ったTypingフォルダーに実際に書いていきます。 かけたらtscコマンドでtsをjsにトランスパイルします。 この際、jsは

Content\JavaScript

に同じ名前で配置されます。 この時、

  1. import * as UE from ‘ue’; で始まってる
  2. ファイル名とクラス名が一致してる
  3. export default クラス名;で終わってる

この3つを満たさないと、エディター上では認識されません。

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() {
        // キャラクターの移動設定
        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;
        
        // カメラのセットアップ
        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;
    }
    
    
    // ゲーム開始時に呼ばれる
    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;

使えるメソッド名や書き方はシンタックスハイライトやLSPを元に気合を入れて書きます。

LLMもある程度は当てになりますが、全然嘘を言うので、直す必要があります。

実行

tscでトランスパイルをしたら、エディターで認識されるか見てみましょう。

上手くいけば認識されているはずです。

結果

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

まとめ

いかがだったでしょうか?

UnrealC++をLLMで書かせるのはちょっと厳しいしなーと思っていた中、TypeScriptなら割と書いてくれるので、あとはドメイン知識を何らかの方法(ex.コンテキストウィンドウ、ファインチューニング)で突っ込んであげれば、割と実用的な可能性も感じています。

ですが現在はまだ、ユーザーにもtsとUnrealと一部C++の知識が求められる状態と言えそうです。

一覧へ戻る