diff --git a/apps/client/src/entities/GameMap.ts b/apps/client/src/entities/GameMap.ts index d464910..bf79d9c 100644 --- a/apps/client/src/entities/GameMap.ts +++ b/apps/client/src/entities/GameMap.ts @@ -50,24 +50,24 @@ // 設定値参照によるマップ外観(背景・グリッド線)の組み立て処理 private drawBaseMap() { const { - MAP_WIDTH, MAP_HEIGHT, GRID_CELL_SIZE, + MAP_WIDTH_PX, MAP_HEIGHT_PX, GRID_CELL_SIZE, MAP_BG_COLOR, MAP_GRID_COLOR, MAP_BORDER_COLOR } = config.GAME_CONFIG; // マップ全域背景レイヤー - this.bgGraphics.rect(0, 0, MAP_WIDTH, MAP_HEIGHT).fill(MAP_BG_COLOR); + this.bgGraphics.rect(0, 0, MAP_WIDTH_PX, MAP_HEIGHT_PX).fill(MAP_BG_COLOR); // 縦方向グリッド線 - for (let x = 0; x <= MAP_WIDTH; x += GRID_CELL_SIZE) { - this.gridGraphics.moveTo(x, 0).lineTo(x, MAP_HEIGHT).stroke({ width: 1, color: MAP_GRID_COLOR }); + for (let x = 0; x <= MAP_WIDTH_PX; x += GRID_CELL_SIZE) { + this.gridGraphics.moveTo(x, 0).lineTo(x, MAP_HEIGHT_PX).stroke({ width: 1, color: MAP_GRID_COLOR }); } // 横方向グリッド線 - for (let y = 0; y <= MAP_HEIGHT; y += GRID_CELL_SIZE) { - this.gridGraphics.moveTo(0, y).lineTo(MAP_WIDTH, y).stroke({ width: 1, color: MAP_GRID_COLOR }); + for (let y = 0; y <= MAP_HEIGHT_PX; y += GRID_CELL_SIZE) { + this.gridGraphics.moveTo(0, y).lineTo(MAP_WIDTH_PX, y).stroke({ width: 1, color: MAP_GRID_COLOR }); } // プレイ領域外枠 - this.gridGraphics.rect(0, 0, MAP_WIDTH, MAP_HEIGHT).stroke({ width: 5, color: MAP_BORDER_COLOR }); + this.gridGraphics.rect(0, 0, MAP_WIDTH_PX, MAP_HEIGHT_PX).stroke({ width: 5, color: MAP_BORDER_COLOR }); } /** diff --git a/apps/client/src/entities/Player.ts b/apps/client/src/entities/Player.ts index 60465fe..1408491 100644 --- a/apps/client/src/entities/Player.ts +++ b/apps/client/src/entities/Player.ts @@ -8,18 +8,22 @@ export abstract class BasePlayer extends Graphics { public id: string; public teamId: number; + public gridX: number; + public gridY: number; constructor(data: playerTypes.PlayerData, isLocal: boolean = false) { super(); this.id = data.id; this.teamId = data.teamId; - // 初期座標のセット - this.position.set(data.x, data.y); + // 初期座標のセット(内部はグリッド単位) + this.gridX = data.x; + this.gridY = data.y; // gameConfigから定数を取得 const { - PLAYER_RADIUS, + GRID_CELL_SIZE, + PLAYER_RADIUS_PX, TEAM_COLORS, PLAYER_LOCAL_STROKE_COLOR, PLAYER_LOCAL_STROKE_WIDTH, @@ -27,6 +31,8 @@ PLAYER_REMOTE_STROKE_WIDTH } = config.GAME_CONFIG; + this.position.set(this.gridX * GRID_CELL_SIZE, this.gridY * GRID_CELL_SIZE); + // チームIDに対応する色をHEX文字列('#RRGGBB')で取得し、PixiJS用の数値(0xRRGGBB)に変換 const colorString = TEAM_COLORS[this.teamId] || '#FFFFFF'; const hexColor = parseInt(colorString.replace("#", "0x"), 16); @@ -36,11 +42,16 @@ const strokeWidth = isLocal ? PLAYER_LOCAL_STROKE_WIDTH : PLAYER_REMOTE_STROKE_WIDTH; // 塗りつぶしと枠線を同時に描画 - this.circle(0, 0, PLAYER_RADIUS) + this.circle(0, 0, PLAYER_RADIUS_PX) .fill(hexColor) .stroke({ width: strokeWidth, color: strokeColor }); } + protected syncDisplayPosition() { + const { GRID_CELL_SIZE } = config.GAME_CONFIG; + this.position.set(this.gridX * GRID_CELL_SIZE, this.gridY * GRID_CELL_SIZE); + } + // 毎フレーム呼ばれる更新メソッド(サブクラスで具体的な処理を実装させる) abstract update(deltaTime: number): void; } @@ -57,15 +68,17 @@ * 入力ベクトルと経過時間基準の座標更新処理 */ public move(vx: number, vy: number, deltaTime: number) { - const { PLAYER_SPEED, MAP_WIDTH, MAP_HEIGHT, PLAYER_RADIUS } = config.GAME_CONFIG; + const { PLAYER_SPEED, GRID_COLS, GRID_ROWS, PLAYER_RADIUS } = config.GAME_CONFIG; const speed = PLAYER_SPEED * deltaTime; - this.x += vx * speed; - this.y += vy * speed; + this.gridX += vx * speed; + this.gridY += vy * speed; // 画面外に出ないようにクランプ - this.x = Math.max(PLAYER_RADIUS, Math.min(MAP_WIDTH - PLAYER_RADIUS, this.x)); - this.y = Math.max(PLAYER_RADIUS, Math.min(MAP_HEIGHT - PLAYER_RADIUS, this.y)); + this.gridX = Math.max(PLAYER_RADIUS, Math.min(GRID_COLS - PLAYER_RADIUS, this.gridX)); + this.gridY = Math.max(PLAYER_RADIUS, Math.min(GRID_ROWS - PLAYER_RADIUS, this.gridY)); + + this.syncDisplayPosition(); } public update(_deltaTime: number): void { @@ -77,21 +90,21 @@ * 他プレイヤー(サーバーからの通信を受信して補間・吸着移動する) */ export class RemotePlayer extends BasePlayer { - private targetX: number; - private targetY: number; + private targetGridX: number; + private targetGridY: number; constructor(data: playerTypes.PlayerData) { super(data, false); - this.targetX = data.x; - this.targetY = data.y; + this.targetGridX = data.x; + this.targetGridY = data.y; } /** * サーバーから受信した最新の座標を目標としてセットする */ public setTargetPosition(x?: number, y?: number) { - if (x !== undefined) this.targetX = x; - if (y !== undefined) this.targetY = y; + if (x !== undefined) this.targetGridX = x; + if (y !== undefined) this.targetGridY = y; } /** @@ -100,21 +113,23 @@ public update(deltaTime: number): void { const { PLAYER_LERP_SNAP_THRESHOLD, PLAYER_LERP_SMOOTHNESS } = config.GAME_CONFIG; - const diffX = this.targetX - this.x; - const diffY = this.targetY - this.y; + const diffX = this.targetGridX - this.gridX; + const diffY = this.targetGridY - this.gridY; // X軸の補間 if (Math.abs(diffX) < PLAYER_LERP_SNAP_THRESHOLD) { - this.x = this.targetX; + this.gridX = this.targetGridX; } else { - this.x += diffX * PLAYER_LERP_SMOOTHNESS * deltaTime; + this.gridX += diffX * PLAYER_LERP_SMOOTHNESS * deltaTime; } // Y軸の補間 if (Math.abs(diffY) < PLAYER_LERP_SNAP_THRESHOLD) { - this.y = this.targetY; + this.gridY = this.targetGridY; } else { - this.y += diffY * PLAYER_LERP_SMOOTHNESS * deltaTime; + this.gridY += diffY * PLAYER_LERP_SMOOTHNESS * deltaTime; } + + this.syncDisplayPosition(); } } \ No newline at end of file diff --git a/apps/client/src/managers/GameManager.ts b/apps/client/src/managers/GameManager.ts index e6024e4..63eb182 100644 --- a/apps/client/src/managers/GameManager.ts +++ b/apps/client/src/managers/GameManager.ts @@ -140,26 +140,28 @@ const me = this.players[this.myId]; if (!me || !(me instanceof LocalPlayer)) return; + const deltaSeconds = ticker.deltaMS / 1000; + // 1. 自プレイヤーの移動と送信 const { x: dx, y: dy } = this.joystickInput; const isMoving = dx !== 0 || dy !== 0; if (isMoving) { - me.move(dx / MAX_DIST, dy / MAX_DIST, ticker.deltaTime); + me.move(dx / MAX_DIST, dy / MAX_DIST, deltaSeconds); const now = performance.now(); if (now - this.lastPositionSentTime >= config.GAME_CONFIG.PLAYER_POSITION_UPDATE_MS) { - socketClient.sendMove(me.x, me.y); + socketClient.sendMove(me.gridX, me.gridY); this.lastPositionSentTime = now; } } else if (this.wasMoving) { - socketClient.sendMove(me.x, me.y); + socketClient.sendMove(me.gridX, me.gridY); } this.wasMoving = isMoving; // 2. 全プレイヤーの更新(Lerpなど) Object.values(this.players).forEach((player) => { - player.update(ticker.deltaTime); + player.update(deltaSeconds); }); // 3. カメラの追従(自分を中心に) diff --git a/apps/server/src/domains/game/GameManager.ts b/apps/server/src/domains/game/GameManager.ts index 49a5fd4..332ef67 100644 --- a/apps/server/src/domains/game/GameManager.ts +++ b/apps/server/src/domains/game/GameManager.ts @@ -27,8 +27,8 @@ // 新規プレイヤー登録と初期位置設定処理 addPlayer(id: string): Player { const player = new Player(id); - player.x = config.GAME_CONFIG.MAP_WIDTH / 2; - player.y = config.GAME_CONFIG.MAP_HEIGHT / 2; + player.x = config.GAME_CONFIG.GRID_COLS / 2; + player.y = config.GAME_CONFIG.GRID_ROWS / 2; this.players.set(id, player); console.log("[GameManager] player added", { playerId: id, totalPlayers: this.players.size }); return player; diff --git a/packages/shared/src/config/gameConfig.ts b/packages/shared/src/config/gameConfig.ts index 681f9dd..35931b5 100644 --- a/packages/shared/src/config/gameConfig.ts +++ b/packages/shared/src/config/gameConfig.ts @@ -6,24 +6,25 @@ // ネットワーク・描画補間設定 PLAYER_POSITION_UPDATE_MS: 50, // 座標送信間隔(20Hz) PLAYER_LERP_SMOOTHNESS: 0.3, // 補間の滑らかさ - PLAYER_LERP_SNAP_THRESHOLD: 0.5, // 吸着距離閾値 + PLAYER_LERP_SNAP_THRESHOLD: 0.005, // 吸着距離閾値(グリッド単位) // 画面サイズ設定 SCREEN_WIDTH: 1280, SCREEN_HEIGHT: 720, - // グリッド(マス)設定を新設 + // グリッド(マス)設定 GRID_CELL_SIZE: 100, // 1マスのサイズ(px) - GRID_COLS: 20, // 横のマス数 - GRID_ROWS: 20, // 縦のマス数 + GRID_COLS: 20, // 横のマス数(グリッド単位) + GRID_ROWS: 20, // 縦のマス数(グリッド単位) // マップサイズはグリッド設定から自動計算させる(ハードコーディングを避ける) - get MAP_WIDTH() { return this.GRID_COLS * this.GRID_CELL_SIZE; }, - get MAP_HEIGHT() { return this.GRID_ROWS * this.GRID_CELL_SIZE; }, + get MAP_WIDTH_PX() { return this.GRID_COLS * this.GRID_CELL_SIZE; }, + get MAP_HEIGHT_PX() { return this.GRID_ROWS * this.GRID_CELL_SIZE; }, - // プレイヤー挙動設定 - PLAYER_RADIUS: 10, // プレイヤー半径 - PLAYER_SPEED: 5, // 60fps基準の1フレーム当たりの移動量(px) + // プレイヤー挙動設定(内部座標はグリッド単位) + PLAYER_RADIUS_PX: 10, // 描画用のプレイヤー半径(px) + get PLAYER_RADIUS() { return this.PLAYER_RADIUS_PX / this.GRID_CELL_SIZE; }, + PLAYER_SPEED: 3, // 1秒当たりの移動量(グリッド単位) // チームカラー設定 // teamId インデックス順カラー配列 diff --git a/packages/shared/src/domains/gridMap/gridMap.logic.ts b/packages/shared/src/domains/gridMap/gridMap.logic.ts index bb28b3f..31a3ffe 100644 --- a/packages/shared/src/domains/gridMap/gridMap.logic.ts +++ b/packages/shared/src/domains/gridMap/gridMap.logic.ts @@ -1,14 +1,14 @@ import { GAME_CONFIG } from "../../config/gameConfig"; /** - * ピクセル座標からグリッドの1次元配列インデックスを取得する(中心点判定) + * グリッド座標から1次元配列インデックスを取得する(中心点判定) */ export function getGridIndexFromPosition(x: number, y: number): number | null { - const { GRID_CELL_SIZE, GRID_COLS, GRID_ROWS } = GAME_CONFIG; + const { GRID_COLS, GRID_ROWS } = GAME_CONFIG; // 座標がどのマス(列・行)に属するか計算 - const col = Math.floor(x / GRID_CELL_SIZE); - const row = Math.floor(y / GRID_CELL_SIZE); + const col = Math.floor(x); + const row = Math.floor(y); // マップ外の場合は null を返す if (col < 0 || col >= GRID_COLS || row < 0 || row >= GRID_ROWS) { diff --git a/packages/shared/src/domains/player/player.type.ts b/packages/shared/src/domains/player/player.type.ts index ff22892..809c395 100644 --- a/packages/shared/src/domains/player/player.type.ts +++ b/packages/shared/src/domains/player/player.type.ts @@ -1,6 +1,7 @@ // クライアント・サーバー間共有プレイヤー基本情報型 export interface PlayerData { id: string; + // グリッド単位の座標 x: number; y: number; teamId: number; // 0〜3 のチームID @@ -8,6 +9,7 @@ // 移動イベント送信ペイロード型 export interface MovePayload { + // グリッド単位の座標 x: number; y: number; } \ No newline at end of file diff --git a/test/load-bot.constants.ts b/test/load-bot.constants.ts index 6331e98..553ddc6 100644 --- a/test/load-bot.constants.ts +++ b/test/load-bot.constants.ts @@ -10,8 +10,8 @@ export const BOT_SPEED = GAME_CONFIG.PLAYER_SPEED; export const BOT_RADIUS = GAME_CONFIG.PLAYER_RADIUS; export const START_DELAY_MS = 800; -export const MAX_X = GAME_CONFIG.MAP_WIDTH; -export const MAX_Y = GAME_CONFIG.MAP_HEIGHT; +export const MAX_X = GAME_CONFIG.GRID_COLS; +export const MAX_Y = GAME_CONFIG.GRID_ROWS; export const ROOM_ID = "1"; export const START_GAME = true; export const SOCKET_PATH = NETWORK_CONFIG.SOCKET_IO_PATH; diff --git a/test/load-bot.ts b/test/load-bot.ts index c458ed9..65aba05 100644 --- a/test/load-bot.ts +++ b/test/load-bot.ts @@ -106,9 +106,9 @@ }; const tickMove = () => { - const frameDelta = MOVE_TICK_MS / (1000 / 60); - posX += dirX * BOT_SPEED * frameDelta; - posY += dirY * BOT_SPEED * frameDelta; + const dtSec = MOVE_TICK_MS / 1000; + posX += dirX * BOT_SPEED * dtSec; + posY += dirY * BOT_SPEED * dtSec; if (posX < BOT_RADIUS) { posX = BOT_RADIUS;