PuerTS 是一个在 Unity/Unreal 里用 Typescript 编写逻辑的技术。其执行性能,开发便捷度,与宿主平台的融合度都远强于 Lua。
特性
-
JavaScript 生态有众多的库和工具链,结合专业商业引擎的渲染能力,快速打造游戏 -
相比游戏领域常用的 lua 脚本,TypeScript 的静态类型检查有助于编写更健壮,可维护性更好的程序 -
高效:全引擎,全平台支持反射调用,无需额外步骤即可与宿主 C++/C# 通信 -
高性能:全引擎,全平台支持生成静态调用桥梁,兼顾了高性能的场景 -
WebGL 平台下的天生优势:相比 Lua 脚本在 WebGL 版本的表现,PuerTS 在性能和效率上都有极大提升,目前极限情况甚至比 C# 更快
可用引擎
-
unreal engine 4.22 ~ latest -
unity 5 ~ latest -
任意.net 环境
可用平台
-
iOS -
Android -
Windows -
Macos
Unity 使用 PuerTS 示例
在 Unity 里准备好一个场景及一个 MonoBehaviour 组件,在 MonoBehaviour 里编写如下代码:
//1. Hello World
void Start() {
Puerts.JsEnv env = new Puerts.JsEnv();
env.Eval(@"
console.log('hello world');
")
}
执行后,你能看见 Unity 控制台中打印出了Hello world
。成功了!这就意味着,我们在 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