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 信息
});
配置项详解
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
width | number | 800 | canvas 的 CSS 宽度 |
height | number | 600 | canvas 的 CSS 高度 |
backgroundColor | number | 0x000000 | 背景色 |
backgroundAlpha | number | 1 | 背景透明度(0-1) |
antialias | boolean | false | 抗锯齿(影响性能) |
resolution | number | 1 | 渲染分辨率倍数 |
autoDensity | boolean | false | 自动调整 canvas 的 CSS 尺寸以匹配分辨率 |
preference | string | ‘webgl’ | 渲染器偏好:‘webgl’ 或 ‘webgpu’ |
powerPreference | string | ‘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 详解
| 属性 | 类型 | 说明 |
|---|---|---|
deltaTime | number | 归一化帧间隔(以 60fps 为基准 ≈ 1) |
deltaMS | number | 帧间隔(毫秒) |
elapsedMS | number | 上一帧到现在的总毫秒 |
FPS | number | 当前帧率 |
⚠️ 注意: 始终使用 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 为 undefined | 在 init() 之前访问 | 确保 await app.init() 完成后再访问 |
| 画面闪烁 | 多次调用 init() | 只调用一次 init() |
| 高分屏模糊 | resolution 为 1 | 设置为 window.devicePixelRatio |
| ticker 回调不触发 | app.ticker.stop() 被意外调用 | 检查是否有暂停逻辑 |
| 销毁后仍执行回调 | 未移除 ticker 监听 | 在 app.destroy() 前移除自定义回调 |
💡 进阶提示
WebGPU 渲染器切换:
const app = new Application(); await app.init({ preference: 'webgpu', // v8 会自动创建合适的渲染器 });调试用 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)}`; });固定时间步长物理模拟:
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; } });
扩展阅读
- Pixi.js Application API
- Pixi.js Ticker 指南
- WebGL 渲染管线详解
- 游戏循环模式(Game Loop Pattern)
- requestAnimationFrame 最佳实践
上一章:01 - Pixi.js 简介与环境搭建 下一章:03 - Sprite 与纹理