Unity/UE 下的 TypeScript 编程解决方案

Unity/UE 下的 TypeScript 编程解决方案PuerTS 是一个在 Unity/Unreal 里用 Typescript 编写逻辑的技术。其执行性能,开发便捷度,与宿主平台的融合度都远强于 Lua。

特性

  • JavaScript 生态有众多的库和工具链,结合专业商业引擎的渲染能力,快速打造游戏
  • 相比游戏领域常用的 lua 脚本,TypeScript 的静态类型检查有助于编写更健壮,可维护性更好的程序
  • 高效:全引擎,全平台支持反射调用,无需额外步骤即可与宿主 C++/C# 通信
  • 高性能:全引擎,全平台支持生成静态调用桥梁,兼顾了高性能的场景
  • WebGL 平台下的天生优势:相比 Lua 脚本在 WebGL 版本的表现,PuerTS 在性能和效率上都有极大提升,目前极限情况甚至比 C# 更快

可用引擎

  • unreal engine 4.22 ~ latest
  • unity 5 ~ latest
  • 任意.net 环境

可用平台

Unity 使用 PuerTS 示例

在 Unity 里准备好一个场景及一个 MonoBehaviour 组件,在 MonoBehaviour 里编写如下代码:

//1. Hello World
void Start() {
    Puerts.JsEnv env = new Puerts.JsEnv();
    env.Eval(@"
        console.log('hello world');
    "
)
}

执行后,你能看见 Unity 控制台中打印出了Hello worldUnity/UE 下的 TypeScript 编程解决方案成功了!这就意味着,我们在 Unity 里执行了一段真正的 Javascript!

PuerTS 就是这么容易!

在 Javascript 调用 C

对象创建

//2. 创建C#对象
void Start() {
    Puerts.JsEnv env = new Puerts.JsEnv();
    env.Eval(@"
        console.log(new CS.UnityEngine.Vector3(1, 2, 3));
        // (1.0, 2.0, 3.0)
    "
);
}

在本例中,我们直接在 Javascript 中创建了一个 C# 的 Vector!

在 PuerTS 所创建的 Javascript 环境里,你可以通过 CS 这个对象,输入任意类的 FullName (包含完整命名空间的路径),访问任意的 C# 类,包括直接创建一个 Vector3 对象。

当然,写出完整的命名空间还是比较麻烦的,不过你也可以通过声明一个变量别名来简化:

    const Vector2 = CS.UnityEngine.Vector2;
    console.log(Vector2.one)

属性访问

对象创建出来了,调用其方法、访问其属性也是非常容易的。

//3. 调用C#函数或对象方法
void Start() {
    Puerts.JsEnv env = new Puerts.JsEnv();
    env.Eval(@"
        CS.UnityEngine.Debug.Log('Hello World');
        const rect = new CS.UnityEngine.Rect(0, 0, 2, 2);
        CS.UnityEngine.Debug.Log(rect.Contains(CS.UnityEngine.Vector2.one)); // True
        rect.width = 0.1
        CS.UnityEngine.Debug.Log(rect.Contains(CS.UnityEngine.Vector2.one)); // False
    "
);
}

可以看出,不管是函数调用还是属性访问/赋值,用法上都和 C# 一模一样。

在 C#中调用 Javascript

通过 Delegate 调用

PuerTS 提供了一个关键能力:将 Javascript 函数转换为 C# 的 delegate。依靠这个能力,你就可以在 C# 侧调用 Javascript。

public class TestClass
{
    Callback1 callback1;

    public delegate void Callback1(string str);

    public void AddEventCallback1(Callback1 callback1)
    {
        this.callback1 += callback1;
    }
    public void Trigger()
    {
        if (callback1 != null)
        {
            callback1("test");
        }
    }
}

void Start() {
    Puerts.JsEnv env = new Puerts.JsEnv();
    env.Eval(@"
        const obj = new CS.TestClass();
        obj.AddEventCallback1(i => console.log(i));
        obj.Trigger();
        // 打印了obj变量
        // 虽然是JS触发的,但实际上是C#调用JS函数,完成了console.log
    "
);
}

从 C# 往 Javascript 传参

