Newer
Older
PixelPaintWar / apps / client / src / scenes / game / entities / bomb / BombHitBlinkRenderer.ts
/**
 * BombHitBlinkRenderer
 * 爆弾被弾時の点滅演出を描画オブジェクトへ適用する
 * 点滅開始,停止,破棄を一元管理する
 */
import { Ticker, type Container } from "pixi.js";

type BombHitBlinkRendererOptions = {
  target: Container;
  ticker?: Ticker;
  blinkIntervalMs?: number;
  hiddenAlpha?: number;
  maxDeltaMs?: number;
};

/** 爆弾被弾時の点滅演出を制御するレンダラー */
export class BombHitBlinkRenderer {
  private readonly target: Container;
  private readonly ticker: Ticker;
  private readonly blinkIntervalMs: number;
  private readonly hiddenAlpha: number;
  private readonly maxDeltaMs: number;
  private isVisibleFrame = true;
  private isPlaying = false;
  private remainingDurationMs = 0;
  private elapsedSinceToggleMs = 0;

  constructor({
    target,
    ticker = Ticker.shared,
    blinkIntervalMs = 100,
    hiddenAlpha = 0.2,
    maxDeltaMs = 50,
  }: BombHitBlinkRendererOptions) {
    this.target = target;
    this.ticker = ticker;
    this.blinkIntervalMs = blinkIntervalMs;
    this.hiddenAlpha = hiddenAlpha;
    this.maxDeltaMs = maxDeltaMs;
  }

  /** 指定時間だけ点滅演出を再生する */
  public play(durationMs: number): void {
    this.stop();
    if (durationMs <= 0) {
      return;
    }

    this.isPlaying = true;
    this.remainingDurationMs = durationMs;
    this.elapsedSinceToggleMs = 0;
    this.isVisibleFrame = true;
    this.target.alpha = 1;
    this.ticker.add(this.handleTick);
  }

  /** 点滅演出を停止し描画状態を初期化する */
  public stop(): void {
    if (this.isPlaying) {
      this.ticker.remove(this.handleTick);
    }

    this.isPlaying = false;
    this.remainingDurationMs = 0;
    this.elapsedSinceToggleMs = 0;
    this.isVisibleFrame = true;
    this.target.alpha = 1;
  }

  /** 管理中タイマーを破棄し演出を停止する */
  public destroy(): void {
    this.stop();
  }

  private handleTick = (ticker: Ticker): void => {
    if (!this.isPlaying) {
      return;
    }

    const deltaMs = Math.min(ticker.deltaMS, this.maxDeltaMs);
    this.remainingDurationMs -= deltaMs;
    this.elapsedSinceToggleMs += deltaMs;

    while (this.elapsedSinceToggleMs >= this.blinkIntervalMs) {
      this.elapsedSinceToggleMs -= this.blinkIntervalMs;
      this.isVisibleFrame = !this.isVisibleFrame;
      this.target.alpha = this.isVisibleFrame ? 1 : this.hiddenAlpha;
    }

    if (this.remainingDurationMs <= 0) {
      this.stop();
    }
  };
}