Newer
Older
PixelPaintWar / apps / client / src / scenes / game / entities / player / PlayerController.ts
/**
 * PlayerController
 * 外部入出力とModel/Viewの橋渡しを担うコントローラー群
 * ローカル入力適用,リモート更新適用,描画同期を分離して扱う
 */
import type { playerTypes } from '@repo/shared';
import { PlayerModel } from './PlayerModel';
import { PlayerView } from './PlayerView';

/** ローカル移動入力を表す型 */
export type LocalInput = {
  axisX: number;
  axisY: number;
  deltaTime: number;
};

/** リモート移動更新を表す型 */
export type RemoteUpdate = Partial<playerTypes.MovePayload>;

/**
 * ローカル用コントローラーとリモート用コントローラーの共通基底
 */
abstract class BasePlayerController {
  protected readonly model: PlayerModel;
  protected readonly view: PlayerView;

  /** 共通初期化としてModelとViewを生成する */
  protected constructor(data: playerTypes.PlayerData, isLocal: boolean) {
    this.model = new PlayerModel(data);
    this.view = new PlayerView(data.teamId, isLocal);

    const pos = this.model.getPosition();
    this.view.syncPosition(pos.x, pos.y);
  }

  /** 描画オブジェクトを取得する */
  public getDisplayObject() {
    return this.view.displayObject;
  }

  /** 現在座標を取得する */
  public getPosition(): playerTypes.MovePayload {
    return this.model.getPosition();
  }

  /** 外部送信用スナップショットを取得する */
  public getSnapshot(): playerTypes.PlayerData {
    return this.model.getSnapshot();
  }

  /** 管理中の描画リソースを破棄する */
  public destroy(): void {
    this.view.destroy();
  }
}

/** ローカルプレイヤーの入力適用と描画同期を担うコントローラー */
export class LocalPlayerController extends BasePlayerController {
  /** ローカルプレイヤー用コントローラーを初期化する */
  constructor(data: playerTypes.PlayerData) {
    super(data, true);
  }

  /** ローカル入力を座標計算へ適用する */
  public applyLocalInput(input: LocalInput): void {
    this.model.moveLocal(input.axisX, input.axisY, input.deltaTime);
  }

  /** 毎フレームの描画同期を行う */
  public tick(): void {
    const pos = this.model.getPosition();
    this.view.syncPosition(pos.x, pos.y);
  }
}

/** リモートプレイヤーの更新適用と補間同期を担うコントローラー */
export class RemotePlayerController extends BasePlayerController {
  /** リモートプレイヤー用コントローラーを初期化する */
  constructor(data: playerTypes.PlayerData) {
    super(data, false);
  }

  /** ネットワーク更新を目標座標へ反映する */
  public applyRemoteUpdate(update: RemoteUpdate): void {
    this.model.setRemoteTarget(update);
  }

  /** 毎フレームの補間更新と描画同期を行う */
  public tick(deltaTime: number): void {
    this.model.updateRemoteLerp(deltaTime);
    const pos = this.model.getPosition();
    this.view.syncPosition(pos.x, pos.y);
  }
}