PixiJS 文本处理 #

文本渲染概述 #

PixiJS 提供两种文本渲染方式:Text 和 BitmapText。

text
┌─────────────────────────────────────────────────────────────┐
│                    文本渲染方式                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   Text(矢量文本)                                          │
│   ├── 使用系统字体或 Web 字体                               │
│   ├── 支持丰富的样式                                        │
│   ├── 动态生成纹理                                          │
│   └── 适合文本内容变化的场景                                │
│                                                             │
│   BitmapText(位图文本)                                     │
│   ├── 使用预渲染的位图字体                                  │
│   ├── 性能更高                                              │
│   ├── 需要提前生成字体文件                                  │
│   └── 适合大量文本或频繁更新的场景                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Text(矢量文本) #

创建文本 #

javascript
import { Text } from 'pixi.js';

// 简单创建
const text = new Text({ text: 'Hello PixiJS!' });
app.stage.addChild(text);

// 带样式创建
const styledText = new Text({
  text: 'Hello PixiJS!',
  style: {
    fontFamily: 'Arial',
    fontSize: 36,
    fill: 0xffffff
  }
});
app.stage.addChild(styledText);

文本样式 #

javascript
const text = new Text({
  text: 'Styled Text',
  style: {
    // 字体
    fontFamily: 'Arial',
    fontSize: 48,
    fontWeight: 'bold',
    fontStyle: 'italic',
    
    // 颜色
    fill: 0xff9900,
    
    // 描边
    stroke: {
      color: 0x000000,
      width: 2
    },
    
    // 阴影
    dropShadow: {
      color: 0x000000,
      alpha: 0.5,
      blur: 4,
      distance: 4,
      angle: Math.PI / 4
    },
    
    // 对齐
    align: 'center',
    
    // 行高和字间距
    lineHeight: 60,
    letterSpacing: 2,
    
    // 换行
    wordWrap: true,
    wordWrapWidth: 300,
    breakWords: false,
    
    // 省略
    textBaseline: 'middle',
    trim: false,
    whiteSpace: 'pre-wrap'
  }
});

动态更新 #

javascript
const text = new Text({
  text: 'Score: 0',
  style: {
    fontFamily: 'Arial',
    fontSize: 36,
    fill: 0xffffff
  }
});

// 更新文本内容
text.text = 'Score: 100';

// 更新样式
text.style.fill = 0xff0000;
text.style.fontSize = 48;

// 强制更新纹理
text._textureID = -1;

文本属性 #

javascript
const text = new Text({ text: 'Hello' });

// 位置
text.x = 100;
text.y = 100;
text.anchor.set(0.5);

// 尺寸
console.log(text.width);
console.log(text.height);

// 分辨率
text.resolution = 2;

// 自动分辨率
text.autoResolution = true;

多行文本 #

javascript
const text = new Text({
  text: 'This is a long text that will wrap to multiple lines when word wrap is enabled.',
  style: {
    fontFamily: 'Arial',
    fontSize: 24,
    fill: 0xffffff,
    wordWrap: true,
    wordWrapWidth: 300,
    align: 'center',
    lineHeight: 36
  }
});

渐变填充 #

javascript
const text = new Text({
  text: 'Gradient Text',
  style: {
    fontFamily: 'Arial',
    fontSize: 64,
    fill: {
      type: 'linear',
      x1: 0, y1: 0,
      x2: 0, y2: 1,
      stops: [
        { offset: 0, color: 0xff0000 },
        { offset: 0.5, color: 0xffff00 },
        { offset: 1, color: 0x00ff00 }
      ]
    }
  }
});

使用 Web 字体 #

javascript
// 方法1:CSS 加载字体
// 在 HTML 中添加:
// <link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">

// 方法2:使用 FontFace API
async function loadFont() {
  const font = new FontFace('CustomFont', 'url(/fonts/custom.woff2)');
  await font.load();
  document.fonts.add(font);
  
  const text = new Text({
    text: 'Custom Font',
    style: {
      fontFamily: 'CustomFont',
      fontSize: 48
    }
  });
}

