Files
shooter-demo/client-cocos/assets/scripts/Modules/Pinball/Renderer/PinballRenderer.ts
2025-11-28 18:10:10 +08:00

292 lines
8.6 KiB
TypeScript

/**
* Pinball 渲染器实现
* 负责渲染物理对象、粒子效果等
*/
import { _decorator, Camera, Color, Component, Graphics, Node, Prefab, Sprite, tween, Vec3 } from 'cc';
import { PhysicsBodyData, Vector2 } from '../Core/GameData';
import { IRenderer, ParticleEffect, RenderObject } from '../Core/IRenderer';
const { ccclass, property } = _decorator;
@ccclass
export class PinballRenderer extends Component implements IRenderer {
@property(Node)
private renderContainer: Node = null!;
@property(Prefab)
private ballPrefab: Prefab = null!;
private renderObjects: Map<string, Node> = new Map();
private particlePool: Node[] = [];
private camera: Camera = null!;
/**
* 设置相机
*/
setCamera(camera: Camera): void {
this.camera = camera;
console.log('Camera set for PinballRenderer');
}
/**
* 设置世界边界
*/
setWorldBounds(width: number, height: number): void {
console.log(`World bounds set to ${width} x ${height}`);
// 可以在此处设置渲染边界或背景
}
/**
* 初始化渲染器
*/
async initialize(parentNode: any): Promise<void> {
console.log('Initializing PinballRenderer...');
// 设置渲染容器
if (parentNode) {
this.renderContainer = parentNode;
} else {
this.renderContainer = this.node;
}
// 获取相机 - 在 Cocos Creator 3.x 中需要通过场景查找
const cameraNode = this.renderContainer.scene?.getChildByName('Main Camera');
if (cameraNode) {
this.camera = cameraNode.getComponent(Camera);
}
if (!this.camera) {
console.warn('Main camera not found, creating default camera');
const cameraNode = new Node('PinballCamera');
this.camera = cameraNode.addComponent(Camera);
this.renderContainer.addChild(cameraNode);
}
// 创建默认球体预制件(如果没有提供)
if (!this.ballPrefab) {
await this.createDefaultBallPrefab();
}
console.log('PinballRenderer initialized successfully');
}
/**
* 创建默认的球体预制件
*/
private async createDefaultBallPrefab(): Promise<void> {
// 创建一个简单的圆形节点作为默认球体
const ballNode = new Node('DefaultBall');
// 添加 Sprite 组件
const sprite = ballNode.addComponent(Sprite);
// 创建圆形材质
const graphics = ballNode.addComponent(Graphics);
graphics.fillColor = Color.WHITE;
graphics.circle(0, 0, 25); // 半径 25 像素
graphics.fill();
console.log('Created default ball prefab');
}
/**
* 渲染物理体
*/
renderBodies(bodies: PhysicsBodyData[]): void {
// 清理不存在的渲染对象
const currentBodyIds = new Set(bodies.map(body => body.id.toString()));
for (const [id, node] of this.renderObjects) {
if (!currentBodyIds.has(id)) {
this.removeRenderObject(id);
}
}
// 更新或创建渲染对象
for (const body of bodies) {
const bodyIdStr = body.id.toString();
let renderNode = this.renderObjects.get(bodyIdStr);
if (!renderNode) {
// 创建新的渲染对象
const renderObject = this.createRenderObject(body);
renderNode = this.createRenderNode(renderObject);
this.renderObjects.set(bodyIdStr, renderNode);
this.renderContainer.addChild(renderNode);
}
// 更新位置
renderNode.setPosition(body.position.x * 100, body.position.y * 100); // 放大显示
}
}
/**
* 创建渲染对象数据
*/
createRenderObject(body: PhysicsBodyData): RenderObject {
return {
id: body.id.toString(),
position: body.position,
radius: body.radius,
color: body.isStatic ?
{ r: 128, g: 128, b: 128, a: 255 } : // 静态物体:灰色
{ r: 255, g: 100, b: 100, a: 255 }, // 动态物体:红色
layer: 0
};
}
/**
* 创建渲染节点
*/
private createRenderNode(renderObject: RenderObject): Node {
const node = new Node(`Ball_${renderObject.id}`);
// 添加 Graphics 组件用于绘制圆形
const graphics = node.addComponent(Graphics);
graphics.fillColor = new Color(
renderObject.color.r,
renderObject.color.g,
renderObject.color.b,
renderObject.color.a
);
const radius = Math.max(renderObject.radius * 100, 10); // 最小半径 10 像素
graphics.circle(0, 0, radius);
graphics.fill();
// 添加边框
graphics.strokeColor = Color.BLACK;
graphics.lineWidth = 2;
graphics.circle(0, 0, radius);
graphics.stroke();
return node;
}
/**
* 更新渲染对象
*/
updateRenderObject(renderObject: RenderObject, body: PhysicsBodyData): void {
const node = this.renderObjects.get(renderObject.id);
if (node) {
// 更新位置
node.setPosition(body.position.x * 100, body.position.y * 100);
// 可以在此添加更多属性的更新,如颜色、大小等
}
}
/**
* 移除渲染对象
*/
removeRenderObject(bodyId: string): void {
const node = this.renderObjects.get(bodyId);
if (node) {
node.removeFromParent();
this.renderObjects.delete(bodyId);
console.log(`Removed render object ${bodyId}`);
}
}
/**
* 播放粒子效果
*/
playParticleEffect(effect: ParticleEffect): void {
// 创建简单的粒子效果节点
const particleNode = this.getParticleNode();
particleNode.setPosition(effect.position.x * 100, effect.position.y * 100);
// 使用 Cocos Creator 3.x 的 tween 系统
tween(particleNode)
.parallel(
tween().to(effect.lifetime, { opacity: 0 }),
tween().by(effect.lifetime, {
position: new Vec3(effect.velocity.x * 50, effect.velocity.y * 50, 0)
})
)
.call(() => {
this.recycleParticleNode(particleNode);
})
.start();
this.renderContainer.addChild(particleNode);
}
/**
* 获取粒子节点(对象池)
*/
private getParticleNode(): Node {
if (this.particlePool.length > 0) {
return this.particlePool.pop()!;
}
const node = new Node('Particle');
const graphics = node.addComponent(Graphics);
graphics.fillColor = Color.YELLOW;
graphics.circle(0, 0, 3);
graphics.fill();
return node;
}
/**
* 回收粒子节点
*/
private recycleParticleNode(node: Node): void {
node.removeFromParent();
// 重置透明度 - 在 Cocos Creator 3.x 中通过 UIOpacity 组件
const uiOpacity = node.getComponent('cc.UIOpacity') as any;
if (uiOpacity) {
uiOpacity.opacity = 255;
}
node.setPosition(0, 0, 0); // 重置位置
this.particlePool.push(node);
}
/**
* 清除所有渲染对象
*/
clear(): void {
for (const [id, node] of this.renderObjects) {
node.removeFromParent();
}
this.renderObjects.clear();
// 清理粒子池
for (const particle of this.particlePool) {
particle.removeFromParent();
}
this.particlePool = [];
console.log('Cleared all render objects');
}
/**
* 设置相机位置
*/
setCameraPosition(position: Vector2): void {
if (this.camera) {
this.camera.node.setPosition(position.x * 100, position.y * 100);
}
}
/**
* 设置相机缩放
*/
setCameraZoom(zoom: number): void {
if (this.camera) {
// 在 Cocos Creator 3.x 中使用 orthoHeight 控制缩放
this.camera.orthoHeight = 600 / zoom; // 基础高度 600
}
}
/**
* 销毁渲染器
*/
dispose(): void {
this.clear();
console.log('PinballRenderer disposed');
}
}