Pixi.js 游戏开发教程 / 文本渲染(Text / BitmapText)
文本渲染(Text / BitmapText)
概述
文本是任何交互式应用不可或缺的元素。Pixi.js 提供了三种文本方案:
| 方案 | 适用场景 | 性能 | 灵活性 |
|---|---|---|---|
Text | 通用文本、动态内容 | 中等 | 高 |
BitmapText | 大量静态文本、游戏UI | 高 | 中 |
HTMLText | 富文本、HTML 混排 | 低 | 最高 |
Text 基础
import { Application, Text, TextStyle } from 'pixi.js';
const app = new Application();
await app.init({ width: 800, height: 600, background: '#1a1a2e' });
document.body.appendChild(app.canvas);
// 方式一:简洁创建
const simple = new Text({
text: 'Hello Pixi.js!',
style: { fontSize: 32, fill: '#ffffff' },
});
simple.position.set(50, 50);
app.stage.addChild(simple);
// 方式二:使用 TextStyle 对象
const style = new TextStyle({
fontFamily: 'Arial',
fontSize: 48,
fontWeight: 'bold',
fill: '#ff6b6b',
stroke: { color: '#000000', width: 4 },
dropShadow: {
color: '#000000',
blur: 4,
angle: Math.PI / 6,
distance: 6,
},
});
const rich = new Text({ text: 'Rich Text', style });
rich.position.set(50, 120);
app.stage.addChild(rich);
TextStyle 配置
完整属性表
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
fontFamily | string | ‘Arial’ | 字体 |
fontSize | number | 26 | 字号(像素) |
fontStyle | string | ’normal' | ’normal’/‘italic’/‘oblique’ |
fontWeight | string | ’normal’ | ’normal’/‘bold’/数字 |
fill | number/string | ‘#ffffff’ | 文字颜色 |
stroke | object | - | 描边 {color, width} |
letterSpacing | number | 0 | 字间距(像素) |
lineHeight | number | - | 行高 |
align | string | ’left’ | ’left’/‘center’/‘right’ |
wordWrap | boolean | false | 自动换行 |
wordWrapWidth | number | 100 | 换行宽度(像素) |
breakWords | boolean | false | 允许断字 |
padding | number | 0 | 纹理边缘留白 |
dropShadow | object/boolean | false | 阴影配置 |
文字阴影
const shadowStyle = new TextStyle({
fontSize: 36,
fill: '#ecf0f1',
dropShadow: {
color: '#000000',
alpha: 0.5,
blur: 4,
angle: Math.PI / 4,
distance: 4,
},
});
文字描边
const strokeStyle = new TextStyle({
fontSize: 48,
fill: '#3498db',
stroke: {
color: '#ffffff',
width: 5,
join: 'round',
},
});
Web 字体加载
使用 Google Fonts 或自定义字体前,必须确保字体已加载完成:
// 方法一:CSS @font-face + document.fonts.ready
// 在 CSS 中定义:
// @font-face { font-family: 'GameFont'; src: url('/font/game.woff2') format('woff2'); }
await document.fonts.load('48px GameFont');
await document.fonts.ready;
const text = new Text({
text: '自定义字体',
style: { fontFamily: 'GameFont', fontSize: 48, fill: '#ffffff' },
});
// 方法二:使用 FontFace API
const font = new FontFace('MyFont', 'url(/font/myfont.woff2)');
await font.load();
document.fonts.add(font);
const text2 = new Text({
text: 'FontFace API',
style: { fontFamily: 'MyFont', fontSize: 36, fill: '#f1c40f' },
});
⚠️ 注意: 字体未加载完成时,Text 会使用回退字体渲染,导致文字尺寸计算错误。务必使用 await 等待字体就绪。
BitmapText(位图字体)
BitmapText 使用预渲染的字形图集,渲染性能远优于普通 Text。
优势对比
| 特性 | Text | BitmapText |
|---|---|---|
| 渲染方式 | Canvas 2D 实时渲染 | GPU 纹理采样 |
| 首次渲染 | 较慢(需绘制) | 快 |
| 大量文字 | 每个 Text 独立纹理 | 共享图集 |
| 动态字体大小 | ✅ | ❌(固定) |
| 特效(描边等) | ✅ | 有限 |
使用 BitmapText
import { BitmapFont, BitmapText } from 'pixi.js';
// 注册位图字体
BitmapFont.install({
name: 'GameFont',
style: {
fontFamily: 'Arial',
fontSize: 32,
fill: '#ffffff',
},
// 可选:指定字符集
chars: BitmapFont.ASCII,
});
// 创建 BitmapText
const bmpText = new BitmapText({
text: 'Score: 99999',
style: {
fontFamily: 'GameFont',
fontSize: 32,
},
});
bmpText.position.set(50, 250);
app.stage.addChild(bmpText);
加载外部位图字体
import { Assets } from 'pixi.js';
// 加载 .fnt 文件(BMFont 格式)
await Assets.load('/font/game.fnt');
const text = new BitmapText({
text: 'Bitmap Font',
style: { fontFamily: 'GameFont' },
});
HTMLText(富文本)
HTMLText 使用 HTML 渲染文本,支持富文本样式:
import { HTMLText } from 'pixi.js';
const htmlText = new HTMLText({
text: `<span style="color: red; font-size: 32px;">Red</span>
<span style="color: blue; font-size: 24px;">Blue</span>
<b>Bold</b> and <i>Italic</i>`,
style: {
fontFamily: 'Arial',
fontSize: 28,
fill: '#ffffff',
},
});
htmlText.position.set(50, 350);
app.stage.addChild(htmlText);
⚠️ 注意: HTMLText 依赖浏览器的 HTML 渲染引擎,性能较低。大量文字时不建议使用。
文本动画:打字机效果
import { Text, TextStyle } from 'pixi.js';
class TypewriterText {
text: Text;
fullText: string;
currentIndex: number = 0;
speed: number; // 每帧显示的字符数
constructor(fullText: string, x: number, y: number, speed: number = 1) {
this.fullText = fullText;
this.speed = speed;
const style = new TextStyle({
fontFamily: 'Courier New',
fontSize: 24,
fill: '#00ff00',
wordWrap: true,
wordWrapWidth: 600,
});
this.text = new Text({ text: '', style });
this.text.position.set(x, y);
}
update(delta: number): boolean {
this.currentIndex += this.speed * delta;
const chars = Math.floor(this.currentIndex);
if (chars <= this.fullText.length) {
this.text.text = this.fullText.substring(0, chars);
return false; // 未完成
}
this.text.text = this.fullText;
return true; // 完成
}
}
// 使用
const typewriter = new TypewriterText(
'在遥远的大陆上,一位勇者踏上了冒险之旅...',
50, 450, 2
);
app.stage.addChild(typewriter.text);
app.ticker.add((ticker) => {
typewriter.update(ticker.deltaTime);
});
动态文本更新
// FPS 显示
const fpsText = new Text({
text: 'FPS: 0',
style: { fontSize: 16, fill: '#00ff00', fontFamily: 'monospace' },
});
fpsText.position.set(10, 10);
app.stage.addChild(fpsText);
// 分数(格式化显示)
const scoreText = new Text({
text: 'SCORE: 0000000',
style: { fontSize: 28, fill: '#f1c40f', fontFamily: 'monospace' },
});
scoreText.position.set(600, 20);
app.stage.addChild(scoreText);
let score = 0;
app.ticker.add(() => {
fpsText.text = `FPS: ${Math.round(app.ticker.FPS)}`;
score += 1;
scoreText.text = `SCORE: ${String(score).padStart(7, '0')}`;
});
💡 提示: 频繁更新 text.text 会导致纹理重新生成。如果文字变化极频繁(每帧更新),考虑使用 BitmapText。
字体回退策略
const fallbackStyle = new TextStyle({
fontFamily: 'GameFont, "PingFang SC", "Microsoft YaHei", sans-serif',
fontSize: 28,
fill: '#ffffff',
});
// 更安全的回退:先检测字体是否可用
function isFontAvailable(fontName: string): boolean {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d')!;
ctx.font = `72px monospace`;
const baseline = ctx.measureText('test').width;
ctx.font = `72px '${fontName}', monospace`;
return ctx.measureText('test').width !== baseline;
}
if (!isFontAvailable('GameFont')) {
console.warn('GameFont 未安装,使用回退字体');
}
文本性能优化
| 策略 | 说明 |
|---|---|
| 使用 BitmapText | 大量文字时性能远优于 Text |
| 减少 Text 数量 | 合并多个 Text 为一个 |
| 降低更新频率 | 不需要每帧更新的文字设为缓存 |
| 控制 padding | 减少 Text 纹理尺寸 |
| 使用 resolution: 1 | 不需要高清的文字降低分辨率 |
// 低性能需求文字 —— 降低分辨率
const cheapText = new Text({
text: 'Debug Info',
style: { fontSize: 12, fill: '#888' },
resolution: 1, // 始终使用 1x 分辨率
});
游戏开发场景
场景:对话系统
interface DialogLine {
speaker: string;
content: string;
}
class DialogBox {
container: Container;
speakerText: Text;
contentText: TypewriterText;
bg: Graphics;
lines: DialogLine[];
currentLine: number = 0;
onComplete?: () => void;
constructor(lines: DialogLine[]) {
this.lines = lines;
this.container = new Container();
// 对话框背景
this.bg = new Graphics();
this.bg.roundRect(0, 0, 760, 150, 12);
this.bg.fill({ color: 0x000000, alpha: 0.8 });
this.bg.stroke({ width: 2, color: 0x3498db });
this.bg.position.set(20, 430);
this.container.addChild(this.bg);
// 说话人
this.speakerText = new Text({
text: '',
style: { fontSize: 20, fill: '#f1c40f', fontWeight: 'bold' },
});
this.speakerText.position.set(40, 440);
this.container.addChild(this.speakerText);
// 内容
this.contentText = new TypewriterText('', 40, 470, 2);
this.container.addChild(this.contentText.text);
this.showLine(0);
}
showLine(index: number) {
const line = this.lines[index];
this.speakerText.text = line.speaker;
this.contentText = new TypewriterText(line.content, 40, 470, 2);
}
next(): boolean {
this.currentLine++;
if (this.currentLine >= this.lines.length) return false;
this.showLine(this.currentLine);
return true;
}
}
⚠️ 常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 文字模糊 | 分辨率不足 | 设置合适的 resolution |
| 中文不显示 | 字体不支持中文 | 使用支持中文的字体 |
| 文字尺寸变化后位置错误 | 未重新计算布局 | 监听 text.text 变化更新布局 |
| 大量 Text 卡顿 | 每个 Text 独立纹理 | 使用 BitmapText 或合并文本 |
| 字体加载闪白 | 字体未就绪就渲染 | 确保 await document.fonts.ready |
💡 进阶提示
Text 复用: 避免频繁创建/销毁 Text,使用对象池:
class TextPool { private pool: Text[] = []; private style: TextStyle; constructor(style: TextStyle) { this.style = style; } get(content: string): Text { const text = this.pool.pop() || new Text({ text: '', style: this.style }); text.text = content; text.visible = true; return text; } release(text: Text) { text.visible = false; text.removeFromParent(); this.pool.push(text); } }文本缓存策略:
// 静态文本 —— 缓存为纹理后禁用更新 staticText.cacheAsBitmap = true;Unicode Emoji 支持:
const emojiText = new Text({ text: '🎮 Game Over! 🏆', style: { fontSize: 36, fill: '#ffffff' }, }); // 需要系统字体支持 Emoji
扩展阅读
上一章:05 - 图形绘制(Graphics) 下一章:07 - 动画基础(Ticker)