358 lines
11 KiB
TypeScript
358 lines
11 KiB
TypeScript
import { _decorator, Camera, Component, Node } from 'cc';
|
|
import { EventBus } from '../Core/EventBus';
|
|
import { PhysicsBodyData, Vector2 } from '../Core/GameData';
|
|
import { IPhysicsEngine } from '../Core/IPhysicsEngine';
|
|
import { IRenderer } from '../Core/IRenderer';
|
|
import { InputManager } from '../Input/InputManager';
|
|
import { MouseInputEvent, TouchInputEvent } from '../Input/InputTypes';
|
|
import { WasmPhysicsEngine } from '../Physics/WasmPhysicsEngine';
|
|
import { PinballRenderer } from '../Renderer/PinballRenderer';
|
|
|
|
const { ccclass, property } = _decorator;
|
|
|
|
/**
|
|
* Standalone模式 - 单机弹珠物理游戏模式
|
|
* 整合物理引擎、渲染器和输入管理器,提供完整的单机游戏体验
|
|
*/
|
|
@ccclass('StandaloneMode')
|
|
export class StandaloneMode extends Component {
|
|
|
|
@property(Camera)
|
|
gameCamera: Camera = null;
|
|
|
|
@property(Node)
|
|
renderNode: Node = null;
|
|
|
|
@property({ type: Node, tooltip: "用于显示游戏边界的节点" })
|
|
boundsNode: Node = null;
|
|
|
|
// 核心系统
|
|
private physicsEngine: IPhysicsEngine = null;
|
|
private renderer: IRenderer = null;
|
|
private inputManager: InputManager = null;
|
|
private eventBus: EventBus = null;
|
|
|
|
// 游戏配置
|
|
@property({ tooltip: "游戏世界宽度" })
|
|
worldWidth: number = 800;
|
|
|
|
@property({ tooltip: "游戏世界高度" })
|
|
worldHeight: number = 600;
|
|
|
|
@property({ tooltip: "重力加速度" })
|
|
gravity: number = -9.81;
|
|
|
|
@property({ tooltip: "弹珠默认半径" })
|
|
ballRadius: number = 10;
|
|
|
|
@property({ tooltip: "弹珠默认密度" })
|
|
ballDensity: number = 1.0;
|
|
|
|
@property({ tooltip: "弹珠默认弹性系数" })
|
|
ballRestitution: number = 0.8;
|
|
|
|
// 游戏状态
|
|
private isInitialized: boolean = false;
|
|
private ballCount: number = 0;
|
|
private activeBalls: Map<number, PhysicsBodyData> = new Map();
|
|
|
|
async onLoad() {
|
|
// 初始化事件总线
|
|
this.eventBus = EventBus.getInstance();
|
|
|
|
// 初始化物理引擎
|
|
await this.initializePhysics();
|
|
|
|
// 初始化渲染器
|
|
this.initializeRenderer();
|
|
|
|
// 初始化输入管理器
|
|
this.initializeInput();
|
|
|
|
// 注册事件监听
|
|
this.registerEventHandlers();
|
|
|
|
console.log('[StandaloneMode] 初始化完成');
|
|
}
|
|
|
|
onEnable() {
|
|
if (this.isInitialized) {
|
|
this.startGameLoop();
|
|
}
|
|
}
|
|
|
|
onDisable() {
|
|
this.stopGameLoop();
|
|
}
|
|
|
|
onDestroy() {
|
|
this.cleanup();
|
|
}
|
|
|
|
/**
|
|
* 初始化物理引擎
|
|
*/
|
|
private async initializePhysics(): Promise<void> {
|
|
this.physicsEngine = new WasmPhysicsEngine();
|
|
await this.physicsEngine.initialize({
|
|
gravity: { x: 0, y: this.gravity },
|
|
timeStep: 1 / 60,
|
|
maxBodies: 1000
|
|
});
|
|
|
|
// 创建物理世界
|
|
await this.physicsEngine.createWorld({ x: 0, y: this.gravity });
|
|
|
|
// 创建世界边界
|
|
this.createWorldBounds();
|
|
|
|
console.log('[StandaloneMode] 物理引擎初始化完成');
|
|
}
|
|
|
|
/**
|
|
* 初始化渲染器
|
|
*/
|
|
private initializeRenderer(): void {
|
|
if (!this.renderNode) {
|
|
console.error('[StandaloneMode] renderNode 未设置');
|
|
return;
|
|
}
|
|
|
|
this.renderer = this.renderNode.getComponent(PinballRenderer);
|
|
if (!this.renderer) {
|
|
this.renderer = this.renderNode.addComponent(PinballRenderer);
|
|
}
|
|
|
|
// 设置渲染器参数
|
|
if (this.gameCamera) {
|
|
this.renderer.setCamera(this.gameCamera);
|
|
}
|
|
|
|
this.renderer.setWorldBounds(this.worldWidth, this.worldHeight);
|
|
|
|
console.log('[StandaloneMode] 渲染器初始化完成');
|
|
}
|
|
|
|
/**
|
|
* 初始化输入管理器
|
|
*/
|
|
private initializeInput(): void {
|
|
this.inputManager = this.getComponent(InputManager);
|
|
if (!this.inputManager) {
|
|
this.inputManager = this.addComponent(InputManager);
|
|
}
|
|
|
|
// 设置输入管理器参数
|
|
if (this.gameCamera) {
|
|
this.inputManager.setCamera(this.gameCamera);
|
|
}
|
|
|
|
console.log('[StandaloneMode] 输入管理器初始化完成');
|
|
}
|
|
|
|
/**
|
|
* 注册事件处理器
|
|
*/
|
|
private registerEventHandlers(): void {
|
|
// 监听输入事件
|
|
this.eventBus.on('input.mouse.click', (event: MouseInputEvent) => this.onMouseClick(event));
|
|
this.eventBus.on('input.touch.start', (event: TouchInputEvent) => this.onTouchStart(event));
|
|
|
|
// 监听物理事件
|
|
this.eventBus.on('physics.collision', (collisionData: any) => this.onPhysicsCollision(collisionData));
|
|
|
|
console.log('[StandaloneMode] 事件处理器注册完成');
|
|
|
|
this.isInitialized = true;
|
|
}
|
|
|
|
/**
|
|
* 创建世界边界
|
|
*/
|
|
private createWorldBounds(): void {
|
|
const halfWidth = this.worldWidth / 2;
|
|
const halfHeight = this.worldHeight / 2;
|
|
const wallThickness = 10;
|
|
|
|
// 创建四面墙壁
|
|
const walls = [
|
|
// 底部墙
|
|
{ x: 0, y: -halfHeight - wallThickness / 2, width: this.worldWidth + wallThickness, height: wallThickness },
|
|
// 顶部墙
|
|
{ x: 0, y: halfHeight + wallThickness / 2, width: this.worldWidth + wallThickness, height: wallThickness },
|
|
// 左侧墙
|
|
{ x: -halfWidth - wallThickness / 2, y: 0, width: wallThickness, height: this.worldHeight + wallThickness },
|
|
// 右侧墙
|
|
{ x: halfWidth + wallThickness / 2, y: 0, width: wallThickness, height: this.worldHeight + wallThickness }
|
|
];
|
|
|
|
for (const wall of walls) {
|
|
this.physicsEngine.createBox({
|
|
position: { x: wall.x, y: wall.y },
|
|
size: { x: wall.width, y: wall.height },
|
|
isStatic: true,
|
|
restitution: 0.8,
|
|
friction: 0.3
|
|
});
|
|
}
|
|
|
|
console.log('[StandaloneMode] 世界边界创建完成');
|
|
}
|
|
|
|
/**
|
|
* 鼠标点击事件处理
|
|
*/
|
|
private onMouseClick(event: MouseInputEvent): void {
|
|
this.createBallAtPosition(event.position);
|
|
}
|
|
|
|
/**
|
|
* 触摸开始事件处理
|
|
*/
|
|
private onTouchStart(event: TouchInputEvent): void {
|
|
this.createBallAtPosition(event.position);
|
|
}
|
|
|
|
/**
|
|
* 在指定位置创建弹珠
|
|
*/
|
|
private createBallAtPosition(position: Vector2): void {
|
|
const ballId = this.physicsEngine.createCircle({
|
|
position: position,
|
|
radius: this.ballRadius,
|
|
isStatic: false,
|
|
density: this.ballDensity,
|
|
restitution: this.ballRestitution,
|
|
friction: 0.3
|
|
});
|
|
|
|
// 创建球体数据
|
|
const ballData: PhysicsBodyData = {
|
|
id: ballId,
|
|
position: position,
|
|
rotation: 0,
|
|
velocity: { x: 0, y: 0 },
|
|
angularVelocity: 0,
|
|
bodyType: 'circle',
|
|
radius: this.ballRadius,
|
|
isStatic: false
|
|
};
|
|
|
|
this.activeBalls.set(ballId, ballData);
|
|
this.ballCount++;
|
|
|
|
// 通知渲染器
|
|
this.eventBus.emit('ball.created', {
|
|
id: ballId,
|
|
position: position,
|
|
radius: this.ballRadius
|
|
});
|
|
|
|
console.log(`[StandaloneMode] 创建弹珠 #${ballId} 在位置 (${position.x}, ${position.y})`);
|
|
}
|
|
|
|
/**
|
|
* 物理碰撞事件处理
|
|
*/
|
|
private onPhysicsCollision(collisionData: any): void {
|
|
// 播放碰撞音效或效果
|
|
console.log('[StandaloneMode] 物理碰撞:', collisionData);
|
|
}
|
|
|
|
/**
|
|
* 开始游戏循环
|
|
*/
|
|
private startGameLoop(): void {
|
|
// 在update中已经自动运行物理和渲染循环
|
|
console.log('[StandaloneMode] 游戏循环已开始');
|
|
}
|
|
|
|
/**
|
|
* 停止游戏循环
|
|
*/
|
|
private stopGameLoop(): void {
|
|
console.log('[StandaloneMode] 游戏循环已停止');
|
|
}
|
|
|
|
/**
|
|
* 游戏更新循环
|
|
*/
|
|
update(deltaTime: number) {
|
|
if (!this.isInitialized || !this.physicsEngine) {
|
|
return;
|
|
}
|
|
|
|
// 步进物理引擎
|
|
this.physicsEngine.step(deltaTime);
|
|
|
|
// 更新所有活动球体的状态
|
|
for (const [ballId, ballData] of this.activeBalls) {
|
|
const updatedData = this.physicsEngine.getBodyData(ballId);
|
|
if (updatedData) {
|
|
// 更新本地数据
|
|
ballData.position = updatedData.position;
|
|
ballData.rotation = updatedData.rotation;
|
|
ballData.velocity = updatedData.velocity;
|
|
ballData.angularVelocity = updatedData.angularVelocity;
|
|
|
|
// 发送更新事件给渲染器
|
|
this.eventBus.emit('ball.updated', {
|
|
id: ballId,
|
|
position: ballData.position,
|
|
rotation: ballData.rotation
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 清理资源
|
|
*/
|
|
private cleanup(): void {
|
|
// 清理事件监听器
|
|
this.eventBus.off('input.mouse.click', this.onMouseClick);
|
|
this.eventBus.off('input.touch.start', this.onTouchStart);
|
|
this.eventBus.off('physics.collision', this.onPhysicsCollision);
|
|
|
|
// 清理物理引擎
|
|
if (this.physicsEngine) {
|
|
this.physicsEngine.cleanup();
|
|
this.physicsEngine = null;
|
|
}
|
|
|
|
// 清理数据
|
|
this.activeBalls.clear();
|
|
this.ballCount = 0;
|
|
this.isInitialized = false;
|
|
|
|
console.log('[StandaloneMode] 资源清理完成');
|
|
}
|
|
|
|
/**
|
|
* 获取游戏统计信息
|
|
*/
|
|
public getGameStats() {
|
|
return {
|
|
ballCount: this.ballCount,
|
|
activeBalls: this.activeBalls.size,
|
|
worldSize: { width: this.worldWidth, height: this.worldHeight },
|
|
isInitialized: this.isInitialized
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 重置游戏
|
|
*/
|
|
public resetGame(): void {
|
|
// 移除所有球体
|
|
for (const ballId of this.activeBalls.keys()) {
|
|
this.physicsEngine.removeBody(ballId);
|
|
this.eventBus.emit('ball.removed', { id: ballId });
|
|
}
|
|
|
|
this.activeBalls.clear();
|
|
this.ballCount = 0;
|
|
|
|
console.log('[StandaloneMode] 游戏重置完成');
|
|
}
|
|
} |