| name | spatial-calculation-ui-layout |
| description | Spatial calculation helpers and UI layout patterns for Phaser games and web applications. Use when positioning UI elements, calculating text widths, or working with coordinate systems. Reduces UI positioning iterations from 3-5 to 1-2 by providing calculation patterns and common gotchas. |
Spatial Calculation and UI Layout
Spatial calculation helpers and UI layout patterns to reduce UI positioning iterations from 3-5 to 1-2. Provides coordinate system documentation, text width measurement utilities, and layout calculation patterns.
Overview
Problem: UI positioning tasks require 3-5 iterations due to spatial calculation challenges. Agents struggle with coordinate systems, text width calculations, and layout positioning.
Solution: Coordinate system documentation, text width measurement utilities, layout calculation patterns, and common gotchas.
Impact: Reduces UI positioning iterations from 3-5 to 1-2, saves 2-3 minutes per UI layout task, improves first-attempt success rate
Coordinate System Documentation
World Coordinates vs Screen Coordinates
Understanding coordinate systems is critical for UI positioning tasks.
World Coordinates:
- Game world space (e.g., 800x600 game world)
- Camera-independent (objects exist in world space)
- Used for game objects, sprites, physics bodies
- Example:
sprite.x = 400 (400 pixels from world origin)
Screen Coordinates:
- Viewport/camera space (what player sees)
- Camera-dependent (changes with camera scroll)
- Used for UI elements, HUD, overlays
- Example:
ui.x = 400 (400 pixels from screen edge, camera-independent)
Key Difference:
sprite.x = 400;
ui.x = 400;
Framework-Specific Coordinate Systems
Phaser 3:
- World coordinates:
sprite.x, sprite.y (in world space)
- Screen coordinates:
ui.x, ui.y (camera-independent)
- Camera scroll:
this.cameras.main.scrollX, this.cameras.main.scrollY
React:
- Screen coordinates:
style.left, style.top (relative to viewport)
- Container coordinates: Relative to parent container
Canvas/WebGL:
- World coordinates: Canvas drawing coordinates
- Screen coordinates: Viewport-relative coordinates
Camera Scroll Offset Handling
When calculating positions, account for camera scroll:
const worldX = 400;
sprite.x = worldX;
const cameraX = this.cameras.main.scrollX;
const worldX = 400;
sprite.x = cameraX + worldX;
For UI Elements (Screen Coordinates):
ui.x = 400;
Text Width Measurement Utilities
Measure Text Width Before Positioning
Always measure text width before calculating positions:
text.x = 400;
const textWidth = text.width;
const screenWidth = this.cameras.main.width;
const centerX = (screenWidth - textWidth) / 2;
text.x = centerX;
Text Width Measurement Patterns
Pattern 1: Measure Existing Text
const textWidth = text.width;
const textHeight = text.height;
Pattern 2: Measure Text Before Creation
const tempText = this.add.text(0, 0, "Score: 100", style);
const textWidth = tempText.width;
const textHeight = tempText.height;
tempText.destroy();
const centerX = (screenWidth - textWidth) / 2;
text.x = centerX;
Pattern 3: Measure Text with Different Content
const longestText = "Score: 999999";
const tempText = this.add.text(0, 0, longestText, style);
const maxWidth = tempText.width;
tempText.destroy();
const centerX = (screenWidth - maxWidth) / 2;
Framework-Specific Text Measurement
Phaser 3:
const text = this.add.text(0, 0, "Text", style);
const width = text.width;
const height = text.height;
Canvas:
const ctx = canvas.getContext('2d');
ctx.font = '16px Arial';
const width = ctx.measureText("Text").width;
React:
const textRef = useRef<HTMLDivElement>(null);
const width = textRef.current?.offsetWidth || 0;
Layout Calculation Patterns
Pattern 1: Center Text on Screen
const textWidth = text.width;
const screenWidth = this.cameras.main.width;
const centerX = (screenWidth - textWidth) / 2;
text.x = centerX;
Common Mistake: Not accounting for text width
text.x = screenWidth / 2;
text.x = (screenWidth - text.width) / 2;
Pattern 2: Position Relative to Another Object
const textBottom = text.y + text.height;
const spacing = 20;
button.y = textBottom + spacing;
Common Mistake: Not accounting for object height
button.y = text.y;
button.y = text.y + text.height + spacing;
Pattern 3: Account for Origin
sprite.setOrigin(0.5, 0.5);
sprite.x = 400;
sprite.setOrigin(0, 0);
sprite.x = 400;
Common Mistake: Not accounting for origin
sprite.x = 400;
sprite.setOrigin(0.5, 0.5);
sprite.x = 400;
Pattern 4: Layout with Spacing
const elements = [text1, text2, text3];
const spacing = 20;
let currentY = 100;
elements.forEach(element => {
element.y = currentY;
currentY += element.height + spacing;
});
Pattern 5: Responsive Layout
const screenWidth = this.cameras.main.width;
const screenHeight = this.cameras.main.height;
text.x = (screenWidth - text.width) / 2;
const bottomMargin = 50;
text.y = screenHeight - text.height - bottomMargin;
Common Positioning Gotchas
Gotcha 1: Not Accounting for Text Width
text.x = 400;
const textWidth = text.width;
const centerX = (screenWidth - textWidth) / 2;
text.x = centerX;
Gotcha 2: Confusing World vs Screen Coordinates
ui.x = sprite.x;
ui.x = 400;
Gotcha 3: Not Considering Sprite Origin Offsets
sprite.x = 100;
sprite.setOrigin(0.5, 0.5);
sprite.x = 100;
Gotcha 4: Not Accounting for Object Height
button.y = text.y;
button.y = text.y + text.height + spacing;
Gotcha 5: Assuming HMR Has Applied Changes
makeCodeChange();
captureScreenshot();
makeCodeChange();
waitForHMR();
captureScreenshot();
Visual Debugging Techniques
Technique 1: Programmatic Verification
Verify calculations programmatically before visual verification:
const centerX = (screenWidth - text.width) / 2;
text.x = centerX;
const expectedX = (screenWidth - text.width) / 2;
console.log(`Text x: ${text.x}, Expected: ${expectedX}`);
if (Math.abs(text.x - expectedX) < 1) {
console.log('Position correct');
} else {
console.log('Position incorrect');
}
Technique 2: Test Seam Verification
Use test seam commands to verify positions:
agent-browser eval "window.__TEST__?.getCurrentScene()?.texts?.score?.x"
agent-browser eval "window.__TEST__?.getCurrentScene()?.texts?.score?.y"
Technique 3: Visual Markers
Add visual markers for debugging:
const marker = this.add.rectangle(centerX, 300, 5, 5, 0xff0000);
Examples of Successful Layout Calculations
Example 1: Centered Score Display
const scoreText = "Score: 100";
const text = this.add.text(0, 0, scoreText, style);
const textWidth = text.width;
const screenWidth = this.cameras.main.width;
const centerX = (screenWidth - textWidth) / 2;
text.x = centerX;
const topMargin = 50;
text.y = topMargin;
Key Points:
- Calculate text width before positioning
- Account for screen width (not world width)
- Use spacing constants for consistency
Example 2: Button Below Text
const text = this.add.text(100, 100, "Click Me", textStyle);
const textBottom = text.y + text.height;
const spacing = 20;
const button = this.add.rectangle(100, textBottom + spacing, 200, 50, 0x00ff00);
Key Points:
- Calculate text bottom position
- Add spacing between elements
- Align horizontally (same x for text and button)
Example 3: Responsive Bottom Bar
const screenWidth = this.cameras.main.width;
const screenHeight = this.cameras.main.height;
const barHeight = 60;
const bar = this.add.rectangle(
screenWidth / 2,
screenHeight - barHeight / 2,
screenWidth,
barHeight,
0x000000
);
const text = this.add.text(0, 0, "Game Over", textStyle);
text.x = (screenWidth - text.width) / 2;
text.y = screenHeight - barHeight / 2 - text.height / 2;
Key Points:
- Use screen dimensions for responsive layout
- Calculate center positions
- Account for object dimensions
Programmatic Verification Patterns
Pattern 1: Verify Before Visual Check
Verify calculations programmatically before capturing screenshots:
const centerX = (screenWidth - text.width) / 2;
text.x = centerX;
const isCentered = Math.abs(text.x - centerX) < 1;
if (!isCentered) {
console.error('Text not centered');
}
agent-browser screenshot screenshots/verification.png;
Pattern 2: Test Seam Position Verification
Use test seam to verify positions:
agent-browser eval "
const scene = window.__TEST__?.getCurrentScene();
const text = scene?.texts?.score;
if (text) {
const screenWidth = scene.cameras.main.width;
const expectedX = (screenWidth - text.width) / 2;
Math.abs(text.x - expectedX) < 1 ? 'Centered' : 'Not centered'
} else {
'Text not found'
}
"
Pattern 3: Calculation Validation
Validate calculations before applying:
function validateCenterPosition(text: Phaser.GameObjects.Text, screenWidth: number): boolean {
const expectedX = (screenWidth - text.width) / 2;
const actualX = text.x;
const tolerance = 1;
return Math.abs(actualX - expectedX) < tolerance;
}
if (!validateCenterPosition(text, screenWidth)) {
console.error('Position calculation incorrect');
}
Best Practices
- Calculate text width first before positioning
- Account for screen width (not world width) for UI elements
- Use spacing constants for consistency
- Account for origin when positioning sprites
- Verify calculations programmatically before visual verification
- Use test seam commands for position verification
- Document coordinate system used (world vs screen)
- Test at different screen sizes for responsive layouts
Integration with Other Skills
- phaser-game-testing: Uses coordinate system patterns
- agent-browser: Uses test seam for position verification
- screenshot-handling: Captures screenshots after programmatic verification
Related Skills
phaser-game-testing - Phaser testing patterns
agent-browser - Browser automation
screenshot-handling - Screenshot capture
Remember
- Measure text width before positioning
- Use screen coordinates for UI elements
- Account for origin when positioning sprites
- Verify programmatically before visual verification
- Use test seam for position verification
- Document coordinate system used
- Test responsive layouts at different screen sizes