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

Pixi.js 游戏开发教程 / Application 与渲染流程

Application 与渲染流程

Application 概述

Application 是 Pixi.js 的核心入口,它将渲染器(Renderer)、舞台(Stage)、计时器(Ticker)组合在一起,提供统一的生命周期管理。理解 Application 的配置和渲染流程是掌握 Pixi.js 的基础。


Application 基础配置

初始化参数

import { Application } from 'pixi.js';

const app = new Application();
await app.init({
  width: 1024,                    // 画布宽度(像素)
  height: 768,                    // 画布高度(像素)
  backgroundColor: 0x1a1a2e,     // 背景色(十六进制)
  antialias: true,                // 抗锯齿
  resolution: 2,                  // 分辨率倍数(像素比)
  autoDensity: true,              // 自动适配 CSS 尺寸
  hello: true,                    // 控制台输出 WebGL 信息
});

配置项详解

参数类型默认值说明
widthnumber800canvas 的 CSS 宽度
heightnumber600canvas 的 CSS 高度
backgroundColornumber0x000000背景色
backgroundAlphanumber1背景透明度(0-1)
antialiasbooleanfalse抗锯齿(影响性能)
resolutionnumber1渲染分辨率倍数
autoDensitybooleanfalse自动调整 canvas 的 CSS 尺寸以匹配分辨率
preferencestring‘webgl’渲染器偏好:‘webgl’ 或 ‘webgpu’
powerPreferencestring‘default’GPU 电源偏好:‘default’/‘high-performance’

⚠️ 注意: resolution 设置为 window.devicePixelRatio 时,在高 DPI 屏幕上可以获得清晰的渲染效果,但会增加显存消耗。2D 游戏通常设置为 1 即可。


app.stage 根容器

app.stage 是整个场景的根容器,所有要显示的对象都必须添加到 stage 或其子容器中。

import { Application, Sprite, Graphics, Text, TextStyle } from 'pixi.js';

const app = new Application();
await app.init({ width: 800, height: 600, background: '#1099bb' });
document.body.appendChild(app.canvas);

// 创建各种显示对象
const rect = new Graphics();
rect.rect(0, 0, 120, 80);
rect.fill(0xff6b6b);
rect.position.set(100, 100);

const label = new Text({
  text: 'Hello Pixi!',
  style: new TextStyle({ fontSize: 24, fill: '#ffffff' }),
});
label.position.set(100, 200);

// 添加到 stage
app.stage.addChild(rect);
app.stage.addChild(label);

// stage 本身也是一个 Container
console.log(app.stage.children.length); // 2

💡 提示: app.stage 是一个特殊的 Container,它永远是场景图的根节点。不要将它从任何父节点移除。


渲染循环(app.ticker)

ticker 基础

app.ticker 基于 requestAnimationFrame 实现,提供稳定的帧回调。

// 基本用法
app.ticker.add((ticker) => {
  const delta = ticker.deltaTime; // 帧间隔(归一化,60fps 时约为 1)
  const elapsedMS = ticker.deltaMS; // 帧间隔(毫秒)

  // 更新逻辑
  sprite.rotation += 0.01 * delta;
});

ticker 回调管理

// 添加回调 —— 返回唯一 ID
const callbackId = app.ticker.add(() => {
  console.log('每帧执行');
});

// 通过对象添加
const myUpdater = {
  update(ticker: Ticker) {
    // 自定义更新逻辑
  }
};
app.ticker.add(myUpdater);

// 移除回调
app.ticker.remove(myUpdater);

// 暂停/恢复
app.ticker.stop();
app.ticker.start();

// 设置最大帧率
app.ticker.maxFPS = 30;

deltaTime 详解

属性类型说明
deltaTimenumber归一化帧间隔(以 60fps 为基准 ≈ 1)
deltaMSnumber帧间隔(毫秒)
elapsedMSnumber上一帧到现在的总毫秒
FPSnumber当前帧率

⚠️ 注意: 始终使用 deltaTime 来乘以移动/旋转速度,确保不同帧率下行为一致:

// ✅ 正确 —— 帧率无关
sprite.x += 200 * delta / 60;

// ❌ 错误 —— 60fps 时速度正常,30fps 时慢一半
sprite.x += 200 / 60;

app.canvas

app.canvas 是实际的 HTML Canvas 元素(v8 中替代了 v7 的 app.view)。

// 获取 canvas 元素
const canvas = app.canvas;

// 添加到 DOM
document.body.appendChild(canvas);

// 或添加到指定容器
document.getElementById('game-container')!.appendChild(canvas);

// 设置样式
canvas.style.cursor = 'pointer';
canvas.style.border = '2px solid #333';

视口尺寸管理与 resize 适配

响应式画布

// 获取容器尺寸
const container = document.getElementById('game-container')!;

const app = new Application();
await app.init({
  resizeTo: container, // 自动监听容器尺寸变化
  backgroundColor: 0x1a1a2e,
  antialias: true,
  resolution: window.devicePixelRatio || 1,
  autoDensity: true,
});
container.appendChild(app.canvas);

手动 resize

// 监听窗口大小变化
window.addEventListener('resize', () => {
  app.renderer.resize(window.innerWidth, window.innerHeight);
  // 同时更新游戏逻辑中的宽高引用
  layoutGameObjects();
});