await loadFont();

使用 Assets 加载字体 #

javascript
import { Assets } from 'pixi.js';

// 加载字体
await Assets.load({
  src: 'https://fonts.googleapis.com/css2?family=Roboto&display=swap',
  data: {
    family: 'Roboto',
    display: 'swap'
  }
});

const text = new Text({
  text: 'Roboto Font',
  style: {
    fontFamily: 'Roboto',
    fontSize: 36
  }
});

BitmapText(位图文本) #

什么是位图字体 #

位图字体是将字符预渲染为纹理,运行时直接使用这些纹理拼接文本,性能更高。

text
┌─────────────────────────────────────────────────────────────┐
│                    位图字体结构                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   字体文件 (.fnt 或 .xml)                                   │
│   ├── 字符映射信息                                          │
│   ├── 字符位置和尺寸                                        │
│   └── 字符间距信息                                          │
│                                                             │
│   纹理文件 (.png)                                           │
│   └── 所有字符的预渲染图像                                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

加载位图字体 #

javascript
import { BitmapText, Assets } from 'pixi.js';

// 加载位图字体
await Assets.load('fonts/desycel.xml');

// 创建位图文本
const bitmapText = new BitmapText({
  text: 'Hello Bitmap!',
  style: {
    fontFamily: 'Desycel',
    fontSize: 48
  }
});

app.stage.addChild(bitmapText);

BitmapText 样式 #

javascript
const bitmapText = new BitmapText({
  text: 'Bitmap Text',
  style: {
    fontFamily: 'Desycel',
    fontSize: 48,
    
    // 对齐
    align: 'center',
    
    // 字母间距
    letterSpacing: 2,
    
    // 最大宽度
    maxWidth: 300
  }
});

动态更新 #

javascript
const bitmapText = new BitmapText({
  text: 'Score: 0',
  style: {
    fontFamily: 'Desycel',
    fontSize: 36
  }
});

// 更新文本
bitmapText.text = 'Score: 100';

// 更新样式
bitmapText.style.fontSize = 48;

创建位图字体 #

可以使用工具生成位图字体:

工具 说明
BMFont Windows 经典工具
Glyph Designer macOS 工具
Littera 在线工具
FontBuilder 开源工具

生成步骤:

  1. 选择字体和大小
  2. 设置字符集(如 ASCII、中文等)
  3. 导出为 XML/JSON + PNG

Text vs BitmapText #

性能对比 #

text
┌─────────────────────────────────────────────────────────────┐
│                    性能对比                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   Text                                                      │
│   ├── 文本变化时需要重新生成纹理                            │
│   ├── 适合少量动态文本                                      │
│   └── 内存占用较小                                          │
│                                                             │
│   BitmapText                                                │
│   ├── 无需生成纹理,直接拼接                                │
│   ├── 适合大量文本或频繁更新                                │
│   └── 需要预加载字体纹理                                    │
│                                                             │
│   性能排序:                                                │
│   BitmapText > Text(静态) > Text(频繁更新)              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

选择建议 #

场景 推荐方式
UI 标签(静态) Text
分数/计时器(频繁更新) BitmapText
大量文本(如游戏对话) BitmapText
多语言支持 Text
特殊字体效果 Text
性能敏感场景 BitmapText

高级技巧 #

文本测量 #

javascript
const text = new Text({
  text: 'Measure Me',
  style: { fontFamily: 'Arial', fontSize: 36 }
});

// 获取文本尺寸
console.log('宽度:', text.width);
console.log('高度:', text.height);

// 获取精确边界
const bounds = text.getBounds();
console.log('边界:', bounds);

文本截断 #

javascript
function truncateText(text, maxWidth) {
  const originalText = text.text;
  
  if (text.width <= maxWidth) {
    return originalText;
  }
  
  let truncated = originalText;
  while (text.width > maxWidth && truncated.length > 0) {
    truncated = truncated.slice(0, -1);
    text.text = truncated + '...';
  }
  
  return text.text;
}

