diff --git a/apps/client/src/scenes/game/GameManager.ts b/apps/client/src/scenes/game/GameManager.ts
index 6bff087..9429ad1 100644
--- a/apps/client/src/scenes/game/GameManager.ts
+++ b/apps/client/src/scenes/game/GameManager.ts
@@ -215,14 +215,21 @@
}
private getUiStateSnapshot(): GameUiState {
+ const miniMapTeamIds = this.runtime.getMiniMapTeamIds();
+
return {
- remainingTimeSec: Math.floor(this.sessionFacade.getRemainingTime()),
- startCountdownSec: this.sessionFacade.getStartCountdownSec(),
- isInputEnabled: this.runtime.isInputEnabled(),
- teamPaintRates: this.runtime.getPaintRatesByTeam(),
- miniMapTeamIds: this.runtime.getMiniMapTeamIds(),
- localBombHitCount: this.localBombHitCount,
- localPlayerPosition: this.runtime.getLocalPlayerPosition(),
+ hud: {
+ remainingTimeSec: Math.floor(this.sessionFacade.getRemainingTime()),
+ startCountdownSec: this.sessionFacade.getStartCountdownSec(),
+ isInputEnabled: this.runtime.isInputEnabled(),
+ teamPaintRates: this.runtime.getPaintRatesByTeam(),
+ localBombHitCount: this.localBombHitCount,
+ },
+ miniMap: {
+ mapRevision: this.runtime.getMiniMapRevision(),
+ teamIds: miniMapTeamIds,
+ localPlayerPosition: this.runtime.getLocalPlayerPosition(),
+ },
};
}
diff --git a/apps/client/src/scenes/game/GameView.tsx b/apps/client/src/scenes/game/GameView.tsx
index e17cc44..55e51a6 100644
--- a/apps/client/src/scenes/game/GameView.tsx
+++ b/apps/client/src/scenes/game/GameView.tsx
@@ -8,10 +8,6 @@
GAME_VIEW_BOMB_HIT_DEBUG_STYLE,
GAME_VIEW_FEVER_TEXT_STYLE,
GAME_VIEW_HURRICANE_WARNING_STYLE,
- GAME_VIEW_TOP_RIGHT_OVERLAY_STYLE,
- GAME_VIEW_PAINT_RATE_ITEM_STYLE,
- GAME_VIEW_PAINT_RATE_PANEL_STYLE,
- GAME_VIEW_PAINT_RATE_SQUARE_STYLE,
GAME_VIEW_PIXI_LAYER_STYLE,
GAME_VIEW_ROOT_STYLE,
GAME_VIEW_START_COUNTDOWN_STYLE,
@@ -19,7 +15,7 @@
} from "./styles/GameView.styles";
import { config } from "@client/config";
import { buildRespawnHeartGauge } from "./input/presentation/GameUiPresenter";
-import { MiniMapPanel } from "./input/minimap/presentation/MiniMapPanel";
+import { TopRightHud } from "./presentation/TopRightHud";
/** 表示と入力に必要なプロパティ */
type Props = {
@@ -66,37 +62,6 @@
);
};
-const TeamPaintRateOverlay = ({
- teamPaintRates,
- remainingSeconds,
-}: {
- teamPaintRates: number[];
- remainingSeconds: number;
-}) => {
- const shouldMaskPaintRate = remainingSeconds <= 30;
-
- return (
-
- {teamPaintRates.map((rate, index) => (
-
-
- ■
-
- {shouldMaskPaintRate ? "???%" : `${Math.round(rate)}%`}
-
- ))}
-
- );
-};
-
/** 画面描画と入力UIをまとめて描画する */
export const GameView = ({
timeLeft,
@@ -121,16 +86,12 @@
{/* タイマーUIの表示 */}
HP: {heartGauge}
-
-
-
-
+
{remainingSeconds === 60 && (
!Fever Tieme!
diff --git a/apps/client/src/scenes/game/application/runtime/GameSceneRuntime.ts b/apps/client/src/scenes/game/application/runtime/GameSceneRuntime.ts
index 89f0c5f..6fb417e 100644
--- a/apps/client/src/scenes/game/application/runtime/GameSceneRuntime.ts
+++ b/apps/client/src/scenes/game/application/runtime/GameSceneRuntime.ts
@@ -60,6 +60,12 @@
private joystickInput = { x: 0, y: 0 };
private tickerHandler: ((ticker: Ticker) => void) | null = null;
private lifecycleState: RuntimeLifecycleState = "created";
+ private miniMapCache = {
+ revision: -1,
+ teamIds: new Array(
+ config.GAME_CONFIG.GRID_COLS * config.GAME_CONFIG.GRID_ROWS,
+ ).fill(-1),
+ };
constructor({
app,
@@ -196,11 +202,23 @@
/** ミニマップ描画用の全セルteamId配列を返す */
public getMiniMapTeamIds(): number[] {
if (!this.gameMap) {
- const totalCells = config.GAME_CONFIG.GRID_COLS * config.GAME_CONFIG.GRID_ROWS;
- return new Array(totalCells).fill(-1);
+ return this.miniMapCache.teamIds;
}
- return this.gameMap.getAllCellTeamIds();
+ const revision = this.gameMap.getMapRevision();
+ if (revision !== this.miniMapCache.revision) {
+ this.miniMapCache = {
+ revision,
+ teamIds: this.gameMap.getAllCellTeamIds(),
+ };
+ }
+
+ return this.miniMapCache.teamIds;
+ }
+
+ /** ミニマップ描画用のマップ更新リビジョンを返す */
+ public getMiniMapRevision(): number {
+ return this.miniMapCache.revision;
}
/** ローカルプレイヤーの現在座標を返す */
diff --git a/apps/client/src/scenes/game/application/ui/GameUiStateSyncService.ts b/apps/client/src/scenes/game/application/ui/GameUiStateSyncService.ts
index 16065fd..0ccf12e 100644
--- a/apps/client/src/scenes/game/application/ui/GameUiStateSyncService.ts
+++ b/apps/client/src/scenes/game/application/ui/GameUiStateSyncService.ts
@@ -11,28 +11,25 @@
const UI_STATE_SECOND_MS = 1000;
/** ゲーム画面UIへ通知する状態スナップショット */
-export type GameUiState = {
+export type GameHudState = {
remainingTimeSec: number;
startCountdownSec: number;
isInputEnabled: boolean;
teamPaintRates: number[];
- miniMapTeamIds: number[];
localBombHitCount: number;
+};
+
+/** ミニマップへ通知する状態スナップショット */
+export type MiniMapState = {
+ mapRevision: number;
+ teamIds: number[];
localPlayerPosition: { x: number; y: number } | null;
};
-const isSameMiniMapTeamIds = (a: number[], b: number[]): boolean => {
- if (a.length !== b.length) {
- return false;
- }
-
- for (let index = 0; index < a.length; index += 1) {
- if (a[index] !== b[index]) {
- return false;
- }
- }
-
- return true;
+/** ゲーム画面UIへ通知する状態スナップショット */
+export type GameUiState = {
+ hud: GameHudState;
+ miniMap: MiniMapState;
};
const isSameLocalPlayerPosition = (
@@ -101,18 +98,18 @@
if (
!force &&
this.lastState &&
- this.lastState.remainingTimeSec === snapshot.remainingTimeSec &&
- this.lastState.startCountdownSec === snapshot.startCountdownSec &&
- this.lastState.isInputEnabled === snapshot.isInputEnabled &&
- this.lastState.localBombHitCount === snapshot.localBombHitCount &&
- isSamePaintRates(this.lastState.teamPaintRates, snapshot.teamPaintRates) &&
- isSameMiniMapTeamIds(
- this.lastState.miniMapTeamIds,
- snapshot.miniMapTeamIds,
+ this.lastState.hud.remainingTimeSec === snapshot.hud.remainingTimeSec &&
+ this.lastState.hud.startCountdownSec === snapshot.hud.startCountdownSec &&
+ this.lastState.hud.isInputEnabled === snapshot.hud.isInputEnabled &&
+ this.lastState.hud.localBombHitCount === snapshot.hud.localBombHitCount &&
+ isSamePaintRates(
+ this.lastState.hud.teamPaintRates,
+ snapshot.hud.teamPaintRates,
) &&
+ this.lastState.miniMap.mapRevision === snapshot.miniMap.mapRevision &&
isSameLocalPlayerPosition(
- this.lastState.localPlayerPosition,
- snapshot.localPlayerPosition,
+ this.lastState.miniMap.localPlayerPosition,
+ snapshot.miniMap.localPlayerPosition,
)
) {
return;
diff --git a/apps/client/src/scenes/game/entities/map/GameMapController.ts b/apps/client/src/scenes/game/entities/map/GameMapController.ts
index 5d6733d..337fca4 100644
--- a/apps/client/src/scenes/game/entities/map/GameMapController.ts
+++ b/apps/client/src/scenes/game/entities/map/GameMapController.ts
@@ -61,6 +61,11 @@
return this.model.getAllTeamIds();
}
+ /** 現在のマップ更新リビジョンを取得する */
+ public getMapRevision(): number {
+ return this.model.getRevision();
+ }
+
/** すべてのセルteamIdを描画色へ変換する */
private resolveAllCellColors(teamIds: number[]): Array {
return teamIds.map((teamId) => this.resolveCellColor(teamId));
diff --git a/apps/client/src/scenes/game/entities/map/GameMapModel.ts b/apps/client/src/scenes/game/entities/map/GameMapModel.ts
index c9ad37c..c95324d 100644
--- a/apps/client/src/scenes/game/entities/map/GameMapModel.ts
+++ b/apps/client/src/scenes/game/entities/map/GameMapModel.ts
@@ -9,6 +9,7 @@
/** マップセル状態の計算責務を担うモデル */
export class GameMapModel {
private readonly cellTeamIds: number[];
+ private revision = 0;
/** 設定値に基づいて初期セル状態を構築する */
constructor() {
@@ -18,21 +19,42 @@
/** 全体マップ状態を適用する */
public applyMapState(state: domain.game.gridMap.MapState): void {
+ let hasChanged = false;
const maxLength = Math.min(
this.cellTeamIds.length,
state.gridColors.length,
);
for (let index = 0; index < maxLength; index++) {
- this.cellTeamIds[index] = state.gridColors[index];
+ const nextTeamId = state.gridColors[index];
+ if (this.cellTeamIds[index] === nextTeamId) {
+ continue;
+ }
+
+ this.cellTeamIds[index] = nextTeamId;
+ hasChanged = true;
+ }
+
+ if (hasChanged) {
+ this.revision += 1;
}
}
/** 差分セル更新を適用する */
public applyUpdates(updates: domain.game.gridMap.CellUpdate[]): void {
+ let hasChanged = false;
updates.forEach(({ index, teamId }) => {
if (!this.isValidIndex(index)) return;
+ if (this.cellTeamIds[index] === teamId) {
+ return;
+ }
+
this.cellTeamIds[index] = teamId;
+ hasChanged = true;
});
+
+ if (hasChanged) {
+ this.revision += 1;
+ }
}
/** 指定セルのチームIDを取得する */
@@ -46,6 +68,11 @@
return [...this.cellTeamIds];
}
+ /** 現在のマップ更新リビジョンを返す */
+ public getRevision(): number {
+ return this.revision;
+ }
+
/** チームごとの塗り率配列を取得する */
public getPaintRatesByTeam(teamCount: number): number[] {
if (teamCount <= 0) {
diff --git a/apps/client/src/scenes/game/hooks/useGameSceneController.ts b/apps/client/src/scenes/game/hooks/useGameSceneController.ts
index f253f00..f285bdd 100644
--- a/apps/client/src/scenes/game/hooks/useGameSceneController.ts
+++ b/apps/client/src/scenes/game/hooks/useGameSceneController.ts
@@ -48,23 +48,23 @@
gameManagerRef.current = manager;
const unsubscribeUiState = manager.subscribeUiState((state) => {
- const nextDisplay = formatRemainingTime(state.remainingTimeSec);
+ const nextDisplay = formatRemainingTime(state.hud.remainingTimeSec);
setTimeLeft((prev) => (prev === nextDisplay ? prev : nextDisplay));
- const nextCountdown = buildStartCountdownText(state.startCountdownSec);
+ const nextCountdown = buildStartCountdownText(state.hud.startCountdownSec);
setStartCountdownText((prev) =>
prev === nextCountdown ? prev : nextCountdown,
);
- const nextInputEnabled = state.isInputEnabled;
+ const nextInputEnabled = state.hud.isInputEnabled;
setIsInputEnabled((prev) =>
prev === nextInputEnabled ? prev : nextInputEnabled,
);
- setTeamPaintRates(state.teamPaintRates);
- setMiniMapTeamIds(state.miniMapTeamIds);
- setLocalBombHitCount(state.localBombHitCount);
- setLocalPlayerPosition(state.localPlayerPosition);
+ setTeamPaintRates(state.hud.teamPaintRates);
+ setMiniMapTeamIds(state.miniMap.teamIds);
+ setLocalBombHitCount(state.hud.localBombHitCount);
+ setLocalPlayerPosition(state.miniMap.localPlayerPosition);
});
return () => {
diff --git a/apps/client/src/scenes/game/input/bomb/presentation/BombButton.tsx b/apps/client/src/scenes/game/input/bomb/presentation/BombButton.tsx
index 77c3b54..43887d0 100644
--- a/apps/client/src/scenes/game/input/bomb/presentation/BombButton.tsx
+++ b/apps/client/src/scenes/game/input/bomb/presentation/BombButton.tsx
@@ -8,6 +8,7 @@
buildBombButtonHitAreaStyle,
buildBombButtonStyle,
} from "./BombButton.styles";
+import { useImmediatePressHandlers } from "@client/scenes/game/input/presentation/useImmediatePressHandlers";
/** 爆弾設置ボタンの入力プロパティ */
export type BombButtonProps = {
@@ -38,39 +39,24 @@
onPress();
};
- const handlePointerDown = (event: React.PointerEvent) => {
- event.preventDefault();
- handleActivate();
- };
-
- const handleTouchStart = (event: React.TouchEvent) => {
- event.preventDefault();
- handleActivate();
- };
-
- const handleMouseDown = (event: React.MouseEvent) => {
- event.preventDefault();
- handleActivate();
- };
+ const { onPointerDown: onHitAreaPointerDown, onClick: onHitAreaClick } =
+ useImmediatePressHandlers(handleActivate, {
+ stopPropagation: false,
+ });
+ const { onPointerDown: onButtonPointerDown, onClick: onButtonClick } =
+ useImmediatePressHandlers(handleActivate);
return (