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:
// World coordinates (game object)
sprite.x = 400; // 400 pixels in world space
// Screen coordinates (UI element)
ui.x = 400; // 400 pixels from screen edge (camera-independent)
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:
// ❌ WRONG: Not accounting for camera scroll
const worldX = 400;
sprite.x = worldX; // May be off-screen if camera scrolled
// ✅ CORRECT: Account for camera scroll
const cameraX = this.cameras.main.scrollX;
const worldX = 400;
sprite.x = cameraX + worldX; // Correct position relative to camera
For UI Elements (Screen Coordinates):
// UI elements use screen coordinates (camera-independent)
ui.x = 400; // Always 400 pixels from screen edge, regardless of camera
Text Width Measurement Utilities
Measure Text Width Before Positioning
Always measure text width before calculating positions:
// ❌ WRONG: Assuming fixed width
text.x = 400; // May not be centered if text width varies
// ✅ CORRECT: Calculate based on actual width
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
// Measure text that's already created
const textWidth = text.width;
const textHeight = text.height;
Pattern 2: Measure Text Before Creation
// Create temporary text to measure
const tempText = this.add.text(0, 0, "Score: 100", style);
const textWidth = tempText.width;
const textHeight = tempText.height;
tempText.destroy(); // Clean up temporary text
// Use measurements for positioning
const centerX = (screenWidth - textWidth) / 2;
text.x = centerX;
Pattern 3: Measure Text with Different Content
// Measure text with longest expected content
const longestText = "Score: 999999";
const tempText = this.add.text(0, 0, longestText, style);
const maxWidth = tempText.width;
tempText.destroy();
// Use max width for layout
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
// Calculate text width first
const textWidth = text.width;
const screenWidth = this.cameras.main.width;
const centerX = (screenWidth - textWidth) / 2;
text.x = centerX; // Center text horizontally
Common Mistake: Not accounting for text width
// ❌ WRONG: Assuming text is centered at screenWidth / 2
text.x = screenWidth / 2; // Text starts at center, not centered
// ✅ CORRECT: Account for text width
text.x = (screenWidth - text.width) / 2; // Text is centered
Pattern 2: Position Relative to Another Object
// Position button below text
const textBottom = text.y + text.height;
const spacing = 20;
button.y = textBottom + spacing;
Common Mistake: Not accounting for object height
// ❌ WRONG: Assuming objects align at same y
button.y = text.y; // Button overlaps text
// ✅ CORRECT: Account for text height
button.y = text.y + text.height + spacing;
Pattern 3: Account for Origin
// Sprite origin affects position calculation
sprite.setOrigin(0.5, 0.5); // Center origin
sprite.x = 400; // Center of sprite at x=400
// If origin is (0, 0), sprite.x is top-left corner
sprite.setOrigin(0, 0);
sprite.x = 400; // Top-left corner at x=400
Common Mistake: Not accounting for origin
// ❌ WRONG: Assuming origin is (0, 0)
sprite.x = 400; // May not be where expected if origin is (0.5, 0.5)
// ✅ CORRECT: Account for origin
sprite.setOrigin(0.5, 0.5);
sprite.x = 400; // Center of sprite at x=400
Pattern 4: Layout with Spacing
// Layout multiple elements with consistent 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
// Responsive layout based on screen size
const screenWidth = this.cameras.main.width;
const screenHeight = this.cameras.main.height;
// Center horizontally
text.x = (screenWidth - text.width) / 2;
// Position from bottom
const bottomMargin = 50;
text.y = screenHeight - text.height - bottomMargin;
Common Positioning Gotchas
Gotcha 1: Not Accounting for Text Width
// ❌ WRONG: Assuming fixed width
text.x = 400; // May not be centered if text width varies
// ✅ CORRECT: Calculate based on actual width
const textWidth = text.width;
const centerX = (screenWidth - textWidth) / 2;
text.x = centerX;
Gotcha 2: Confusing World vs Screen Coordinates
// ❌ WRONG: Using world coordinates for UI
ui.x = sprite.x; // UI will move with camera scroll
// ✅ CORRECT: Use screen coordinates for UI
ui.x = 400; // UI stays fixed on screen
Gotcha 3: Not Considering Sprite Origin Offsets
// ❌ WRONG: Assuming origin is (0, 0)
sprite.x = 100; // May not be where expected if origin is (0.5, 0.5)
// ✅ CORRECT: Account for origin
sprite.setOrigin(0.5, 0.5);
sprite.x = 100; // Center of sprite at x=100
Gotcha 4: Not Accounting for Object Height
// ❌ WRONG: Assuming objects align at same y
button.y = text.y; // Button overlaps text
// ✅ CORRECT: Account for text height
button.y = text.y + text.height + spacing;
Gotcha 5: Assuming HMR Has Applied Changes
// ❌ WRONG: Assuming changes are applied immediately
makeCodeChange();
captureScreenshot(); // May capture stale state
// ✅ CORRECT: Verify HMR has applied changes
makeCodeChange();
waitForHMR(); // Wait for HMR to apply
captureScreenshot(); // Now captures updated state
Visual Debugging Techniques
Technique 1: Programmatic Verification
Verify calculations programmatically before visual verification:
// Calculate position
const centerX = (screenWidth - text.width) / 2;
text.x = centerX;
// Verify calculation
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:
# Verify text position via test seam
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:
// Add visual marker at expected position
const marker = this.add.rectangle(centerX, 300, 5, 5, 0xff0000);
// Text should be centered at marker
Examples of Successful Layout Calculations
Example 1: Centered Score Display
// Calculate text width first
const scoreText = "Score: 100";
const text = this.add.text(0, 0, scoreText, style);
const textWidth = text.width;
const screenWidth = this.cameras.main.width;
// Center text horizontally
const centerX = (screenWidth - textWidth) / 2;
text.x = centerX;
// Position from top with margin
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
// Position text first
const text = this.add.text(100, 100, "Click Me", textStyle);
const textBottom = text.y + text.height;
// Position button below text with spacing
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
// Get screen dimensions
const screenWidth = this.cameras.main.width;
const screenHeight = this.cameras.main.height;
// Create bottom bar
const barHeight = 60;
const bar = this.add.rectangle(
screenWidth / 2, // Center horizontally
screenHeight - barHeight / 2, // Position from bottom
screenWidth, // Full width
barHeight,
0x000000
);
// Add text on bar
const text = this.add.text(0, 0, "Game Over", textStyle);
text.x = (screenWidth - text.width) / 2; // Center text
text.y = screenHeight - barHeight / 2 - text.height / 2; // Center vertically on bar
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:
// Calculate position
const centerX = (screenWidth - text.width) / 2;
text.x = centerX;
// Verify calculation
const isCentered = Math.abs(text.x - centerX) < 1;
if (!isCentered) {
console.error('Text not centered');
// Fix calculation before proceeding
}
// Only capture screenshot after verification
agent-browser screenshot screenshots/verification.png;
Pattern 2: Test Seam Position Verification
Use test seam to verify positions:
# Verify text position
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:
// Validate calculation
function validateCenterPosition(text: Phaser.GameObjects.Text, screenWidth: number): boolean {
const expectedX = (screenWidth - text.width) / 2;
const actualX = text.x;
const tolerance = 1; // 1 pixel tolerance
return Math.abs(actualX - expectedX) < tolerance;
}
// Use validation
if (!validateCenterPosition(text, screenWidth)) {
console.error('Position calculation incorrect');
// Recalculate
}
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 patternsagent-browser- Browser automationscreenshot-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