Спрайт-листы (.sprite.ts)

Спрайт-листы как данные: один файл .sprite.ts на изображение, export default defineSpriteSheet({...}). ОДНА модель: каждая именованная запись — регион изображения (прямоугольник), по желанию нарезанный на сетку анимации, по желанию несущий именованные клипы по этой сетке — так что игровой код ссылается на спрайты и клипы по имени и никогда не зашивает прямоугольники. Визуальный редактор спрайтов (lecodes-2d-editor) читает и пишет эти файлы; ручные правки сохраняются.

Обзор

TypeScript
// hero.sprite.ts
export default defineSpriteSheet({
  image: asset('./hero.png'),
  anchor: [0.5, 1],
  fps: 10,
  sprites: {
    hero: {
      rect: [0, 0, 1344, 1536],
      slice: [7, 8],                       // сетка 7×8 из ячеек по 192px
      directions: ['S', 'SE', 'E', 'NE', 'N', 'NW', 'W', 'SW'],
      clips: {
        idle: { frames: [0], perDirection: true },
        walk: { frames: [1, 2, 3, 4, 5, 6], perDirection: true },
      },
    },
  },
})
TypeScript
// props.sprite.ts
export default defineSpriteSheet({
  image: asset('./props.png'),
  anchor: [0.5, 1],
  sprites: {
    tree: { rect: [0, 0, 64, 128] },
    water: { rect: [64, 0, 128, 32], slice: [4, 1] },    // четыре кадра по 32px
  },
})
TypeScript
// main.ts
import hero from './hero.sprite'
import props from './props.sprite'

const scene = new Scene2D()
scene.add(props.make('tree', { position: [80, 144], layer: 1 }))
scene.add(props.make('water', { position: [0, 0] }))    // нарезан, без клипов → зацикливается сам
const player = hero.make('hero', { clip: 'idle', layer: 1 })
scene.add(player)
player.anim.play('walk', [1, 0])                          // ходьба, лицом на восток
scene.open()

Определение

TypeScript
defineSpriteSheet({
  image: string                     // asset('./sheet.png')
  anchor?: [x, y]                   // пивот по умолчанию для каждого спрайта; [0.5, 1] = ступни
  fps?: number                      // fps воспроизведения по умолчанию для нарезанных спрайтов / клипов (иначе 12)
  sprites?: Record<string, {
    rect?: [x, y, w, h]             // пиксельный прямоугольник региона в изображении
    slice?: [cols, rows]            // разбить прямоугольник на сетку анимации (по желанию)
    fps?: number                    // переопределение fps для этого спрайта
    clips?: Record<string, Clip>    // именованные прогоны по ЛОКАЛЬНОЙ сетке (по желанию; см. SpriteAnimation)
    directions?: string[]           // имена направлений в порядке локальных строк для клипов perDirection
    anchor?: [x, y]                 //   переопределение для этого спрайта
  }>
}): SpriteSheet

Три уровня спрайта, каждый по желанию:

  • только rect — статичный спрайт (пропсы, предметы).
  • rect + slice — анимированный тайл: все ячейки зацикливаются по порядку с момента создания (вода, огонь, факелы). Клип вызывать не нужно — он идёт сам.
  • rect + slice + clips — персонаж: клипы именуют прогоны кадров по локальной сетке спрайта (построчно, индекс 0 = левая верхняя ячейка прямоугольника), управляются по имени через .anim.

Создание спрайтов

TypeScript
sheet.make(name, opts & { clip: string }): Sprite & { anim: SpriteAnimation }  // запускает клип
sheet.make(name, opts?): Sprite
// opts: { position?, layer?, anchor?, clip?, direction? }

make() собирает АРТ узла: кадр, мировой размер, якорь, анимацию. Неизвестное имя бросает исключение. Лист никогда не несёт семантику мира — коллайдеры и тела принадлежат сцене: прикрепи Shape2D / Physics2D / Trigger2D к размещённому узлу (список aspects: в .scene2d.ts или .aspect(...) в коде).

Мировой размер нарезанного спрайта — одна ячейка (rect / количество), известен до загрузки текстуры. Текстура загружается лениво при первом make() (или явном sheet.load()); спрайты, созданные до её прихода, получают кадр по прибытии, так что раскладка и Y-сортировка не ждут пикселей.

TypeScript
sheet.load(): Promise<Texture2D>   // идемпотентная явная предзагрузка
sheet.texture                      // Texture2D | null (null до загрузки)
sheet.spriteNames                  // string[]
sheet.rectOf(name)                 // пиксельный прямоугольник региона (весь rect, нарезан или нет), или null
sheet.def                          // определение (по соглашению только для чтения)

Подводные камни

  • Индексы кадров клипа локальны для rect спрайта — индекс 0 это левая верхняя ячейка прямоугольника, а не изображения.
  • Нарезанный спрайт без клипов играет один неявный клип, 'all'; make() типизирует его без .anim (он доступен как sprite.get(SpriteAnimation)). Нужен контроль? Назови клип и используй .anim.
  • Ищешь collider:? Листы — только арт: ставь Shape2D (+ Physics2D / Trigger2D) на размещённый узел в сцене.

Смотрите также

  • SpriteAnimation — клипы, perDirection, воспроизведение по вектору
  • Sprite — узел, который строит make()
  • Physics2D — тела для размещённых спрайтов (через аспекты Shape2D)