refactor: рабочая база на three.js + меню/правила уровней + доки
Полный рефакторинг проекта в стабильную расширяемую базу рогалика. Почему: после разнесения 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>
This commit is contained in:
+142
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* 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, // шанс назначить комнату боссом
|
||||
};
|
||||
Reference in New Issue
Block a user