/**
* PlayerController
* 外部入出力とModel/Viewの橋渡しを担うコントローラー群
* ローカル入力適用,リモート更新適用,描画同期を分離して扱う
*/
import { domain } from "@repo/shared";
import { config } from "@client/config";
import { AppearanceResolver } from "@client/scenes/game/application/AppearanceResolver";
import { BombHitBlinkRenderer } from "@client/scenes/game/entities/bomb/BombHitBlinkRenderer";
import { PlayerModel } from "./PlayerModel";
import { PlayerView } from "./PlayerView";
/** ローカル移動入力を表す型 */
export type LocalInput = {
axisX: number;
axisY: number;
deltaTime: number;
};
/** リモート移動更新を表す型 */
export type RemoteUpdate = Partial<domain.game.player.MovePayload>;
/**
* ローカル用コントローラーとリモート用コントローラーの共通基底
*/
abstract class BasePlayerController {
protected readonly model: PlayerModel;
protected readonly view: PlayerView;
private readonly bombHitBlinkRenderer: BombHitBlinkRenderer;
/** 共通初期化としてModelとViewを生成する */
protected constructor(
data: domain.game.player.PlayerData,
isLocal: boolean,
appearanceResolver: AppearanceResolver,
) {
this.model = new PlayerModel(data);
this.view = new PlayerView(
appearanceResolver.resolvePlayerImageFile(data.teamId),
data.name,
isLocal,
);
this.bombHitBlinkRenderer = new BombHitBlinkRenderer({
target: this.view.displayObject,
blinkIntervalMs: config.GAME_CONFIG.PLAYER_HIT_EFFECT.BLINK_INTERVAL_MS,
hiddenAlpha: config.GAME_CONFIG.PLAYER_HIT_EFFECT.BLINK_HIDDEN_ALPHA,
maxDeltaMs: config.GAME_CONFIG.PLAYER_HIT_EFFECT.BLINK_MAX_DELTA_MS,
});
const pos = this.model.getPosition();
this.view.syncPosition(pos.x, pos.y);
}
/** 描画オブジェクトを取得する */
public getDisplayObject() {
return this.view.displayObject;
}
/** 現在座標を取得する */
public getPosition(): domain.game.player.MovePayload {
return this.model.getPosition();
}
/** 外部送信用スナップショットを取得する */
public getSnapshot(): domain.game.player.PlayerData {
return this.model.getSnapshot();
}
/** 初期位置へリスポーンし描画位置を同期する */
public respawnToInitialPosition(): void {
this.model.resetToInitialPosition();
const position = this.model.getPosition();
this.view.syncPosition(position.x, position.y);
}
/** 爆弾被弾時の点滅演出を再生する */
public playBombHitBlink(durationMs: number): void {
this.bombHitBlinkRenderer.play(durationMs);
}
/** 管理中の描画リソースを破棄する */
public destroy(): void {
this.bombHitBlinkRenderer.destroy();
this.view.destroy();
}
}
/** ローカルプレイヤーの入力適用と描画同期を担うコントローラー */
export class LocalPlayerController extends BasePlayerController {
/** ローカルプレイヤー用コントローラーを初期化する */
constructor(
data: domain.game.player.PlayerData,
appearanceResolver: AppearanceResolver,
) {
super(data, true, appearanceResolver);
}
/** ローカル入力を座標計算へ適用する */
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: domain.game.player.PlayerData,
appearanceResolver: AppearanceResolver,
) {
super(data, false, appearanceResolver);
}
/** ネットワーク更新を目標座標へ反映する */
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);
}
}