渲染器设计
前端应用的核心是基于 PixiJS 的高性能 2D 渲染器,它负责将后端提供的世界状态数据转化为可视化的像素艺术地图。本设计文档将详细阐述渲染器的分层结构、渲染流程、性能优化策略以及关键组件。
1. 核心理念
- 分层渲染 (Layered Rendering):将地图元素划分为不同的渲染层,便于管理、优化和实现复杂的视觉效果(如 Y 轴深度排序、天气遮罩)。
- 数据驱动 (Data-Driven):渲染器仅负责根据后端提供的世界状态数据进行渲染,不包含业务逻辑。
- 按需渲染 (On-Demand Rendering):利用 PixiJS 的脏矩形(Dirty Rectangle)或
needsRedraw标志,只在必要时更新渲染内容,减少不必要的 GPU 负载。 - 模块化 (Modular):将不同类型的渲染逻辑封装到独立的渲染器组件中,提高可维护性和可扩展性。
2. 渲染层级
为了实现 2.5D 俯视角下的正确层叠关系和视觉效果,我们将渲染对象划分为以下 13 个层级。层级越低,越先渲染(在底部);层级越高,越后渲染(在顶部)。
| 层级 | 描述 | 渲染器组件 | 备注 |
|---|---|---|---|
| 0: 地形基底 | 草地、泥土、石地、沙地等基础地形瓦片。 | TerrainRenderer.ts | 季节化颜色,无缝拼接。 |
| 1: 道路/路径 | 泥路、石板路、桥梁等路径瓦片。 | PathRenderer.ts | 弯曲路径,与建筑对齐。 |
| 2: 水域 | 河流、池塘、码头水域瓦片。 | WaterRenderer.ts | 季节化(结冰效果),动画。 |
| 3: 建筑阴影 | 每栋建筑下方的像素化投影。 | BuildingRenderer.ts | 由建筑精灵图自带或单独生成。 |
| 4: 地面装饰 | 围栏、花坛、小石头等紧贴地面的装饰物。 | DecorationRenderer.ts | |
| 5: 低层自然物 | 灌木、草地装饰、蘑菇等,Y 轴深度排序。 | NatureRenderer.ts | 季节化变体。 |
| 6: 建筑主体 | 所有建筑的精灵图。 | BuildingRenderer.ts | 与角色进行 Y 轴深度排序。 |
| 7: 角色/NPC | 所有角色和 NPC 的精灵图。 | CharacterRenderer.ts | 与建筑进行 Y 轴深度排序,包含动画。 |
| 8: 高层自然物 | 树木、高大植物等,Y 轴深度排序。 | NatureRenderer.ts | 季节化变体。 |
| 9: 天气粒子 | 雨滴、雪花、花瓣等粒子效果。 | WeatherParticleRenderer.ts | |
| 10: 天气遮罩 | 半透明的颜色叠加层,模拟天气氛围。 | WeatherOverlayRenderer.ts | |
| 11: 闪电效果 | 雷阵雨时的闪电视觉效果。 | LightningEffectRenderer.ts | |
| 12: UI 元素 | 游戏内 UI 元素,如角色名称、地点标签、血条等。 | UIRenderer.ts | 始终在最顶层。 |
3. 渲染流程
- 初始化 PixiJS 应用:在
WorldMap.vue(或主渲染组件) 中创建PIXI.Application实例,并挂载到 DOM 元素。 - 创建主容器:创建一个
PIXI.Container作为世界的主容器 (worldContainer),所有地图元素都将添加到此容器中。 - 创建分层容器:为每个渲染层创建一个独立的
PIXI.Container,并按层级顺序添加到worldContainer中。 - 加载素材:通过
textureAtlas.ts模块加载所需的精灵图集 (PNG + JSON)。 - 数据更新:
useWorldcomposable 接收后端数据更新 (通过 SSE 或轮询)。 - 触发渲染:当世界状态数据发生变化时,设置
needsRedraw = true。 - 渲染循环:在
requestAnimationFrame循环中:- 检查
needsRedraw标志。如果为true,则调用各个渲染器组件的draw()方法。 - 每个渲染器组件负责清空其对应的层容器,并根据最新的数据重新绘制精灵。
- 对于需要 Y 轴深度排序的层 (如建筑、角色、自然物),在绘制前对精灵进行排序。
- 更新角色动画等动态效果。
- 重置
needsRedraw = false。
- 检查
- 交互处理:PixiJS 的事件系统处理用户的拖拽、缩放和点击事件,并更新
worldContainer的位置和缩放。
4. 渲染器组件设计
每个渲染器组件 (TerrainRenderer.ts, BuildingRenderer.ts 等) 都将是一个独立的 TypeScript 模块,负责特定层级的渲染逻辑。它们通常会暴露以下接口:
typescript
// src/renderers/BuildingRenderer.ts 示例
import * as PIXI from 'pixi.js';
import type { Location, Character } from '../../types';
export class BuildingRenderer {
private container: PIXI.Container;
private app: PIXI.Application;
constructor(app: PIXI.Application, parentContainer: PIXI.Container) {
this.app = app;
this.container = new PIXI.Container();
parentContainer.addChild(this.container);
}
/**
* 绘制建筑
* @param locations 当前世界中的地点数据
* @param characters 当前世界中的角色数据 (用于 Y 轴排序)
*/
async draw(locations: Location[], characters: Character[]): Promise<void> {
this.container.removeChildren(); // 清空当前层
const renderableObjects: Array<{ y: number; sprite: PIXI.Sprite }> = [];
// 遍历 locations 数据,创建或更新建筑精灵
for (const loc of locations) {
// ... 根据 loc 数据和 SpriteSpec 创建 PIXI.Sprite ...
// ... 设置 sprite 的纹理、位置、锚点等 ...
// ... 计算 buildingWorldY 用于 Y 轴排序 ...
renderableObjects.push({ y: buildingWorldY, sprite: buildingSprite });
}
// 进行 Y 轴深度排序
renderableObjects.sort((a, b) => a.y - b.y);
// 将排序后的精灵添加到容器
for (const obj of renderableObjects) {
this.container.addChild(obj.sprite);
}
}
// 其他辅助方法,如更新单个建筑、处理交互等
}5. 性能优化
- 纹理共享:所有精灵图集都应通过
textureAtlas.ts进行管理,确保相同的纹理只加载一次。 - 对象池 (Object Pooling):对于频繁创建和销毁的粒子、特效等,使用对象池进行复用,减少 GC 压力。
- 视口裁剪 (Frustum Culling):只渲染当前屏幕可见区域内的对象,避免绘制屏幕外的元素。
- 批处理 (Batching):PixiJS 会自动对使用相同纹理的精灵进行批处理,减少 Draw Call。合理组织图集可以最大化批处理效果。
- 脏矩形/脏标志:只在数据变化时才重新绘制受影响的区域或层。
image-rendering: pixelated:在 CSS 中设置canvas元素的image-rendering: pixelated属性,确保 PixiJS 渲染的像素艺术在缩放时保持清晰的像素边缘。
作者:Manus AI 日期:2026年7月10日