pixijs-ticker

Par pixijs · pixijs-skills

Utilisez cette skill pour exécuter une logique par frame ou contrôler la boucle de rendu PixiJS v8. Couvre `Ticker.add`/`addOnce`/`remove`, `deltaTime` vs `deltaMS` vs `elapsedMS`, l'ordonnancement `UPDATE_PRIORITY`, le plafonnement `maxFPS`/`minFPS`, la mise à l'échelle de la vitesse, `Ticker.shared` vs les nouvelles instances, le hook `onRender` par objet, et le rendu manuel. Se déclenche sur : `Ticker`, `UPDATE_PRIORITY`, `deltaTime`, `deltaMS`, `elapsedMS`, `onRender`, `app.ticker`, `maxFPS`, `minFPS`, `Ticker.shared`.

npx skills add https://github.com/pixijs/pixijs-skills --skill pixijs-ticker

app.ticker exécute les callbacks enregistrés chaque frame et pilote app.render() à UPDATE_PRIORITY.LOW. Chaque callback reçoit l'instance Ticker ; lisez deltaTime comme un multiplicateur indépendant de la fréquence d'images (≈1.0 à 60fps) ou deltaMS pour les calculs en temps réel.

Démarrage rapide

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);
};

Skills associées : pixijs-application (configuration de l'Application et option sharedTicker), pixijs-performance (optimisation du taux de rafraîchissement), pixijs-migration-v8 (changements de signature du ticker en v7).

Modèles principaux

Unités de temps

Le Ticker expose trois valeurs de temps, chacune pour des cas d'usage différents :

Propriété Type Affectée par speed ? Limitée par minFPS ? Cas d'usage
deltaTime sans dimension (~1.0 à 60fps) oui oui Multiplicateurs d'animation indépendants de la fréquence d'images
deltaMS millisecondes oui oui Calculs basés sur le temps (pixels/sec)
elapsedMS millisecondes non non Mesure brute, profiling
import { Application } from "pixi.js";

const app = new Application();
await app.init({ width: 800, height: 600 });

app.ticker.add((ticker) => {
  // deltaTime: scalaire sans dimension, ~1.0 à 60fps
  sprite.rotation += 0.1 * ticker.deltaTime;

  // deltaMS: millisecondes réelles (affectée par speed, limitée)
  sprite.x += (200 / 1000) * ticker.deltaMS; // 200 pixels par seconde

  // elapsedMS: millisecondes brutes (sans scaling, sans limite)
  console.log(`Raw frame time: ${ticker.elapsedMS}ms`);
});

Note importante : deltaTime n'est pas des millisecondes. C'est deltaMS * Ticker.targetFPMS où targetFPMS est 0.06 (soit 1/16.67). À exactement 60fps, deltaTime est 1.0. À 30fps, deltaTime est 2.0. C'est un point qui surprend les développeurs qui le traitent comme une valeur de temps.

Ordonnancement des priorités et liaison de contexte

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() est enregistré à LOW par le TickerPlugin

app.ticker.add(
  (ticker) => {
    // La physique s'exécute avant les callbacks de priorité normale
    updatePhysics(ticker.deltaMS);
  },
  undefined,
  UPDATE_PRIORITY.HIGH,
);

app.ticker.add((ticker) => {
  // Priorité par défaut (NORMAL = 0), s'exécute après HIGH mais avant render
  updateAnimations(ticker.deltaTime);
});

// Passez `this` comme deuxième argument pour préserver le contexte sur les méthodes de classe
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); // doit correspondre à fn et context

Limitation du taux de rafraîchissement

import { Ticker } from "pixi.js";

const ticker = new Ticker();

ticker.maxFPS = 30; // Limiter à 30fps (ignore les frames pour maintenir l'intervalle)
ticker.minFPS = 10; // Limiter deltaTime pour qu'il ne dépasse jamais 10fps worth

// Si maxFPS < minFPS, minFPS est abaissé pour correspondre
// Si minFPS > maxFPS, maxFPS est augmenté pour correspondre

maxFPS ignore les appels de mise à jour pour appliquer un plafond. minFPS limite deltaTime/deltaMS pour que les grandes chutes de frame ne produisent pas de deltas énormes (minFPS par défaut est 10).

Hook onRender par objet

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 est appelé lors du parcours du graphe de scène, avant le rendu GPU. C'est une alternative à un callback ticker global quand la logique est liée à un objet d'affichage spécifique.

Ticker.shared, Ticker.system, et new Ticker

