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 的直接依赖
This commit is contained in:
@@ -1,22 +1,44 @@
|
||||
/**
|
||||
* Web 平台输入子系统
|
||||
* Web platform input subsystem
|
||||
*/
|
||||
|
||||
import type {
|
||||
IPlatformInputSubsystem,
|
||||
TouchHandler,
|
||||
TouchEvent
|
||||
TouchEvent,
|
||||
KeyboardHandler,
|
||||
KeyboardEventInfo,
|
||||
MouseHandler,
|
||||
MouseEventInfo,
|
||||
WheelHandler,
|
||||
WheelEventInfo
|
||||
} from '@esengine/platform-common';
|
||||
import { MouseButton } from '@esengine/platform-common';
|
||||
|
||||
/**
|
||||
* Web 平台输入子系统实现
|
||||
* Web platform input subsystem implementation
|
||||
*/
|
||||
export class WebInputSubsystem implements IPlatformInputSubsystem {
|
||||
// ========== 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();
|
||||
|
||||
// ========== 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));
|
||||
@@ -82,9 +104,122 @@ export class WebInputSubsystem implements IPlatformInputSubsystem {
|
||||
}
|
||||
|
||||
supportsPressure(): boolean {
|
||||
return 'force' in Touch.prototype;
|
||||
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);
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -99,4 +234,103 @@ export class WebInputSubsystem implements IPlatformInputSubsystem {
|
||||
timeStamp: e.timeStamp
|
||||
};
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user