const text = new Text({
  text: 'This is a very long text that needs to be truncated',
  style: { fontFamily: 'Arial', fontSize: 24 }
});

truncateText(text, 200);

打字机效果 #

javascript
class TypeWriter {
  constructor(text, textObject, speed = 50) {
    this.fullText = text;
    this.textObject = textObject;
    this.speed = speed;
    this.currentIndex = 0;
    this.isTyping = false;
  }
  
  start() {
    this.currentIndex = 0;
    this.isTyping = true;
    this.type();
  }
  
  type() {
    if (!this.isTyping) return;
    
    if (this.currentIndex < this.fullText.length) {
      this.textObject.text = this.fullText.slice(0, this.currentIndex + 1);
      this.currentIndex++;
      setTimeout(() => this.type(), this.speed);
    } else {
      this.isTyping = false;
    }
  }
  
  stop() {
    this.isTyping = false;
  }
  
  complete() {
    this.textObject.text = this.fullText;
    this.isTyping = false;
  }
}

const text = new Text({
  text: '',
  style: { fontFamily: 'Arial', fontSize: 24, fill: 0xffffff }
});
app.stage.addChild(text);

const typewriter = new TypeWriter(
  'Hello, this is a typewriter effect!',
  text,
  50
);
typewriter.start();

文本闪烁效果 #

javascript
const text = new Text({
  text: 'Blinking Text',
  style: { fontFamily: 'Arial', fontSize: 36, fill: 0xffffff }
});

let alpha = 1;
let direction = -1;

app.ticker.add(() => {
  alpha += direction * 0.02;
  
  if (alpha <= 0.3) direction = 1;
  if (alpha >= 1) direction = -1;
  
  text.alpha = alpha;
});

文本阴影效果 #

javascript
const text = new Text({
  text: 'Shadow Text',
  style: {
    fontFamily: 'Arial',
    fontSize: 48,
    fill: 0xffffff,
    dropShadow: {
      color: 0x000000,
      alpha: 0.5,
      blur: 4,
      distance: 4,
      angle: Math.PI / 4
    }
  }
});

文本发光效果 #

javascript
import { GlowFilter } from 'pixi-filters';

const text = new Text({
  text: 'Glowing Text',
  style: {
    fontFamily: 'Arial',
    fontSize: 48,
    fill: 0xffffff
  }
});

text.filters = [new GlowFilter({
  distance: 15,
  outerStrength: 2,
  innerStrength: 0,
  color: 0x00ff00,
  quality: 0.5
})];

实战示例 #

分数显示 #

javascript
class ScoreDisplay extends Container {
  constructor() {
    super();
    
    this.score = 0;
    
    this.label = new Text({
      text: 'SCORE',
      style: {
        fontFamily: 'Arial',
        fontSize: 18,
        fill: 0x888888
      }
    });
    
    this.value = new BitmapText({
      text: '0',
      style: {
        fontFamily: 'Desycel',
        fontSize: 48
      }
    });
    
    this.value.y = 25;
    
    this.addChild(this.label, this.value);
  }
  
  setScore(score) {
    this.score = score;
    this.value.text = score.toString().padStart(6, '0');
  }
  
  addScore(points) {
    this.setScore(this.score + points);
  }
}

const scoreDisplay = new ScoreDisplay();
scoreDisplay.setScore(12345);
app.stage.addChild(scoreDisplay);

对话框系统 #

