Skip to content

渲染器设计

前端应用的核心是基于 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. 渲染流程

  1. 初始化 PixiJS 应用:在 WorldMap.vue (或主渲染组件) 中创建 PIXI.Application 实例,并挂载到 DOM 元素。
  2. 创建主容器:创建一个 PIXI.Container 作为世界的主容器 (worldContainer),所有地图元素都将添加到此容器中。
  3. 创建分层容器:为每个渲染层创建一个独立的 PIXI.Container,并按层级顺序添加到 worldContainer 中。
  4. 加载素材:通过 textureAtlas.ts 模块加载所需的精灵图集 (PNG + JSON)。
  5. 数据更新useWorld composable 接收后端数据更新 (通过 SSE 或轮询)。
  6. 触发渲染:当世界状态数据发生变化时,设置 needsRedraw = true
  7. 渲染循环:在 requestAnimationFrame 循环中:
    • 检查 needsRedraw 标志。如果为 true,则调用各个渲染器组件的 draw() 方法。
    • 每个渲染器组件负责清空其对应的层容器,并根据最新的数据重新绘制精灵。
    • 对于需要 Y 轴深度排序的层 (如建筑、角色、自然物),在绘制前对精灵进行排序。
    • 更新角色动画等动态效果。
    • 重置 needsRedraw = false
  8. 交互处理: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日

Released under the MIT License.