export class MapView { constructor(scene) { this.scene = scene; this.config = { width: 4000, height: 3000, zoom: 1.2, moveSpeed: 200 }; this.target = null; this.nodes = []; this.nodeSprites = new Map(); this.otherSprites = new Map(); // socket.id -> { img,label } } create(me, world, nodes) { const W = world?.width || this.config.width; const H = world?.height || this.config.height; this.config.width = W; this.config.height = H; // background this.scene.add.rectangle(W / 2, H / 2, W, H, 0x020617).setDepth(-100); // player sprite this.player = this.scene.add.image(me.x, me.y, 'player').setDepth(10); this.nameText = this.scene .add.text(me.x, me.y - 22, me.username || me.name || 'You', { fontSize: '12px', color: '#e5e7eb' }) .setOrigin(0.5); // camera follow const cam = this.scene.cameras.main; cam.setBounds(0, 0, W, H); cam.startFollow(this.player, true, 0.15, 0.15); cam.setZoom(this.config.zoom); this.replaceNodes(nodes || []); } setTarget(x, y) { this.target = { x, y }; } setZoom(z) { this.config.zoom = z; this.scene.cameras.main.setZoom(z); } replaceNodes(list) { for (const sprite of this.nodeSprites.values()) { sprite.destroy(); } this.nodeSprites.clear(); this.nodes = list.slice(); for (const n of this.nodes) { const key = this.getTextureKeyForNode(n.type); const spr = this.scene.add.image(n.x, n.y, key).setDepth(1); spr.setAlpha(n.alive ? 1 : 0.2); this.nodeSprites.set(n.id, spr); } } updateNode(u) { const n = this.nodes.find(n => n.id === u.id); if (n) n.alive = u.alive; const spr = this.nodeSprites.get(u.id); if (spr) spr.setAlpha(u.alive ? 1 : 0.2); } getTextureKeyForNode(type) { if (type === 'wood') return 'wood'; if (type === 'stone') return 'stone'; if (type === 'ore') return 'ore'; if (type === 'fiber') return 'fiber'; return 'wood'; } getClosestNodeIdInRange(range) { if (!this.player) return null; let bestId = null; let bestDist = Infinity; for (const n of this.nodes) { if (!n.alive) continue; const d = Phaser.Math.Distance.Between( this.player.x, this.player.y, n.x, n.y ); if (d <= range && d < bestDist) { bestDist = d; bestId = n.id; } } return bestId; } updateOtherPlayers(players) { const seen = new Set(); for (const p of players) { seen.add(p.id); if (!this.otherSprites.has(p.id)) { const img = this.scene.add.image(p.x, p.y, 'other').setDepth(5); const label = this.scene .add.text(p.x, p.y - 22, p.name || 'Player', { fontSize: '12px', color: '#9ca3af' }) .setOrigin(0.5); this.otherSprites.set(p.id, { img, label }); } else { const rec = this.otherSprites.get(p.id); rec.img.setPosition(p.x, p.y); rec.label.setPosition(p.x, p.y - 22); } } for (const [id, rec] of this.otherSprites) { if (!seen.has(id)) { rec.img.destroy(); rec.label.destroy(); this.otherSprites.delete(id); } } } update(delta) { if (!this.target || !this.player) return; const dt = delta / 1000; const dx = this.target.x - this.player.x; const dy = this.target.y - this.player.y; const dist = Math.hypot(dx, dy); const step = this.config.moveSpeed * dt; if (dist <= step) { this.player.setPosition(this.target.x, this.target.y); this.nameText.setPosition(this.player.x, this.player.y - 22); this.target = null; } else { this.player.setPosition( this.player.x + (dx / dist) * step, this.player.y + (dy / dist) * step ); this.nameText.setPosition(this.player.x, this.player.y - 22); } } }