SpriteAnimation
Анимация спрайт-листа как аспект на Sprite — аксессор node.anim. Клипы
объявляются как данные (размер ячейки кадра плюс именованные списки индексов кадров) и затем
управляются по имени; после определения воспроизведение идёт полностью в нативном коде.
Обзор
const hero = new Sprite({ texture: tex, anchor: [0.5, 1] })
.aspect(SpriteAnimation, {
size: [32, 48], // одна ячейка кадра, в пикселях текстуры
fps: 10,
clips: {
idle: [0, 1],
walk: [4, 5, 6, 7],
die: { frames: [8, 9, 10], fps: 6, loop: false },
},
})
hero.anim.play('walk')
hero.addEventListener('completed', clip => { if (clip === 'die') hero.destroy() })Настройка
sprite.aspect(SpriteAnimation, {
size?: [w, h] // размер ячейки кадра в пикселях текстуры; по умолчанию = вся текстура
cols?: number // переопределение числа столбцов сетки; по умолчанию floor(texture.width / cellW)
origin?: [x, y] // пиксельное начало сетки (область листа); по умолчанию [0, 0]
fps?: number // fps по умолчанию для клипов; по умолчанию 12
loop?: boolean // loop по умолчанию для клипов; по умолчанию true
directions?: string[] // имена направлений в порядке строк текстуры (см. perDirection)
clips?: Record<string, Clip> // Clip = number[] | { frames, fps?, loop?, perDirection? }
})Индексы кадров считаются построчно по сетке листа: сетка имеет floor(texture.width / cellW)
столбцов, индекс 0 — верхняя-левая ячейка, индексы продолжаются слева направо, затем вниз. Клип — это
либо простой массив индексов (наследует fps/loop), либо объект с переопределениями на клип.
Листы по направлениям (одна строка текстуры на направление): задай directions в порядке строк
— компасные токены S/SE/E/NE/N/NW/W/SW — и пометь клипы perDirection: true. Каждый
разворачивается в один нативный клип на направление (walk_SE, …), где кадры базовой строки 0
сдвинуты вниз на номер строки.
нативные клипы нарезаются из пиксельного размера текстуры, поэтому они определены, когда у спрайта
есть текстура на момент прикрепления. Без неё (ленивая загрузка
SpriteSheet) определение откладывается — вызовы play() встают в
очередь по имени и стартуют, как только текстура приедет (неизвестные имена тогда бросают в момент
определения).
передача size также задаёт мировой size спрайта равным размеру ячейки, поэтому одна ячейка
рисуется 1 тексель = 1 мировая единица.
sprite.anim.define(): this // пересобрать нативные клипы после изменения anim.clipsВоспроизведение
sprite.anim.play(name: string, dir?: string | Vec2Like): this
sprite.anim.stop(): this
sprite.anim.speed = 2 // множитель скорости воспроизведения (только запись)
sprite.anim.current // имя играющего клипа (развёрнутое, напр. 'walk_SE')
sprite.anim.direction // текущее направление или null до первого направленного play
sprite.anim.frame // текущий индекс кадра (только чтение)Повторный запуск уже активного клипа — no-op — безопасно звать play('walk') каждый кадр из кода
ввода, не перезапуская анимацию.
Для клипа с perDirection направление выбирает dir: имя направления или вектор движения
(Y вверх; побеждает ближайшая компасная строка — play('walk', inputVector) и есть весь цикл
ходьбы). Последнее направление залипает, поэтому более поздний play('idle') сохраняет разворот;
нулевой вектор тоже его сохраняет.
События
События анимации эмитятся на узле, с именем клипа как аргументом:
hero.addEventListener('loopReached', clip => {}) // зацикленный клип обернулся (каждый цикл)
hero.addEventListener('completed', clip => {}) // незацикленный клип завершился