diff --git a/apps/client/public/Bomb.svg b/apps/client/public/Bomb.svg
new file mode 100644
index 0000000..9ae8186
--- /dev/null
+++ b/apps/client/public/Bomb.svg
@@ -0,0 +1,997 @@
+
\ No newline at end of file
diff --git a/apps/client/src/config/index.ts b/apps/client/src/config/index.ts
index 3ec6399..fcdeb0e 100644
--- a/apps/client/src/config/index.ts
+++ b/apps/client/src/config/index.ts
@@ -1,5 +1,9 @@
import { config as sharedConfig } from "@repo/shared";
+const sharedBombRenderScale =
+ (sharedConfig.GAME_CONFIG as { BOMB_RENDER_SCALE?: number })
+ .BOMB_RENDER_SCALE ?? 1;
+
const CLIENT_GAME_CONFIG = {
TIMER_DISPLAY_UPDATE_MS: 250,
JOIN_REQUEST_TIMEOUT_MS: 8000,
@@ -28,6 +32,9 @@
get PLAYER_RADIUS_PX(): number {
return this.PLAYER_RADIUS * this.GRID_CELL_SIZE;
},
+ get BOMB_RENDER_RADIUS_PX(): number {
+ return this.GRID_CELL_SIZE * 0.2 * sharedBombRenderScale;
+ },
} as const;
const NETWORK_CONFIG = {
diff --git a/apps/client/src/scenes/game/GameManager.ts b/apps/client/src/scenes/game/GameManager.ts
index a12a820..0cea6e5 100644
--- a/apps/client/src/scenes/game/GameManager.ts
+++ b/apps/client/src/scenes/game/GameManager.ts
@@ -55,7 +55,7 @@
public applyPlacedBombAck(payload: BombPlacedAckPayload): void {
this.bombManager?.applyPlacedBombAck(payload);
}
-
+
// 入力と状態管理
private joystickInput = { x: 0, y: 0 };
private isInitialized = false;
@@ -72,6 +72,7 @@
this.myId = myId;
this.app = new Application();
this.worldContainer = new Container();
+ this.worldContainer.sortableChildren = true;
}
/**
@@ -79,12 +80,16 @@
*/
public async init() {
// PixiJS本体の初期化
- await this.app.init({ resizeTo: window, backgroundColor: 0x111111, antialias: true });
+ await this.app.init({
+ resizeTo: window,
+ backgroundColor: 0x111111,
+ antialias: true,
+ });
// 初期化完了前に destroy() が呼ばれていたら、ここで処理を中断して破棄する
if (this.isDestroyed) {
- this.app.destroy(true, { children: true });
- return;
+ this.app.destroy(true, { children: true });
+ return;
}
this.container.appendChild(this.app.canvas);
@@ -164,8 +169,8 @@
this.bombManager = null;
this.players = {};
this.isInputLocked = false;
-
+
// イベント購読の解除
this.networkSync?.unbind();
}
-}
\ No newline at end of file
+}
diff --git a/apps/client/src/scenes/game/entities/bomb/BombView.ts b/apps/client/src/scenes/game/entities/bomb/BombView.ts
index 4a71107..12fe1c4 100644
--- a/apps/client/src/scenes/game/entities/bomb/BombView.ts
+++ b/apps/client/src/scenes/game/entities/bomb/BombView.ts
@@ -4,26 +4,56 @@
* 設置中の見た目と爆風円の表示を管理する
*/
import { Container, Graphics } from "pixi.js";
+import { Assets, Sprite, Texture } from "pixi.js";
import { config } from "@client/config";
import type { BombState } from "./BombModel";
+const ENABLE_DEBUG_LOG = import.meta.env.DEV;
+
/** 爆弾の描画表現を管理するビュー */
export class BombView {
public readonly displayObject: Container;
- private bombGraphic: Graphics;
+ private bombSprite: Sprite;
+ private bombFallbackGraphic: Graphics;
private explosionGraphic: Graphics;
private lastRenderedState: BombState | null = null;
private lastRenderedRadiusGrid: number | null = null;
private lastRenderedColor: number | null = null;
+ private isBombTextureReady = false;
constructor() {
this.displayObject = new Container();
- this.bombGraphic = new Graphics();
+ this.displayObject.zIndex = 1000;
+ this.bombSprite = new Sprite(Texture.WHITE);
+ this.bombSprite.anchor.set(0.5, 0.5);
+ this.bombSprite.visible = false;
+ this.bombFallbackGraphic = new Graphics();
+ this.bombFallbackGraphic.visible = false;
this.explosionGraphic = new Graphics();
this.displayObject.addChild(this.explosionGraphic);
- this.displayObject.addChild(this.bombGraphic);
+ this.displayObject.addChild(this.bombFallbackGraphic);
+ this.displayObject.addChild(this.bombSprite);
+
+ void this.applyBombTexture();
+ }
+
+ private async applyBombTexture(): Promise {
+ const imageUrl = `${import.meta.env.BASE_URL}Bomb.svg`;
+
+ try {
+ const texture = await Assets.load(imageUrl);
+ this.bombSprite.texture = texture;
+ this.isBombTextureReady = true;
+
+ if (ENABLE_DEBUG_LOG) {
+ console.log(`[BombView] Bomb.svg 読み込み成功: ${imageUrl}`);
+ }
+ } catch (error) {
+ this.isBombTextureReady = false;
+ console.error(`[BombView] Bomb.svg 読み込み失敗: ${imageUrl}`, error);
+ }
}
public syncPosition(gridX: number, gridY: number): void {
@@ -41,30 +71,40 @@
if (
this.lastRenderedState === state &&
this.lastRenderedRadiusGrid === radiusGrid &&
- this.lastRenderedColor === color
+ this.lastRenderedColor === color &&
+ state !== "armed"
) {
return;
}
- const { GRID_CELL_SIZE } = config.GAME_CONFIG;
- const bombRadiusPx = GRID_CELL_SIZE * 0.2;
+ const { GRID_CELL_SIZE, BOMB_RENDER_RADIUS_PX } = config.GAME_CONFIG;
+ const bombRadiusPx = BOMB_RENDER_RADIUS_PX;
const explosionRadiusPx = radiusGrid * GRID_CELL_SIZE;
this.lastRenderedState = state;
this.lastRenderedRadiusGrid = radiusGrid;
this.lastRenderedColor = color;
- this.bombGraphic.clear();
this.explosionGraphic.clear();
+ this.bombFallbackGraphic.clear();
if (state === "armed") {
- this.bombGraphic.circle(0, 0, bombRadiusPx);
- this.bombGraphic.fill({ color, alpha: 0.95 });
- this.bombGraphic.stroke({ color: 0xffffff, width: 2 });
+ this.bombSprite.visible = this.isBombTextureReady;
+ this.bombSprite.tint = 0xffffff;
+ this.bombSprite.alpha = 1;
+ this.bombSprite.width = bombRadiusPx * 2;
+ this.bombSprite.height = bombRadiusPx * 2;
+
+ this.bombFallbackGraphic.visible = true;
+ this.bombFallbackGraphic.circle(0, 0, bombRadiusPx);
+ this.bombFallbackGraphic.fill({ color, alpha: 0.92 });
+ this.bombFallbackGraphic.stroke({ color: 0xffffff, width: 3 });
return;
}
if (state === "exploded") {
+ this.bombSprite.visible = false;
+ this.bombFallbackGraphic.visible = false;
this.explosionGraphic.circle(0, 0, explosionRadiusPx);
this.explosionGraphic.fill({ color, alpha: 0.35 });
this.explosionGraphic.stroke({ color, width: 3 });
diff --git a/packages/shared/src/config/gameConfig.ts b/packages/shared/src/config/gameConfig.ts
index 97a5515..c9ac7ac 100644
--- a/packages/shared/src/config/gameConfig.ts
+++ b/packages/shared/src/config/gameConfig.ts
@@ -22,6 +22,7 @@
// 爆弾設定(内部座標はグリッド単位、時間はms、契約値)
BOMB_RADIUS_GRID: 1.5, // 爆風半径(グリッド単位、円形当たり判定)
+ BOMB_RENDER_SCALE: 1.0, // 爆弾見た目サイズ倍率(1=等倍)
BOMB_FUSE_MS: 1000, // 設置から爆発までの時間(ms)
BOMB_COOLDOWN_MS: 3000, // 設置後に次の爆弾を置けるまでの待機時間(ms)
BOMB_DEDUP_EXTRA_TTL_MS: 1000, // 重複排除保持時間の追加分(ms)
@@ -48,9 +49,9 @@
/** teamId が有効範囲内かを真偽値で判定する */
export const isKnownTeamId = (teamId: number): boolean => {
- return Number.isInteger(teamId)
- && teamId >= 0
- && teamId < GAME_CONFIG.TEAM_COUNT;
+ return (
+ Number.isInteger(teamId) && teamId >= 0 && teamId < GAME_CONFIG.TEAM_COUNT
+ );
};
/** TEAM_COUNT と TEAM_NAMES の整合性を検証する */