Newer
Older
PixelPaintWar / apps / client / src / scenes / game / entities / bomb / BombView.ts
/**
 * BombView
 * 爆弾の描画責務を担うビュー
 * 設置中の見た目と爆風円の表示を管理する
 */
import { Container, Graphics } from "pixi.js";
import { config } from "@client/config";
import type { BombState } from "./BombModel";

/** 爆弾の描画表現を管理するビュー */
export class BombView {
  public readonly displayObject: Container;

  private bombGraphic: Graphics;
  private explosionGraphic: Graphics;
  private lastRenderedState: BombState | null = null;
  private lastRenderedRadiusGrid: number | null = null;
  private lastRenderedTeamId: number | null = null;

  constructor() {
    this.displayObject = new Container();
    this.bombGraphic = new Graphics();
    this.explosionGraphic = new Graphics();

    this.displayObject.addChild(this.explosionGraphic);
    this.displayObject.addChild(this.bombGraphic);
  }

  public syncPosition(gridX: number, gridY: number): void {
    const { GRID_CELL_SIZE } = config.GAME_CONFIG;

    this.displayObject.x = gridX * GRID_CELL_SIZE + GRID_CELL_SIZE / 2;
    this.displayObject.y = gridY * GRID_CELL_SIZE + GRID_CELL_SIZE / 2;
  }

  public renderState(state: BombState, radiusGrid: number, teamId: number): void {
    if (
      this.lastRenderedState === state
      && this.lastRenderedRadiusGrid === radiusGrid
      && this.lastRenderedTeamId === teamId
    ) {
      return;
    }

    const { GRID_CELL_SIZE } = config.GAME_CONFIG;
    const bombRadiusPx = GRID_CELL_SIZE * 0.2;
    const explosionRadiusPx = radiusGrid * GRID_CELL_SIZE;

    this.lastRenderedState = state;
    this.lastRenderedRadiusGrid = radiusGrid;
    this.lastRenderedTeamId = teamId;

    const teamColor = this.resolveTeamColor(teamId);

    this.bombGraphic.clear();
    this.explosionGraphic.clear();

    if (state === "armed") {
      this.bombGraphic.circle(0, 0, bombRadiusPx);
      this.bombGraphic.fill({ color: teamColor, alpha: 0.95 });
      this.bombGraphic.stroke({ color: 0xffffff, width: 2 });
      return;
    }

    if (state === "exploded") {
      this.explosionGraphic.circle(0, 0, explosionRadiusPx);
      this.explosionGraphic.fill({ color: teamColor, alpha: 0.35 });
      this.explosionGraphic.stroke({ color: teamColor, width: 3 });
    }
  }

  private resolveTeamColor(teamId: number): number {
    const teamColorCode = config.GAME_CONFIG.TEAM_COLORS[teamId];
    if (typeof teamColorCode !== "string") {
      return config.GAME_CONFIG.MAP_GRID_COLOR;
    }

    const normalizedColorCode = teamColorCode.startsWith("#")
      ? teamColorCode.slice(1)
      : teamColorCode;

    const parsedColor = Number.parseInt(normalizedColorCode, 16);
    if (Number.isNaN(parsedColor)) {
      return config.GAME_CONFIG.MAP_GRID_COLOR;
    }

    return parsedColor;
  }

  public destroy(): void {
    this.displayObject.destroy({ children: true });
  }
}