一、为什么 Behavior 模式在 Babylon 里这么香?
Babylon.js 把"逻辑"和"节点"拆成两条线:
-
节点(Mesh/Camera/TransformNode) 负责"我在哪儿、我长啥样";
-
行为(Behavior) 负责"我要干啥、我咋交互"。
这样做的好处一目了然:
-
同一段逻辑可以像乐高一样插到任意节点上;
-
序列化/反序列化(*.babylon / GLTF / 场景缓存)时,引擎只认节点,行为可以延迟挂载,避免"在没 Scene 的地方 new Mesh"这种致命错误;
-
官方已经内置了 20+ 常用行为(SixDofDragBehavior、PointerDragBehavior、SurfaceMagnetismBehavior...),学会套路后,你可以无缝对接到同一套生态里。
二、官方接口只有 3 个方法,但"潜规则"有 4 步
TypeScript
interface Behavior<T> {
init(): void; // 引擎在反序列化后调一次,一般空着
attach(target: T): void; // 真正拿到节点和场景,开始创建资源
detach(): void; // 清理资源,防止内存泄漏
}
为了让 TS 不报错、让运行时不会拿到 undefined,社区总结出了"四步模板":
| 阶段 | 目的 | 推荐做法 |
|---|---|---|
| ① 声明 | 先占坑,不 new | 属性后加 !,告诉编译器"我稍后一定赋值" |
| ② attach | 拿节点 & 场景 | target.getScene() 保存到私有字段 |
| ③ 创建 | 统一 new 资源 | 抽一个 _createDependentObjects(),所有 Mesh/Material/Texture 在这里一次搞定 |
| ④ detach | dispose & 置空 | 先 xxx.dispose(),再把引用设为 null!,防止重复 detach 报错 |
三、最小可运行骨架(复制即可用)
TypeScript
import { Behavior, TransformNode, Scene, Mesh, MeshBuilder } from '@babylonjs/core';
export class CustomBehavior implements Behavior<TransformNode> {
// 1️⃣ 占位不初始化
private _scene!: Scene;
private _target!: TransformNode;
private _helper!: Mesh;
name = 'custom';
init(): void { /* 空着即可 */ }
// 2️⃣ 真正拿到节点和场景
attach(target: TransformNode): void {
this._target = target;
this._scene = target.getScene();
this._createDependentObjects();
}
// 3️⃣ 集中创建所有资源
private _createDependentObjects(): void {
this._helper = MeshBuilder.CreateBox('helper', { size: 0.2 }, this._scene);
this._helper.parent = this._target;
this._helper.isPickable = false; // 不参与拾取,避免误触发
}
// 4️⃣ 清理 + 置空
detach(): void {
this._helper?.dispose();
this._helper = null!; // 防止重复 detach
this._target = null!;
this._scene = null!;
}
}
使用端只要两行:
TypeScript
const node = scene.getTransformNodeByName('MyNode')!;
node.addBehavior(new CustomBehavior());
四、容易踩的 5 个坑
-
忘了
!编译就崩TS 严格模式下会报"属性没有初始化器",加
!是最低成本方案;如果你实在不想用!,可以把字段设成undefined | T,但后面每次用都要if判断,更啰嗦。 -
在构造函数里
new Mesh此时还没 attach,取不到 Scene,运行时会直接抛"No scene defined"。
-
detach 不 dispose
行为会被反复添加/移除,不 dispose 就等于内存泄漏;
官方行为全部会在 detach 里把自身创建的资源清干净,照抄即可。
-
把节点本身 dispose 了,却忘了 detach
Babylon 不会自动帮你调
detach,所以如果你在外部mesh.dispose(),一定先removeBehavior或者自己调一遍detach,否则内部引用还在,GC 不掉。 -
同名行为重复添加
默认允许重复,你可以通过
name字段做唯一标识,然后在attach里判断target.getBehaviorByName已存在就提前返回。
五、进阶:让行为可配置、可序列化
TypeScript
interface ICustomBehaviorOptions {
size?: number;
color?: Color3;
}
export class CustomBehavior implements Behavior<TransformNode> {
public name = 'custom';
constructor(private options: ICustomBehaviorOptions = {}) {}
private _createDependentObjects(): void {
const size = this.options.size ?? 0.2;
this._helper = MeshBuilder.CreateBox('helper', { size }, this._scene);
const mat = new StandardMaterial('helperMat', this._scene);
mat.diffuseColor = this.options.color ?? Color3.White();
this._helper.material = mat;
}
}
这样既能通过代码 new CustomBehavior({ size: 0.5, color: Color3.Red() }) 注入,也能在编辑器里把配置序列化到 *.babylon 文件,下次打开场景自动还原。
六、一行总结
把"声明→attach 才拿 Scene→延迟创建→detach 清理"这四步写成肌肉记忆,
你就拥有了和 Babylon 官方行为同一水准的可复用、无泄漏、易维护的自定义 Behavior。