Files
Binding-Fignyaac/docs/HOWTO.md
mayatnikovandClaude Opus 4.8 7dabfd4fff docs: синхронизировать описание ассетов с реальностью (PNG из src/assets + фолбэк)
Ревью отметило устаревшие тексты после перехода на загрузку PNG:
- assets.ts: заголовок описывал «процедурно, без внешних файлов» — теперь PNG из
  src/assets/<ключ>.png с процедурным фолбэком.
- HOWTO §8: убран совет «заменить тело drawX() на TextureLoader» (уже сделано);
  актуальный путь — положить PNG с нужным именем в src/assets/.
- README/ARCHITECTURE/ASSET_BRIEF: статус «подключено», корректный механизм (рантайм-
  загрузка по пути, не импорт-бандлинг).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 16:15:36 +03:00

11 KiB
Raw Permalink Blame History

HOWTO — рецепты доработки

Практические инструкции «как сделать X». Каждый рецепт перечисляет все файлы, которые нужно тронуть. После любой правки — bun run check и проверка в браузере (bun run dev). Общие правила — в CLAUDE.md.


1. Покрутить баланс (скорость, HP, урон, число врагов)

Всё в одном файле — src/config.ts. Например:

  • игрок быстрее: PLAYER.speed;
  • больше HP у босса: ENEMY_STATS.boss.hp;
  • больше врагов в комнате: SPAWN.normalMin / SPAWN.normalExtra;
  • крупнее данжен: MIN_ROOMS / EXTRA_ROOMS / MAP_RADIUS.

Код менять не нужно. Это и есть смысл config.ts.


2. Добавить новый тип врага (например, tank)

  1. src/core/types.ts — добавь в union: export type EnemyType = 'normal' | 'fast' | 'boss' | 'tank';
  2. src/config.ts — строка характеристик в ENEMY_STATS:
    tank: { size: 40, hp: 8, speed: 0.7, damage: 2 },
    
  3. src/core/systems/spawner.ts — реши, когда он спавнится (логика выбора типа в начале цикла). Напр. с шансом: rng.chance(0.15) ? 'tank' : rng.chance(ENEMY.fastChance) ? 'fast' : 'normal'.
  4. src/render/assets.ts — добавь ключ в SpriteKey и ветку в Assets.sprite() (рисуется через drawCharacter(...) с твоими цветами).
  5. src/render/ThreeRenderer.ts — добавь строку в enemyMatKey, сопоставив новый Enemy['type'] этому SpriteKey (без этого не пройдёт проверка типов).

ИИ, урон, отбрасывание, мигание при попадании — общие, их трогать не нужно. Добавь тест в tests/spawner.test.ts, если ввёл особое правило спавна.


3. Добавить новое поведение врага (особый ИИ)

Сейчас все враги ведут себя одинаково (преследование игрока) в Game.updateEnemies (src/core/Game.ts). Чтобы развести поведение:

  • по e.type внутри updateEnemies развилкой (просто, для 2–3 типов), или
  • вынеси ИИ в src/core/systems/ai.ts как функции update<Type>(enemy, player, room) и диспетчеризуй по типу (чище, когда типов много).

Держи это в core/ (без рендера) и старайся писать чистыми функциями — их легко покрыть тестом.


4. Добавить оружие / третий режим боя

Режимы — это MODE_RANGED = 0 / MODE_MELEE = 1 и тип CombatMode = 0 | 1. Для третьего режима:

  1. src/config.ts — константа MODE_X = 2 и блок баланса.
  2. src/core/types.ts — расширь CombatMode (0 | 1 | 2).
  3. src/core/Game.ts — в handleAttack добавь ветку создания нужного снаряда/ эффекта; в consumeActions смена оружия циклом по всем режимам.
  4. src/render/HudOverlay.ts — подпись/иконка режима.
  5. Если это новый вид снаряда — заведи сущность в src/core/entities/ и обновляй её в Game.step (по образцу Projectile/updateTears), а в ThreeRenderer добавь её отрисовку.

5. Добавить тип комнаты (например, shop)

  1. src/core/types.ts — добавь в RoomType.
  2. src/core/world/RoomMap.ts — правило назначения типа в generate().
  3. src/core/systems/spawner.ts — сколько врагов (часто 0).
  4. src/core/Game.ts — если врагов 0, комната уже авто-зачищается при входе (см. enterRoom); особая логика комнаты — здесь же.
  5. src/render/HudOverlay.ts — цвет на миникарте и подпись (drawMinimap/drawHud).

6. Поменять управление

src/input/KeyboardController.ts — метод poll() (маппинг клавиш на InputState) и набор PREVENT (клавиши, у которых гасим поведение браузера). Логику игры это не затрагивает — она читает только InputState.

