Полный рефакторинг проекта в стабильную расширяемую базу рогалика. Почему: после разнесения single-file на модули потерялся вызов генерации карты (RoomMap не генерировал комнаты) → игра падала на старте; баг скрывался тем, что Bun-бандлер не проверяет типы. Архитектура: - Логика игры (src/core/) полностью отделена от рендера: без three.js и DOM, тестируется без браузера. - Рендер мира на three.js с ортокамерой (2D-вид); HUD/миникарта — 2D-канвас поверх. - Фиксированный игровой цикл 60 Гц + интерполяция (раньше скорость зависела от частоты кадров). - Seeded-RNG, ввод через абстрактные «намерения» (InputState). Возможности: - Стартовое меню с выбором уровня (Esc → меню). - Конфигуратор уровней: LevelRules + 5 пресетов (размер карты, плотность/сила врагов, HP, фиксированный seed). - Тема внешнего вида (render/theme.ts) — задел под кастомные ассеты. Качество: - 27 юнит-тестов ядра (генерация, симметрия дверей, коллизии, спавн, правила). - Два круга adversarial-ревью; исправлено 6 реальных багов (кнокбэк сквозь стены → софт-лок; незакрываемая сокровищница; фикс-сид после рестарта; перенос ввода между забегами; нет source maps; неточности в доках). - Документация: README, docs/ARCHITECTURE.md, CLAUDE.md, docs/HOWTO.md. - dist/ исключён из гита; bun.lock зафиксирован. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
143 lines
8.9 KiB
TypeScript
143 lines
8.9 KiB
TypeScript
/**
|
|
* config.ts — ВСЕ настройки игры в одном месте.
|
|
*
|
|
* Здесь сосредоточены: размеры мира, геометрия комнат/дверей и баланс
|
|
* (скорости, здоровье, перезарядки). Меняй числа тут, чтобы «крутить»
|
|
* игру — больше ничего трогать не нужно.
|
|
*
|
|
* ВАЖНО: единица времени — один «шаг» симуляции (= 1/60 секунды), а НЕ
|
|
* кадр браузера. Цикл игры (см. engine/GameLoop.ts) гарантирует ровно
|
|
* 60 шагов в секунду на любом мониторе, поэтому скорости заданы «пикселей
|
|
* за шаг», а перезарядки — «в шагах».
|
|
*/
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// Холст и сетка
|
|
// ─────────────────────────────────────────────────────────────
|
|
export const CW = 880; // ширина области отрисовки (px)
|
|
export const CH = 660; // высота области отрисовки (px)
|
|
export const TILE = 44; // размер одного тайла (px)
|
|
export const COLS = 15; // тайлов по горизонтали в комнате
|
|
export const ROWS = 11; // тайлов по вертикали в комнате
|
|
export const RW = COLS * TILE; // ширина комнаты в пикселях
|
|
export const RH = ROWS * TILE; // высота комнаты в пикселях
|
|
export const OX = (CW - RW) / 2; // отступ комнаты слева
|
|
export const OY = 80; // отступ комнаты сверху (место под HUD)
|
|
|
|
// Частота симуляции. Логика всегда обновляется с этим шагом.
|
|
export const FIXED_FPS = 60;
|
|
export const FIXED_DT = 1 / FIXED_FPS; // секунд на шаг
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// Типы тайлов
|
|
// ─────────────────────────────────────────────────────────────
|
|
export const T_WALL = 0;
|
|
export const T_FLOOR = 1;
|
|
export const T_DOOR = 2;
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// Режимы боя
|
|
// ─────────────────────────────────────────────────────────────
|
|
export const MODE_RANGED = 0;
|
|
export const MODE_MELEE = 1;
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// Направления
|
|
// ─────────────────────────────────────────────────────────────
|
|
import type { Dir } from './core/types';
|
|
|
|
/** Единичный вектор смещения для каждого направления (x вправо, y вниз). */
|
|
export const DIR: Record<Dir, readonly [number, number]> = {
|
|
up: [0, -1],
|
|
down: [0, 1],
|
|
left: [-1, 0],
|
|
right: [1, 0],
|
|
};
|
|
|
|
/** Противоположное направление. Используется при простановке дверей соседей. */
|
|
export const OPP: Record<Dir, Dir> = {
|
|
up: 'down',
|
|
down: 'up',
|
|
left: 'right',
|
|
right: 'left',
|
|
};
|
|
|
|
/**
|
|
* Геометрия дверей: проём шириной в 3 тайла по центру каждой стороны.
|
|
* cx/cy — координата тайла-центра двери (для спавна игрока и расчётов).
|
|
*/
|
|
export const DOOR = {
|
|
up: { cols: [6, 7, 8] as number[], row: 0, cx: 7, cy: 0 },
|
|
down: { cols: [6, 7, 8] as number[], row: ROWS - 1, cx: 7, cy: ROWS - 1 },
|
|
left: { col: 0, rows: [4, 5, 6] as number[], cx: 0, cy: 5 },
|
|
right: { col: COLS - 1, rows: [4, 5, 6] as number[], cx: COLS - 1, cy: 5 },
|
|
};
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// Генерация карты (случайное блуждание)
|
|
// ─────────────────────────────────────────────────────────────
|
|
export const MAP_RADIUS = 3; // карта вмещается в сетку (2*R+1)²
|
|
export const MIN_ROOMS = 8; // минимум комнат
|
|
export const EXTRA_ROOMS = 4; // + случайно до этого числа
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// Баланс: игрок
|
|
// ─────────────────────────────────────────────────────────────
|
|
export const PLAYER = {
|
|
size: 26,
|
|
speed: 3.2, // px за шаг
|
|
maxHp: 6,
|
|
invFrames: 60, // неуязвимость после удара, в шагах
|
|
rangedCooldown: 10, // перезарядка выстрела, в шагах
|
|
meleeCooldown: 22, // перезарядка удара ближнего боя, в шагах
|
|
transitionLock: 15, // блок повторного перехода между комнатами, в шагах
|
|
};
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// Баланс: снаряд (слеза) и ближний бой
|
|
// ─────────────────────────────────────────────────────────────
|
|
export const PROJECTILE = {
|
|
radius: 5,
|
|
speed: 7, // px за шаг
|
|
damage: 1,
|
|
life: 80, // время жизни в шагах
|
|
};
|
|
|
|
export const MELEE = {
|
|
reach: 22, // отступ хитбокса от центра игрока
|
|
size: 50, // сторона квадратного хитбокса
|
|
life: 10, // длительность взмаха в шагах
|
|
damage: 2,
|
|
knockback: 10,
|
|
};
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// Баланс: враги (таблица характеристик по типу)
|
|
// ─────────────────────────────────────────────────────────────
|
|
export const ENEMY_STATS = {
|
|
normal: { size: 32, hp: 3, speed: 1.15, damage: 1 },
|
|
fast: { size: 26, hp: 2, speed: 1.9, damage: 1 },
|
|
boss: { size: 46, hp: 10, speed: 0.9, damage: 2 },
|
|
} as const;
|
|
|
|
export const ENEMY = {
|
|
aggroRange: 500, // дистанция, с которой враг начинает преследование
|
|
attackCooldown: 30, // пауза между контактными ударами, в шагах
|
|
hitFlash: 8, // длительность «мигания» при попадании, в шагах
|
|
knockbackDecay: 0.85,
|
|
fastChance: 0.3, // доля быстрых врагов в обычной комнате
|
|
};
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// Спавн врагов
|
|
// ─────────────────────────────────────────────────────────────
|
|
export const SPAWN = {
|
|
normalMin: 2, // минимум врагов в обычной комнате
|
|
normalExtra: 2, // + случайно до этого числа
|
|
minDistFromDoor: 180, // не спавнить ближе к двери входа
|
|
minDistFromPlayer: 150,
|
|
minDistBetween: 60,
|
|
treasureChance: 0.12, // шанс комнаты-сокровищницы
|
|
bossChance: 0.2, // шанс назначить комнату боссом
|
|
};
|