- name
- text-and-bitmaptext
- description
- Use this skill when displaying text in Phaser 4. Covers Text game objects, BitmapText, web fonts, text styling, word wrap, alignment, padding, and dynamic text content. Triggers on: Text, BitmapText, this.add.text, font, word wrap, text style.
# Text and BitmapText
> Displaying text in Phaser 4 -- the Canvas-based Text game object, TextStyle configuration, word wrap, BitmapText (static and dynamic), retro fonts, text alignment, text bounds, and padding.
**Key source paths:** `src/gameobjects/text/`, `src/gameobjects/bitmaptext/static/`, `src/gameobjects/bitmaptext/dynamic/`
**Related skills:** ../loading-assets/SKILL.md, ../sprites-and-images/SKILL.md, ../game-object-components/SKILL.md
## Quick Start
```js
// Canvas-based Text (flexible styling, uses browser fonts)
const title = this.add.text(400, 50, 'Hello World', {
fontFamily: 'Arial',
fontSize: '32px',
color: '#ffffff'
});
// BitmapText (pre-rendered font texture, faster rendering)
// Font must be loaded first: this.load.bitmapFont('myFont', 'font.png', 'font.xml')
const score = this.add.bitmapText(400, 100, 'myFont', 'Score: 0', 32);
// DynamicBitmapText (per-character manipulation via callback)
const fancy = this.add.dynamicBitmapText(400, 200, 'myFont', 'Wavy!', 32);
fancy.setDisplayCallback(function (data) {
data.y += Math.sin(data.index * 0.5 + fancy.scene.time.now * 0.005) * 10;
return data;
});
```
## Core Concepts
### Text vs BitmapText
| Feature | Text | BitmapText | DynamicBitmapText |
|---|---|---|---|
| Rendering method | Canvas 2D API, uploaded as texture | Pre-rendered font texture | Pre-rendered font texture |
| Web/CSS fonts | Yes | No | No |
| Shadows, gradients, strokes | Yes (Canvas API) | Drop shadow only (WebGL) | No |
| Word wrap | Built-in (width, callback, advanced) | `setMaxWidth` | `setMaxWidth` |
| Per-character effects | No | `setCharacterTint`, `setWordTint` (WebGL) | `setDisplayCallback` |
| Performance | Slower (re-creates canvas texture on change) | Fast (batched rendering) | Moderate (callback per char) |
| Alignment | `left`, `right`, `center`, `justify` | `ALIGN_LEFT` (0), `ALIGN_CENTER` (1), `ALIGN_RIGHT` (2) | Same as BitmapText |
| RTL support | Yes (`rtl` style option) | No | No |
**Rule of thumb:** Use `Text` for UI elements that need flexible styling, web fonts, or infrequent updates. Use `BitmapText` for scores, timers, and anything that updates frequently or is rendered in large quantities. Use `DynamicBitmapText` only when you need per-character animation effects.
### TextStyle
The `Text` game object delegates all styling to its `TextStyle` instance, accessible via `text.style`. You pass a style config object when creating the Text, or update it later with setter methods. The style object uses Canvas 2D properties internally.
The `fontFamily` default is `'Courier'`, `fontSize` default is `'16px'`, and `color` default is `'#fff'`. The `font` shorthand property (e.g. `'bold 24px Arial'`) overrides `fontFamily`, `fontSize`, and `fontStyle` when provided.
## Common Patterns
### Basic Text Creation
```js
// Simple text with inline style
const label = this.add.text(100, 100, 'Player 1', {
fontFamily: 'Georgia',
fontSize: '24px',
color: '#00ff00'
});
// Using the font shorthand (sets fontStyle, fontSize, fontFamily)
const bold = this.add.text(100, 150, 'Bold Text', {
font: 'bold 28px Arial'
});
// Array of strings creates multi-line text
const multi = this.add.text(100, 200, ['Line 1', 'Line 2', 'Line 3'], {
fontFamily: 'Arial',
fontSize: '18px',
color: '#ffffff'
});
// Text origin defaults to (0, 0) -- top-left corner
label.setOrigin(0.5); // center the text on its position
```
### Styling Text
```js
const text = this.add.text(400, 300, 'Styled', {
fontFamily: 'Verdana',
fontSize: '48px',
color: '#ff0000',
stroke: '#000000',
strokeThickness: 4,
backgroundColor: '#333333',
shadow: { offsetX: 2, offsetY: 2, color: '#000', blur: 4, stroke: false, fill: true }
});
// Modify style after creation (all return `this` for chaining)
text.setColor('#00ffff');
text.setFontSize(64);
text.setFontFamily('Courier');
text.setFontStyle('italic');
text.setStroke('#ff00ff', 6);
text.setShadow(3, 3, '#000', 5, false, true);
text.setBackgroundColor('#222');
// Replace entire style at once
text.setStyle({ fontSize: '64px', fontFamily: 'Arial', color: '#ffffff', align: 'center' });
// Canvas gradients and patterns work as fill/stroke
const gradient = text.context.createLinearGradient(0, 0, 0, text.height);
gradient.addColorStop(0, '#ff0000');
gradient.addColorStop(1, '#0000ff');
text.setFill(gradient);
```
### Word Wrap
```js
// Basic word wrap by pixel width
const wrapped = this.add.text(50, 50, 'Long text here...', {
fontFamily: 'Arial',
fontSize: '20px',
color: '#fff',
wordWrap: { width: 300 }
});
// Or set after creation
wrapped.setWordWrapWidth(300);
// Advanced wrap (collapses spaces, trims whitespace)
wrapped.setWordWrapWidth(300, true);
// Custom word wrap callback
wrapped.setWordWrapCallback(function (text, textObject) {
// Return wrapped text as string with \n or array of lines
return text.split(' ').join('\n');
});
// Get wrapped lines as array
const lines = wrapped.getWrappedText();
```
### Text Alignment
```js
// Alignment only affects multi-line text: 'left' (default), 'right', 'center', 'justify'
const aligned = this.add.text(400, 100, 'Line 1\nLonger Line 2\nLine 3', {
fontFamily: 'Arial', fontSize: '24px', color: '#fff', align: 'center'
});
aligned.setAlign('right');
// For center alignment to look correct, combine with fixedWidth:
aligned.setFixedSize(300, 0);
aligned.setAlign('center');
```
### Dynamic Text Updates
```js
const scoreText = this.add.text(10, 10, 'Score: 0', {
fontFamily: 'Arial',
fontSize: '24px',
color: '#fff'
});
// setText replaces content (accepts string or string[])
scoreText.setText('Score: 100');
scoreText.setText(['Score: 100', 'Lives: 3']); // joins with \n
// appendText adds to existing content
scoreText.appendText('Extra line'); // prepends \n by default
scoreText.appendText(' more', false); // no carriage return
// Read current text
const current = scoreText.text;
```
### Text Padding
```js
// Padding adds space around text inside the canvas
const padded = this.add.text(100, 100, 'Padded', {
fontFamily: 'Arial', fontSize: '24px', color: '#fff',
backgroundColor: '#333',
padding: { left: 10, right: 10, top: 5, bottom: 5 }
});
padded.setPadding({ x: 10, y: 5 }); // shorthand: x=left+right, y=top+bottom
padded.setPadding(10, 5, 10, 5); // left, top, right, bottom
padded.setPadding(10); // single value = all sides
```
### Fixed Size, Max Lines, and Spacing
```js
// Fixed dimensions create a text canvas of exact size
const fixed = this.add.text(100, 100, 'Fixed box', {
fontFamily: 'Arial', fontSize: '18px', color: '#fff',
fixedWidth: 200, fixedHeight: 100,
wordWrap: { width: 200 }, align: 'center'
});
fixed.setMaxLines(3); // limit displayed lines
// Line and letter spacing (in style config or via setters)
const spaced = this.add.text(100, 200, 'Spaced\nLines', {
fontFamily: 'Arial', fontSize: '24px', color: '#fff',
lineSpacing: 10, letterSpacing: 2
});
spaced.setLineSpacing(20);
spaced.setLetterSpacing(5);
```
### BitmapText Creation
```js
// Load in preload: this.load.bitmapFont('pixelFont', 'font.png', 'font.xml');
// Static BitmapText -- fast, batched rendering
const bmpText = this.add.bitmapText(100, 100, 'pixelFont', 'Hello', 32);
// With alignment (for multi-line)
const aligned = this.add.bitmapText(100, 200, 'pixelFont', 'Line 1\nLine 2', 24, 1);
// align: 0 = left, 1 = center, 2 = right
// Convenience alignment methods
bmpText.setLeftAlign();
bmpText.setCenterAlign();
bmpText.setRightAlign();
// Modify text
bmpText.setText('Updated!');
bmpText.text = 'Also works';
// Font size
bmpText.setFontSize(48);
bmpText.fontSize = 48;
// Spacing
bmpText.setLetterSpacing(2);
bmpText.setLineSpacing(5);
// Word wrap by max pixel width
bmpText.setMaxWidth(200);
```
### BitmapText Drop Shadow and Tinting
```js
// Drop shadow (WebGL only, static BitmapText only)
bmpText.setDropShadow(2, 2, 0x000000, 0.5);
// Clear shadow
bmpText.setDropShadow();
// Tint individual characters (WebGL only)
bmpText.setCharacterTint(0, 5, Phaser.TintModes.MULTIPLY, 0xff0000);
// start index, length (-1 for all from start), tintMode, color
// Tint by word (string match or word index)
bmpText.setWordTint('Score', -1, Phaser.TintModes.MULTIPLY, 0x00ff00);
```
### DynamicBitmapText
```js
const dynamic = this.add.dynamicBitmapText(100, 100, 'pixelFont', 'Dynamic!', 32);
// Per-character display callback -- invoked each render frame per character
dynamic.setDisplayCallback(function (data) {
// data properties: parent, tint, index, charCode, x, y, scale, rotation, data
data.x += Math.sin(data.index + dynamic.scene.time.now * 0.01) * 5;
data.y += Math.cos(data.index + dynamic.scene.time.now * 0.01) * 5;
return data;
});
// Scrolling text window
dynamic.setSize(200, 50); // crop region in pixels
dynamic.setScrollX(10);
dynamic.setScrollY(0);
```
### Retro Fonts
Retro fonts use a fixed-width character grid from a standard image (no XML/JSON font file needed).
```js
// In preload -- load the image containing the font characters
this.load.image('retroFont', 'retroFont.png');
// In create -- parse the retro font configuration
const config = {
image: 'retroFont',
width: 8, // character width in pixels
height: 8, // character height in pixels
chars: Phaser.GameObjects.RetroFont.TEXT_SET1,
charsPerRow: 16,
offset: { x: 0, y: 0 },
spacing: { x: 0, y: 0 },
lineSpacing: 0
};
// Parse adds it to the BitmapFont cache
this.cache.bitmapFont.add('retroFont', { data: Phaser.GameObjects.RetroFont.Parse(this, config) });
// Now use it like any BitmapText
const retro = this.add.bitmapText(100, 100, 'retroFont', 'RETRO TEXT', 8);
```
Available `Phaser.GameObjects.RetroFont` TEXT_SET constants: `TEXT_SET1` (full ASCII printable), `TEXT_SET2` (space through Z), `TEXT_SET3`-`TEXT_SET11` (various subsets of uppercase, digits, punctuation). `TEXT_SET10` is uppercase letters only.
### Text Bounds (BitmapText)
```js
// getTextBounds returns local, global, line, word, and character data
const bounds = bmpText.getTextBounds();
// bounds.local -- { x, y, width, height } at origin 0,0
// bounds.global -- { x, y, width, height } with scale + world position
// bounds.lines -- per-line { x, y, width, height }
// bounds.words -- word objects with positions
// bounds.characters -- character objects with positions
// width/height are read-only properties (computed from global bounds)
console.log(bmpText.width, bmpText.height);
// Get character at world position (useful for click detection)
const char = bmpText.getCharacterAt(pointer.worldX, pointer.worldY);
```
### Resolution (High DPI)
```js
// Higher resolution for crisp text on HiDPI displays (costs more memory)
Ver no GitHub