实现 Babylon.js 鼠标输入管理单例 (MouseController) 的最佳实践

在现代 Web3D 开发中,高效的输入管理是创建流畅交互体验的关键。本文将详细介绍如何在 Babylon.js 中实现一个强大的鼠标输入管理单例,帮助你优雅地处理所有指针事件。

为什么需要鼠标输入管理单例?

在复杂的 3D 场景中,鼠标/指针输入管理面临几个挑战:

  1. 事件分散:多个对象需要响应鼠标事件

  2. 状态管理:需要跟踪鼠标位置、点击状态等

  3. 性能优化:避免重复注册事件监听器

  4. 代码组织:集中管理输入逻辑,避免分散在各处

单例模式完美解决了这些问题,它提供了:

  • 全局唯一的访问点

  • 统一的输入事件分发

  • 更好的性能表现

  • 更清晰的代码结构

基础实现

首先让我们看一个基础的 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);

性能优化技巧

  1. 事件节流:对高频事件如 POINTERMOVE 进行节流处理

    import { throttle } from 'lodash-es';this.scene.onPointerObservable.add(throttle((pointerInfo) => {// 处理逻辑
    }, 100));

  2. 按需监听:只在需要时添加监听器,及时移除

  3. 事件冒泡控制:对于复杂场景,使用 pointerInfo.skipOnPointerObservable 控制事件传播

与 Babylon.js 生态集成

MouseController 可以很好地与其他 Babylon.js 功能配合:

  1. 与 ActionManager 结合:优先使用单例处理全局事件,特定对象交互使用 ActionManager

  2. 与 GUI 系统集成:检测鼠标是否在 GUI 元素上

  3. 与物理引擎配合:基于鼠标点击实现物理交互

测试建议

为确保 MouseController 的可靠性,建议编写以下测试:

  1. 单例模式测试 - 确保全局唯一实例

  2. 事件分发测试 - 验证所有事件类型正确触发

  3. 内存泄漏测试 - 检查 dispose 方法是否彻底清理

  4. 性能测试 - 监控高频事件处理的性能影响

总结

本文介绍的 MouseController 单例模式为 Babylon.js 应用提供了:

  1. 集中化的输入管理 - 统一处理所有鼠标/指针事件

  2. 灵活的事件系统 - 支持原生和自定义事件

  3. 状态跟踪能力 - 实时掌握鼠标状态

  4. 性能优化基础 - 为复杂交互提供高效处理机制

  5. 良好的扩展性 - 方便添加新功能如手势识别等

这种模式特别适合中大型 Babylon.js 项目,能够显著提高代码的可维护性和交互体验的一致性。你可以根据项目需求进一步扩展这个基础实现,比如添加触摸支持或游戏手柄输入集成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/78742.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【LLM+Code】Cursor Agent 46.11 版本PromptTools最细致解读

一、cursor Agent cursor的agent模式, 多说一句&#xff0c;cursor目前我付费使用&#xff0c;是我目前为止使用过AI coding工具里最喜欢的一个&#xff0c;cursor nb&#xff01; https://gist.github.com/sshh12/25ad2e40529b269a88b80e7cf1c38084version&#xff1a;46.11 …

Flask + ajax上传文件(二)--多文件上传

Flask多文件上传完整教程 本教程将详细介绍如何使用Flask实现多文件上传功能,并使用时间戳为上传文件自动命名,避免文件名冲突。 一、环境准备 确保已安装Python和Flask pip install flask项目结构 flask_upload/ ├── app.py ├── upload/ # 上传文…

多级缓存入门:Caffeine、Lua、OpenResty、Canal

之前写过——Google Guava Cache简介 本文系统学习一下多级缓存 目录 0.什么是多级缓存商品查询业务案例导入1.JVM进程缓存初识Caffeine实现JVM进程缓存2.Lua语法入门HelloWorld数据类型、变量和循环函数、条件控制3.Nginx业务编码实现多级缓存安装OpenRestyOpenResty快速入门…

Python + Playwright:如何在Docker 容器运行测试?

Python + Playwright:如何在Docker 容器运行测试? 前言一、简介二、环境准备1. 安装 DockerWindows 用户macOS 用户Linux 用户(以 Ubuntu 为例)2. 启动 browserless 服务拉取 browserless 镜像启动 browserless 容器验证 browserless 是否启动成功三、创建自动化测试项目1.…

语音合成之四大语言模型(LLM)与TTS的深度融合

基于LLM的语音合成 1.技术架构1.1 LlaSA1.2 CosyVoice (和 CosyVoice2)1.3 SparkTTS 2 特性对比2.1 零样本语音克隆2.2 多语种支持2.3 可控语音生成2.4 计算效率和模型大小 总结 当前&#xff0c;在大型语言模型&#xff08;Large Language Models&#xff0c;LLMs&#xff09;…

使用 Conda 创建新环境

使用 Conda 创建新环境 在使用 Conda 进行包管理和环境隔离时&#xff0c;创建新环境是一个非常常见的操作。通过创建独立的环境&#xff0c;可以避免不同项目之间的依赖冲突&#xff0c;并且能够灵活地管理各个项目的运行环境。 以下是使用 Conda 创建和管理新环境的详细步骤…

Unity AssetBundle (AB) 打包详解

AssetBundle 是 Unity 提供的一种资源打包机制&#xff0c;允许开发者将游戏资源&#xff08;如模型、纹理、预制体等&#xff09;打包成独立的文件&#xff0c;便于动态加载和热更新。 一、AssetBundle 基础概念 1. 什么是 AssetBundle 资源压缩包&#xff0c;包含序列化资源…

Python flask入门

