Демо «Типичная ночь в Мытищинском лесу» — ночной лес, свет, волны, апгрейды

Самый крупный пример игры на $: зомби-шутер в духе Vampire Survivors. Игрок сам стреляет по ближайшему врагу, игрок только уворачивается и собирает добычу. В темноте видно ровно столько, сколько светит фонарь.

./build/russiano2d --game demos --scene shooter_witch   # сам бой
./build/russiano2d --game demos --scene witch_menu      # меню-интро
Управление Что делает
WASD / стрелки идти
1 / 2 / 3 или мышь выбрать карту апгрейда
F переключить камерный пресет вручную
R заново после смерти
Esc в меню демо

1. Что показывает движок

Слой Чем сделан Где смотреть
лес <tilemap> из ASCII, автотайл тропы по соседям buildForest()
деревья спрайты с коллизиями и подсказками для акустики buildForest()
свет <light> (мягкое свечение) + engine.light.visibility для теней от стволов buildLights(), drawVisibility()
звук $.audio.zone/obstacles/damping, реверберация поляны, панорама выстрелов installAudio()
VFX кровь, лужи, трассеры ($.fx.ribbon), молнии, ударные волны, hit-stop, тряска tickGore(), $.fx.*
камера пресеты кадра: «ночной лес», «кровавая луна» tickPresets(), $.gfx.postPreset
апгрейды пауза + три карты, выбранное меняет оружие и статы openUpgrade(), tickWeapons()
меню и смерть отдельные сцены witch_menu и экран проигрыша createMenuScene()

2. Как это устроено

Две сцены в одном демо: witch_menu — интро-меню (кнопка «Типичная ночь в Мытищинском лесу» в лаунчере ведёт именно сюда), shooter_witch — сам забег. Обе живут в одном файле и делят общие данные, но мир строится заново на входе в каждую — так видно, что $.scene меняет и музыку, и весь мир.

// земля: автотайл сам подбирает тайлы тропы по соседям
const ground = $.tilemap.fromASCII(rows, { '.': 1, ',': 2, 'p': PATH_MARK });
ground.autotile({ mode: 'terrain', terrain: 'path' });

// фонарь: свет — обычный узел, платится одним свечением на кадр
$('<light>', { radius: 235, intensity: 1, color: '#ffbe73', falloff: 2.2 })
    .at(lamp.x, lamp.y).appendTo($.world);

3. Своё такое демо

  1. Мир. <tilemap> + autotile для органичной земли, спрайты для объектов — docs/highlevel/tilemap.md.
  2. Свет. <light> на фонарях; если нужны тени от препятствий — engine.light.visibility(...) и рисование полигона из $.render (демо light больше нет, рабочий пример остался здесь).
  3. Волны. Таймер + спавн за экраном: $.time.every(ms, …) и проверка, что враг вне кадра.
  4. Прогресс. Кристаллы как <pickup>, уровень — свой счётчик, карты апгрейдов — пауза $.time.pause() и три кнопки.
  5. Проверка без рук. python3 tests/agent/demos_test.py shooter_witch — тест гоняет 900 кадров боя и требует, чтобы враги гибли и в журнале не было ошибок игрового кода.

4. Производительность

Демо сознательно тяжёлое: тысячи тайлов, десятки врагов, кровь и частицы. Ориентиры, на которые смотрели при написании: список узлов мира переиспользуется, свет — узлы <light> вместо ручных полигонов на каждого врага, кровь — пул объектов ($.pool). Если своё демо тормозит, начните с $.debug.profiler.on(true) и F1 — разбор в docs/HIGH_LEVEL_API_PERF.md.

5. Ассеты

Лес — CC0 Kenney RPG/Urban, персонаж — лист из demos/assets/art/characters, музыка и звуки — CC0 Juhani Junkala. Полный список с лицензиями: demos/assets/CREDITS.md.