Sprite Attachments
Sprite attachments are UI elements that follow and stick to sprites - like directional arrows, health bars, name tags, or custom indicators.
The Pit of Success Pattern
martini-kit’s attachment helpers follow a “pit of success” design:
✅ Auto-update by default - No manual update() calls needed ✅ Auto-cleanup - Destroys when sprite/scene is destroyed ✅ Zero boilerplate - Just attach and forget
Directional Indicators
Show which direction a sprite is facing with an arrow or indicator.
Basic Usage (Auto-Update)
import { attachDirectionalIndicator } from '@martini-kit/phaser';
// In SpriteManager onCreate:
onCreate: (key, data) => {
const car = this.add.rectangle(data.x, data.y, 30, 20, data.color);
// That's it! Arrow auto-updates every frame
attachDirectionalIndicator(this, car, {
shape: 'triangle',
offset: 20,
color: 0xffffff
});
return car;
} How it works:
- The indicator subscribes to the scene’s
updateevent - Every frame, it recalculates position/rotation based on the sprite
- When the sprite or scene is destroyed, it auto-cleans up
Available Shapes
Triangle (Default)
Classic arrow pointer - best for compact indicators.
attachDirectionalIndicator(this, sprite, {
shape: 'triangle',
offset: 20,
color: 0xffffff
}); Arrow
Longer arrow with distinct head and tail - best for visibility.
attachDirectionalIndicator(this, sprite, {
shape: 'arrow',
offset: 30,
color: 0xff00ff,
size: 1.5 // Make it larger
}); Chevron
V-shaped indicator - best for minimalist design.
attachDirectionalIndicator(this, sprite, {
shape: 'chevron',
offset: 25,
color: 0x00ff00
}); Configuration Options
interface DirectionalIndicatorConfig {
// Shape type
shape?: 'triangle' | 'arrow' | 'chevron'; // default: 'triangle'
// Distance from sprite center
offset?: number; // default: 20
// Indicator color (hex)
color?: number; // default: 0xffffff (white)
// Scale/size multiplier
size?: number; // default: 1.0
// Z-depth for layering
depth?: number;
// Auto-update every frame (recommended)
autoUpdate?: boolean; // default: true
} Manual Update Mode
For advanced use cases where you need fine control over when updates happen:
onCreate: (key, data) => {
const car = this.add.rectangle(data.x, data.y, 30, 20, data.color);
// Store indicator reference with autoUpdate disabled
car.directionArrow = attachDirectionalIndicator(this, car, {
shape: 'triangle',
offset: 20,
color: 0xffffff,
autoUpdate: false // Disable automatic updates
});
return car;
}
// Then in your scene's update loop:
update() {
for (const [, sprite] of this.spriteManager.getAll()) {
if (sprite.directionArrow) {
sprite.directionArrow.update();
}
}
} Note: SpriteManager also provides a fallback for manual mode if you store the update function as _updateArrow:
onAdd: (sprite) => {
sprite._updateArrow = () => sprite.directionArrow?.update();
} Phaser Rotation Convention
Understanding Phaser’s rotation system helps when working with directional indicators.
Rotation Values
Phaser uses radians where:
0= pointing RIGHT (positive X axis)Math.PI/2= pointing DOWN (positive Y axis)Math.PI= pointing LEFT (negative X axis)-Math.PI/2or3*Math.PI/2= pointing UP (negative Y axis)
Automatic Offset Handling
The attachDirectionalIndicator helper automatically adds a π/2 (90°) rotation offset because:
- Triangle shapes naturally point UP (negative Y) at rotation 0
- Phaser’s 0 rotation points RIGHT (positive X)
- We add +90° to align them
You don’t need to think about this! The helper handles it automatically.
Advanced Patterns
Per-Player Colors
onCreate: (key, data) => {
const sprite = this.add.rectangle(data.x, data.y, 30, 20, data.color);
// Use player color for the arrow
attachDirectionalIndicator(this, sprite, {
shape: 'arrow',
offset: 25,
color: data.color, // Match player color
depth: 100 // Render on top
});
return sprite;
} Conditional Indicators
Only show arrows for certain players:
onCreate: (key, data) => {
const sprite = this.add.rectangle(data.x, data.y, 30, 20, data.color);
// Only show arrow for the local player
if (key === `player-${this.adapter.getMyPlayerId()}`) {
attachDirectionalIndicator(this, sprite, {
shape: 'triangle',
offset: 20,
color: 0xffff00 // Yellow for "you"
});
}
return sprite;
} Multiple Indicators
Attach multiple indicators to the same sprite:
onCreate: (key, data) => {
const sprite = this.add.rectangle(data.x, data.y, 30, 20, data.color);
// Front arrow
attachDirectionalIndicator(this, sprite, {
shape: 'triangle',
offset: 25,
color: 0xffffff
});
// Rear indicator (opposite direction)
const rearIndicator = this.add.triangle(
sprite.x, sprite.y,
0, 5, -4, -5, 4, -5,
0xff0000
);
rearIndicator.setOrigin(0.5);
// Manual update for rear indicator
sprite._updateRear = () => {
const rearX = sprite.x - Math.cos(sprite.rotation) * 25;
const rearY = sprite.y - Math.sin(sprite.rotation) * 25;
rearIndicator.setPosition(rearX, rearY);
rearIndicator.setRotation(sprite.rotation + Math.PI / 2);
};
return sprite;
} Best Practices
✅ Do
- Use auto-update (default) - It’s the pit of success!
- Attach in onCreate - Ensures proper lifecycle
- Trust auto-cleanup - The helper handles sprite/scene destruction
- Use appropriate shapes - Triangle for compact, arrow for visibility
- Set proper depth - Ensure indicators render on top
❌ Don’t
- Don’t disable autoUpdate unless you have a specific reason
- Don’t manually manage cleanup - The helper does it for you
- Don’t forget rotation offsets - The helper handles this automatically
- Don’t create indicators in update() - Too expensive, create once in onCreate
Performance Considerations
Auto-Update Performance
The auto-update feature uses Phaser’s scene event system, which is highly optimized. Each indicator adds one event listener.
Cost per indicator:
- ~0.1ms per 100 indicators on modern hardware
- Negligible for typical games (< 50 sprites)
When to Use Manual Mode
Consider manual mode (autoUpdate: false) only if:
- You have hundreds of sprites with indicators
- You want to batch updates for optimization
- You need conditional updates (e.g., only when sprite moves)
For most games, auto-update is the right choice.
Troubleshooting
Arrow Not Moving
Problem: Arrow stays at spawn position.
Solution: Ensure you’re using the default autoUpdate: true or calling update() manually:
// ✅ Good (auto-update)
attachDirectionalIndicator(this, sprite, { ... });
// ✅ Good (manual update)
sprite.arrow = attachDirectionalIndicator(this, sprite, {
...,
autoUpdate: false
});
// In update():
sprite.arrow.update();
// ❌ Bad (no update calls)
attachDirectionalIndicator(this, sprite, {
...,
autoUpdate: false
});
// Arrow created but never updated! Wrong Rotation Offset
Problem: Arrow points in the wrong direction (e.g., 90° off).
Solution: The helper automatically handles rotation offsets. If you’re seeing issues:
- Check that your sprite’s rotation is in radians (not degrees)
- Ensure you’re not manually adding rotation offsets
- Verify sprite.rotation reflects the correct direction
// ✅ Good
sprite.rotation = Math.atan2(dy, dx); // radians
// ❌ Bad
sprite.rotation = angle * (Math.PI / 180); // manual conversion
sprite.rotation += Math.PI / 2; // manual offset (don't do this!) Memory Leaks
Problem: Indicators persist after scene change.
Solution: The helper auto-cleans up on sprite/scene destroy. If you see leaks:
- Ensure you’re using
autoUpdate: true - Don’t call
scene.events.off()on shutdown - the helper does this - If using manual mode, call
indicator.destroy()explicitly
Creating Custom Attachments (Advanced)
For building your own attachment types, see the SpriteAttachment API reference which provides:
- Generic
createSpriteAttachment()function createSpriteAttachments()for multiple attachmentscreateCompositeAttachment()for complex multi-element attachments- Full TypeScript types and interfaces
- Performance optimization patterns
See Also
- SpriteAttachment API - Generic attachment system
- Phaser Helpers - All available helpers
- SpriteManager - Sprite lifecycle management
- UI and HUD Guide - General UI patterns