Newer
Older
PixelPaintWar / apps / client / src / scenes / game / entities / map / GameMapView.ts
/**
 * GameMapView
 * マップ背景,グリッド線,セル塗りの描画を担うビュー
 * 計算済みセル状態を受けてPixi描画へ反映する
 */
import { Container, Graphics } from 'pixi.js';
import { config } from '@repo/shared';

/** マップ描画責務を担うビュー */
export class GameMapView {
  public readonly displayObject: Container;

  private readonly bgGraphics: Graphics;
  private readonly gridGraphics: Graphics;
  private readonly cells: Graphics[] = [];

  /** 背景,グリッド線,セル描画オブジェクトを初期化する */
  constructor() {
    this.displayObject = new Container();
    this.bgGraphics = new Graphics();
    this.gridGraphics = new Graphics();

    this.displayObject.addChild(this.bgGraphics);
    this.initCells();
    this.displayObject.addChild(this.gridGraphics);
    this.drawBaseMap();
  }

  /** 全セル状態をまとめて描画へ反映する */
  public renderAll(teamIds: number[]): void {
    const maxLength = Math.min(this.cells.length, teamIds.length);
    for (let index = 0; index < maxLength; index++) {
      this.renderCell(index, teamIds[index]);
    }
  }

  /** 指定セルの状態を描画へ反映する */
  public renderCell(index: number, teamId: number): void {
    const cell = this.cells[index];
    if (!cell) return;

    const { GRID_CELL_SIZE } = config.GAME_CONFIG;

    // 対象セルをクリアしてから必要に応じて再塗布する
    cell.clear();
    if (teamId === -1) return;

    const hexColor = this.toHexColor(teamId);
    cell.rect(0, 0, GRID_CELL_SIZE, GRID_CELL_SIZE).fill(hexColor);
  }

  /** 描画リソースを破棄する */
  public destroy(): void {
    this.displayObject.destroy({ children: true });
  }

  /** 設定値に基づいてセル描画オブジェクトを初期化する */
  private initCells(): void {
    const { GRID_COLS, GRID_ROWS, GRID_CELL_SIZE } = config.GAME_CONFIG;
    const totalCells = GRID_COLS * GRID_ROWS;

    for (let index = 0; index < totalCells; index++) {
      const col = index % GRID_COLS;
      const row = Math.floor(index / GRID_COLS);

      const cell = new Graphics();
      cell.x = col * GRID_CELL_SIZE;
      cell.y = row * GRID_CELL_SIZE;

      this.displayObject.addChild(cell);
      this.cells.push(cell);
    }
  }

  /** マップ背景とグリッド線を描画する */
  private drawBaseMap(): void {
    const {
      MAP_WIDTH_PX,
      MAP_HEIGHT_PX,
      GRID_CELL_SIZE,
      MAP_BG_COLOR,
      MAP_GRID_COLOR,
      MAP_BORDER_COLOR,
    } = config.GAME_CONFIG;

    this.bgGraphics.rect(0, 0, MAP_WIDTH_PX, MAP_HEIGHT_PX).fill(MAP_BG_COLOR);

    for (let x = 0; x <= MAP_WIDTH_PX; x += GRID_CELL_SIZE) {
      this.gridGraphics.moveTo(x, 0).lineTo(x, MAP_HEIGHT_PX).stroke({ width: 1, color: MAP_GRID_COLOR });
    }

    for (let y = 0; y <= MAP_HEIGHT_PX; y += GRID_CELL_SIZE) {
      this.gridGraphics.moveTo(0, y).lineTo(MAP_WIDTH_PX, y).stroke({ width: 1, color: MAP_GRID_COLOR });
    }

    this.gridGraphics.rect(0, 0, MAP_WIDTH_PX, MAP_HEIGHT_PX).stroke({ width: 5, color: MAP_BORDER_COLOR });
  }

  /** チームIDから塗り色の16進数カラー値を取得する */
  private toHexColor(teamId: number): number {
    config.assertValidTeamId(teamId);
    const { TEAM_COLORS } = config.GAME_CONFIG;
    const colorString = TEAM_COLORS[teamId];
    return parseInt(colorString.replace('#', '0x'), 16);
  }
}