import Phaser from 'phaser' import { WORLD_TILES } from '../config' import { stateManager } from '../StateManager' import type { LocalAdapter } from '../NetworkAdapter' const CAMERA_SPEED = 400 // px/s const MIN_ZOOM = 0.25 const MAX_ZOOM = 2.0 const ZOOM_STEP = 0.1 export class CameraSystem { private scene: Phaser.Scene private adapter: LocalAdapter private keys!: { up: Phaser.Input.Keyboard.Key down: Phaser.Input.Keyboard.Key left: Phaser.Input.Keyboard.Key right: Phaser.Input.Keyboard.Key w: Phaser.Input.Keyboard.Key s: Phaser.Input.Keyboard.Key a: Phaser.Input.Keyboard.Key d: Phaser.Input.Keyboard.Key } private saveTimer = 0 private readonly SAVE_TICK = 2000 private middlePanActive = false private lastPanX = 0 private lastPanY = 0 private debugCross?: Phaser.GameObjects.Graphics constructor(scene: Phaser.Scene, adapter: LocalAdapter) { this.scene = scene this.adapter = adapter } create(): void { const state = stateManager.getState() const cam = this.scene.cameras.main // Start at saved player position (reused as camera anchor) cam.scrollX = state.player.x - cam.width / 2 cam.scrollY = state.player.y - cam.height / 2 const kb = this.scene.input.keyboard! this.keys = { up: kb.addKey(Phaser.Input.Keyboard.KeyCodes.UP), down: kb.addKey(Phaser.Input.Keyboard.KeyCodes.DOWN), left: kb.addKey(Phaser.Input.Keyboard.KeyCodes.LEFT), right: kb.addKey(Phaser.Input.Keyboard.KeyCodes.RIGHT), w: kb.addKey(Phaser.Input.Keyboard.KeyCodes.W), s: kb.addKey(Phaser.Input.Keyboard.KeyCodes.S), a: kb.addKey(Phaser.Input.Keyboard.KeyCodes.A), d: kb.addKey(Phaser.Input.Keyboard.KeyCodes.D), } // Debug cross at viewport center (fixed to screen, not world) this.debugCross = this.scene.add.graphics() this.debugCross.setScrollFactor(0).setDepth(999) this.debugCross.lineStyle(2, 0xff0000, 1) const cx = cam.width / 2 const cy = cam.height / 2 this.debugCross.lineBetween(cx - 12, cy, cx + 12, cy) this.debugCross.lineBetween(cx, cy - 12, cx, cy + 12) // Scroll wheel zoom this.scene.input.on('wheel', (ptr: Phaser.Input.Pointer, _objs: unknown, _dx: number, dy: number) => { const newZoom = Phaser.Math.Clamp(cam.zoom - Math.sign(dy) * ZOOM_STEP, MIN_ZOOM, MAX_ZOOM) cam.setZoom(newZoom) const centerX = cam.scrollX + cam.width / (2 * cam.zoom) const centerY = cam.scrollY + cam.height / (2 * cam.zoom) console.log(`[zoom] ptr.x=${ptr.x.toFixed(0)} ptr.y=${ptr.y.toFixed(0)} | ptr.worldX=${ptr.worldX.toFixed(0)} ptr.worldY=${ptr.worldY.toFixed(0)} | center=(${centerX.toFixed(0)}, ${centerY.toFixed(0)}) zoom=${cam.zoom.toFixed(2)}`) }) // Middle-click pan: start on button down this.scene.input.on('pointerdown', (ptr: Phaser.Input.Pointer) => { if (ptr.middleButtonDown()) { this.middlePanActive = true this.lastPanX = ptr.x this.lastPanY = ptr.y } }) // Middle-click pan: move camera while held this.scene.input.on('pointermove', (ptr: Phaser.Input.Pointer) => { if (!this.middlePanActive) return const dx = (ptr.x - this.lastPanX) / cam.zoom const dy = (ptr.y - this.lastPanY) / cam.zoom cam.scrollX -= dx cam.scrollY -= dy this.lastPanX = ptr.x this.lastPanY = ptr.y }) // Middle-click pan: stop on button release this.scene.input.on('pointerup', (ptr: Phaser.Input.Pointer) => { if (this.middlePanActive && !ptr.middleButtonDown()) { this.middlePanActive = false } }) } update(delta: number): void { const cam = this.scene.cameras.main const speed = CAMERA_SPEED * (delta / 1000) / cam.zoom const up = this.keys.up.isDown || this.keys.w.isDown const down = this.keys.down.isDown || this.keys.s.isDown const left = this.keys.left.isDown || this.keys.a.isDown const right = this.keys.right.isDown || this.keys.d.isDown let dx = 0, dy = 0 if (left) dx -= speed if (right) dx += speed if (up) dy -= speed if (down) dy += speed if (dx !== 0 && dy !== 0) { dx *= 0.707; dy *= 0.707 } const worldW = WORLD_TILES * 32 // TILE_SIZE hardcoded since WORLD_PX may not exist const worldH = WORLD_TILES * 32 cam.scrollX = Phaser.Math.Clamp(cam.scrollX + dx, 0, worldW - cam.width / cam.zoom) cam.scrollY = Phaser.Math.Clamp(cam.scrollY + dy, 0, worldH - cam.height / cam.zoom) // Periodically save camera center as "player position" this.saveTimer += delta if (this.saveTimer >= this.SAVE_TICK) { this.saveTimer = 0 this.adapter.send({ type: 'PLAYER_MOVE', x: cam.scrollX + cam.width / 2, y: cam.scrollY + cam.height / 2, }) } } getCenterWorld(): { x: number; y: number } { const cam = this.scene.cameras.main return { x: cam.scrollX + cam.width / (2 * cam.zoom), y: cam.scrollY + cam.height / (2 * cam.zoom), } } getCenterTile(): { tileX: number; tileY: number } { const { x, y } = this.getCenterWorld() return { tileX: Math.floor(x / 32), tileY: Math.floor(y / 32) } } }