385 lines
12 KiB
TypeScript
385 lines
12 KiB
TypeScript
/**
|
|
* 输入管理器实现
|
|
* 处理鼠标和触摸输入,转换为游戏事件
|
|
*/
|
|
|
|
import { Camera, Component, EventMouse, EventTouch, Vec2, Vec3, _decorator } from 'cc';
|
|
import { EventBus } from '../Core/EventBus';
|
|
import { Vector2 } from '../Core/GameData';
|
|
import {
|
|
BaseInputEvent,
|
|
InputButton,
|
|
InputCallback,
|
|
InputConfig,
|
|
InputEventType,
|
|
InputState,
|
|
MouseInputEvent,
|
|
TouchInputEvent
|
|
} from './InputTypes';
|
|
|
|
const { ccclass, property } = _decorator;
|
|
|
|
@ccclass
|
|
export class InputManager extends Component {
|
|
|
|
@property({ tooltip: '启用鼠标输入' })
|
|
enableMouse: boolean = true;
|
|
|
|
@property({ tooltip: '启用触摸输入' })
|
|
enableTouch: boolean = true;
|
|
|
|
@property({ tooltip: '双击时间间隔(ms)' })
|
|
doubleClickTime: number = 300;
|
|
|
|
@property({ tooltip: '长按时间(ms)' })
|
|
longPressTime: number = 500;
|
|
|
|
@property({ tooltip: '拖拽阈值(像素)' })
|
|
dragThreshold: number = 10;
|
|
|
|
private eventBus: EventBus;
|
|
private camera: Camera | null = null;
|
|
private inputState: InputState;
|
|
private config: InputConfig;
|
|
private callbacks: Map<InputEventType, InputCallback[]> = new Map();
|
|
|
|
/**
|
|
* 组件初始化
|
|
*/
|
|
onLoad(): void {
|
|
this.eventBus = EventBus.getInstance();
|
|
this.initializeInputState();
|
|
this.initializeConfig();
|
|
this.setupInputEvents();
|
|
|
|
console.log('InputManager initialized');
|
|
}
|
|
|
|
/**
|
|
* 初始化输入状态
|
|
*/
|
|
private initializeInputState(): void {
|
|
this.inputState = {
|
|
isMouseDown: false,
|
|
isTouchActive: false,
|
|
lastClickTime: 0,
|
|
lastClickPosition: { x: 0, y: 0 },
|
|
isDragging: false,
|
|
dragStartPosition: { x: 0, y: 0 },
|
|
activeTouches: new Map()
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 初始化配置
|
|
*/
|
|
private initializeConfig(): void {
|
|
this.config = {
|
|
enableMouse: this.enableMouse,
|
|
enableTouch: this.enableTouch,
|
|
doubleClickTime: this.doubleClickTime,
|
|
longPressTime: this.longPressTime,
|
|
dragThreshold: this.dragThreshold
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 设置相机引用
|
|
*/
|
|
setCamera(camera: Camera): void {
|
|
this.camera = camera;
|
|
}
|
|
|
|
/**
|
|
* 设置事件回调
|
|
*/
|
|
on<T extends BaseInputEvent>(eventType: InputEventType, callback: InputCallback<T>): void {
|
|
if (!this.callbacks.has(eventType)) {
|
|
this.callbacks.set(eventType, []);
|
|
}
|
|
this.callbacks.get(eventType)!.push(callback as InputCallback);
|
|
}
|
|
|
|
/**
|
|
* 移除事件回调
|
|
*/
|
|
off<T extends BaseInputEvent>(eventType: InputEventType, callback: InputCallback<T>): void {
|
|
const callbacks = this.callbacks.get(eventType);
|
|
if (callbacks) {
|
|
const index = callbacks.indexOf(callback as InputCallback);
|
|
if (index > -1) {
|
|
callbacks.splice(index, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 设置输入事件监听
|
|
*/
|
|
private setupInputEvents(): void {
|
|
if (this.config.enableMouse) {
|
|
this.node.on('mousedown', this.onMouseDown, this);
|
|
this.node.on('mouseup', this.onMouseUp, this);
|
|
this.node.on('mousemove', this.onMouseMove, this);
|
|
}
|
|
|
|
if (this.config.enableTouch) {
|
|
this.node.on('touchstart', this.onTouchStart, this);
|
|
this.node.on('touchend', this.onTouchEnd, this);
|
|
this.node.on('touchmove', this.onTouchMove, this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 鼠标按下事件
|
|
*/
|
|
private onMouseDown(event: EventMouse): void {
|
|
const position = this.screenToWorldPosition(event.getUILocation());
|
|
const inputEvent: MouseInputEvent = {
|
|
type: InputEventType.MOUSE_DOWN,
|
|
position,
|
|
screenPosition: { x: event.getUILocation().x, y: event.getUILocation().y },
|
|
timestamp: Date.now(),
|
|
button: event.getButton() as InputButton,
|
|
ctrlKey: false, // Cocos Creator 3.x 中需要通过其他方式获取
|
|
shiftKey: false,
|
|
altKey: false
|
|
};
|
|
|
|
this.inputState.isMouseDown = true;
|
|
this.inputState.dragStartPosition = position;
|
|
|
|
// 检测双击
|
|
const timeSinceLastClick = inputEvent.timestamp - this.inputState.lastClickTime;
|
|
if (timeSinceLastClick < this.config.doubleClickTime) {
|
|
const distance = this.calculateDistance(position, this.inputState.lastClickPosition);
|
|
if (distance < this.config.dragThreshold) {
|
|
this.emitDoubleClickEvent(inputEvent);
|
|
}
|
|
}
|
|
|
|
this.inputState.lastClickTime = inputEvent.timestamp;
|
|
this.inputState.lastClickPosition = position;
|
|
|
|
this.emitEvent(inputEvent);
|
|
}
|
|
|
|
/**
|
|
* 鼠标释放事件
|
|
*/
|
|
private onMouseUp(event: EventMouse): void {
|
|
const position = this.screenToWorldPosition(event.getUILocation());
|
|
const inputEvent: MouseInputEvent = {
|
|
type: InputEventType.MOUSE_UP,
|
|
position,
|
|
screenPosition: { x: event.getUILocation().x, y: event.getUILocation().y },
|
|
timestamp: Date.now(),
|
|
button: event.getButton() as InputButton,
|
|
ctrlKey: false,
|
|
shiftKey: false,
|
|
altKey: false
|
|
};
|
|
|
|
this.inputState.isMouseDown = false;
|
|
this.inputState.isDragging = false;
|
|
|
|
this.emitEvent(inputEvent);
|
|
}
|
|
|
|
/**
|
|
* 鼠标移动事件
|
|
*/
|
|
private onMouseMove(event: EventMouse): void {
|
|
const position = this.screenToWorldPosition(event.getUILocation());
|
|
const inputEvent: MouseInputEvent = {
|
|
type: InputEventType.MOUSE_MOVE,
|
|
position,
|
|
screenPosition: { x: event.getUILocation().x, y: event.getUILocation().y },
|
|
timestamp: Date.now(),
|
|
button: event.getButton() as InputButton,
|
|
ctrlKey: false,
|
|
shiftKey: false,
|
|
altKey: false
|
|
};
|
|
|
|
// 检测拖拽开始
|
|
if (this.inputState.isMouseDown && !this.inputState.isDragging) {
|
|
const distance = this.calculateDistance(position, this.inputState.dragStartPosition);
|
|
if (distance > this.config.dragThreshold) {
|
|
this.inputState.isDragging = true;
|
|
this.emitDragStartEvent(inputEvent);
|
|
}
|
|
}
|
|
|
|
this.emitEvent(inputEvent);
|
|
}
|
|
|
|
/**
|
|
* 触摸开始事件
|
|
*/
|
|
private onTouchStart(event: EventTouch): void {
|
|
const touches = event.getAllTouches();
|
|
|
|
for (const touch of touches) {
|
|
const position = this.screenToWorldPosition(touch.getUILocation());
|
|
const touchEvent: TouchInputEvent = {
|
|
type: InputEventType.TOUCH_START,
|
|
position,
|
|
screenPosition: { x: touch.getUILocation().x, y: touch.getUILocation().y },
|
|
timestamp: Date.now(),
|
|
touchId: touch.getID(),
|
|
force: undefined // Cocos Creator 3.x 中 getForce 方法可能不可用
|
|
};
|
|
|
|
this.inputState.activeTouches.set(touch.getID(), touchEvent);
|
|
this.inputState.isTouchActive = true;
|
|
|
|
this.emitEvent(touchEvent);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 触摸结束事件
|
|
*/
|
|
private onTouchEnd(event: EventTouch): void {
|
|
const touches = event.getAllTouches();
|
|
|
|
for (const touch of touches) {
|
|
const position = this.screenToWorldPosition(touch.getUILocation());
|
|
const touchEvent: TouchInputEvent = {
|
|
type: InputEventType.TOUCH_END,
|
|
position,
|
|
screenPosition: { x: touch.getUILocation().x, y: touch.getUILocation().y },
|
|
timestamp: Date.now(),
|
|
touchId: touch.getID(),
|
|
force: undefined // Cocos Creator 3.x 中 getForce 方法可能不可用
|
|
};
|
|
|
|
this.inputState.activeTouches.delete(touch.getID());
|
|
if (this.inputState.activeTouches.size === 0) {
|
|
this.inputState.isTouchActive = false;
|
|
}
|
|
|
|
this.emitEvent(touchEvent);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 触摸移动事件
|
|
*/
|
|
private onTouchMove(event: EventTouch): void {
|
|
const touches = event.getAllTouches();
|
|
|
|
for (const touch of touches) {
|
|
const position = this.screenToWorldPosition(touch.getUILocation());
|
|
const touchEvent: TouchInputEvent = {
|
|
type: InputEventType.TOUCH_MOVE,
|
|
position,
|
|
screenPosition: { x: touch.getUILocation().x, y: touch.getUILocation().y },
|
|
timestamp: Date.now(),
|
|
touchId: touch.getID(),
|
|
force: undefined // Cocos Creator 3.x 中 getForce 方法可能不可用
|
|
};
|
|
|
|
this.emitEvent(touchEvent);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 屏幕坐标转世界坐标
|
|
*/
|
|
private screenToWorldPosition(screenPos: Vec2): Vector2 {
|
|
if (!this.camera) {
|
|
// 如果没有相机,使用简单的坐标转换
|
|
return {
|
|
x: (screenPos.x - 400) / 100, // 假设屏幕中心为 (400, 300),缩放 100倍
|
|
y: (300 - screenPos.y) / 100
|
|
};
|
|
}
|
|
|
|
// 使用相机进行坐标转换 - 转换为 Vec3
|
|
const screenPos3 = new Vec3(screenPos.x, screenPos.y, 0);
|
|
const worldPos = this.camera.screenToWorld(screenPos3);
|
|
return {
|
|
x: worldPos.x / 100, // 转换为物理世界坐标
|
|
y: worldPos.y / 100
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 计算两点距离
|
|
*/
|
|
private calculateDistance(pos1: Vector2, pos2: Vector2): number {
|
|
const dx = pos1.x - pos2.x;
|
|
const dy = pos1.y - pos2.y;
|
|
return Math.sqrt(dx * dx + dy * dy);
|
|
}
|
|
|
|
/**
|
|
* 发射事件
|
|
*/
|
|
private emitEvent(event: BaseInputEvent): void {
|
|
const callbacks = this.callbacks.get(event.type);
|
|
if (callbacks) {
|
|
callbacks.forEach(callback => {
|
|
try {
|
|
callback(event);
|
|
} catch (error) {
|
|
console.error(`Error in input callback for ${event.type}:`, error);
|
|
}
|
|
});
|
|
}
|
|
|
|
// 同时通过事件总线发射
|
|
this.eventBus.emit(event.type, event);
|
|
}
|
|
|
|
/**
|
|
* 发射双击事件
|
|
*/
|
|
private emitDoubleClickEvent(event: MouseInputEvent): void {
|
|
this.eventBus.emit('double_click', event);
|
|
}
|
|
|
|
/**
|
|
* 发射拖拽开始事件
|
|
*/
|
|
private emitDragStartEvent(event: MouseInputEvent): void {
|
|
this.eventBus.emit('drag_start', event);
|
|
}
|
|
|
|
/**
|
|
* 获取当前输入状态
|
|
*/
|
|
getInputState(): InputState {
|
|
return { ...this.inputState };
|
|
}
|
|
|
|
/**
|
|
* 获取事件总线
|
|
*/
|
|
getEventBus(): EventBus {
|
|
return this.eventBus;
|
|
}
|
|
|
|
/**
|
|
* 清理资源
|
|
*/
|
|
onDestroy(): void {
|
|
// 移除事件监听
|
|
this.node.off('mousedown', this.onMouseDown, this);
|
|
this.node.off('mouseup', this.onMouseUp, this);
|
|
this.node.off('mousemove', this.onMouseMove, this);
|
|
this.node.off('touchstart', this.onTouchStart, this);
|
|
this.node.off('touchend', this.onTouchEnd, this);
|
|
this.node.off('touchmove', this.onTouchMove, this);
|
|
|
|
// 清理回调
|
|
this.callbacks.clear();
|
|
|
|
// 清理事件总线
|
|
this.eventBus.clear();
|
|
|
|
console.log('InputManager destroyed');
|
|
}
|
|
} |