- Рендер переведён в псевдо-3D: наклонная PerspectiveCamera, пол лежит плашмя, персонажи/враги — вертикальные спрайты-биллборды, стены с высотой. Починен баг: пол создавался без поворота и стоял вертикально → «вывернутая» перспектива. - Двери видны всегда: закрыты (засов) во время боя, открытый проём после зачистки. - render/assets.ts: процедурные текстуры (спрайты персонажей/врагов, пол, стены, двери, снаряд, тень) + эффекты оружия (вспышка из дула, искры, облачко гибели). - Светлее палитра — исправлено «тёмное на тёмном». - Игра переименована в «Биндим Фигняшку»; полная русификация UI (ДАЛЬНИЙ/БЛИЖНИЙ, ИГРА ОКОНЧЕНА, ПОБЕДА); клавиши-подсказки оставлены латиницей. - Ввод по event.code → WASD/Q/R работают в любой раскладке (вкл. русскую). - theme.ts урезан до реально используемых тинтов (bg/swing/flash). - docs/ASSET_BRIEF.md — бриф и промпт на полную художку; доки синхронизированы. - Ядро (src/core) не тронуто — раунд чисто render/UI. 27 тестов + типы зелёные. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
10 KiB
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)
src/core/types.ts— добавь в union:export type EnemyType = 'normal' | 'fast' | 'boss' | 'tank';src/config.ts— строка характеристик вENEMY_STATS:tank: { size: 40, hp: 8, speed: 0.7, damage: 2 },src/core/systems/spawner.ts— реши, когда он спавнится (логика выбора типа в начале цикла). Напр. с шансом:rng.chance(0.15) ? 'tank' : rng.chance(ENEMY.fastChance) ? 'fast' : 'normal'.src/render/assets.ts— добавь ключ вSpriteKeyи ветку вAssets.sprite()(рисуется черезdrawCharacter(...)с твоими цветами).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.
Для третьего режима:
src/config.ts— константаMODE_X = 2и блок баланса.src/core/types.ts— расширьCombatMode(0 | 1 | 2).src/core/Game.ts— вhandleAttackдобавь ветку создания нужного снаряда/ эффекта; вconsumeActionsсмена оружия циклом по всем режимам.src/render/HudOverlay.ts— подпись/иконка режима.- Если это новый вид снаряда — заведи сущность в
src/core/entities/и обновляй её вGame.step(по образцуProjectile/updateTears), а вThreeRendererдобавь её отрисовку.
5. Добавить тип комнаты (например, shop)
src/core/types.ts— добавь вRoomType.src/core/world/RoomMap.ts— правило назначения типа вgenerate().src/core/systems/spawner.ts— сколько врагов (часто 0).src/core/Game.ts— если врагов 0, комната уже авто-зачищается при входе (см.enterRoom); особая логика комнаты — здесь же.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. Кастомные ассеты (спрайты, текстуры, тема)
Графика (спрайты персонажей/врагов, пол, стены, двери, снаряд, эффекты, тень) рисуется
процедурно в src/render/assets.ts (класс Assets, кэширует текстуры three.js).
-
Свои PNG вместо процедурных: замени тело нужного
drawX()на загрузку картинкиnew THREE.TextureLoader().load(url)и верни её из соответствующего геттера — рендер берёт текстуры по ключам, больше ничего менять не нужно. Полный список нужных ассетов с размерами и готовый промпт для дизайн-ИИ — вdocs/ASSET_BRIEF.md. -
Цвета/тинты/фон (не текстуры) — в
src/render/theme.ts(Theme+DEFAULT_THEME). -
Своя палитра: сделай ещё один объект
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. Логику отлаживай тестом, а не кликами.