import { Ticker, UPDATE_PRIORITY } from "pixi.js";

// Ticker.shared: singleton, autoStart=true, protégé contre destroy()
const shared = Ticker.shared;

// Ticker.system: instance séparée utilisée par les tâches de fond du moteur,
// indépendante du ticker de la scène principale. C'est une instance Ticker ordinaire
// (autoStart=true, _protected=true) sans priorité intrinsèque ; les listeners
// sont généralement ajoutés à priorité UTILITY par convention.
const system = Ticker.system;

// new Ticker(): instance personnalisée, autoStart=false, vous gérez le cycle de vie
const custom = new Ticker();
custom.autoStart = true; // démarrer quand le premier listener est ajouté
custom.add((ticker) => {
  console.log(ticker.deltaMS);
});

// Callback unique qui se supprime automatiquement après son exécution
custom.addOnce(() => console.log("fires once"), null, UPDATE_PRIORITY.NORMAL);

// Quand c'est fini :
custom.stop();
custom.destroy();

Application crée son propre Ticker par défaut. Définissez sharedTicker: true dans app.init() pour utiliser Ticker.shared à la place. Ticker.shared et Ticker.system sont tous deux _protected et ne seront pas réellement détruits si vous appelez destroy() sur eux. Lisez ticker.FPS pour le taux de rafraîchissement mesuré et ticker.count pour le nombre actuel de listeners.

Cycle de vie de l'app et rendu manuel

import { Application } from "pixi.js";

const app = new Application();
await app.init({ autoStart: false });

// Pausez et reprenez la boucle de rendu intégrée à tout moment.
app.start();
app.stop();

// Ou pilotez la boucle vous-même (headless, gated par visibility, timestep fixe, etc.)
function animate() {
  app.ticker.update(); // exécute les callbacks enregistrés
  app.render(); // rend la scène
  requestAnimationFrame(animate);
}
animate();

app.start() et app.stop() sont ajoutés par le TickerPlugin et mappent à ticker.start() / ticker.stop(). Utilisez autoStart: false plus votre propre driver de frame quand vous avez besoin de pause sur blur d'onglet, d'exécuter une boucle avec timestep fixe, ou de rendu hors écran.

Mise à l'échelle de la vitesse

import { Application } from "pixi.js";

const app = new Application();
await app.init({ width: 800, height: 600 });

app.ticker.speed = 0.5; // Demi-vitesse (slow motion)
app.ticker.speed = 2.0; // Vitesse double

// speed affecte deltaTime et deltaMS, mais PAS elapsedMS

Erreurs courantes

[CRITIQUE] Le callback Ticker attend delta comme premier argument

Incorrect :

app.ticker.add((dt) => {
  bunny.rotation += dt;
});

Correct :

app.ticker.add((ticker) => {
  bunny.rotation += ticker.deltaTime;
});

v8 passe l'instance Ticker comme argument du callback, pas un nombre de delta. Le pattern v7 (dt) => ... compile mais dt est l'objet Ticker entier, donc les opérations arithmétiques sur lui produisent NaN.

[ÉLEVÉ] Utiliser updateTransform pour la logique par frame

Incorrect :

class MySprite extends Sprite {
  updateTransform() {
    super.updateTransform();
    this.rotation += 0.01;
  }
}

Correct :

class MySprite extends Sprite {
  constructor() {
    super();
    this.onRender = this._onRender.bind(this);
  }
  private _onRender() {
    this.rotation += 0.01;
  }
}

updateTransform a été supprimé en v8. Utilisez le callback onRender pour la logique par frame et par objet.

[MOYEN] Traiter deltaTime comme des millisecondes

Incorrect :

app.ticker.add((ticker) => {
  // Tente de déplacer 100px/sec mais deltaTime est ~1.0, pas ~16.67
  sprite.x += (100 * ticker.deltaTime) / 1000;
});

Correct :

app.ticker.add((ticker) => {
  // Utiliser deltaMS pour le mouvement basé sur le temps
  sprite.x += (100 / 1000) * ticker.deltaMS;
  // Ou utiliser deltaTime comme multiplicateur de fréquence d'images
  sprite.x += 1.5 * ticker.deltaTime;
});

deltaTime est un scalaire sans dimension (~1.0 à 60fps), pas des millisecondes. Utilisez deltaMS pour les calculs en temps réel. Utilisez deltaTime comme un simple multiplicateur quand vous voulez un comportement « par frame à 60fps ».

Référence API

Skills similaires