🐛 fix HUD overlay zoom + add red center crosshair
Text overlay now uses a dedicated HUD camera (zoom=1, fixed scroll) so it's never scaled by the world zoom. World objects and HUD objects are separated via camera ignore lists. Added red screen-center crosshair to HUD layer as a precise alignment reference.
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
import Phaser from 'phaser'
|
||||
import { TILE_SIZE } from '../config'
|
||||
|
||||
const GRID_TILES = 50 // world size in tiles
|
||||
const MIN_ZOOM = 0.25
|
||||
const MAX_ZOOM = 4.0
|
||||
const ZOOM_STEP = 0.1
|
||||
const MARKER_EVERY = 5 // small crosshair every N tiles
|
||||
const LABEL_EVERY = 10 // coordinate label every N tiles
|
||||
const CAMERA_SPEED = 400 // px/s
|
||||
const SNAPSHOT_EVERY = 2000 // ms between periodic log snapshots
|
||||
const GRID_TILES = 50 // world size in tiles
|
||||
const MIN_ZOOM = 0.25
|
||||
const MAX_ZOOM = 4.0
|
||||
const ZOOM_STEP = 0.1
|
||||
const MARKER_EVERY = 5 // small crosshair every N tiles
|
||||
const LABEL_EVERY = 10 // coordinate label every N tiles
|
||||
const CAMERA_SPEED = 400 // px/s
|
||||
const SNAPSHOT_EVERY = 2000 // ms between periodic log snapshots
|
||||
|
||||
/**
|
||||
* First test scene: observes pure Phaser default zoom behavior.
|
||||
@@ -19,6 +19,9 @@ const SNAPSHOT_EVERY = 2000 // ms between periodic log snapshots
|
||||
*/
|
||||
export class ZoomTestScene extends Phaser.Scene {
|
||||
private logText!: Phaser.GameObjects.Text
|
||||
private hudCamera!: Phaser.Cameras.Scene2D.Camera
|
||||
private worldObjects: Phaser.GameObjects.GameObject[] = []
|
||||
private hudObjects: Phaser.GameObjects.GameObject[] = []
|
||||
private keys!: {
|
||||
up: Phaser.Input.Keyboard.Key
|
||||
down: Phaser.Input.Keyboard.Key
|
||||
@@ -43,19 +46,18 @@ export class ZoomTestScene extends Phaser.Scene {
|
||||
this.drawGrid()
|
||||
this.setupCamera()
|
||||
this.setupInput()
|
||||
this.createOverlay()
|
||||
this.createHUD()
|
||||
this.setupCameras()
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the static world grid into world space.
|
||||
* - Faint tile lines on every tile boundary
|
||||
* - Small green crosshairs every MARKER_EVERY tiles
|
||||
* - Yellow labeled crosshairs every LABEL_EVERY tiles
|
||||
* - Red world border
|
||||
* All objects are registered in worldObjects for HUD-camera exclusion.
|
||||
*/
|
||||
private drawGrid(): void {
|
||||
const worldPx = GRID_TILES * TILE_SIZE
|
||||
const g = this.add.graphics()
|
||||
this.worldObjects.push(g)
|
||||
|
||||
// Background fill
|
||||
g.fillStyle(0x111811)
|
||||
@@ -90,12 +92,13 @@ export class ZoomTestScene extends Phaser.Scene {
|
||||
// Coordinate labels at LABEL_EVERY intersections
|
||||
for (let tx = 0; tx <= GRID_TILES; tx += LABEL_EVERY) {
|
||||
for (let ty = 0; ty <= GRID_TILES; ty += LABEL_EVERY) {
|
||||
this.add.text(
|
||||
const label = this.add.text(
|
||||
tx * TILE_SIZE + 4,
|
||||
ty * TILE_SIZE + 4,
|
||||
`${tx},${ty}`,
|
||||
{ fontSize: '9px', color: '#ffff88', fontFamily: 'monospace' }
|
||||
).setDepth(1)
|
||||
this.worldObjects.push(label)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,27 +144,26 @@ export class ZoomTestScene extends Phaser.Scene {
|
||||
_dx: number,
|
||||
dy: number
|
||||
) => {
|
||||
const zoomBefore = cam.zoom
|
||||
const zoomBefore = cam.zoom
|
||||
const scrollXBefore = cam.scrollX
|
||||
const scrollYBefore = cam.scrollY
|
||||
|
||||
const newZoom = Phaser.Math.Clamp(cam.zoom - Math.sign(dy) * ZOOM_STEP, MIN_ZOOM, MAX_ZOOM)
|
||||
cam.setZoom(newZoom)
|
||||
|
||||
// Log after Phaser has applied the zoom (next microtask so values are updated)
|
||||
setTimeout(() => {
|
||||
this.writeLog('zoom', {
|
||||
direction: dy > 0 ? 'out' : 'in',
|
||||
zoomBefore: +zoomBefore.toFixed(4),
|
||||
zoomAfter: +cam.zoom.toFixed(4),
|
||||
direction: dy > 0 ? 'out' : 'in',
|
||||
zoomBefore: +zoomBefore.toFixed(4),
|
||||
zoomAfter: +cam.zoom.toFixed(4),
|
||||
scrollX_before: +scrollXBefore.toFixed(2),
|
||||
scrollY_before: +scrollYBefore.toFixed(2),
|
||||
scrollX_after: +cam.scrollX.toFixed(2),
|
||||
scrollY_after: +cam.scrollY.toFixed(2),
|
||||
scrollX_delta: +(cam.scrollX - scrollXBefore).toFixed(2),
|
||||
scrollY_delta: +(cam.scrollY - scrollYBefore).toFixed(2),
|
||||
mouseScreen: { x: +ptr.x.toFixed(1), y: +ptr.y.toFixed(1) },
|
||||
mouseWorld: { x: +ptr.worldX.toFixed(2), y: +ptr.worldY.toFixed(2) },
|
||||
mouseScreen: { x: +ptr.x.toFixed(1), y: +ptr.y.toFixed(1) },
|
||||
mouseWorld: { x: +ptr.worldX.toFixed(2), y: +ptr.worldY.toFixed(2) },
|
||||
centerWorld_after: {
|
||||
x: +(cam.scrollX + (cam.width / cam.zoom) / 2).toFixed(2),
|
||||
y: +(cam.scrollY + (cam.height / cam.zoom) / 2).toFixed(2),
|
||||
@@ -176,9 +178,25 @@ export class ZoomTestScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the fixed HUD text overlay (scroll factor 0 = screen space).
|
||||
* Creates all HUD elements: log overlay and screen-center crosshair.
|
||||
* All objects are registered in hudObjects for main-camera exclusion.
|
||||
* Uses a dedicated HUD camera (zoom=1, fixed) so elements are never scaled.
|
||||
*/
|
||||
private createOverlay(): void {
|
||||
private createHUD(): void {
|
||||
const w = this.scale.width
|
||||
const h = this.scale.height
|
||||
|
||||
// Screen-center crosshair (red)
|
||||
const cross = this.add.graphics()
|
||||
const arm = 16
|
||||
cross.lineStyle(1, 0xff2222, 0.9)
|
||||
cross.lineBetween(w / 2 - arm, h / 2, w / 2 + arm, h / 2)
|
||||
cross.lineBetween(w / 2, h / 2 - arm, w / 2, h / 2 + arm)
|
||||
cross.fillStyle(0xff2222, 1.0)
|
||||
cross.fillCircle(w / 2, h / 2, 2)
|
||||
this.hudObjects.push(cross)
|
||||
|
||||
// Log text overlay
|
||||
this.logText = this.add.text(10, 10, '', {
|
||||
fontSize: '13px',
|
||||
color: '#e8e8e8',
|
||||
@@ -186,16 +204,27 @@ export class ZoomTestScene extends Phaser.Scene {
|
||||
padding: { x: 10, y: 8 },
|
||||
lineSpacing: 3,
|
||||
fontFamily: 'monospace',
|
||||
})
|
||||
.setScrollFactor(0)
|
||||
.setDepth(100)
|
||||
}).setDepth(100)
|
||||
this.hudObjects.push(this.logText)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a dedicated HUD camera (zoom=1, no scroll) and separates
|
||||
* world objects from HUD objects so neither camera renders both layers.
|
||||
*/
|
||||
private setupCameras(): void {
|
||||
this.hudCamera = this.cameras.add(0, 0, this.scale.width, this.scale.height)
|
||||
this.hudCamera.setScroll(0, 0)
|
||||
this.hudCamera.setZoom(1)
|
||||
|
||||
this.cameras.main.ignore(this.hudObjects)
|
||||
this.hudCamera.ignore(this.worldObjects)
|
||||
}
|
||||
|
||||
update(_time: number, delta: number): void {
|
||||
this.handleKeyboard(delta)
|
||||
this.updateOverlay()
|
||||
|
||||
// Periodic snapshot
|
||||
this.snapshotTimer += delta
|
||||
if (this.snapshotTimer >= SNAPSHOT_EVERY) {
|
||||
this.snapshotTimer = 0
|
||||
@@ -208,8 +237,8 @@ export class ZoomTestScene extends Phaser.Scene {
|
||||
* @param delta - Frame delta in milliseconds
|
||||
*/
|
||||
private handleKeyboard(delta: number): void {
|
||||
const cam = this.cameras.main
|
||||
const speed = CAMERA_SPEED * (delta / 1000) / cam.zoom
|
||||
const cam = this.cameras.main
|
||||
const speed = CAMERA_SPEED * (delta / 1000) / cam.zoom
|
||||
const worldPx = GRID_TILES * TILE_SIZE
|
||||
|
||||
let dx = 0, dy = 0
|
||||
@@ -231,27 +260,17 @@ export class ZoomTestScene extends Phaser.Scene {
|
||||
const cam = this.cameras.main
|
||||
const ptr = this.input.activePointer
|
||||
|
||||
// Viewport size in world pixels (what is actually visible)
|
||||
const vpWidthPx = cam.width / cam.zoom
|
||||
const vpHeightPx = cam.height / cam.zoom
|
||||
|
||||
// Viewport size in tiles
|
||||
const vpWidthPx = cam.width / cam.zoom
|
||||
const vpHeightPx = cam.height / cam.zoom
|
||||
const vpWidthTiles = vpWidthPx / TILE_SIZE
|
||||
const vpHeightTiles = vpHeightPx / TILE_SIZE
|
||||
|
||||
// Camera center in world coords
|
||||
const centerWorldX = cam.scrollX + vpWidthPx / 2
|
||||
const centerWorldY = cam.scrollY + vpHeightPx / 2
|
||||
|
||||
// Tile under mouse
|
||||
const mouseTileX = Math.floor(ptr.worldX / TILE_SIZE)
|
||||
const mouseTileY = Math.floor(ptr.worldY / TILE_SIZE)
|
||||
|
||||
// Tile at camera center
|
||||
const centerTileX = Math.floor(centerWorldX / TILE_SIZE)
|
||||
const centerTileY = Math.floor(centerWorldY / TILE_SIZE)
|
||||
|
||||
const renderer = this.game.renderer.type === Phaser.WEBGL ? 'WebGL' : 'Canvas'
|
||||
const centerWorldX = cam.scrollX + vpWidthPx / 2
|
||||
const centerWorldY = cam.scrollY + vpHeightPx / 2
|
||||
const mouseTileX = Math.floor(ptr.worldX / TILE_SIZE)
|
||||
const mouseTileY = Math.floor(ptr.worldY / TILE_SIZE)
|
||||
const centerTileX = Math.floor(centerWorldX / TILE_SIZE)
|
||||
const centerTileY = Math.floor(centerWorldY / TILE_SIZE)
|
||||
const renderer = this.game.renderer.type === Phaser.WEBGL ? 'WebGL' : 'Canvas'
|
||||
|
||||
const lines = [
|
||||
'── ZOOM TEST [Phaser default] ──',
|
||||
|
||||
Reference in New Issue
Block a user