diff --git a/apps/client/src/scenes/game/GameView.tsx b/apps/client/src/scenes/game/GameView.tsx
index 6f5ccdc..1d67d7a 100644
--- a/apps/client/src/scenes/game/GameView.tsx
+++ b/apps/client/src/scenes/game/GameView.tsx
@@ -16,6 +16,7 @@
GAME_VIEW_TIMER_STYLE,
} from "./styles/GameView.styles";
import { config } from "@client/config";
+import { buildRespawnHeartGauge } from "./input/presentation/GameUiPresenter";
/** 表示と入力に必要なプロパティ */
type Props = {
@@ -91,17 +92,6 @@
);
};
-const buildHeartGauge = (localBombHitCount: number): string => {
- const maxHearts = Math.max(
- 1,
- Math.floor(config.GAME_CONFIG.PLAYER_RESPAWN_HIT_COUNT),
- );
- const clampedHitCount = Math.min(Math.max(localBombHitCount, 0), maxHearts);
- const remainingHearts = maxHearts - clampedHitCount;
-
- return `${"❤️".repeat(remainingHearts)}${"🤍".repeat(clampedHitCount)}`;
-};
-
/** 画面描画と入力UIをまとめて描画する */
export const GameView = ({
timeLeft,
@@ -116,7 +106,7 @@
const remainingSeconds = parseRemainingSeconds(timeLeft);
const isFeverTime =
remainingSeconds <= config.GAME_CONFIG.BOMB_FEVER_START_REMAINING_SEC;
- const heartGauge = buildHeartGauge(localBombHitCount);
+ const heartGauge = buildRespawnHeartGauge(localBombHitCount);
return (
diff --git a/apps/client/src/scenes/game/application/combat/CombatLifecycleFacade.ts b/apps/client/src/scenes/game/application/combat/CombatLifecycleFacade.ts
index 5feea20..f5058e1 100644
--- a/apps/client/src/scenes/game/application/combat/CombatLifecycleFacade.ts
+++ b/apps/client/src/scenes/game/application/combat/CombatLifecycleFacade.ts
@@ -20,6 +20,8 @@
onLocalBombHitCountChanged: (count: number) => void;
};
+type RespawnState = "idle" | "counting" | "pendingRespawn";
+
/** 被弾関連ライフサイクルの制御を担当する */
export class CombatLifecycleFacade {
private readonly players: GamePlayers;
@@ -30,7 +32,7 @@
private readonly playerHitPolicy: PlayerHitPolicy;
private readonly playerHitEffectOrchestrator: PlayerHitEffectOrchestrator;
private localBombHitCount = 0;
- private isRespawnPending = false;
+ private respawnState: RespawnState = "idle";
private respawnTimer: ReturnType | null = null;
constructor({
@@ -64,7 +66,7 @@
public handleBombExploded(payload: BombExplodedPayload): void {
const hitPlayerId = this.bombHitOrchestrator.evaluateHit(payload);
if (!hitPlayerId) return;
- if (this.isRespawnPending) return;
+ if (this.respawnState === "pendingRespawn") return;
this.handleLocalBombHit();
this.playerHitPolicy.applyLocalHitStun();
@@ -97,6 +99,10 @@
}
private handleLocalBombHit(): void {
+ if (this.respawnState === "idle") {
+ this.respawnState = "counting";
+ }
+
this.localBombHitCount += 1;
this.onLocalBombHitCountChanged(this.localBombHitCount);
@@ -104,7 +110,7 @@
return;
}
- this.isRespawnPending = true;
+ this.respawnState = "pendingRespawn";
if (this.respawnTimer) {
clearTimeout(this.respawnTimer);
this.respawnTimer = null;
@@ -119,7 +125,7 @@
}
this.localBombHitCount = 0;
- this.isRespawnPending = false;
+ this.respawnState = "idle";
this.onLocalBombHitCountChanged(this.localBombHitCount);
}, config.GAME_CONFIG.PLAYER_HIT_STUN_MS);
}
diff --git a/apps/client/src/scenes/game/input/presentation/GameUiPresenter.ts b/apps/client/src/scenes/game/input/presentation/GameUiPresenter.ts
index b9b8cec..9c29eff 100644
--- a/apps/client/src/scenes/game/input/presentation/GameUiPresenter.ts
+++ b/apps/client/src/scenes/game/input/presentation/GameUiPresenter.ts
@@ -22,4 +22,16 @@
/** ゲーム画面の初期残り時間表示を返す */
export const getInitialTimeDisplay = (): string => {
return formatRemainingTime(config.GAME_CONFIG.GAME_DURATION_SEC);
+};
+
+/** 被弾回数からハートゲージ表示を生成する */
+export const buildRespawnHeartGauge = (localBombHitCount: number): string => {
+ const maxHearts = Math.max(
+ 1,
+ Math.floor(config.GAME_CONFIG.PLAYER_RESPAWN_HIT_COUNT),
+ );
+ const clampedHitCount = Math.min(Math.max(localBombHitCount, 0), maxHearts);
+ const remainingHearts = maxHearts - clampedHitCount;
+
+ return `${"❤️".repeat(remainingHearts)}${"🤍".repeat(clampedHitCount)}`;
};
\ No newline at end of file