在现代 Web3D 开发中,高效的输入管理是创建流畅交互体验的关键。本文将详细介绍如何在 Babylon.js 中实现一个强大的鼠标输入管理单例,帮助你优雅地处理所有指针事件。
为什么需要鼠标输入管理单例?
在复杂的 3D 场景中,鼠标/指针输入管理面临几个挑战:
事件分散:多个对象需要响应鼠标事件
状态管理:需要跟踪鼠标位置、点击状态等
性能优化:避免重复注册事件监听器
代码组织:集中管理输入逻辑,避免分散在各处
单例模式完美解决了这些问题,它提供了:
全局唯一的访问点
统一的输入事件分发
更好的性能表现
更清晰的代码结构
基础实现
首先让我们看一个基础的 MouseController
实现:
import { Scene, PointerEventTypes, PointerInfo } from "@babylonjs/core";
type PointerEventCallback = (pointerInfo: PointerInfo) => void;
export class MouseController {
private static instance: MouseController | null = null;
private scene: Scene;
private eventCallbacks: Map<PointerEventTypes, PointerEventCallback[]> = new Map();
private constructor(scene: Scene) {
this.scene = scene;
this.initialize();
}
public static initialize(scene: Scene): MouseController {
if (!MouseController.instance) {
MouseController.instance = new MouseController(scene);
}
return MouseController.instance;
}
public static getInstance(): MouseController {
if (!MouseController.instance) {
throw new Error("MouseController not initialized. Call initialize() first.");
}
return MouseController.instance;
}
private initialize(): void {
Object.values(PointerEventTypes).forEach(eventType => {
this.eventCallbacks.set(eventType, []);
});
this.scene.onPointerObservable.add((pointerInfo) => {
const callbacks = this.eventCallbacks.get(pointerInfo.type);
callbacks?.forEach(callback => callback(pointerInfo));
});
}
public on(eventType: PointerEventTypes, callback: PointerEventCallback): void {
this.eventCallbacks.get(eventType)?.push(callback);
}
public off(eventType: PointerEventTypes, callback: PointerEventCallback): void {
const callbacks = this.eventCallbacks.get(eventType);
if (callbacks) {
const index = callbacks.indexOf(callback);
if (index !== -1) callbacks.splice(index, 1);
}
}
public static dispose(): void {
if (MouseController.instance) {
MouseController.instance.eventCallbacks.clear();
MouseController.instance = null;
}
}
}
高级功能扩展
基础版本已经可用,但我们还可以添加更多实用功能:
1. 自定义高级事件
// 在 initialize 方法中添加特殊事件处理
this.scene.onPointerObservable.add((pointerInfo) => {
// ...基础事件处理...
switch(pointerInfo.type) {
case PointerEventTypes.POINTERPICK:
if (pointerInfo.pickInfo?.hit) {
this.emit('meshClicked', {
pointerInfo,
mesh: pointerInfo.pickInfo.pickedMesh
});
}
break;
case PointerEventTypes.POINTERMOVE:
if (pointerInfo.pickInfo?.hit) {
this.emit('meshHover', {
pointerInfo,
mesh: pointerInfo.pickInfo.pickedMesh
});
}
break;
}
});
2. 鼠标状态跟踪
private mouseState = {
position: { x: 0, y: 0 },
isDown: false,
lastClickedMesh: null as AbstractMesh | null
};
// 在事件处理中更新状态
case PointerEventTypes.POINTERDOWN:
this.mouseState.isDown = true;
break;
case PointerEventTypes.POINTERUP:
this.mouseState.isDown = false;
break;
case PointerEventTypes.POINTERMOVE:
this.mouseState.position.x = this.scene.pointerX;
this.mouseState.position.y = this.scene.pointerY;
break;
3. 双击检测
private lastClickTime = 0;
// 在 POINTERPICK 处理中
const now = Date.now();
if (now - this.lastClickTime < 300) { // 300ms 内再次点击视为双击
this.emit('doubleClick', { pointerInfo, mesh: pointerInfo.pickInfo.pickedMesh });
}
this.lastClickTime = now;
使用示例
// 初始化
const mouseController = MouseController.initialize(scene);
// 监听事件
mouseController.on(PointerEventTypes.POINTERPICK, ({ pickInfo }) => {
console.log('点击了:', pickInfo.pickedMesh?.name);
});
mouseController.on('meshHover', ({ mesh }) => {
console.log('鼠标悬停在:', mesh.name);
});
// 获取鼠标状态
const currentPos = mouseController.getMousePosition();
console.log('当前鼠标位置:', currentPos);
性能优化技巧
事件节流:对高频事件如 POINTERMOVE 进行节流处理
import { throttle } from 'lodash-es'; this.scene.onPointerObservable.add(throttle((pointerInfo) => { // 处理逻辑 }, 100));
按需监听:只在需要时添加监听器,及时移除
事件冒泡控制:对于复杂场景,使用
pointerInfo.skipOnPointerObservable
控制事件传播
与 Babylon.js 生态集成
MouseController
可以很好地与其他 Babylon.js 功能配合:
与 ActionManager 结合:优先使用单例处理全局事件,特定对象交互使用 ActionManager
与 GUI 系统集成:检测鼠标是否在 GUI 元素上
与物理引擎配合:基于鼠标点击实现物理交互
测试建议
为确保 MouseController
的可靠性,建议编写以下测试:
单例模式测试 - 确保全局唯一实例
事件分发测试 - 验证所有事件类型正确触发
内存泄漏测试 - 检查 dispose 方法是否彻底清理
性能测试 - 监控高频事件处理的性能影响
总结
本文介绍的 MouseController
单例模式为 Babylon.js 应用提供了:
集中化的输入管理 - 统一处理所有鼠标/指针事件
灵活的事件系统 - 支持原生和自定义事件
状态跟踪能力 - 实时掌握鼠标状态
性能优化基础 - 为复杂交互提供高效处理机制
良好的扩展性 - 方便添加新功能如手势识别等
这种模式特别适合中大型 Babylon.js 项目,能够显著提高代码的可维护性和交互体验的一致性。你可以根据项目需求进一步扩展这个基础实现,比如添加触摸支持或游戏手柄输入集成。