Все анимации движка тикаются от фиксированного шага (60 Гц) — Pixi-тикер не используется (sharedTicker: false), Pixi AnimatedSprite не применяется. Чистая математика (клипы, оживители, симуляция частиц) живёт отдельно от тонких Pixi-адаптеров и тестируется в Vitest без браузера.
Модули:
anim/clip.ts чистый степпер клипов (loop/once/pingpong) anim/SpriteAnimator.ts кадры → Sprite, именованные клипы, события anim/FrameAnimation.ts @deprecated-обёртка совместимости над SpriteAnimator
engine.fxЭффекты, аниматоры и вспышки не требуют ручного update(dt) из сцены — добавьте объект в engine.fx (core/Updatables), движок тикает его сам между твин-ами и логикой сцены:
engine.fx.add(emitter); // возвращает тот же объект
engine.fx.add(new SpriteMotion(view, { bob: {...} }, { seed: 7 }));
engine.fx.remove(fx); // редко: обычно достаточно destroyed
engine.fx.clear(); // при выходе из сцены, если она не разрушает дерево
Самозачистка: объект с destroyed === true вычищается из реестра после очередного прохода (Pixi destroy() выставляет флаг сам — эффекты, живущие в разрушаемом дереве мира, отпадают без кода). Допускается и локальный инстанс Updatables внутри сцены (тот же класс, свой тик).
import { SpriteAnimator } from '@rpg/engine';
const animator = new SpriteAnimator(sprite, {
walk_down: { frames: assets.frames('chars/hero_sheet.json', 'hero_down'), fps: 6 },
idle: { frames: [assets.frames('chars/hero_sheet.json', 'hero_down')[0]], loop: 'once' }
}, 'walk_down', { randomPhase: seed }); // seed — рассинхрон толпы
animator.play('walk_down', { restart: true }); // тот же клип без restart — продолжение
animator.pause(); // ... resume(), showStep(0) — заморозить на шаге
animator.onFrame = (step) => ...; // смена шага
animator.onFinish = (clip) => ...; // конец once/pingpong-прохода
animator.setClipFrames('walk_down', otherFrames); // кадры на лету (вариации окраса)
Режимы клипа (ClipLoop): 'loop' — цикл; 'once' — один раз и стоп на последнем кадре (после конца аниматор сам паузится); 'pingpong' — туда-обратно без дубля крайних кадров. Математика — чистая stepClip в anim/clip.ts: время накапливается в «шагах», dt больше периода проматывает промежуточные шаги (сообщается только финальный) — по аналогии с пересечением delay в Tween.
sprite.texture пишется только при смене шага. Флип и anchor — зона владельца спрайта (см. рецепт ходьбы в recipes.md).
FrameAnimation(sprite, frames, fps, loop) — прежний API одного клипа, оставлен как тонкая обёртка (@deprecated): setFrames(f, reset) продолжает с текущего шага без reset и стартует с нуля с ним.