Python flask入门 一、路由1.1 常规路由1.2 动态路由1.3 路由的其他高级用法 二、变量规则2.1 示例1&#xff1a;字符串类型&#xff08;默认&#xff09;2.2 示例2&#xff1a;整数类型2.3 示例3&#xff1a;路径类型 三、自定义转换器3.1 核心组件详解3.2 工作流程详解 四、f…

AI赋能守护行车安全新防线,基于YOLOv5全系列【n/s/m/l/x】参数模型开发构建驾驶车辆场景下驾驶员疲劳分心驾驶行为智能检测预警系统

在当今社会&#xff0c;随着科技生产力的飞速发展&#xff0c;汽车早已成为人们日常出行不可或缺的交通工具。它不仅极大地提高了人们的出行效率&#xff0c;也为生活带来了诸多便利。然而&#xff0c;随着汽车保有量的不断增加&#xff0c;交通安全问题也日益凸显。疲劳驾驶和…

onloyoffice历史版本功能实现,版本恢复功能,编辑器功能实现 springboot+vue2

文章目录 onloyoffice历史版本功能实现&#xff0c;版本恢复功能&#xff0c;编辑器功能实现 springbootvue2前提 需要注意把这个 (改成自己服务器的ip或者域名) 改成 自己服务器的域名或者地址我使用的onloyoffice版本 8.1.3.41. onloyoffice服务器部署 搜索其他文章2. 前段代…

概率论与统计(不确定性分析)主要应用在什么方面?涉及到具体知识是什么?

用户问的是概率论与统计&#xff08;不确定性分析&#xff09;的主要应用方面&#xff0c;涉及的具体知识以及具体公式。首先&#xff0c;我需要确定概率论与统计在哪些领域有应用&#xff0c;比如工程、金融、医学、数据科学等等。然后&#xff0c;具体知识部分应该包括概率论…

如何利用快照与备份快速恢复服务器的数据

在服务器上利用**快照&#xff08;Snapshot&#xff09;**和**备份&#xff08;Backup&#xff09;**快速恢复数据&#xff0c;可显著减少停机时间并确保业务连续性。以下是具体操作步骤和最佳实践&#xff1a; --- ### **1. 快照&#xff08;Snapshot&#xff09;恢复** **适…

安卓APP开发项目源码

在移动互联网蓬勃发展的今天&#xff0c;安卓应用几乎覆盖了人们生活的方方面面。从社交、购物&#xff0c;到医疗、教育&#xff0c;APP 的需求呈指数级增长。然而&#xff0c;如何高效、低成本地开发一款质量可靠的安卓应用&#xff0c;仍是很多开发者和团队关注的核心问题。…

遨游三防|30200mAh、双露营灯三防平板,见证堆料天花板

在工业4.0与智能化转型的浪潮中&#xff0c;专业设备对性能、防护及场景适应性的要求日益严苛。遨游通讯作为国家级高新技术企业&#xff0c;依托“危、急、特”场景的深耕经验&#xff0c;推出的旗舰级产品AORO-P300三防平板&#xff0c;以30200mAh超大容量电池、双露营灯设计…

【Python】Matplotlib:立体永生花绘制

本文代码部分实现参考自CSDN博客&#xff1a;https://blog.csdn.net/ak_bingbing/article/details/135852038 一、引言 Matplotlib作为Python生态中最著名的可视化库&#xff0c;其三维绘图功能可以创造出令人惊叹的数学艺术。本文将通过一个独特的参数方程&#xff0c;结合极…

OpenCV 图形API(57)颜色空间转换-----将图像从 RGB 色彩空间转换为 YUV 色彩空间函数RGB2YUV()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 将图像从 RGB 色彩空间转换为 YUV 色彩空间。 该函数将输入图像从 RGB 色彩空间转换为 YUV。R、G 和 B 通道值的常规范围是 0 到 255。 在进行线…

Kubernetes(K8S)入门阶段详细指南

Kubernetes(K8S)入门阶段详细指南 一、容器技术基础:Docker核心操作与理解 1.1 Docker核心操作 镜像管理: 拉取镜像:docker pull ubuntu(以Ubuntu为例)查看本地镜像:docker images删除镜像:docker rmi <image_id>容器生命周期管理: 启动容器:docker run -d -…

AI大模型学习十一:‌尝鲜ubuntu 25.04 桌面版私有化sealos cloud + devbox+minio,实战运行成功

一、说明 没意思&#xff0c;devbox私有化不支持&#xff0c;看来这个开源意义不大&#xff0c;和宣传差距很大啊&#xff0c;那devbox就不用玩 用了ubuntu 25.04&#xff0c;内核为GNU/Linux 6.14.0-15-generic x86_64&#xff0c;升级了部分image&#xff0c;过程曲折啊 se…

[GXYCTF2019]Ping Ping Ping

解题步骤 1、先使用 内敛执行 查看当前的php文件 执行 命令执行 发现空格被过滤 ?ip127.0.0.1$IFS|$IFSwhomi 还有一个点就是这个 执行的命令是不能进行拼接的 可能就是被过滤了 | 所以我们使用 ; 进行绕过一下 空格过滤代替 $IFS ${IFS} ${IFS}$9 //这里$1到$9都可以 $IFS$1…

重温TCP通信过程

文章目录 1. 慢启动2. 拥塞避免 3. 快速重传和快速恢复 初识tcp报文 我们先来简单认识一下报文的格式,具体理解需要后面详细介绍 源端口和目的端口:顾名思义就是标识传输双方的信息首部长度:指的是TCP报头的长度,换句话来说,我们需要用一个属性来描述报头的长度,就说明TCP的报…