前一篇介绍了duktape-unity的大概情况,这次来点具体的,以很少的代码来实现一个简易的小游戏例子。代码在这里ship-example。
rec.gif
因为这个例子中脚本会用到的C#和Unity类并不多,因此采用了手工指定导出类的方式:
// Assets\Source\Editor\JSClassBinding.cs
public class JSClassBinding : AbstractBindingProcess
{
public override void OnPreExporting(BindingManager bindingManager)
{
bindingManager.AddExportedType(typeof(UnityEngine.SkinnedMeshRenderer), true);
bindingManager.AddExportedType(typeof(UnityEngine.MeshRenderer), true);
bindingManager.AddExportedType(typeof(UnityEngine.Renderer), true);
bindingManager.AddExportedType(typeof(UnityEngine.MeshFilter), true);
bindingManager.AddExportedType(typeof(UnityEngine.MeshCollider), true);
// ...
// 因为类型导出过程在 Editor 环境下执行, 有一部分特殊的类成员在实际运行环境中并不存在,
// 因此最终打包到目标二进制时会报错, 这里可以单独剔除这些特殊的成员
bindingManager.AddExportedType(typeof(UnityEngine.MonoBehaviour), true)
.SetMemberBlocked("runInEditMode");
// ...
}
执行菜单 Duktape -> Generate Bindings 生成所有绑定代码,之后就可以在脚本中访问所有导出的类及类成员。
打开场景 Assets/Scenes/launcher.unity,场景中主要放置了一个简单的星空背景,以及用于在游戏中显示提示信息的 Text Mesh,脚本入口代码在 Laucher.cs 进行了基本的初始化,就不赘述了。接下来的工作就全部在 typescript 脚本中完成了,首先是入口:
(function () {
let go = new UnityEngine.GameObject("_jsgo");
let bridge = go.AddComponent(DuktapeJS.Bridge);
bridge.SetBridge(new MyBridge());
})();
这段代码利用 Duktape 预先定义好的 Bridge 类,该类用于将 MonoBehaviour 中基本的几个响应方法重定向到脚本的类实例上。
import { IGame } from "./game/common/game";
let GameDefs = {
shot: "./game/shot/shot_game",
};
export class MyBridge {
private _game: IGame
Awake() {
let proto = require(GameDefs["shot"]);
this._game = new proto.default();
this._game.init(() => {
this._game.restart();
});
}
Update(deltaTime: number) {
this._game.update(deltaTime);
}
OnApplicationQuit() {
console.log("byebye!");
}
}
接下来就是这个例子具体的逻辑代码了,不详细贴出来了,运行过程中每隔一小段时间会从四周边缘向玩家控制的飞船发射一颗子弹,子弹由一个简单的对象池管理,飞船被子弹命中时游戏结束,结算时间。其中资源加载的部分使用了unityfs的接口
// shot_bullet.ts
// 加载一个材质资源
this._solidMatAsset = UnityFS.ResourceManager.LoadAsset("Assets/Data/Materials/solid.mat", UnityEngine.Material);
// 等待材质资源加载完成, 执行回调, 开始实际游戏逻辑
this._solidMatAsset.completed.on((asset: UnityFS.UAsset) => {
// console.log("solid mat loaded", asset.GetObject());
onfinish();
});
// ...
// shot_ship.ts
export class ShotShip {
// ...
init(game: ShotGame) {
this._game = game;
// PrefabLoader 负责载入一个 prefab 资源(可能异步),loader 本身会同步创建一个 GameObject
// 实际 prefab 实例化后将挂载为子节点,因为代码中并不需要与实例内容交互,因为无需关心是否加载完成
this._loader = UnityFS.Utils.PrefabLoader.Load("Assets/Data/Prefabs/ship.prefab");
this._gameObject = this._loader.gameObject;
this._transform = this._gameObject.transform;
this._transform.localScale = new UnityEngine.Vector3(4.5, 4.5, 4.5);
}
}
碰撞判定直接利用了Unity的物理接口 Physics.BoxCast:
// shot_ship.ts
// ...
update(dt: number) {
if (this._alive) {
// ...
if (UnityEngine.Physics.BoxCast(this._transform.localPosition, this._halfExt, UnityEngine.Vector3.forward)) {
this._alive = false;
}
}
}
注: 因为脚本在update过程中使用 Input.GetKey() 进行操作的判断,间接用到了 Enum.ToObject 产生了不必要的GC,之后会在 duktape-unity 中优化这个问题。除此之外,例子中 update 过程不产生多余GC。
以上便实现了开篇图中的效果,还是非常简单的(同时也比较简陋☺)。有兴趣的朋友可以clone了看看,欢迎交流意见~
网友评论