Files
Binding-Fignyaac/docs/HOWTO.md
T
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

177 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# HOWTO — рецепты доработки
Практические инструкции «как сделать X». Каждый рецепт перечисляет все файлы,
которые нужно тронуть. После любой правки — `bun run check` и проверка в браузере
(`bun run dev`). Общие правила — в [`CLAUDE.md`](../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`:
```ts
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`). Например:
```ts
{
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`. Примеры:
```js
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`. Логику отлаживай
тестом, а не кликами.