游戏开发中如何分离UI与数据?求推荐适用设计模式
适配PixiJS的声明式风格解耦方案
从React的声明式开发切换到PixiJS的命令式API,核心痛点是如何保持数据层与UI层的分离,避免耦合。以下是几个实用的设计模式,能帮你管理复杂度:
1. 状态管理+专用渲染控制器
核心思路是将纯数据逻辑完全独立,用发布订阅模式让渲染层监听数据变化,仅负责执行Pixi的命令式操作。
数据层(纯逻辑,无UI依赖)
class BulletPointData { constructor() { this.items = []; this.listeners = []; } addItem(text) { this.items.push(text); this._notify('add', { text, index: this.items.length - 1 }); } removeItem(index) { const removed = this.items.splice(index, 1)[0]; this._notify('remove', { index, text: removed }); } subscribe(callback) { this.listeners.push(callback); } unsubscribe(callback) { this.listeners = this.listeners.filter(cb => cb !== callback); } _notify(event, data) { this.listeners.forEach(cb => cb(event, data)); } }
渲染控制器(仅处理Pixi操作)
class BulletPointRenderer { constructor(stage, dataStore) { this.stage = stage; this.dataStore = dataStore; this.sprites = new Map(); // 映射索引到Pixi精灵 // 订阅数据变化 this.dataStore.subscribe((event, data) => this._handleEvent(event, data)); // 初始化现有数据 this.dataStore.items.forEach((text, index) => this._createSprite(text, index)); } _createSprite(text, index) { const sprite = new PIXI.Text(text); sprite.y = index * 20; // 垂直排列 this.stage.addChild(sprite); this.sprites.set(index, sprite); } _removeSprite(index) { const sprite = this.sprites.get(index); if (!sprite) return; this.stage.removeChild(sprite); sprite.destroy(); this.sprites.delete(index); // 调整后续精灵位置 this.sprites.forEach((sprite, idx) => { if (idx > index) sprite.y -= 20; }); } _handleEvent(event, data) { switch(event) { case 'add': this._createSprite(data.text, data.index); break; case 'remove': this._removeSprite(data.index); break; } } }
使用时,数据层的addItem/removeItem仅修改数据,渲染器自动响应变化处理UI,完全符合单一职责。
2. 响应式状态管理库(如MobX)
利用响应式库自动同步数据与UI,无需手动实现发布订阅,代码更简洁。
数据层(MobX observable)
import { makeAutoObservable } from "mobx"; class BulletPointStore { items = []; constructor() { makeAutoObservable(this); } addItem(text) { this.items.push(text); } removeItem(index) { this.items.splice(index, 1); } }
渲染控制器(自动响应数据变化)
import { autorun } from "mobx"; class BulletPointRenderer { constructor(stage, store) { this.stage = stage; this.store = store; this.sprites = []; // 自动同步数据与UI autorun(() => this._syncSprites()); } _syncSprites() { // 移除多余精灵 while (this.sprites.length > this.store.items.length) { const sprite = this.sprites.pop(); this.stage.removeChild(sprite); sprite.destroy(); } // 创建或更新精灵 this.store.items.forEach((text, index) => { if (!this.sprites[index]) { this.sprites[index] = new PIXI.Text(text); this.stage.addChild(this.sprites[index]); } this.sprites[index].text = text; this.sprites[index].y = index * 20; }); } }
MobX的autorun会自动追踪store.items的变化,触发同步逻辑,省去手动事件触发的代码。
3. Entity-Component-System (ECS) 模式
适合复杂游戏场景,将数据(Component)、逻辑(System)、实体(Entity)完全分离,渲染System仅负责根据Component状态更新Pixi对象。
定义纯数据Component
class TextComponent { constructor(text) { this.text = text; } } class PositionComponent { constructor(x, y) { this.x = x; this.y = y; } }
Entity(组件容器)
class Entity { constructor() { this.components = new Map(); } addComponent(component) { this.components.set(component.constructor.name, component); } getComponent(type) { return this.components.get(type); } }
Render System(仅处理渲染逻辑)
class RenderSystem { constructor(stage) { this.stage = stage; this.entitySprites = new Map(); // Entity -> Pixi Sprite } update(entities) { // 移除已销毁Entity的精灵 this.entitySprites.forEach((sprite, entity) => { if (!entities.includes(entity)) { this.stage.removeChild(sprite); sprite.destroy(); this.entitySprites.delete(entity); } }); // 更新或创建精灵 entities.forEach(entity => { const textComp = entity.getComponent('TextComponent'); const posComp = entity.getComponent('PositionComponent'); if (!textComp || !posComp) return; let sprite = this.entitySprites.get(entity); if (!sprite) { sprite = new PIXI.Text(textComp.text); this.stage.addChild(sprite); this.entitySprites.set(entity, sprite); } sprite.text = textComp.text; sprite.x = posComp.x; sprite.y = posComp.y; }); } }
使用时,只需管理Entity的创建/销毁,Render System会自动处理UI同步,数据与渲染完全解耦。
内容的提问来源于stack exchange,提问作者user3808430
相关产品推荐
相关产品推荐