缩放适配策略

function resize(app: Application) {
  const screenW = app.screen.width;
  const screenH = app.screen.height;
  const gameW = 800;  // 设计宽度
  const gameH = 600;  // 设计高度

  // 等比缩放适配(保持宽高比,可能有黑边)
  const scale = Math.min(screenW / gameW, screenH / gameH);
  app.stage.scale.set(scale);

  // 居中
  app.stage.x = (screenW - gameW * scale) / 2;
  app.stage.y = (screenH - gameH * scale) / 2;
}

app.renderer.on('resize', () => resize(app));
resize(app);

app.renderer

app.renderer 是底层渲染器实例,提供更精细的控制。

const renderer = app.renderer;

// 手动渲染(通常不需要)
renderer.render(app.stage);

// 获取屏幕信息
console.log(renderer.width);     // 渲染宽度(像素)
console.log(renderer.height);    // 渲染高度(像素)
console.log(renderer.resolution); // 分辨率倍数

// 导出 canvas 为图片
const image = app.canvas.toDataURL('image/png');

// 截取指定区域
const extract = app.renderer.extract;
const imageElement = extract.image(app.stage);

Application 生命周期

new Application()
       │
       ▼
  app.init()          ← 异步初始化渲染器
       │
       ▼
  添加到 DOM           ← document.body.appendChild(app.canvas)
       │
       ▼
  添加显示对象         ← app.stage.addChild(...)
       │
       ▼
  app.ticker.add()    ← 注册更新循环
       │
       ▼
  (运行中...)
       │
       ▼
  app.destroy()       ← 销毁所有资源

销毁应用

// 完全销毁(包括 canvas)
app.destroy(true);

// 仅销毁内部资源,保留 canvas
app.destroy();

canvas 与 CSS 布局

/* 全屏画布 */
body {
  margin: 0;
  overflow: hidden;
}
canvas {
  display: block;
  width: 100vw;
  height: 100vh;
}
/* 容器内画布 */
#game-container {
  width: 800px;
  height: 600px;
  margin: 0 auto;
  border: 1px solid #ccc;
}

游戏开发场景

场景:完整的游戏启动模板

import { Application, Graphics, Text, TextStyle } from 'pixi.js';

class Game {
  app: Application;
  player!: Graphics;
  score: number = 0;
  scoreText!: Text;

  constructor() {
    this.app = new Application();
  }

  async start() {
    await this.app.init({
      width: 800,
      height: 600,
      backgroundColor: 0x2c3e50,
      antialias: true,
      resolution: window.devicePixelRatio || 1,
      autoDensity: true,
    });

    document.body.appendChild(this.app.canvas);

    this.createUI();
    this.createPlayer();

    // 主游戏循环
    this.app.ticker.add((ticker) => this.update(ticker.deltaTime));
  }

  createUI() {
    this.scoreText = new Text({
      text: 'Score: 0',
      style: new TextStyle({
        fontSize: 28,
        fill: '#ecf0f1',
        fontWeight: 'bold',
      }),
    });
    this.scoreText.position.set(20, 20);
    this.app.stage.addChild(this.scoreText);
  }

  createPlayer() {
    this.player = new Graphics();
    this.player.rect(0, 0, 40, 40);
    this.player.fill(0xe74c3c);
    this.player.position.set(400, 300);
    this.player.pivot.set(20, 20);
    this.app.stage.addChild(this.player);
  }

  update(delta: number) {
    this.player.rotation += 0.03 * delta;
    this.score += delta;
    this.scoreText.text = `Score: ${Math.floor(this.score)}`;
  }
}

// 启动游戏
const game = new Game();
game.start();

⚠️ 常见陷阱

问题原因解决方案
app.canvas 为 undefinedinit() 之前访问确保 await app.init() 完成后再访问
画面闪烁多次调用 init()只调用一次 init()
高分屏模糊resolution 为 1设置为 window.devicePixelRatio
ticker 回调不触发app.ticker.stop() 被意外调用检查是否有暂停逻辑
销毁后仍执行回调未移除 ticker 监听app.destroy() 前移除自定义回调

💡 进阶提示

  1. WebGPU 渲染器切换:

    const app = new Application();
    await app.init({
      preference: 'webgpu',
      // v8 会自动创建合适的渲染器
    });
    
  2. 调试用 FPS 显示器:

    const fpsText = new Text({ text: 'FPS: 0', style: { fontSize: 16, fill: '#0f0' } });
    fpsText.position.set(10, 10);
    app.stage.addChild(fpsText);
    
    app.ticker.add(() => {
      fpsText.text = `FPS: ${Math.round(app.ticker.FPS)}`;
    });
    
  3. 固定时间步长物理模拟:

    const FIXED_STEP = 1 / 60;
    let accumulator = 0;
    
    app.ticker.add((ticker) => {
      accumulator += ticker.deltaMS / 1000;
      while (accumulator >= FIXED_STEP) {
        physicsUpdate(FIXED_STEP);
        accumulator -= FIXED_STEP;
      }
    });
    

扩展阅读


上一章:01 - Pixi.js 简介与环境搭建 下一章:03 - Sprite 与纹理