把 JS 函数转换成 delegate 的时候,你也可以将其转换成带参数的 delegate、这样你就可以把任意 C# 变量传递给 Javascript。传参时,类型转换的规则和把变量从 C# 返回值到 Javascript 是一致的。

void Start() {
    Puerts.JsEnv env = new Puerts.JsEnv();
    // 这里可以直接通过 Eval 的结果获得 delegate
    System.Action<int> LogInt = env.Eval<System.Action<int>>(@"
        const func = function(a) {
            console.log(a);
        }
        func;
    "
);

    LogInt(3); // 3
}

需要注意的是,如果你生成的 delegate 带有值类型参数,需要添加 UsingAction 或者 UsingFunc 声明。

从 C# 调用 Javascript 并获得返回值

与上一部分类似。只需要将 Action delegate 变成 Func delegate 就可以了。

void Start() {
    Puerts.JsEnv env = new Puerts.JsEnv();
    // 这里可以直接通过 Eval 的结果获得 delegate
    System.Func<int, int> Add3 = env.Eval<System.Func<int, int>>(@"
        const func = function(a) {
            return 3 + a;
        }
        func;
    "
);

    System.Console.WriteLine(Add3(1)); // 4
}

需要注意的是,如果你生成的 delegate 带有值类型参数,需要添加 UsingAction 或者 UsingFunc 声明。

UE 使用 PuerTS 示例

脚本调用普通 C++

以一个最简单的普通 c++ class 为例

//Calc.h
class Calc
{
public:
    static int32_t Add(int32_t a, int32_t b)
    {
        return a + b;
    }
};

我们按如下方式声明

#include "Calc.h"
#include "Binding.hpp"

UsingCppType(Calc);

struct AutoRegisterForCPP
{
    AutoRegisterForCPP()
    {
        puerts::DefineClass<Calc>()
            .Function("Add", MakeFunction(&Calc::Add))
            .Register();
    }
};

AutoRegisterForCPP _AutoRegisterForCPP__;

(编译,进入 UE 界面,点击生成按钮)即可在 TypeScript 中调用

import * as cpp from 'cpp'

let Calc = cpp.Calc;

//static function
console.log(Calc.Add(12, 34));

C++调用脚本

C++定义

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FNotifyWithInt, int32, A);
DECLARE_DYNAMIC_DELEGATE_RetVal_OneParam(FString, FNotifyWithStringRet, FString, A);
DECLARE_DYNAMIC_DELEGATE_OneParam(FNotifyWithRefString, FString&, A);

UCLASS()
class PUERTS_UNREAL_DEMO_API AMyActor : public AActor
{
    GENERATED_BODY()

public:
    UPROPERTY()
    FNotifyWithInt NotifyWithInt;

    UPROPERTY()
    FNotifyWithRefString NotifyWithRefString;

    UPROPERTY()
    FNotifyWithStringRet NotifyWithStringRet;
    //...
};

TypeScript 绑定

function MutiCast1(i) {
    console.warn("MutiCast1<<<", i);
}

function MutiCast2(i) {
    console.warn("MutiCast2>>>", i);
}

actor.NotifyWithInt.Add(MutiCast1)
actor.NotifyWithInt.Add(MutiCast2)

actor.NotifyWithRefString.Bind((strRef) => {
    console.log("NotifyWithRefString"$unref(strRef));
    $set(strRef, "out to NotifyWithRefString");//引用参数输出
});

actor.NotifyWithStringRet.Bind((inStr) => {
    return "////" + inStr;
});

C++触发

NotifyWithInt.Broadcast(0);
NotifyWithStringRet.ExecuteIfBound("hi...");
if (NotifyWithRefString.IsBound())
{
    FString Str = TEXT("hello john che ");

    NotifyWithRefString.Execute(Str);
    UE_LOG(LogTemp, Warning, TEXT("NotifyWithRefString out ? %s"), *Str);
}

传送门

开源协议:BSD 3-Clause

开源地址:https://github.com/Tencent/puerts


-END-


原文始发于微信公众号(开源技术专栏):Unity/UE 下的 TypeScript 编程解决方案

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/166686.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!