#pragma once // ----------------------------------------------------------------------------- // Процедурная генерация всех спрайтов при старте: базовый набор не зависит ни // от одного файла на диске. // // Тайлы можно подменить на собранные SpriteForge (LoadTiles, dev-режим // --sf-tiles). Бойцы, твари и иконки остаются процедурными сознательно: на // 480x270 боец занимает 14 пикселей, и цвет там занят НОМЕРОМ слота, а силуэт — // КЛАССОМ оружия. И то, и другое живёт в коде, а не в манифесте // (см. docs/12-assets.md). // // Правило чтения картинки, из которого всё вытекает: // * номер бойца -> цвет тела; // * класс оружия -> силуэт (ствол/клинок, длинный/короткий/тяжёлый); // * вид твари -> и силуэт, и цвет; их всего пять, и путать их нельзя. // ----------------------------------------------------------------------------- #include "engine/hash.h" #include "engine/pixel.h" #include "engine/tileset.h" #include "engine/framebuffer.h" #include "sim/items.h" #include "sim/target.h" #include "tuning.h" class SpriteLibrary; // Силуэт бойца. Не «какое именно оружие», а «как оно читается»: карабин и // автомат на 14 пикселях — один и тот же длинный ствол, и притворяться, что // игрок их различит, бессмысленно. enum class BodyLook : uint8_t { GUN_LONG = 0, // карабин, автомат GUN_SHORT, // пистолет GUN_HEAVY, // дробовик, магнум: толстый ствол, широкие плечи BLADE_LONG, // сабля, тесак BLADE_SHORT, // нож COUNT, }; BodyLook LookOf(ItemId weapon); struct SpriteSet { // Пол и стены живут в движке: ими рисует и игра, и редактор комнат // (engine/tileset.h). Здесь остаётся только то, что знает про правила, — // бойцы, твари и иконки предметов. TileSet tiles; // Тело бойца: силуэт по оружию x цвет по слоту. Sprite body[int(BodyLook::COUNT)][NUM_AGENTS]; // Павший: тот же цвет, но лежит. Отряд обязан видеть, кого потерял. Sprite corpse[NUM_AGENTS]; // Твари. DUMMY — прежняя мишень приёмки, её вид не менялся. Sprite enemy[int(EnemyKind::COUNT)]; Sprite target; // синоним enemy[DUMMY], чтобы не ломать вызовы // Анимированный вариант твари: ID ассета SpriteForge, 0 — «нет, рисуй // процедурный». Кадры здесь НЕ хранятся: они лежат в SpriteLibrary в одном // экземпляре на ассет, и рендер достаёт нужный по времени клипа. // // Пока клип один — цикл шага. Появятся idle/attack — это будут соседние // массивы и соседние ассеты _idle, а не вторая анимация внутри // этого: бэкенд import имён клипов не читает (assets/manifests/enemies.yaml). uint32_t enemyWalk[int(EnemyKind::COUNT)] = {}; // Иконки предметов для инвентаря и HUD. Sprite icon[int(ItemId::COUNT)]; // Спрайта пули нет: снаряд рисуется светящимся трассером (render/fx.cpp). void Generate(); // Заменить пол и стену на тайлы из библиотеки SpriteForge. Всё или ничего: // при любой осечке набор остаётся тем, что дал Generate(), и возвращается // false. Вызывать только после Generate(). bool LoadTiles(const SpriteLibrary& lib); // Включить анимированных тварей из библиотеки. Тоже всё или ничего: одна // анимированная тварь среди пяти статичных выглядит хуже пяти статичных. // Мишень приёмки (DUMMY) и обстановка не анимируются никогда. bool LoadEnemies(const SpriteLibrary& lib); const Sprite& AgentBody(int slot, ItemId weapon) const { const int s = (slot >= 0 && slot < NUM_AGENTS) ? slot : 0; return body[int(LookOf(weapon))][s]; } const Sprite& EnemyBody(EnemyKind kind) const { const int k = (int(kind) >= 0 && int(kind) < int(EnemyKind::COUNT)) ? int(kind) : 0; return enemy[k]; } const Sprite& Icon(ItemId id) const { const int i = (int(id) >= 0 && int(id) < int(ItemId::COUNT)) ? int(id) : 0; return icon[i]; } // Какой ромб пола и какой блок стены лежат на тайле. Одно место на весь // рендер: запечённая кровь копирует ИМЕННО этот ромб, иначе шахматка // поедет (render/blood.h). Тему тайл несёт сам (Tilemap::ThemeAt). const Sprite& FloorTile(int tx, int ty, uint8_t theme) const { return tiles.Floor(tx, ty, theme); } const Sprite& WallTile(int tx, int ty, uint8_t theme) const { return tiles.Wall(tx, ty, theme); } }; // Цвет слота отряда: номер бойца читается по цвету и больше ничем. uint32_t SlotColor(int slot);