javascript
class DialogBox extends Container {
  constructor(width, height) {
    super();
    
    this.boxWidth = width;
    this.boxHeight = height;
    
    this.background = new Graphics();
    this.background.roundRect(0, 0, width, height, 10);
    this.background.fill({ color: 0x000000, alpha: 0.8 });
    this.background.stroke({ width: 2, color: 0xffffff });
    
    this.text = new Text({
      text: '',
      style: {
        fontFamily: 'Arial',
        fontSize: 20,
        fill: 0xffffff,
        wordWrap: true,
        wordWrapWidth: width - 40,
        lineHeight: 28
      }
    });
    this.text.x = 20;
    this.text.y = 20;
    
    this.speakerName = new Text({
      text: '',
      style: {
        fontFamily: 'Arial',
        fontSize: 16,
        fill: 0xffff00,
        fontWeight: 'bold'
      }
    });
    this.speakerName.x = 20;
    this.speakerName.y = -25;
    
    this.addChild(this.background, this.speakerName, this.text);
    
    this.typewriter = null;
  }
  
  show(speaker, message) {
    this.speakerName.text = speaker;
    this.text.text = '';
    this.visible = true;
    
    this.typewriter = new TypeWriter(message, this.text, 30);
    this.typewriter.start();
  }
  
  hide() {
    this.visible = false;
    if (this.typewriter) {
      this.typewriter.stop();
    }
  }
}

const dialog = new DialogBox(600, 150);
dialog.y = 400;
dialog.show('NPC', 'Welcome to the game! Press any key to continue.');
app.stage.addChild(dialog);

通知系统 #

javascript
class Notification extends Container {
  constructor(message, duration = 3000) {
    super();
    
    this.text = new Text({
      text: message,
      style: {
        fontFamily: 'Arial',
        fontSize: 18,
        fill: 0xffffff
      }
    });
    
    this.background = new Graphics();
    this.background.roundRect(
      -10,
      -5,
      this.text.width + 20,
      this.text.height + 10,
      5
    );
    this.background.fill({ color: 0x333333, alpha: 0.9 });
    
    this.addChild(this.background, this.text);
    
    this.alpha = 0;
    this.y = -50;
    
    this.animateIn();
    
    setTimeout(() => this.animateOut(), duration);
  }
  
  animateIn() {
    const targetY = this.y;
    this.y = targetY - 20;
    
    const animate = () => {
      this.alpha += 0.1;
      this.y += 2;
      
      if (this.alpha < 1) {
        requestAnimationFrame(animate);
      }
    };
    
    animate();
  }
  
  animateOut() {
    const animate = () => {
      this.alpha -= 0.1;
      
      if (this.alpha > 0) {
        requestAnimationFrame(animate);
      } else {
        this.destroy();
      }
    };
    
    animate();
  }
}

class NotificationManager {
  constructor(container) {
    this.container = container;
    this.notifications = [];
    this.spacing = 50;
  }
  
  show(message) {
    const notification = new Notification(message);
    notification.x = this.container.width / 2;
    notification.y = 50 + this.notifications.length * this.spacing;
    
    this.container.addChild(notification);
    this.notifications.push(notification);
    
    notification.on('destroyed', () => {
      const index = this.notifications.indexOf(notification);
      if (index > -1) {
        this.notifications.splice(index, 1);
        this.reposition();
      }
    });
  }
  
  reposition() {
    this.notifications.forEach((n, i) => {
      n.y = 50 + i * this.spacing;
    });
  }
}

性能优化 #

缓存文本 #

javascript
const text = new Text({
  text: 'Static Text',
  style: { fontFamily: 'Arial', fontSize: 36 }
});

// 对于不变化的文本,启用缓存
text.cacheAsBitmap = true;

批量更新 #

javascript
// 不推荐:多次更新
text.text = 'Score: ' + score;
text.style.fill = 0xff0000;
text.x = 100;

// 推荐:批量更新后一次性渲染
text.text = 'Score: ' + score;
text.style.fill = 0xff0000;
text.x = 100;
text.updateText();

使用 BitmapText #

javascript
// 对于频繁更新的文本,使用 BitmapText
const score = new BitmapText({
  text: '0',
  style: { fontFamily: 'Desycel', fontSize: 48 }
});

app.ticker.add(() => {
  score.text = currentScore.toString();
});

下一步 #

掌握了文本处理后,接下来学习 交互事件,了解如何处理用户交互!

最后更新:2026-03-29