强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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 配置

完整属性表

属性类型默认值说明
fontFamilystring‘Arial’字体
fontSizenumber26字号(像素)
fontStylestring’normal'’normal’/‘italic’/‘oblique’
fontWeightstring’normal’’normal’/‘bold’/数字
fillnumber/string‘#ffffff’文字颜色
strokeobject-描边 {color, width}
letterSpacingnumber0字间距(像素)
lineHeightnumber-行高
alignstring’left’’left’/‘center’/‘right’
wordWrapbooleanfalse自动换行
wordWrapWidthnumber100换行宽度(像素)
breakWordsbooleanfalse允许断字
paddingnumber0纹理边缘留白
dropShadowobject/booleanfalse阴影配置

文字阴影

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。

优势对比

特性TextBitmapText
渲染方式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

💡 进阶提示

  1. 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);
      }
    }
    
  2. 文本缓存策略:

    // 静态文本 —— 缓存为纹理后禁用更新
    staticText.cacheAsBitmap = true;
    
  3. Unicode Emoji 支持:

    const emojiText = new Text({
      text: '🎮 Game Over! 🏆',
      style: { fontSize: 36, fill: '#ffffff' },
    });
    // 需要系统字体支持 Emoji
    

扩展阅读


上一章:05 - 图形绘制(Graphics) 下一章:07 - 动画基础(Ticker)