Арт игры генерируется кодом: сценарий apps/game/tools/pixelart/gen.mjs рисует спрайты на канвасе из движка и кодирует PNG без сторонних зависимостей (node:zlib + свой CRC32). Позже генератор можно заменить на рисованный арт — движку всё равно, откуда текстуры.
| Файл | Что делает |
|---|---|
packages/engine/tools/png.mjs |
PNG-кодек без зависимостей: encodePng/decodePng (RGBA 8 бит) |
packages/engine/tools/canvas.mjs |
Canvas (таблица цветов — параметр): set/get пикселя, fillDiamond (изо-ромб), fromAscii, seeded rng |
packages/engine/tools/imaging.mjs |
Кроп по альфе, nearest-масштаб, квантизация в палитру, обзорный лист; findFigures/resizeRect — фигуры на однотонном фоне |
packages/engine/tools/atlas.mjs |
buildAtlas — кадры в строку + Pixi Spritesheet JSON (с animations) |
packages/engine/tools/frames.mjs |
Производные кадры из опорных: shift/bob/squash/swing/mirror, deriveCycle |
apps/game/tools/pixelart/palette.mjs |
Палитра игры из 32 цветов (hex + RGBA) — сценарный слой |
apps/game/tools/pixelart/gen.mjs |
Генерация всех спрайтов игры → apps/game/assets/ (канвас с палитрой игры) |
npm run art # node apps/game/tools/pixelart/gen.mjs — перегенерировать все PNG
Выходные файлы кладутся в apps/game/assets/ и грузятся в игре по ключам (tiles/grass, chars/hero_down_1, ...). Список ключей — в apps/game/src/Game.ts.
docs/art-style.md.apps/game/tools/pixelart/sizes.mjs (его импортируют gen.mjs и линтер aiart/lint-art.mjs — новые размеры добавляются в одном месте); рантайм размер кадра читает из атласного JSON (AssetLoader.frameSize).Персонажи задаются ASCII-картами (символ → цвет палитры) — читается и правится прямо в коде:
const heroDown = fromAscii(`
....0000....
...011111...
...
`, { '0': 'P4', '1': 'F2' });
Тайлы земли рисуются шумом (seeded rng) + ромбы fillDiamond (свет — верхние склоны, тень — нижние). Дерево: «облачная» крона из перекрывающихся окружностей, ствол, свисающий кабель — детали сеттинга.
Если спрайтов много (анимации), генератор может собрать атлас: кадры в один PNG + Spritesheet JSON. Формат Pixi:
const atlas = {
frames: {
hero_walk_1: { frame: { x: 0, y: 0, w: 16, h: 24 } },
hero_walk_2: { frame: { x: 16, y: 0, w: 16, h: 24 } }
},
meta: { image: 'hero_sheet.png', size: { w: 32, h: 24 }, scale: 1 }
};
Загрузка в игре — assets.loadAtlas('chars/hero_sheet'), кадры — assets.frames(key, 'hero_walk'). Генераторы могут заполнить animations (имя → список имён кадров, формат Pixi) — такие анимации игра берёт целиком: assets.animation('chars/hero_sheet', 'hero_walk_down').
Сборка атласа из готовых кадров — buildAtlas(frames, names, { frameW, frameH, image, animations? }) из @rpg/engine/tools/atlas.mjs → { png, json } (формат выше); animations — Record<имя, number[]> (индексы в names).
Дешёвые циклы из 1–2 опорных кадров — @rpg/engine/tools/frames.mjs (операции над RGBA Uint8Array, всё nearest):
shiftFrame(src, w, h, dx, dy) — сдвиг, пустота — прозрачность;bobFrame(src, w, h, dy = 1) — «парение» вверх (idle-кадр сгустков/духов);squashFrame(src, w, h) — «приседание» вниз на 1 px (2-й кадр ходьбы из стоячей позы — так AI-листы с одинаковыми позами становятся циклом);swingFrame(src, w, h, angle, pivot?) — nearest-поворот вокруг опоры (низ-центр по умолчанию) — кадры покачивания;mirrorFrame(src, w, h) — зеркалирование (кадр «вправо» → «влево»);deriveCycle(src, w, h, ops) — цикл по списку дескрипторов { dx?, dy?, bob?, squash?, swing?, mirror? }.Пример: в gen.mjs NPC idle-кадры — bobFrame от опорного; вода — два кадра с бликами, сдвинутыми на 1 px (кадр 2 рисуется со сдвигом штрихов).
Для пост-обработки AI-листов (несколько фигур на белом фоне) — imaging.mjs:
findFigures(img, { bg = 'corner', dist = 60, maxCount = 4, minArea = 400 }) — bbox'ы связных компонент пикселей, отстоящих от фона ('corner' — угловой пиксель) дальше dist; сортировка сверху-вниз/слева-направо.resizeRect(img, rect, w, h, { bg, dist }) — nearest-даунскейл прямоугольника до w×h; пиксели ближе dist к фону становятся прозрачными (квантизация — отдельный шаг quantize с палитрой-параметром).Сценарии применения (манифест персонажей, gen/build/promote, примерка tryon.mjs — подмена целевого атласа сборкой и скриншот в игре до апрува, ремапер recolor.mjs, линтер lint-art.mjs) — в игре: apps/game/tools/aiart/ (см. docs/plan.md, работа B).
Движку не важно происхождение текстур. Чтобы перейти на рисованный арт:
apps/game/assets/.docs/art-style.md).Aseprite/Piskel: экспорт в PNG с теми же именами файлов, что даёт генератор.