Геймпад/тач: сделай новый контроллер с тем же poll(): InputState и подставь его в main.ts.


7. Добавить уровень (пресет правил) в меню

src/core/rules.ts — добавь объект в массив PRESETS. Он сразу появится кнопкой в стартовом меню (меню строится из PRESETS). Например:

{
  id: 'swarm',
  name: 'Орда',
  description: 'Очень много слабых быстрых врагов.',
  map: { minRooms: 10, extraRooms: 4, mapRadius: 4 },
  player: { maxHp: 6, speed: 3.2 },
  enemies: { densityMul: 2.5, fastChance: 0.9, hpMul: 0.5, speedMul: 1.2, bossHpMul: 1 },
},
  • seed (необязательный) фиксирует данжен — одинаковый забег каждый раз.
  • Нужен новый «рычаг» (например, шанс сокровищниц)? Добавь поле в LevelRules и читай его там, где раньше брал константу из config.ts (генерация — RoomMap, спавн — spawner). Тест на влияние правил — в tests/game.test.ts.

8. Кастомные ассеты (спрайты, текстуры, тема)

Графика грузится из PNG в src/assets/ (класс Assets в src/render/assets.ts тянет assets/<ключ>.png через THREE.TextureLoader); если файла нет — рисуется процедурный фолбэк на canvas, и игра не ломается.

  • Свой/новый ассет: просто положи PNG с именем <ключ>.png в src/assets/ (dev-сервер отдаёт их сразу, прод-сборка копирует в dist/assets). Код менять не нужно. Ключи = именам файлов (player-ranged, enemy-boss, floor, door-closed, tear, heart-full, icon-ranged, …); полный список и промпт для дизайн-ИИ — в docs/ASSET_BRIEF.md.

  • Запасной рисунок (фолбэк): функции drawX() в assets.ts — правь их, только если нужен другой плейсхолдер на случай отсутствия PNG.

  • Цвета/тинты/фон (не текстуры) — в src/render/theme.ts (Theme + DEFAULT_THEME).

  • HUD-ассеты (сердечки heart-*, иконки icon-*) грузит HudOverlay; меню (logo, menu-bg) — index.html. Те же имена в src/assets/.

  • Своя палитра: сделай ещё один объект Theme и передай его в new ThreeRenderer(canvas, myTheme) в main.ts. Сейчас рендер создаётся один раз при старте с темой по умолчанию и переиспользуется между забегами, поэтому чтобы выбирать тему по правилам уровня, нужно либо перенести new ThreeRenderer(...) внутрь startGame(rules)dispose() предыдущий), либо добавить рендеру метод setTheme(theme).

  • Перейти на спрайты/текстуры: расширь Theme полями с путями к картинкам (см. комментарий-задел в theme.ts), загрузи их через THREE.TextureLoader в ThreeRenderer и положи текстуру в material.map соответствующих мешей вместо (или вместе с) color. Логика игры при этом НЕ меняется — это чисто рендер.


9. Поменять или нарастить рендер (вплоть до 3D)

Рендер изолирован за интерфейсом src/render/Renderer.ts (render(game, alpha)

  • dispose()). Варианты:
  • Доработать вид (спрайты, текстуры, частицы) — внутри ThreeRenderer (камера уже наклонная PerspectiveCamera, сущности — биллборд-спрайты).
  • Усилить 3D — для настоящего объёма замени MeshBasicMaterial (он без света) на MeshStandardMaterial, добавь источники света и объёмные меши вместо плоскостей. Мир рисуется по тем же координатам сущностей из Game — логику менять не нужно.
  • Другой рендер целиком (например, Canvas2D для отладки) — реализуй Renderer и подставь в main.ts. Ядро не трогается вообще.

Помни про управление ресурсами (правило 6 в CLAUDE.md).


10. Отладка

  • В консоли браузера доступен game — текущий Game. Примеры:
    game.player.hp = 99            // бессмертие на тест
    game.curRoom.enemies.length    // сколько врагов в комнате
    game.reset()                   // новая карта
    
  • Детерминизм: чтобы воспроизвести конкретную карту, задай seed правилам — проще всего поставить seed: 42 нужному пресету в core/rules.ts (см. пресет daily). Тот же seed — та же генерация и спавн, в т.ч. после рестарта. В тестах можно передать свой ГПСЧ вторым аргументом: new Game(rules, new Rng(42)) (первый аргумент — правила, не ГПСЧ).
  • Тесты ядра гоняются мгновенно и без браузера: bun test. Логику отлаживай тестом, а не кликами.