关于 text-and-bitmaptext
Phaser 4 文本与 BitmapText 使用指南,面向制作游戏界面的开发者。说明文本样式、换行和对齐,以及静态与动态位图文本的适用方式。
主要功能
- 01配置 Canvas 文本样式
- 02设置换行、对齐与边距
- 03使用静态和动态 BitmapText
- 04查询文本边界与字符信息
适用场景
- 01制作带样式的游戏标签和标题
- 02显示频繁更新的分数或计时器
- 03为字符添加逐字动画效果
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ phaserjs/ phaser --skill text-and-bitmaptext安装后在 Claude Code 中输入/text-and-bitmaptext确认已加载。
详细介绍
文本与 BitmapText
在 Phaser 4 中显示文本 —— 基于 Canvas 的 Text 游戏对象、TextStyle 配置、自动换行、BitmapText(静态与动态)、复古字体、文本对齐、文本边界以及内边距。
关键源码路径: src/gameobjects/text/、src/gameobjects/bitmaptext/static/、src/gameobjects/bitmaptext/dynamic/
相关技能: ../loading-assets/SKILL.md、../sprites-and-images/SKILL.md、../game-object-components/SKILL.md
快速开始
// 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;
});
核心概念
Text 与 BitmapText 对比
| 特性 | Text | BitmapText | DynamicBitmapText |
|---|---|---|---|
| 渲染方式 | Canvas 2D API,作为纹理上传 | 预渲染字体纹理 | 预渲染字体纹理 |
| Web/CSS 字体 | 是 | 否 | 否 |
| 阴影、渐变、描边 | 是(Canvas API) | 仅投影(WebGL) | 否 |
| 自动换行 | 内置(width、callback、高级) | setMaxWidth | setMaxWidth |
| 逐字符效果 | 否 | setCharacterTint、setWordTint(WebGL) | setDisplayCallback |
| 性能 | 较慢(变更时重新创建 canvas 纹理) | 快(批量渲染) | 中等(逐字符回调) |
| 对齐 | left、right、center、justify | ALIGN_LEFT(0)、ALIGN_CENTER(1)、ALIGN_RIGHT(2) | 与 BitmapText 相同 |
| RTL 支持 | 是(rtl 样式选项) | 否 | 否 |
经验法则: 对于需要灵活样式、Web 字体或更新不频繁的 UI 元素,使用 Text。对于分数、计时器以及任何更新频繁或大量渲染的内容,使用 BitmapText。仅当需要逐字符动画效果时,才使用 DynamicBitmapText。
TextStyle
Text 游戏对象将所有样式委托给其 TextStyle 实例,可通过 text.style 访问。创建 Text 时可传入样式配置对象,之后也可用 setter 方法更新。样式对象内部使用 Canvas 2D 属性。
fontFamily 默认为 'Courier',fontSize 默认为 '16px',color 默认为 '#fff'。font 简写属性(例如 'bold 24px Arial')在提供时会覆盖 fontFamily、fontSize 和 fontStyle。
常见模式
创建基础文本
// 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
设置文本样式
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);
自动换行
// 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();
文本对齐
// 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');
动态更新文本
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;
文本内边距
// 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 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
// 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 (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
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);
复古字体
复古字体使用标准图像中的固定宽度字符网格(无需 XML/JSON 字体文件)。
// 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);
可用的 Phaser.GameObjects.RetroFont TEXT_SET 常量:TEXT_SET1(完整 ASCII 可打印字符)、TEXT_SET2(空格到 Z)、TEXT_SET3-TEXT_SET11(大写字母、数字、标点的各种子集)。TEXT_SET10 仅包含大写字母。
文本边界(BitmapText)
// 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);
分辨率(高 DPI)
// Higher resolution for crisp text on HiDPI displays (costs more memory)
const crisp = this.add.text(100, 100, 'Sharp!', {
fontFamily: 'Arial', fontSize: '24px', color: '#fff', resolution: 2
});
crisp.setResolution(window.devicePixelRatio); // change after creation
配置参考
TextStyle 属性
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
fontFamily | string | 'Courier' | CSS 字体族 |
fontSize | string/number | '16px' | 字体大小(数字会自动追加 'px') |
fontStyle | string | '' | CSS 字体样式('bold'、'italic'、'bold italic') |
font | string | - | 简写:'bold 24px Arial'(覆盖字体族/大小/样式) |
color | string/CanvasGradient/CanvasPattern | '#fff' | 填充颜色 |
stroke | string/CanvasGradient/CanvasPattern | '#fff' | 描边颜色 |
strokeThickness | number | 0 | 描边宽度(0 = 不描边) |
backgroundColor | string | null | 纯色背景颜色 |
align | string | 'left' | 多行对齐方式:'left'、'right'、'center'、'justify' |
maxLines | number | 0 | 最大渲染行数(0 = 不限) |
fixedWidth | number | 0 | 固定画布宽度(0 = 自动) |
fixedHeight | number | 0 | 固定画布高度(0 = 自动) |
resolution | number | 0 | 画布分辨率(0 = 使用游戏配置) |
rtl | boolean | false | 从右到左渲染 |
baselineX | number | 1.2 | 字体度量的水平内边距 |
baselineY | number | 1.4 | 字体度量的垂直内边距 |
testString | string | '|MEqgy' | 用于测量字体高度的字符串 |
wordWrap | object | - | { width, callback, callbackScope, useAdvancedWrap } |
shadow | object | - | { offsetX, offsetY, color, blur, stroke, fill } |
padding | object | - | { left, right, top, bottom } 或 { x, y } |
lineSpacing | number | 0 | 行与行之间的额外垂直间距 |
letterSpacing | number | 0 | 字符与字符之间的额外水平间距 |
metrics | object | - | 预先计算的 { ascent, descent, fontSize },用于跳过测量 |
工厂方法
// Text -- Canvas-based
this.add.text(x, y, text, style);
// x, y: number; text: string | string[]; style: TextStyle (optional)
// Returns: Phaser.GameObjects.Text
// BitmapText -- static, fast
this.add.bitmapText(x, y, font, text, size, align);
// x, y: number; font: string (cache key); text: string | string[] (optional)
// size: number (optional, defaults to font data size); align: number (optional, 0)
// Returns: Phaser.GameObjects.BitmapText
// DynamicBitmapText -- per-character callback
this.add.dynamicBitmapText(x, y, font, text, size);
// Same as bitmapText but returns DynamicBitmapText
// Returns: Phaser.GameObjects.DynamicBitmapText
API 快速参考
Text 方法
setText(value)、appendText(value, addCR?)、setStyle(style)、setFont(font)、setFontFamily(family)、setFontSize(size)、setFontStyle(style)、setColor(color)、setFill(color)、setStroke(color, thickness)、setShadow(x, y, color, blur, stroke, fill)、setBackgroundColor(color)、setWordWrapWidth(width, useAdvanced?)、setWordWrapCallback(callback, scope?)、setAlign(align)、setPadding(left, top?, right?, bottom?)、setFixedSize(width, height)、setMaxLines(max)、setLineSpacing(value)、setLetterSpacing(value)、setResolution(value)、setRTL(rtl?)、getWrappedText(text?)、updateText()
所有 setter 均返回 this 以支持链式调用。属性:text(可读/可写)、style(TextStyle 实例)、padding、lineSpacing、letterSpacing、autoRound、splitRegExp。
BitmapText 方法
setText(value)、setFont(font, size?, align?)、setFontSize(size)、setLetterSpacing(spacing?)、setLineSpacing(spacing?)、setMaxWidth(value, wordWrapCharCode?)、setLeftAlign()、setCenterAlign()、setRightAlign()、setDropShadow(x?, y?, color?, alpha?)、setCharacterTint(start?, length?, tintMode?, tl?, tr?, bl?, br?)、setWordTint(word, count?, tintMode?, tl?, tr?, bl?, br?)、getTextBounds(round?)、getCharacterAt(x, y, camera?)
属性(可读/可写):text、fontSize、letterSpacing、lineSpacing、align(数字)、maxWidth。只读:width、height、font、fontData。
对齐常量:BitmapText.ALIGN_LEFT(0)、BitmapText.ALIGN_CENTER(1)、BitmapText.ALIGN_RIGHT(2)。
DynamicBitmapText 方法(额外)
setDisplayCallback(callback)、setSize(width, height)、setScrollX(value)、setScrollY(value)
属性:scrollX、scrollY、cropWidth、cropHeight、displayCallback、callbackData。
注意事项
-
每次更改都会重新渲染文本。 每次调用
setText、setStyle、setColor等都会重新创建内部 canvas,并且(在 WebGL 中)重新上传纹理。请批量进行样式更改,并只调用一次updateText(),或者直接设置样式对象的属性,最后再调用updateText()。 -
字体必须在使用前加载。 Text 对象使用 Canvas 的
fillTextAPI,因此字体必须在浏览器中可用(通过 CSS、Web 字体加载器加载,或已安装)。Phaser 不会加载 Web 字体——请使用第三方加载器或 CSS@font-face。 -
带有特殊字符的字体名称需要加引号。 包含数字或特殊字符的字体名称必须在字符串内使用双引号:
fontFamily: '"Press Start 2P"'。 -
文本原点默认为 (0, 0)。 与默认原点为 (0.5, 0.5) 的 Sprite 不同,Text 对象默认原点为左上角。调用
setOrigin(0.5)可居中。 -
对齐只影响多行文本。
align: 'center'对单行文本没有任何作用。要居中单行文本,请使用setOrigin(0.5)或手动定位。 -
BitmapText 字体必须在缓存中。 传递给
this.add.bitmapText()的字体键必须与通过this.load.bitmapFont()加载的键匹配。如果缺失,你会看到控制台警告和未定义行为。 -
BitmapText 的
setMaxWidth只会在空白字符处换行。 如果未找到空白字符(默认字符编码 32 = 空格),则不会发生换行。可以通过setMaxWidth的第二个参数更改换行字符。 -
DynamicBitmapText 开销更大。 显示回调会每帧对每个字符运行。仅在你需要逐字符操作时使用它。
-
setCharacterTint和setWordTint仅适用于 WebGL。 这些在 Canvas 渲染器中没有任何效果。 -
setDropShadow仅适用于 WebGL 和静态 BitmapText。 它不适用于 DynamicBitmapText 或 Canvas 渲染器。 -
Text 上的
letterSpacing开销很大。 当它非零时,Phaser 会使用fillText逐个渲染每个字符,而不是一次渲染整行。对于带有自定义字母间距的大量文本,请使用 BitmapText。 -
setFill和setColor是等价的。 两者都会设置 TextStyle 上的color属性。样式配置也接受fill作为color的别名。 -
必须在
setText之前调用setRTL。 从右到左模式会改变内部 canvas 的构建方式。如果你在设置文本之后调用setRTL(true),文本可能无法正确渲染。请在样式配置中设置 RTL,或在任何setText()调用之前调用setRTL()。 -
对于频繁更新的内容,BitmapText 要快得多。 Text 会在每次更改时重新渲染整个 Canvas 并重新上传 GPU 纹理。BitmapText 只是更新现有纹理图集中的顶点数据。对于分数、计时器、伤害数字,或任何每帧都会变化的文本,请始终优先使用 BitmapText。
源文件映射
| 文件 | 描述 |
|---|---|
src/gameobjects/text/Text.js | Text 类(基于 Canvas 的渲染) |
src/gameobjects/text/TextFactory.js | this.add.text() 工厂方法 |
src/gameobjects/text/TextStyle.js | TextStyle 类(所有样式属性 + setter) |
src/gameobjects/text/GetTextSize.js | 内部文本测量 |
src/gameobjects/text/MeasureText.js | 字体度量测量 |
src/gameobjects/text/typedefs/ | TextStyle、TextWordWrap、TextPadding、TextShadow 类型定义 |
src/gameobjects/bitmaptext/static/BitmapText.js | 静态 BitmapText 类 |
src/gameobjects/bitmaptext/static/BitmapTextFactory.js | this.add.bitmapText() 工厂方法 |
src/gameobjects/bitmaptext/dynamic/DynamicBitmapText.js | DynamicBitmapText(继承自 BitmapText) |
src/gameobjects/bitmaptext/dynamic/DynamicBitmapTextFactory.js | this.add.dynamicBitmapText() 工厂方法 |
src/gameobjects/bitmaptext/ParseRetroFont.js | 复古字体解析器(RetroFont.Parse) |
src/gameobjects/bitmaptext/RetroFont.js | RetroFont 命名空间(Parse + TEXT_SET 常量) |
src/gameobjects/bitmaptext/const.js | TEXT_SET1 至 TEXT_SET11 常量 |
src/gameobjects/bitmaptext/GetBitmapTextSize.js | BitmapText 边界计算 |
src/gameobjects/bitmaptext/typedefs/ | RetroFontConfig、DisplayCallbackConfig、BitmapTextSize 类型定义 |