diff --git a/src/core/config/GameConfig.ts b/src/core/config/GameConfig.ts index bc0f045..f31cf7a 100644 --- a/src/core/config/GameConfig.ts +++ b/src/core/config/GameConfig.ts @@ -96,6 +96,9 @@ export const GAME_CONFIG = { rendering: { tileSize: 16, cameraZoom: 2, + minZoom: 0.5, + maxZoom: 4, + zoomStep: 0.1, wallColor: 0x2b2b2b, floorColor: 0x161616, exitColor: 0xffd166, diff --git a/src/scenes/GameScene.ts b/src/scenes/GameScene.ts index c2d1546..c98c656 100644 --- a/src/scenes/GameScene.ts +++ b/src/scenes/GameScene.ts @@ -33,6 +33,7 @@ export class GameScene extends Phaser.Scene { private playerPath: Vec2[] = []; private awaitingPlayer = false; + private followPlayer = true; // Sub-systems private dungeonRenderer!: DungeonRenderer; @@ -144,8 +145,66 @@ export class GameScene extends Phaser.Scene { }); + + // Zoom Control + this.input.on( + "wheel", + ( + _pointer: Phaser.Input.Pointer, + _gameObjects: any, + _deltaX: number, + deltaY: number, + _deltaZ: number + ) => { + if (this.isMenuOpen || this.isInventoryOpen || this.dungeonRenderer.isMinimapVisible()) return; + + const zoomDir = deltaY > 0 ? -1 : 1; + const newZoom = Phaser.Math.Clamp( + this.cameras.main.zoom + zoomDir * GAME_CONFIG.rendering.zoomStep, + GAME_CONFIG.rendering.minZoom, + GAME_CONFIG.rendering.maxZoom + ); + this.cameras.main.setZoom(newZoom); + } + ); + + // Disable context menu for right-click panning + this.input.mouse?.disableContextMenu(); + + // Camera Panning + this.input.on("pointermove", (p: Phaser.Input.Pointer) => { + if (!p.isDown) return; + if (this.isMenuOpen || this.isInventoryOpen || this.dungeonRenderer.isMinimapVisible()) return; + + // Pan with Middle Click or Right Click + // Note: p.button is not always reliable in move events for holding, + // so we use specific button down checks or the shift key modifier. + const isRightDrag = p.rightButtonDown(); + const isMiddleDrag = p.middleButtonDown(); + const isShiftDrag = p.isDown && p.event.shiftKey; + + if (isRightDrag || isMiddleDrag || isShiftDrag) { + const { x, y } = p.position; + const { x: prevX, y: prevY } = p.prevPosition; + + const dx = (x - prevX) / this.cameras.main.zoom; + const dy = (y - prevY) / this.cameras.main.zoom; + + this.cameras.main.scrollX -= dx; + this.cameras.main.scrollY -= dy; + + this.followPlayer = false; + } + }); + // Mouse click -> compute path (only during player turn, and not while menu/minimap is open) this.input.on("pointerdown", (p: Phaser.Input.Pointer) => { + // Only allow Left Click (0) for movement + if (p.button !== 0) return; + + this.followPlayer = true; + + if (!this.awaitingPlayer) return; if (this.isMenuOpen || this.isInventoryOpen || this.dungeonRenderer.isMinimapVisible()) return; @@ -258,6 +317,7 @@ export class GameScene extends Phaser.Scene { private commitPlayerAction(action: Action) { this.awaitingPlayer = false; + this.followPlayer = true; const playerEvents = applyAction(this.world, this.playerId, action, this.entityManager); const enemyStep = stepUntilPlayerTurn(this.world, this.playerId, this.entityManager); @@ -328,13 +388,16 @@ export class GameScene extends Phaser.Scene { } this.dungeonRenderer.computeFov(this.playerId); - this.centerCameraOnPlayer(); + if (this.followPlayer) { + this.centerCameraOnPlayer(); + } this.dungeonRenderer.render(this.playerPath); this.emitUIUpdate(); } private loadFloor(floor: number) { this.floorIndex = floor; + this.followPlayer = true; const { world, playerId } = generateWorld(floor, this.runState); this.world = world;