关于 pixijs-ticker
面向 PixiJS 开发者的技能,介绍如何使用 Ticker 驱动更新与渲染循环。内容涵盖回调优先级、生命周期控制,以及不同时间值的用途。
主要功能
- 01Ticker 回调与时间值
- 02回调优先级排序
- 03帧率限制与速度缩放
- 04Ticker 和应用生命周期控制
- 05显示对象的 onRender 逻辑
适用场景
- 01以帧率无关方式制作对象动画
- 02限制帧率或控制过大的时间增量
- 03暂停和恢复应用渲染循环
- 04为显示对象添加逐帧逻辑
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ pixijs/ pixijs-skills --skill pixijs-ticker安装后在 Claude Code 中输入/pixijs-ticker确认已加载。
详细介绍
app.ticker 每帧运行已注册的回调,并以 UPDATE_PRIORITY.LOW 驱动 app.render()。每个回调都会接收 Ticker 实例;读取 deltaTime 可获取与帧率无关的乘数(在 60fps 时约为 1.0),读取 deltaMS 可用于实时计算。
快速入门
app.ticker.add((ticker) => {
sprite.rotation += 0.01 * ticker.deltaTime;
sprite.x += (200 / 1000) * ticker.deltaMS;
});
app.ticker.add(
(ticker) => {
updatePhysics(ticker.deltaMS);
},
undefined,
UPDATE_PRIORITY.HIGH,
);
app.ticker.maxFPS = 30;
app.ticker.speed = 0.5;
sprite.onRender = () => {
sprite.scale.x = Math.sin(performance.now() / 500);
};
相关技能: pixijs-application(Application 设置和 sharedTicker 选项)、pixijs-performance(帧率优化)、pixijs-migration-v8(v7 Ticker 签名变更)。
核心模式
时间单位
Ticker 提供三个时间值,分别适用于不同场景:
| 属性 | 类型 | 受 speed 缩放? | 受 minFPS 限制? | 使用场景 |
|---|---|---|---|---|
deltaTime | 无量纲(在 60fps 时约为 1.0) | 是 | 是 | 与帧率无关的动画乘数 |
deltaMS | 毫秒 | 是 | 是 | 基于时间的计算(像素/秒) |
elapsedMS | 毫秒 | 否 | 否 | 原始测量、性能分析 |
import { Application } from "pixi.js";
const app = new Application();
await app.init({ width: 800, height: 600 });
app.ticker.add((ticker) => {
// deltaTime: dimensionless scalar, ~1.0 at 60fps
sprite.rotation += 0.1 * ticker.deltaTime;
// deltaMS: real milliseconds (speed-scaled, capped)
sprite.x += (200 / 1000) * ticker.deltaMS; // 200 pixels per second
// elapsedMS: raw milliseconds (no scaling, no cap)
console.log(`Raw frame time: ${ticker.elapsedMS}ms`);
});
易混淆说明: deltaTime 不是毫秒。它等于 deltaMS * Ticker.targetFPMS,其中 targetFPMS 为 0.06(即 1/16.67)。恰好处于 60fps 时,deltaTime 为 1.0;处于 30fps 时,deltaTime 为 2.0。许多人会误把它当作时间值。
优先级顺序和上下文绑定
import { Application, UPDATE_PRIORITY } from "pixi.js";
const app = new Application();
await app.init({ width: 800, height: 600 });
// INTERACTION (50) > HIGH (25) > NORMAL (0) > LOW (-25) > UTILITY (-50)
// app.render() is registered at LOW by the TickerPlugin
app.ticker.add(
(ticker) => {
// Physics runs before normal-priority callbacks
updatePhysics(ticker.deltaMS);
},
undefined,
UPDATE_PRIORITY.HIGH,
);
app.ticker.add((ticker) => {
// Default priority (NORMAL = 0), runs after HIGH but before render
updateAnimations(ticker.deltaTime);
});
// Pass `this` as the second argument to preserve context on class methods
class GameSystem {
public speed = 5;
public position = 0;
public update(ticker: Ticker): void {
this.position += this.speed * ticker.deltaTime;
}
}
const system = new GameSystem();
app.ticker.add(system.update, system);
app.ticker.remove(system.update, system); // must match both fn and context
帧率上限
import { Ticker } from "pixi.js";
const ticker = new Ticker();
ticker.maxFPS = 30; // Cap at 30fps (skips frames to maintain interval)
ticker.minFPS = 10; // Cap deltaTime so it never exceeds 10fps worth
// If maxFPS < minFPS, minFPS is lowered to match
// If minFPS > maxFPS, maxFPS is raised to match
maxFPS 会跳过更新调用以强制执行帧率上限。minFPS 会限制 deltaTime/deltaMS,避免较大的掉帧产生极大的增量(默认 minFPS 为 10)。
对象级 onRender 钩子
import { Sprite, Assets, Application } from "pixi.js";
const app = new Application();
await app.init({ width: 800, height: 600 });
const texture = await Assets.load("bunny.png");
const sprite = new Sprite(texture);
app.stage.addChild(sprite);
sprite.onRender = (renderer) => {
sprite.rotation += 0.01;
};
onRender 会在场景图遍历期间、GPU 渲染之前调用。当逻辑与特定显示对象相关时,可以用它替代全局 Ticker 回调。
Ticker.shared、Ticker.system 和 new Ticker
import { Ticker, UPDATE_PRIORITY } from "pixi.js";
// Ticker.shared: singleton, autoStart=true, protected from destroy()
const shared = Ticker.shared;
// Ticker.system: separate instance used by engine background tasks,
// independent of the main scene ticker. It's a plain Ticker instance
// (autoStart=true, _protected=true) with no intrinsic priority; listeners
// are typically added at UTILITY priority by convention.
const system = Ticker.system;
// new Ticker(): custom instance, autoStart=false, you manage the lifecycle
const custom = new Ticker();
custom.autoStart = true; // start when first listener is added
custom.add((ticker) => {
console.log(ticker.deltaMS);
});
// One-shot callback that auto-removes after firing
custom.addOnce(() => console.log("fires once"), null, UPDATE_PRIORITY.NORMAL);
// When done:
custom.stop();
custom.destroy();
默认情况下,Application 会创建自己的 Ticker。在 app.init() 中设置 sharedTicker: true,即可改用 Ticker.shared。Ticker.shared 和 Ticker.system 都受 _protected 保护,即使调用它们的 destroy(),也不会真正销毁它们。读取 ticker.FPS 可获取测得的帧率,读取 ticker.count 可获取当前监听器数量。
应用生命周期和手动渲染
import { Application } from "pixi.js";
const app = new Application();
await app.init({ autoStart: false });
// Pause and resume the built-in render loop at any time.
app.start();
app.stop();
// Or drive the loop yourself (headless, visibility-gated, fixed timestep, etc.)
function animate() {
app.ticker.update(); // fires registered callbacks
app.render(); // renders the stage
requestAnimationFrame(animate);
}
animate();
app.start() 和 app.stop() 由 TickerPlugin 添加,分别映射到 ticker.start() / ticker.stop()。当需要在切换浏览器标签页时暂停、运行固定时间步长循环或进行离屏渲染时,可使用 autoStart: false,并自行驱动帧循环。
速度缩放
import { Application } from "pixi.js";
const app = new Application();
await app.init({ width: 800, height: 600 });
app.ticker.speed = 0.5; // Half speed (slow motion)
app.ticker.speed = 2.0; // Double speed
// speed affects deltaTime and deltaMS, but NOT elapsedMS
常见错误
[CRITICAL] Ticker 回调会将 delta 作为第一个参数
错误:
app.ticker.add((dt) => {
bunny.rotation += dt;
});
正确:
app.ticker.add((ticker) => {
bunny.rotation += ticker.deltaTime;
});
v8 传递给回调的参数是 Ticker 实例,而不是 delta 数值。v7 的写法 (dt) => ... 虽然可以编译,但 dt 实际上是整个 Ticker 对象,因此对它进行算术运算会得到 NaN。
[HIGH] 使用 updateTransform 执行逐帧逻辑
错误:
class MySprite extends Sprite {
updateTransform() {
super.updateTransform();
this.rotation += 0.01;
}
}
正确:
class MySprite extends Sprite {
constructor() {
super();
this.onRender = this._onRender.bind(this);
}
private _onRender() {
this.rotation += 0.01;
}
}
v8 已移除 updateTransform。逐对象执行逐帧逻辑时,请使用 onRender 回调。
[MEDIUM] 将 deltaTime 当作毫秒
错误:
app.ticker.add((ticker) => {
// Tries to move 100px/sec but deltaTime is ~1.0, not ~16.67
sprite.x += (100 * ticker.deltaTime) / 1000;
});
正确:
app.ticker.add((ticker) => {
// Using deltaMS for time-based movement
sprite.x += (100 / 1000) * ticker.deltaMS;
// Or using deltaTime as a frame-rate multiplier
sprite.x += 1.5 * ticker.deltaTime;
});
deltaTime 是无量纲标量(在 60fps 时约为 1.0),不是毫秒。实时计算请使用 deltaMS。如果希望实现“以 60fps 运行时每帧执行一次”的效果,请将 deltaTime 用作简单的乘数。