Files
esengine/packages/platform-web/src/subsystems/WebInputSubsystem.ts

337 lines
11 KiB
TypeScript
Raw Normal View History

/**
* Web
feat(engine-core): 添加统一输入系统 (#282) * perf(core): 优化 EntitySystem 迭代性能,添加 CommandBuffer 延迟命令 ReactiveQuery 快照优化: - 添加快照机制,避免每帧拷贝数组 - 只在实体列表变化时创建新快照 - 静态场景下多个系统共享同一快照 CommandBuffer 延迟命令系统: - 支持延迟添加/移除组件、销毁实体、设置实体激活状态 - 每个系统拥有独立的 commands 属性 - 命令在帧末统一执行,避免迭代过程中修改实体列表 Scene 更新: - 在 lateUpdate 后自动刷新所有系统的命令缓冲区 文档: - 更新系统文档,添加 CommandBuffer 使用说明 * fix(ci): upgrade first-interaction action to v1.3.0 Fix Docker build failure in welcome workflow. * fix(ci): upgrade pnpm/action-setup to v4 and fix unused import - Upgrade pnpm/action-setup@v2 to v4 in all workflow files - Remove unused CommandType import in CommandBuffer.test.ts * fix(ci): remove duplicate pnpm version specification * feat(engine-core): 添加统一输入系统 添加完整的输入系统,支持平台抽象: - IPlatformInputSubsystem: 扩展接口支持键盘/鼠标/滚轮事件 - WebInputSubsystem: 浏览器实现,支持事件绑定/解绑 - InputManager: 全局输入状态管理器(键盘、鼠标、触摸) - InputSystem: ECS 系统,连接平台事件到 InputManager - GameRuntime 集成: 自动创建 InputSystem 并绑定平台子系统 使用方式: ```typescript import { Input, MouseButton } from '@esengine/engine-core'; if (Input.isKeyDown('KeyW')) { /* 移动 */ } if (Input.isKeyJustPressed('Space')) { /* 跳跃 */ } if (Input.isMouseButtonDown(MouseButton.Left)) { /* 射击 */ } ``` * fix(runtime-core): 添加缺失的 platform-common 依赖 * fix(runtime-core): 移除 platform-web 依赖避免循环依赖 * fix(runtime-core): 使用工厂函数注入 InputSubsystem 避免循环依赖 - BrowserPlatformAdapter 通过 inputSubsystemFactory 配置接收输入子系统 - 在 IPlatformInputSubsystem 接口添加可选的 dispose 方法 - 移除对 @esengine/platform-web 的直接依赖
2025-12-05 18:15:50 +08:00
* Web platform input subsystem
*/
import type {
IPlatformInputSubsystem,
TouchHandler,
feat(engine-core): 添加统一输入系统 (#282) * perf(core): 优化 EntitySystem 迭代性能,添加 CommandBuffer 延迟命令 ReactiveQuery 快照优化: - 添加快照机制,避免每帧拷贝数组 - 只在实体列表变化时创建新快照 - 静态场景下多个系统共享同一快照 CommandBuffer 延迟命令系统: - 支持延迟添加/移除组件、销毁实体、设置实体激活状态 - 每个系统拥有独立的 commands 属性 - 命令在帧末统一执行,避免迭代过程中修改实体列表 Scene 更新: - 在 lateUpdate 后自动刷新所有系统的命令缓冲区 文档: - 更新系统文档,添加 CommandBuffer 使用说明 * fix(ci): upgrade first-interaction action to v1.3.0 Fix Docker build failure in welcome workflow. * fix(ci): upgrade pnpm/action-setup to v4 and fix unused import - Upgrade pnpm/action-setup@v2 to v4 in all workflow files - Remove unused CommandType import in CommandBuffer.test.ts * fix(ci): remove duplicate pnpm version specification * feat(engine-core): 添加统一输入系统 添加完整的输入系统,支持平台抽象: - IPlatformInputSubsystem: 扩展接口支持键盘/鼠标/滚轮事件 - WebInputSubsystem: 浏览器实现,支持事件绑定/解绑 - InputManager: 全局输入状态管理器(键盘、鼠标、触摸) - InputSystem: ECS 系统,连接平台事件到 InputManager - GameRuntime 集成: 自动创建 InputSystem 并绑定平台子系统 使用方式: ```typescript import { Input, MouseButton } from '@esengine/engine-core'; if (Input.isKeyDown('KeyW')) { /* 移动 */ } if (Input.isKeyJustPressed('Space')) { /* 跳跃 */ } if (Input.isMouseButtonDown(MouseButton.Left)) { /* 射击 */ } ``` * fix(runtime-core): 添加缺失的 platform-common 依赖 * fix(runtime-core): 移除 platform-web 依赖避免循环依赖 * fix(runtime-core): 使用工厂函数注入 InputSubsystem 避免循环依赖 - BrowserPlatformAdapter 通过 inputSubsystemFactory 配置接收输入子系统 - 在 IPlatformInputSubsystem 接口添加可选的 dispose 方法 - 移除对 @esengine/platform-web 的直接依赖
2025-12-05 18:15:50 +08:00
TouchEvent,
KeyboardHandler,
KeyboardEventInfo,
MouseHandler,
MouseEventInfo,
WheelHandler,
WheelEventInfo
} from '@esengine/platform-common';
feat(engine-core): 添加统一输入系统 (#282) * perf(core): 优化 EntitySystem 迭代性能,添加 CommandBuffer 延迟命令 ReactiveQuery 快照优化: - 添加快照机制,避免每帧拷贝数组 - 只在实体列表变化时创建新快照 - 静态场景下多个系统共享同一快照 CommandBuffer 延迟命令系统: - 支持延迟添加/移除组件、销毁实体、设置实体激活状态 - 每个系统拥有独立的 commands 属性 - 命令在帧末统一执行,避免迭代过程中修改实体列表 Scene 更新: - 在 lateUpdate 后自动刷新所有系统的命令缓冲区 文档: - 更新系统文档,添加 CommandBuffer 使用说明 * fix(ci): upgrade first-interaction action to v1.3.0 Fix Docker build failure in welcome workflow. * fix(ci): upgrade pnpm/action-setup to v4 and fix unused import - Upgrade pnpm/action-setup@v2 to v4 in all workflow files - Remove unused CommandType import in CommandBuffer.test.ts * fix(ci): remove duplicate pnpm version specification * feat(engine-core): 添加统一输入系统 添加完整的输入系统,支持平台抽象: - IPlatformInputSubsystem: 扩展接口支持键盘/鼠标/滚轮事件 - WebInputSubsystem: 浏览器实现,支持事件绑定/解绑 - InputManager: 全局输入状态管理器(键盘、鼠标、触摸) - InputSystem: ECS 系统,连接平台事件到 InputManager - GameRuntime 集成: 自动创建 InputSystem 并绑定平台子系统 使用方式: ```typescript import { Input, MouseButton } from '@esengine/engine-core'; if (Input.isKeyDown('KeyW')) { /* 移动 */ } if (Input.isKeyJustPressed('Space')) { /* 跳跃 */ } if (Input.isMouseButtonDown(MouseButton.Left)) { /* 射击 */ } ``` * fix(runtime-core): 添加缺失的 platform-common 依赖 * fix(runtime-core): 移除 platform-web 依赖避免循环依赖 * fix(runtime-core): 使用工厂函数注入 InputSubsystem 避免循环依赖 - BrowserPlatformAdapter 通过 inputSubsystemFactory 配置接收输入子系统 - 在 IPlatformInputSubsystem 接口添加可选的 dispose 方法 - 移除对 @esengine/platform-web 的直接依赖
2025-12-05 18:15:50 +08:00
import { MouseButton } from '@esengine/platform-common';
/**
* Web
feat(engine-core): 添加统一输入系统 (#282) * perf(core): 优化 EntitySystem 迭代性能,添加 CommandBuffer 延迟命令 ReactiveQuery 快照优化: - 添加快照机制,避免每帧拷贝数组 - 只在实体列表变化时创建新快照 - 静态场景下多个系统共享同一快照 CommandBuffer 延迟命令系统: - 支持延迟添加/移除组件、销毁实体、设置实体激活状态 - 每个系统拥有独立的 commands 属性 - 命令在帧末统一执行,避免迭代过程中修改实体列表 Scene 更新: - 在 lateUpdate 后自动刷新所有系统的命令缓冲区 文档: - 更新系统文档,添加 CommandBuffer 使用说明 * fix(ci): upgrade first-interaction action to v1.3.0 Fix Docker build failure in welcome workflow. * fix(ci): upgrade pnpm/action-setup to v4 and fix unused import - Upgrade pnpm/action-setup@v2 to v4 in all workflow files - Remove unused CommandType import in CommandBuffer.test.ts * fix(ci): remove duplicate pnpm version specification * feat(engine-core): 添加统一输入系统 添加完整的输入系统,支持平台抽象: - IPlatformInputSubsystem: 扩展接口支持键盘/鼠标/滚轮事件 - WebInputSubsystem: 浏览器实现,支持事件绑定/解绑 - InputManager: 全局输入状态管理器(键盘、鼠标、触摸) - InputSystem: ECS 系统,连接平台事件到 InputManager - GameRuntime 集成: 自动创建 InputSystem 并绑定平台子系统 使用方式: ```typescript import { Input, MouseButton } from '@esengine/engine-core'; if (Input.isKeyDown('KeyW')) { /* 移动 */ } if (Input.isKeyJustPressed('Space')) { /* 跳跃 */ } if (Input.isMouseButtonDown(MouseButton.Left)) { /* 射击 */ } ``` * fix(runtime-core): 添加缺失的 platform-common 依赖 * fix(runtime-core): 移除 platform-web 依赖避免循环依赖 * fix(runtime-core): 使用工厂函数注入 InputSubsystem 避免循环依赖 - BrowserPlatformAdapter 通过 inputSubsystemFactory 配置接收输入子系统 - 在 IPlatformInputSubsystem 接口添加可选的 dispose 方法 - 移除对 @esengine/platform-web 的直接依赖
2025-12-05 18:15:50 +08:00
* Web platform input subsystem implementation
*/
export class WebInputSubsystem implements IPlatformInputSubsystem {
feat(engine-core): 添加统一输入系统 (#282) * perf(core): 优化 EntitySystem 迭代性能,添加 CommandBuffer 延迟命令 ReactiveQuery 快照优化: - 添加快照机制,避免每帧拷贝数组 - 只在实体列表变化时创建新快照 - 静态场景下多个系统共享同一快照 CommandBuffer 延迟命令系统: - 支持延迟添加/移除组件、销毁实体、设置实体激活状态 - 每个系统拥有独立的 commands 属性 - 命令在帧末统一执行,避免迭代过程中修改实体列表 Scene 更新: - 在 lateUpdate 后自动刷新所有系统的命令缓冲区 文档: - 更新系统文档,添加 CommandBuffer 使用说明 * fix(ci): upgrade first-interaction action to v1.3.0 Fix Docker build failure in welcome workflow. * fix(ci): upgrade pnpm/action-setup to v4 and fix unused import - Upgrade pnpm/action-setup@v2 to v4 in all workflow files - Remove unused CommandType import in CommandBuffer.test.ts * fix(ci): remove duplicate pnpm version specification * feat(engine-core): 添加统一输入系统 添加完整的输入系统,支持平台抽象: - IPlatformInputSubsystem: 扩展接口支持键盘/鼠标/滚轮事件 - WebInputSubsystem: 浏览器实现,支持事件绑定/解绑 - InputManager: 全局输入状态管理器(键盘、鼠标、触摸) - InputSystem: ECS 系统,连接平台事件到 InputManager - GameRuntime 集成: 自动创建 InputSystem 并绑定平台子系统 使用方式: ```typescript import { Input, MouseButton } from '@esengine/engine-core'; if (Input.isKeyDown('KeyW')) { /* 移动 */ } if (Input.isKeyJustPressed('Space')) { /* 跳跃 */ } if (Input.isMouseButtonDown(MouseButton.Left)) { /* 射击 */ } ``` * fix(runtime-core): 添加缺失的 platform-common 依赖 * fix(runtime-core): 移除 platform-web 依赖避免循环依赖 * fix(runtime-core): 使用工厂函数注入 InputSubsystem 避免循环依赖 - BrowserPlatformAdapter 通过 inputSubsystemFactory 配置接收输入子系统 - 在 IPlatformInputSubsystem 接口添加可选的 dispose 方法 - 移除对 @esengine/platform-web 的直接依赖
2025-12-05 18:15:50 +08:00
// ========== Touch handlers ==========
private _touchStartHandlers: Map<TouchHandler, (e: globalThis.TouchEvent) => void> = new Map();
private _touchMoveHandlers: Map<TouchHandler, (e: globalThis.TouchEvent) => void> = new Map();
private _touchEndHandlers: Map<TouchHandler, (e: globalThis.TouchEvent) => void> = new Map();
private _touchCancelHandlers: Map<TouchHandler, (e: globalThis.TouchEvent) => void> = new Map();
feat(engine-core): 添加统一输入系统 (#282) * perf(core): 优化 EntitySystem 迭代性能,添加 CommandBuffer 延迟命令 ReactiveQuery 快照优化: - 添加快照机制,避免每帧拷贝数组 - 只在实体列表变化时创建新快照 - 静态场景下多个系统共享同一快照 CommandBuffer 延迟命令系统: - 支持延迟添加/移除组件、销毁实体、设置实体激活状态 - 每个系统拥有独立的 commands 属性 - 命令在帧末统一执行,避免迭代过程中修改实体列表 Scene 更新: - 在 lateUpdate 后自动刷新所有系统的命令缓冲区 文档: - 更新系统文档,添加 CommandBuffer 使用说明 * fix(ci): upgrade first-interaction action to v1.3.0 Fix Docker build failure in welcome workflow. * fix(ci): upgrade pnpm/action-setup to v4 and fix unused import - Upgrade pnpm/action-setup@v2 to v4 in all workflow files - Remove unused CommandType import in CommandBuffer.test.ts * fix(ci): remove duplicate pnpm version specification * feat(engine-core): 添加统一输入系统 添加完整的输入系统,支持平台抽象: - IPlatformInputSubsystem: 扩展接口支持键盘/鼠标/滚轮事件 - WebInputSubsystem: 浏览器实现,支持事件绑定/解绑 - InputManager: 全局输入状态管理器(键盘、鼠标、触摸) - InputSystem: ECS 系统,连接平台事件到 InputManager - GameRuntime 集成: 自动创建 InputSystem 并绑定平台子系统 使用方式: ```typescript import { Input, MouseButton } from '@esengine/engine-core'; if (Input.isKeyDown('KeyW')) { /* 移动 */ } if (Input.isKeyJustPressed('Space')) { /* 跳跃 */ } if (Input.isMouseButtonDown(MouseButton.Left)) { /* 射击 */ } ``` * fix(runtime-core): 添加缺失的 platform-common 依赖 * fix(runtime-core): 移除 platform-web 依赖避免循环依赖 * fix(runtime-core): 使用工厂函数注入 InputSubsystem 避免循环依赖 - BrowserPlatformAdapter 通过 inputSubsystemFactory 配置接收输入子系统 - 在 IPlatformInputSubsystem 接口添加可选的 dispose 方法 - 移除对 @esengine/platform-web 的直接依赖
2025-12-05 18:15:50 +08:00
// ========== Keyboard handlers ==========
private _keyDownHandlers: Map<KeyboardHandler, (e: globalThis.KeyboardEvent) => void> = new Map();
private _keyUpHandlers: Map<KeyboardHandler, (e: globalThis.KeyboardEvent) => void> = new Map();
// ========== Mouse handlers ==========
private _mouseMoveHandlers: Map<MouseHandler, (e: globalThis.MouseEvent) => void> = new Map();
private _mouseDownHandlers: Map<MouseHandler, (e: globalThis.MouseEvent) => void> = new Map();
private _mouseUpHandlers: Map<MouseHandler, (e: globalThis.MouseEvent) => void> = new Map();
private _wheelHandlers: Map<WheelHandler, (e: globalThis.WheelEvent) => void> = new Map();
// ========== Touch events ==========
onTouchStart(handler: TouchHandler): void {
const nativeHandler = (e: globalThis.TouchEvent) => {
handler(this.convertTouchEvent(e));
};
this._touchStartHandlers.set(handler, nativeHandler);
window.addEventListener('touchstart', nativeHandler);
}
onTouchMove(handler: TouchHandler): void {
const nativeHandler = (e: globalThis.TouchEvent) => {
handler(this.convertTouchEvent(e));
};
this._touchMoveHandlers.set(handler, nativeHandler);
window.addEventListener('touchmove', nativeHandler);
}
onTouchEnd(handler: TouchHandler): void {
const nativeHandler = (e: globalThis.TouchEvent) => {
handler(this.convertTouchEvent(e));
};
this._touchEndHandlers.set(handler, nativeHandler);
window.addEventListener('touchend', nativeHandler);
}
onTouchCancel(handler: TouchHandler): void {
const nativeHandler = (e: globalThis.TouchEvent) => {
handler(this.convertTouchEvent(e));
};
this._touchCancelHandlers.set(handler, nativeHandler);
window.addEventListener('touchcancel', nativeHandler);
}
offTouchStart(handler: TouchHandler): void {
const nativeHandler = this._touchStartHandlers.get(handler);
if (nativeHandler) {
window.removeEventListener('touchstart', nativeHandler);
this._touchStartHandlers.delete(handler);
}
}
offTouchMove(handler: TouchHandler): void {
const nativeHandler = this._touchMoveHandlers.get(handler);
if (nativeHandler) {
window.removeEventListener('touchmove', nativeHandler);
this._touchMoveHandlers.delete(handler);
}
}
offTouchEnd(handler: TouchHandler): void {
const nativeHandler = this._touchEndHandlers.get(handler);
if (nativeHandler) {
window.removeEventListener('touchend', nativeHandler);
this._touchEndHandlers.delete(handler);
}
}
offTouchCancel(handler: TouchHandler): void {
const nativeHandler = this._touchCancelHandlers.get(handler);
if (nativeHandler) {
window.removeEventListener('touchcancel', nativeHandler);
this._touchCancelHandlers.delete(handler);
}
}
supportsPressure(): boolean {
feat(engine-core): 添加统一输入系统 (#282) * perf(core): 优化 EntitySystem 迭代性能,添加 CommandBuffer 延迟命令 ReactiveQuery 快照优化: - 添加快照机制,避免每帧拷贝数组 - 只在实体列表变化时创建新快照 - 静态场景下多个系统共享同一快照 CommandBuffer 延迟命令系统: - 支持延迟添加/移除组件、销毁实体、设置实体激活状态 - 每个系统拥有独立的 commands 属性 - 命令在帧末统一执行,避免迭代过程中修改实体列表 Scene 更新: - 在 lateUpdate 后自动刷新所有系统的命令缓冲区 文档: - 更新系统文档,添加 CommandBuffer 使用说明 * fix(ci): upgrade first-interaction action to v1.3.0 Fix Docker build failure in welcome workflow. * fix(ci): upgrade pnpm/action-setup to v4 and fix unused import - Upgrade pnpm/action-setup@v2 to v4 in all workflow files - Remove unused CommandType import in CommandBuffer.test.ts * fix(ci): remove duplicate pnpm version specification * feat(engine-core): 添加统一输入系统 添加完整的输入系统,支持平台抽象: - IPlatformInputSubsystem: 扩展接口支持键盘/鼠标/滚轮事件 - WebInputSubsystem: 浏览器实现,支持事件绑定/解绑 - InputManager: 全局输入状态管理器(键盘、鼠标、触摸) - InputSystem: ECS 系统,连接平台事件到 InputManager - GameRuntime 集成: 自动创建 InputSystem 并绑定平台子系统 使用方式: ```typescript import { Input, MouseButton } from '@esengine/engine-core'; if (Input.isKeyDown('KeyW')) { /* 移动 */ } if (Input.isKeyJustPressed('Space')) { /* 跳跃 */ } if (Input.isMouseButtonDown(MouseButton.Left)) { /* 射击 */ } ``` * fix(runtime-core): 添加缺失的 platform-common 依赖 * fix(runtime-core): 移除 platform-web 依赖避免循环依赖 * fix(runtime-core): 使用工厂函数注入 InputSubsystem 避免循环依赖 - BrowserPlatformAdapter 通过 inputSubsystemFactory 配置接收输入子系统 - 在 IPlatformInputSubsystem 接口添加可选的 dispose 方法 - 移除对 @esengine/platform-web 的直接依赖
2025-12-05 18:15:50 +08:00
return typeof Touch !== 'undefined' && 'force' in Touch.prototype;
}
// ========== Keyboard events ==========
onKeyDown(handler: KeyboardHandler): void {
const nativeHandler = (e: globalThis.KeyboardEvent) => {
handler(this.convertKeyboardEvent(e));
};
this._keyDownHandlers.set(handler, nativeHandler);
window.addEventListener('keydown', nativeHandler);
}
onKeyUp(handler: KeyboardHandler): void {
const nativeHandler = (e: globalThis.KeyboardEvent) => {
handler(this.convertKeyboardEvent(e));
};
this._keyUpHandlers.set(handler, nativeHandler);
window.addEventListener('keyup', nativeHandler);
}
feat(engine-core): 添加统一输入系统 (#282) * perf(core): 优化 EntitySystem 迭代性能,添加 CommandBuffer 延迟命令 ReactiveQuery 快照优化: - 添加快照机制,避免每帧拷贝数组 - 只在实体列表变化时创建新快照 - 静态场景下多个系统共享同一快照 CommandBuffer 延迟命令系统: - 支持延迟添加/移除组件、销毁实体、设置实体激活状态 - 每个系统拥有独立的 commands 属性 - 命令在帧末统一执行,避免迭代过程中修改实体列表 Scene 更新: - 在 lateUpdate 后自动刷新所有系统的命令缓冲区 文档: - 更新系统文档,添加 CommandBuffer 使用说明 * fix(ci): upgrade first-interaction action to v1.3.0 Fix Docker build failure in welcome workflow. * fix(ci): upgrade pnpm/action-setup to v4 and fix unused import - Upgrade pnpm/action-setup@v2 to v4 in all workflow files - Remove unused CommandType import in CommandBuffer.test.ts * fix(ci): remove duplicate pnpm version specification * feat(engine-core): 添加统一输入系统 添加完整的输入系统,支持平台抽象: - IPlatformInputSubsystem: 扩展接口支持键盘/鼠标/滚轮事件 - WebInputSubsystem: 浏览器实现,支持事件绑定/解绑 - InputManager: 全局输入状态管理器(键盘、鼠标、触摸) - InputSystem: ECS 系统,连接平台事件到 InputManager - GameRuntime 集成: 自动创建 InputSystem 并绑定平台子系统 使用方式: ```typescript import { Input, MouseButton } from '@esengine/engine-core'; if (Input.isKeyDown('KeyW')) { /* 移动 */ } if (Input.isKeyJustPressed('Space')) { /* 跳跃 */ } if (Input.isMouseButtonDown(MouseButton.Left)) { /* 射击 */ } ``` * fix(runtime-core): 添加缺失的 platform-common 依赖 * fix(runtime-core): 移除 platform-web 依赖避免循环依赖 * fix(runtime-core): 使用工厂函数注入 InputSubsystem 避免循环依赖 - BrowserPlatformAdapter 通过 inputSubsystemFactory 配置接收输入子系统 - 在 IPlatformInputSubsystem 接口添加可选的 dispose 方法 - 移除对 @esengine/platform-web 的直接依赖
2025-12-05 18:15:50 +08:00
offKeyDown(handler: KeyboardHandler): void {
const nativeHandler = this._keyDownHandlers.get(handler);
if (nativeHandler) {
window.removeEventListener('keydown', nativeHandler);
this._keyDownHandlers.delete(handler);
}
}
offKeyUp(handler: KeyboardHandler): void {
const nativeHandler = this._keyUpHandlers.get(handler);
if (nativeHandler) {
window.removeEventListener('keyup', nativeHandler);
this._keyUpHandlers.delete(handler);
}
}
// ========== Mouse events ==========
onMouseMove(handler: MouseHandler): void {
const nativeHandler = (e: globalThis.MouseEvent) => {
handler(this.convertMouseEvent(e));
};
this._mouseMoveHandlers.set(handler, nativeHandler);
window.addEventListener('mousemove', nativeHandler);
}
onMouseDown(handler: MouseHandler): void {
const nativeHandler = (e: globalThis.MouseEvent) => {
handler(this.convertMouseEvent(e));
};
this._mouseDownHandlers.set(handler, nativeHandler);
window.addEventListener('mousedown', nativeHandler);
}
onMouseUp(handler: MouseHandler): void {
const nativeHandler = (e: globalThis.MouseEvent) => {
handler(this.convertMouseEvent(e));
};
this._mouseUpHandlers.set(handler, nativeHandler);
window.addEventListener('mouseup', nativeHandler);
}
onWheel(handler: WheelHandler): void {
const nativeHandler = (e: globalThis.WheelEvent) => {
handler(this.convertWheelEvent(e));
};
this._wheelHandlers.set(handler, nativeHandler);
window.addEventListener('wheel', nativeHandler);
}
offMouseMove(handler: MouseHandler): void {
const nativeHandler = this._mouseMoveHandlers.get(handler);
if (nativeHandler) {
window.removeEventListener('mousemove', nativeHandler);
this._mouseMoveHandlers.delete(handler);
}
}
offMouseDown(handler: MouseHandler): void {
const nativeHandler = this._mouseDownHandlers.get(handler);
if (nativeHandler) {
window.removeEventListener('mousedown', nativeHandler);
this._mouseDownHandlers.delete(handler);
}
}
offMouseUp(handler: MouseHandler): void {
const nativeHandler = this._mouseUpHandlers.get(handler);
if (nativeHandler) {
window.removeEventListener('mouseup', nativeHandler);
this._mouseUpHandlers.delete(handler);
}
}
offWheel(handler: WheelHandler): void {
const nativeHandler = this._wheelHandlers.get(handler);
if (nativeHandler) {
window.removeEventListener('wheel', nativeHandler);
this._wheelHandlers.delete(handler);
}
}
// ========== Capability queries ==========
supportsKeyboard(): boolean {
return true;
}
supportsMouse(): boolean {
// 检测是否有鼠标设备 | Check if mouse device exists
return window.matchMedia('(pointer: fine)').matches;
}
// ========== Event converters ==========
private convertTouchEvent(e: globalThis.TouchEvent): TouchEvent {
const convertTouch = (touch: globalThis.Touch) => ({
identifier: touch.identifier,
x: touch.clientX,
y: touch.clientY,
force: (touch as any).force
});
return {
touches: Array.from(e.touches).map(convertTouch),
changedTouches: Array.from(e.changedTouches).map(convertTouch),
timeStamp: e.timeStamp
};
}
feat(engine-core): 添加统一输入系统 (#282) * perf(core): 优化 EntitySystem 迭代性能,添加 CommandBuffer 延迟命令 ReactiveQuery 快照优化: - 添加快照机制,避免每帧拷贝数组 - 只在实体列表变化时创建新快照 - 静态场景下多个系统共享同一快照 CommandBuffer 延迟命令系统: - 支持延迟添加/移除组件、销毁实体、设置实体激活状态 - 每个系统拥有独立的 commands 属性 - 命令在帧末统一执行,避免迭代过程中修改实体列表 Scene 更新: - 在 lateUpdate 后自动刷新所有系统的命令缓冲区 文档: - 更新系统文档,添加 CommandBuffer 使用说明 * fix(ci): upgrade first-interaction action to v1.3.0 Fix Docker build failure in welcome workflow. * fix(ci): upgrade pnpm/action-setup to v4 and fix unused import - Upgrade pnpm/action-setup@v2 to v4 in all workflow files - Remove unused CommandType import in CommandBuffer.test.ts * fix(ci): remove duplicate pnpm version specification * feat(engine-core): 添加统一输入系统 添加完整的输入系统,支持平台抽象: - IPlatformInputSubsystem: 扩展接口支持键盘/鼠标/滚轮事件 - WebInputSubsystem: 浏览器实现,支持事件绑定/解绑 - InputManager: 全局输入状态管理器(键盘、鼠标、触摸) - InputSystem: ECS 系统,连接平台事件到 InputManager - GameRuntime 集成: 自动创建 InputSystem 并绑定平台子系统 使用方式: ```typescript import { Input, MouseButton } from '@esengine/engine-core'; if (Input.isKeyDown('KeyW')) { /* 移动 */ } if (Input.isKeyJustPressed('Space')) { /* 跳跃 */ } if (Input.isMouseButtonDown(MouseButton.Left)) { /* 射击 */ } ``` * fix(runtime-core): 添加缺失的 platform-common 依赖 * fix(runtime-core): 移除 platform-web 依赖避免循环依赖 * fix(runtime-core): 使用工厂函数注入 InputSubsystem 避免循环依赖 - BrowserPlatformAdapter 通过 inputSubsystemFactory 配置接收输入子系统 - 在 IPlatformInputSubsystem 接口添加可选的 dispose 方法 - 移除对 @esengine/platform-web 的直接依赖
2025-12-05 18:15:50 +08:00
private convertKeyboardEvent(e: globalThis.KeyboardEvent): KeyboardEventInfo {
return {
code: e.code,
key: e.key,
altKey: e.altKey,
ctrlKey: e.ctrlKey,
shiftKey: e.shiftKey,
metaKey: e.metaKey,
repeat: e.repeat,
timeStamp: e.timeStamp
};
}
private convertMouseEvent(e: globalThis.MouseEvent): MouseEventInfo {
return {
x: e.clientX,
y: e.clientY,
movementX: e.movementX,
movementY: e.movementY,
button: e.button as MouseButton,
buttons: e.buttons,
altKey: e.altKey,
ctrlKey: e.ctrlKey,
shiftKey: e.shiftKey,
metaKey: e.metaKey,
timeStamp: e.timeStamp
};
}
private convertWheelEvent(e: globalThis.WheelEvent): WheelEventInfo {
return {
x: e.clientX,
y: e.clientY,
deltaX: e.deltaX,
deltaY: e.deltaY,
deltaZ: e.deltaZ,
timeStamp: e.timeStamp
};
}
/**
*
* Dispose and remove all event listeners
*/
dispose(): void {
// 清理触摸事件 | Clean up touch events
this._touchStartHandlers.forEach((handler) => {
window.removeEventListener('touchstart', handler);
});
this._touchStartHandlers.clear();
this._touchMoveHandlers.forEach((handler) => {
window.removeEventListener('touchmove', handler);
});
this._touchMoveHandlers.clear();
this._touchEndHandlers.forEach((handler) => {
window.removeEventListener('touchend', handler);
});
this._touchEndHandlers.clear();
this._touchCancelHandlers.forEach((handler) => {
window.removeEventListener('touchcancel', handler);
});
this._touchCancelHandlers.clear();
// 清理键盘事件 | Clean up keyboard events
this._keyDownHandlers.forEach((handler) => {
window.removeEventListener('keydown', handler);
});
this._keyDownHandlers.clear();
this._keyUpHandlers.forEach((handler) => {
window.removeEventListener('keyup', handler);
});
this._keyUpHandlers.clear();
// 清理鼠标事件 | Clean up mouse events
this._mouseMoveHandlers.forEach((handler) => {
window.removeEventListener('mousemove', handler);
});
this._mouseMoveHandlers.clear();
this._mouseDownHandlers.forEach((handler) => {
window.removeEventListener('mousedown', handler);
});
this._mouseDownHandlers.clear();
this._mouseUpHandlers.forEach((handler) => {
window.removeEventListener('mouseup', handler);
});
this._mouseUpHandlers.clear();
this._wheelHandlers.forEach((handler) => {
window.removeEventListener('wheel', handler);
});
this._wheelHandlers.clear();
}
}