🎮 3 игры с крутой процедурной генерацией — что под капотом
Spelunky (2009)
Уровень = 4×4 блока 10×8 клеток.
Каждый блок — один из заранее нарисованных шаблонов (rooms), выбранный рандомно.
Гарантия проходимости: один «опорный» маршрут через блоки всегда есть.
Вывод: случайность + ручные шаблоны = игровой баланс сохранён.
Dead Cells (2018)
Уровни — не случайные, а сборные из готовых секций.
Секции заранее дизайнерски проработаны, порядок и детали — рандомные.
Вывод: меньше математики, больше контроля над feel.
No Man's Sky (2016)
Планеты, существа, растения — из параметрических «рецептов».
Один seed → 18 квинтиллионов планет. Математика через noise-функции и L-системы.
Вывод: глубокая процедурка требует огромных систем параметров.
Общий урок: в хороших играх процедурка никогда не работает одна —
всегда есть ручная работа, которая задаёт рамки. Даже когда я участвовал в разработке Vector 2, там были огромные Yaml генераторы задающие правила генерации уровней, чтобы рандом был интересным.
#мат_геймдев #МатРазбор #процедурнаяГенерация
Spelunky (2009)
Уровень = 4×4 блока 10×8 клеток.
Каждый блок — один из заранее нарисованных шаблонов (rooms), выбранный рандомно.
Гарантия проходимости: один «опорный» маршрут через блоки всегда есть.
Вывод: случайность + ручные шаблоны = игровой баланс сохранён.
Dead Cells (2018)
Уровни — не случайные, а сборные из готовых секций.
Секции заранее дизайнерски проработаны, порядок и детали — рандомные.
Вывод: меньше математики, больше контроля над feel.
No Man's Sky (2016)
Планеты, существа, растения — из параметрических «рецептов».
Один seed → 18 квинтиллионов планет. Математика через noise-функции и L-системы.
Вывод: глубокая процедурка требует огромных систем параметров.
Общий урок: в хороших играх процедурка никогда не работает одна —
всегда есть ручная работа, которая задаёт рамки. Даже когда я участвовал в разработке Vector 2, там были огромные Yaml генераторы задающие правила генерации уровней, чтобы рандом был интересным.
#мат_геймдев #МатРазбор #процедурнаяГенерация
🔥2
⚙️ Rigidbody изнутри: что реально происходит под капотом
Добавляешь Rigidbody — объект начинает падать. Три переменных делают это.
Mass (масса) — сопротивление изменению скорости.
F = m × a. Одна и та же сила разгоняет лёгкий мяч быстрее, чем танк.
В Unity mass не влияет на скорость свободного падения. Зато влияет при AddForce и столкновениях.
Drag — «воздушное сопротивление», гасит линейную скорость.
Каждый кадр: velocity *= (1 - drag × deltaTime)
Drag = 0 → летит вечно. Drag = 5 → тормозит быстро.
Angular drag — то же самое, но для вращения.
Angular velocity — скорость вращения в рад/сек.
#мат_геймдев #МатРазбор #физика #rigidbody
Добавляешь Rigidbody — объект начинает падать. Три переменных делают это.
Mass (масса) — сопротивление изменению скорости.
F = m × a. Одна и та же сила разгоняет лёгкий мяч быстрее, чем танк.
В Unity mass не влияет на скорость свободного падения. Зато влияет при AddForce и столкновениях.
Drag — «воздушное сопротивление», гасит линейную скорость.
Каждый кадр: velocity *= (1 - drag × deltaTime)
Drag = 0 → летит вечно. Drag = 5 → тормозит быстро.
Angular drag — то же самое, но для вращения.
Angular velocity — скорость вращения в рад/сек.
// Снаряд с закруткой
var rb = bullet.GetComponent<Rigidbody>();
rb.mass = 0.02f;
rb.drag = 0.01f;
rb.angularDrag = 0f;
rb.linearVelocity = transform.forward * 50f;
rb.angularVelocity= transform.forward * 30f; // закрутка
#мат_геймдев #МатРазбор #физика #rigidbody
🔥4❤1
🎯 Rigidbody: настройки для пяти типовых объектов
Одни параметры для пули и для пера — не работает. Вот справочная таблица.
Пуля / снаряд
Mass: 0.01–0.05 · Drag: 0.01 · Angular drag: 0
Gravity: off (или своя) · Collision Detection: Continuous
Игровой персонаж (без CharacterController)
Mass: 70 · Drag: 3–5 · Angular drag: высокий (нет кувырков)
Gravity: on · Constraints: заморозить вращение X и Z
Физический куб / ящик
Mass: 1–10 · Drag: 0.1 · Angular drag: 0.1
Обычные настройки, всё разрешено
Тяжёлый объект (валун, машина)
Mass: 500+ · Drag: 0.5 · Angular drag: 0.5
Высокая масса = сложнее сдвинуть
Перо / листок
Mass: 0.001 · Drag: 10–15 · Angular drag: 10
Высокий drag имитирует сопротивление воздуха
#мат_геймдев #КодПодСкопой #физика #rigidbody
Одни параметры для пули и для пера — не работает. Вот справочная таблица.
Пуля / снаряд
Mass: 0.01–0.05 · Drag: 0.01 · Angular drag: 0
Gravity: off (или своя) · Collision Detection: Continuous
Игровой персонаж (без CharacterController)
Mass: 70 · Drag: 3–5 · Angular drag: высокий (нет кувырков)
Gravity: on · Constraints: заморозить вращение X и Z
Физический куб / ящик
Mass: 1–10 · Drag: 0.1 · Angular drag: 0.1
Обычные настройки, всё разрешено
Тяжёлый объект (валун, машина)
Mass: 500+ · Drag: 0.5 · Angular drag: 0.5
Высокая масса = сложнее сдвинуть
Перо / листок
Mass: 0.001 · Drag: 10–15 · Angular drag: 10
Высокий drag имитирует сопротивление воздуха
#мат_геймдев #КодПодСкопой #физика #rigidbody
🔥5👍4👎1
🐛 #ОшибкаНедели: Is Kinematic — что это вообще значит
Новички часто видят Is Kinematic и включают его, не понимая зачем.
Разберём один раз и навсегда.
Обычный Rigidbody: управляется физикой.
Гравитация тянет вниз, AddForce работает, другие тела толкают его.
Is Kinematic = true: объект управляется кодом/анимацией, не физикой.
Гравитация игнорируется. AddForce не работает.
Но он всё ещё участвует в коллизиях — отталкивает другие тела.
❌ Типичная ошибка: включить Is Kinematic у персонажа, потом удивляться,
почему прыжок не работает.
✅ Когда нужен Kinematic:
→ Платформа, которая движется по скрипту
→ Дверь, управляемая анимацией
→ Враг, которого двигают по NavMesh, но у которого есть коллайдер
→ Объект, управляемый в FixedUpdate вручную
Правило: Kinematic = «я сам двигаю объект», обычный = «физика двигает объект».
#мат_геймдев #ОшибкаНедели #физика #rigidbody
Новички часто видят Is Kinematic и включают его, не понимая зачем.
Разберём один раз и навсегда.
Обычный Rigidbody: управляется физикой.
Гравитация тянет вниз, AddForce работает, другие тела толкают его.
Is Kinematic = true: объект управляется кодом/анимацией, не физикой.
Гравитация игнорируется. AddForce не работает.
Но он всё ещё участвует в коллизиях — отталкивает другие тела.
❌ Типичная ошибка: включить Is Kinematic у персонажа, потом удивляться,
почему прыжок не работает.
✅ Когда нужен Kinematic:
→ Платформа, которая движется по скрипту
→ Дверь, управляемая анимацией
→ Враг, которого двигают по NavMesh, но у которого есть коллайдер
→ Объект, управляемый в FixedUpdate вручную
Правило: Kinematic = «я сам двигаю объект», обычный = «физика двигает объект».
#мат_геймдев #ОшибкаНедели #физика #rigidbody
❤3👍1🔥1
🚀 Velocity, acceleration, friction: Ньютон в аркадной игре
transform.position += speed — работает, но ощущается как деревяшка.
Вот почему и как это исправить.
Три переменных физически честной модели движения:
Каждый кадр (интеграция Эйлера):
Платформер без Rigidbody — конкретный код:
Результат: персонаж разгоняется и плавно тормозит.
Это и есть основа game feel.
#мат_геймдев #МатРазбор #физика
transform.position += speed — работает, но ощущается как деревяшка.
Вот почему и как это исправить.
Три переменных физически честной модели движения:
position — где объект сейчас
velocity — скорость и направление движения
acceleration — как быстро меняется скорость
Каждый кадр (интеграция Эйлера):
velocity += acceleration * Time.deltaTime;
velocity *= (1f - friction * Time.deltaTime); // трение гасит
position += velocity * Time.deltaTime;
Платформер без Rigidbody — конкретный код:
Vector2 velocity = Vector2.zero;
const float ACCEL = 80f;
const float FRICTION= 12f;
const float GRAVITY = 30f;
const float JUMP = 15f;
void Update()
{
float input = Input.GetAxis("Horizontal");
velocity.x += input * ACCEL * Time.deltaTime;
velocity.x *= 1f - FRICTION * Time.deltaTime;
if (!isGrounded) velocity.y -= GRAVITY * Time.deltaTime;
if (isGrounded && Input.GetKeyDown(KeyCode.Space))
velocity.y = JUMP;
transform.position += (Vector3)(velocity * Time.deltaTime);
}
Результат: персонаж разгоняется и плавно тормозит.
Это и есть основа game feel.
#мат_геймдев #МатРазбор #физика
🫡7🔥5👌1
🎲 Демо к статье: WFC + Entropy Bias
Рисуете тепловую карту — получаете уровень.
- W_d — плотность врагов
- W_r — распределение лута
- W_e — точки входа/выхода
Алгоритм решает CSP с весами из вашей карты. Коллапс волновой функции смещается энтропийным биасом под заданные зоны.
👉 Открыть мини-апп по кнопке
🖥 С ПК в фулскрине удобнее.
Рисуете тепловую карту — получаете уровень.
- W_d — плотность врагов
- W_r — распределение лута
- W_e — точки входа/выхода
Алгоритм решает CSP с весами из вашей карты. Коллапс волновой функции смещается энтропийным биасом под заданные зоны.
👉 Открыть мини-апп по кнопке
🖥 С ПК в фулскрине удобнее.
🔥3
Драматургия через математику: как одна формула превращает генератор карт в дизайнера уровней
https://habr.com/ru/articles/1026506/
Вы просили что-то посложнее. Ну держитесь 🙂 Страшные слова, формулы, интерактивное демо. Я заморочился. Так что жду огоньков и репостов друзьям😆
Классический алгоритм Wave Function Collapse создаёт технически валидные, но драматургически «мёртвые» карты, потому что не различает пространство: для него вход, босс-арена и выход равнозначны. Я предлагаю добавить в формулу выбора клетки семантический множитель f(W(x,y)), где W — поле весов, задающее важность каждой зоны: ключевые точки коллапсируют первыми и через propagation определяют структуру всей карты. Разбив поле W на слои (опасность, награда, направление к выходу, нарратив) и добавив модуляцию вероятности тайлов в зависимости от позиции, дизайнер получает инструмент, где задуманная идея уровня воспроизводится в каждом прогоне, а конкретная геометрия остаётся уникальной.
#мат_геймдев #МатРазбор #алгоритмы
https://habr.com/ru/articles/1026506/
Вы просили что-то посложнее. Ну держитесь 🙂 Страшные слова, формулы, интерактивное демо. Я заморочился. Так что жду огоньков и репостов друзьям😆
Классический алгоритм Wave Function Collapse создаёт технически валидные, но драматургически «мёртвые» карты, потому что не различает пространство: для него вход, босс-арена и выход равнозначны. Я предлагаю добавить в формулу выбора клетки семантический множитель f(W(x,y)), где W — поле весов, задающее важность каждой зоны: ключевые точки коллапсируют первыми и через propagation определяют структуру всей карты. Разбив поле W на слои (опасность, награда, направление к выходу, нарратив) и добавив модуляцию вероятности тайлов в зависимости от позиции, дизайнер получает инструмент, где задуманная идея уровня воспроизводится в каждом прогоне, а конкретная геометрия остаётся уникальной.
#мат_геймдев #МатРазбор #алгоритмы
Хабр
Драматургия через математику: WFC + Entropy Bias
Как одна модификация формулы превращает генератор карт в дизайнера уровней Всем привет! Меня зовут Григорий Дядиченко, и я технический продюсер. Играли в Hades? Там дизайнер уровней не бросает кубики....
🔥9❤2
Сделал викторину по математике геймдева
https://dev-math.ru/quiz/
Давно хотел собрать в одном месте те вещи, которые постоянно всплывают на собесах и в рабочих задачах. Начал с короткого списка для себя — и затянуло. Да и захотелось прикольно сделать :)
Если коротко — вопросы про то, что реально ломается в проде: почему персонаж дёргается на больших координатах, когда Lerp превращается в «телепорт», зачем кватернионы, если есть углы Эйлера, и десятки подобных вещей. Если найдёте косяки — кидайте в комменты, поправлю.
Ставим 🔥 и делитесь с друзьями, если зашло. Интересно кто куда пройдет. Постарался всё сделать кайфово, в ностальгической эстетике.
#мат_геймдев #викторина
https://dev-math.ru/quiz/
Давно хотел собрать в одном месте те вещи, которые постоянно всплывают на собесах и в рабочих задачах. Начал с короткого списка для себя — и затянуло. Да и захотелось прикольно сделать :)
Если коротко — вопросы про то, что реально ломается в проде: почему персонаж дёргается на больших координатах, когда Lerp превращается в «телепорт», зачем кватернионы, если есть углы Эйлера, и десятки подобных вещей. Если найдёте косяки — кидайте в комменты, поправлю.
Ставим 🔥 и делитесь с друзьями, если зашло. Интересно кто куда пройдет. Постарался всё сделать кайфово, в ностальгической эстетике.
#мат_геймдев #викторина
🔥16❤1
Математика в Gamedev по-простому pinned «Драматургия через математику: как одна формула превращает генератор карт в дизайнера уровней https://habr.com/ru/articles/1026506/ Вы просили что-то посложнее. Ну держитесь 🙂 Страшные слова, формулы, интерактивное демо. Я заморочился. Так что жду огоньков…»
❓ Загадка недели
Ты написал платформер. Нажимаешь стрелку — персонаж идёт.
Отпускаешь — он продолжает скользить.
Код выглядит вот так:
Почему персонаж не останавливается мгновенно?
Ответ: velocity.x никогда не обнуляется.
При input = 0 скорость не уменьшается, а накапливается сессиями нажатий.
Фикс — добавить трение: velocity.x *= (1 - friction * Time.deltaTime)
или просто обнулять при input == 0:
```csharp
float input = Input.GetAxis("Horizontal");
if (Mathf.Abs(input) > 0.01f)
velocity.x = Mathf.MoveTowards(velocity.x, input * speed, accel * Time.deltaTime);
else
velocity.x = Mathf.MoveTowards(velocity.x, 0f, friction * Time.deltaTime);
```
#мат_геймдев #МатРазбор #физика
Ты написал платформер. Нажимаешь стрелку — персонаж идёт.
Отпускаешь — он продолжает скользить.
Код выглядит вот так:
velocity.x += Input.GetAxis("Horizontal") * speed;
transform.position += velocity * Time.deltaTime;
Почему персонаж не останавливается мгновенно?
При input = 0 скорость не уменьшается, а накапливается сессиями нажатий.
Фикс — добавить трение: velocity.x *= (1 - friction * Time.deltaTime)
или просто обнулять при input == 0:
```csharp
float input = Input.GetAxis("Horizontal");
if (Mathf.Abs(input) > 0.01f)
velocity.x = Mathf.MoveTowards(velocity.x, input * speed, accel * Time.deltaTime);
else
velocity.x = Mathf.MoveTowards(velocity.x, 0f, friction * Time.deltaTime);
```
#мат_геймдев #МатРазбор #физика
❤6👍3🔥2
🕳️ Sweep test и CCD: когда объект «проваливается» сквозь стену
Снаряд летит быстро. Кадр 1 — перед стеной. Кадр 2 — за стеной.
Физика не поймала пересечение — проверяла только конечную позицию.
Это туннелирование (tunneling).
Решение 1: SphereCast / RaycastAll
Двигай не точку, а луч или объём вдоль вектора движения.
Решение 2: CCD в Rigidbody
Rigidbody → Collision Detection → Continuous (для объекта)
или Continuous Dynamic (если сталкивается с другими быстрыми объектами).
Когда какой режим:
→ Discrete — медленные объекты, декор
→ Continuous — быстрые объекты (снаряды) vs статичные стены
→ Continuous Dynamic — быстрые vs быстрые
CCD дороже по CPU. Включай только там, где нужно.
#мат_геймдев #МатРазбор #физика #туннелирование
Снаряд летит быстро. Кадр 1 — перед стеной. Кадр 2 — за стеной.
Физика не поймала пересечение — проверяла только конечную позицию.
Это туннелирование (tunneling).
Решение 1: SphereCast / RaycastAll
Двигай не точку, а луч или объём вдоль вектора движения.
RaycastHit hit;
if (Physics.SphereCast(
transform.position,
col.radius,
velocity.normalized,
out hit,
velocity.magnitude * Time.fixedDeltaTime))
{
transform.position = hit.point + hit.normal * col.radius;
velocity = Vector3.Reflect(velocity, hit.normal) * bounciness;
}
else transform.position += velocity * Time.fixedDeltaTime;
Решение 2: CCD в Rigidbody
Rigidbody → Collision Detection → Continuous (для объекта)
или Continuous Dynamic (если сталкивается с другими быстрыми объектами).
Когда какой режим:
→ Discrete — медленные объекты, декор
→ Continuous — быстрые объекты (снаряды) vs статичные стены
→ Continuous Dynamic — быстрые vs быстрые
CCD дороже по CPU. Включай только там, где нужно.
#мат_геймдев #МатРазбор #физика #туннелирование
👍4🔥3❤2
📡 Raycast, SphereCast, BoxCast — собственно, какой кастер выбрать?
Сталкивались с тем, что выпущенный снаряд пролетает сквозь угол колонны, а персонаж намертво застревает в дверном проёме шириной с него самого? Корень обеих бед — неправильно выбранный кастер. Давайте разберём, чем Raycast, SphereCast и BoxCast отличаются на практике.
По сути все три делают одно: пускают зонд из точки в направлении и сообщают, во что он упёрся. Разница только в форме зонда, и именно от неё зависит, что вы поймаете, а что молча пропустите.
Physics.Raycast — математический луч, сечение нулевое. Берите, когда важна именно точка попадания: курсор прицела, проверка прямой видимости от NPC до игрока, лазер, хитскан в стиле какого-нибудь раннего шутера. Самый дешёвый из тройки.
Где он ломается? Банально на всём, что имеет ширину. Снаряд проходит сквозь угол колонны, потому что луч прошёл мимо на пару сантиметров, а сам-то снаряд не точка.
Physics.SphereCast — то же самое, только вдоль луча движется не точка, а сфера заданного радиуса. Объём проверки получается капсульный (не цилиндр, как иногда пишут — торцы скруглённые). Ставьте на снаряды с физическим размером, проверку тоннелирования у быстрых объектов, swept-коллизии для камеры, чтобы она не клиппилась в стену, как в каком-нибудь старом Skyrim до патчей. Дороже Raycast, но ловит граничные случаи, где тот промолчит.
Physics.BoxCast — та же история, только вдоль луча движется коробка. Инструмент для всего, что само по себе «ящик»: проверки CharacterController, ground check у платформера, тест «пролезет ли персонаж в узкий проход». Ну и не забывайте про ориентацию — Quaternion.identity суют по инерции слишком часто, а коробка-то поворачивается вместе с персонажем.
Чтож, итог простой: представьте объём, который реально движется по сцене. У снаряда — капсула, у персонажа — коробка, у взгляда — линия. Под этот объём и берите кастер. Конечно же, правило не единственно верное, нюансов в физике Unity хватает, но для 90% случаев работает.
#мат_геймдев #КодПодСкопой #физика
Сталкивались с тем, что выпущенный снаряд пролетает сквозь угол колонны, а персонаж намертво застревает в дверном проёме шириной с него самого? Корень обеих бед — неправильно выбранный кастер. Давайте разберём, чем Raycast, SphereCast и BoxCast отличаются на практике.
По сути все три делают одно: пускают зонд из точки в направлении и сообщают, во что он упёрся. Разница только в форме зонда, и именно от неё зависит, что вы поймаете, а что молча пропустите.
Physics.Raycast — математический луч, сечение нулевое. Берите, когда важна именно точка попадания: курсор прицела, проверка прямой видимости от NPC до игрока, лазер, хитскан в стиле какого-нибудь раннего шутера. Самый дешёвый из тройки.
Где он ломается? Банально на всём, что имеет ширину. Снаряд проходит сквозь угол колонны, потому что луч прошёл мимо на пару сантиметров, а сам-то снаряд не точка.
Physics.SphereCast — то же самое, только вдоль луча движется не точка, а сфера заданного радиуса. Объём проверки получается капсульный (не цилиндр, как иногда пишут — торцы скруглённые). Ставьте на снаряды с физическим размером, проверку тоннелирования у быстрых объектов, swept-коллизии для камеры, чтобы она не клиппилась в стену, как в каком-нибудь старом Skyrim до патчей. Дороже Raycast, но ловит граничные случаи, где тот промолчит.
Physics.BoxCast — та же история, только вдоль луча движется коробка. Инструмент для всего, что само по себе «ящик»: проверки CharacterController, ground check у платформера, тест «пролезет ли персонаж в узкий проход». Ну и не забывайте про ориентацию — Quaternion.identity суют по инерции слишком часто, а коробка-то поворачивается вместе с персонажем.
// Ground check через BoxCast — точнее, чем одиночный Raycast,
// и не ловит ложные срабатывания на краях ступенек.
bool IsGrounded()
{
Vector3 origin = transform.position + Vector3.up * 0.1f;
Vector3 halfSize = new Vector3(0.4f, 0.05f, 0.4f);
return Physics.BoxCast(
origin, halfSize, Vector3.down,
transform.rotation, 0.15f, groundMask);
}
Чтож, итог простой: представьте объём, который реально движется по сцене. У снаряда — капсула, у персонажа — коробка, у взгляда — линия. Под этот объём и берите кастер. Конечно же, правило не единственно верное, нюансов в физике Unity хватает, но для 90% случаев работает.
#мат_геймдев #КодПодСкопой #физика
❤6🔥4👍2
Кватернионы через зеркала: как одно геометрическое наблюдение убивает магию формулы q·v·q⁻¹
https://dev-math.ru/articles/rotations/
12 интерактивных демо в браузере, гимбал-лок, plate trick, формулы выводятся честно из геометрии. Если q · v · q⁻¹ когда-нибудь заставляла вас захлопнуть учебник со словами «и тут, конечно же, всё интуитивно понятно» — статья ровно про это. Жду 🔥 и репостов друзьям-геймдевам 😆
В большинстве материалов кватернионы вводятся как магия: «вот вам четыре числа с условием нормировки, применяйте по формуле сэндвича, не задавайте вопросов». Я предлагаю обратный заход: показываю, что любой 3D-поворот можно получить как композицию двух отражений в плоскостях (угол между зеркалами — половина угла поворота, линия пересечения — ось), и кватернион — это просто компактная запись пары зеркал. Из этого естественно вырастает и половинный угол в формулах, и сэндвич q · v · q⁻¹ как «два полуповорота, компенсирующие искажение длины», и дважды-покрытие SO(3) — то самое, из-за чего поворот на 720° возвращает в исходное состояние, а на 360° — нет (в браузере есть честный Dirac belt trick с распутыванием лент). По дороге разбираем зоопарк альтернатив: углы Эйлера с гимбал-локом, матрицы 3×3 как «куда уехали базисные векторы», ось-угол и SLERP — каждое со своими плюсами и минусами, без объявления любимчика заранее.
#мат_геймдев #МатРазбор #кватернионы #графика
https://dev-math.ru/articles/rotations/
12 интерактивных демо в браузере, гимбал-лок, plate trick, формулы выводятся честно из геометрии. Если q · v · q⁻¹ когда-нибудь заставляла вас захлопнуть учебник со словами «и тут, конечно же, всё интуитивно понятно» — статья ровно про это. Жду 🔥 и репостов друзьям-геймдевам 😆
В большинстве материалов кватернионы вводятся как магия: «вот вам четыре числа с условием нормировки, применяйте по формуле сэндвича, не задавайте вопросов». Я предлагаю обратный заход: показываю, что любой 3D-поворот можно получить как композицию двух отражений в плоскостях (угол между зеркалами — половина угла поворота, линия пересечения — ось), и кватернион — это просто компактная запись пары зеркал. Из этого естественно вырастает и половинный угол в формулах, и сэндвич q · v · q⁻¹ как «два полуповорота, компенсирующие искажение длины», и дважды-покрытие SO(3) — то самое, из-за чего поворот на 720° возвращает в исходное состояние, а на 360° — нет (в браузере есть честный Dirac belt trick с распутыванием лент). По дороге разбираем зоопарк альтернатив: углы Эйлера с гимбал-локом, матрицы 3×3 как «куда уехали базисные векторы», ось-угол и SLERP — каждое со своими плюсами и минусами, без объявления любимчика заранее.
#мат_геймдев #МатРазбор #кватернионы #графика
🔥11👍4👏2
Брейк-радары в процедурной генерации — выбрасываем плохой вариант раньше, чем запустить BFS
Сталкивались с тем, что процедурный генератор карт подвисает на пару секунд прямо посреди загрузки? Вроде всё логично: набросали вариант, прогнали BFS, убедились что карта проходима, повторили. Но итераций таких — сотни, и каждая тащит полный обход графа. Давайте разберём, как брейк-радары позволяют выбрасывать провальные кандидаты ещё до старта тяжёлого обхода.
По сути, процедурный генератор — это машина перебора. Вы накидываете вариант, проверяете условия (карта связна, путь существует, длина маршрута не меньше N шагов), если не подходит — выбрасываете и пробуете снова. Банальный подход: BFS на каждый вариант. На карте 100×100 с тысячей итераций вы быстро упрётесь в потолок.
Брейк-радар — это дешёвая проверка *до* тяжёлого алгоритма. Если она говорит «этот вариант точно плохой» — прерываете итерацию сразу, BFS даже не стартует. Хороший радар: O(1) или O(маленькое), зато отсекает значительную долю провалов.
Итак, три примера на пальцах.
Трасса с кольцом
Представьте генератор трасс как у какого-нибудь Mini Motor Racing — тайлы с прямыми, поворотами влево и вправо. Чтобы трасса замкнулась, сумма поворотов должна быть ровно ±360°. Это геометрический факт, BFS здесь вообще не нужен:
Сумма не сходится — трасса физически не замкнётся. Ноль смысла проверять связность.
Данж с изолированными комнатами
Генерируем подземелье, нужно убедиться что все комнаты достижимы. Полный BFS — O(W×H). Но перед ним работает грубый инвариант: если комнат N, то минимальное число соединений для связного графа — N−1 (дерево). Дверей меньше — данж гарантированно несвязен:
Конечно же, это нижняя оценка — двери могут вести в тупики, и BFS всё равно понадобится для финальной проверки. Но очевидные провалы убираем бесплатно.
Минимальная длина пути
Классика головоломок и платформеров: путь от старта до финиша должен быть не короче K шагов. BFS даст точный ответ, но Манхэттенское расстояние даёт бесплатную нижнюю оценку:
Ну и тут важно не перепутать направление неравенства. Манхэттен — *нижняя* граница для кратчайшего пути на сетке без диагоналей. Реальный путь будет длиннее или равен. Так что если манхэттен уже не дотягивает до минимума — дальше незачем смотреть.
Чтож, итог простой: брейк-радар — не замена BFS, а фильтр перед ним. Хорошая система генерации выстраивает радары по возрастанию стоимости — сначала O(1) по счётчикам, потом что-то дешёвое по периметру, и только потом полный обход. Моё решение тут точно не единственное, у каждой задачи свои инварианты, которые можно поймать заранее. Но принцип один: чем раньше выбросили плохой вариант — тем быстрее нашли хороший.
Буду рад обсудить в комментариях, какие радары используете вы.
#мат_геймдев #КодПодСкопой #процедурнаягенерация
Сталкивались с тем, что процедурный генератор карт подвисает на пару секунд прямо посреди загрузки? Вроде всё логично: набросали вариант, прогнали BFS, убедились что карта проходима, повторили. Но итераций таких — сотни, и каждая тащит полный обход графа. Давайте разберём, как брейк-радары позволяют выбрасывать провальные кандидаты ещё до старта тяжёлого обхода.
По сути, процедурный генератор — это машина перебора. Вы накидываете вариант, проверяете условия (карта связна, путь существует, длина маршрута не меньше N шагов), если не подходит — выбрасываете и пробуете снова. Банальный подход: BFS на каждый вариант. На карте 100×100 с тысячей итераций вы быстро упрётесь в потолок.
Брейк-радар — это дешёвая проверка *до* тяжёлого алгоритма. Если она говорит «этот вариант точно плохой» — прерываете итерацию сразу, BFS даже не стартует. Хороший радар: O(1) или O(маленькое), зато отсекает значительную долю провалов.
Итак, три примера на пальцах.
Трасса с кольцом
Представьте генератор трасс как у какого-нибудь Mini Motor Racing — тайлы с прямыми, поворотами влево и вправо. Чтобы трасса замкнулась, сумма поворотов должна быть ровно ±360°. Это геометрический факт, BFS здесь вообще не нужен:
int netRotation = 0;
foreach (var tile in generatedTrack)
netRotation += tile.rotationDelta; // +90 или -90
if (Mathf.Abs(netRotation) != 360)
return false; // выбрасываем без BFS
Сумма не сходится — трасса физически не замкнётся. Ноль смысла проверять связность.
Данж с изолированными комнатами
Генерируем подземелье, нужно убедиться что все комнаты достижимы. Полный BFS — O(W×H). Но перед ним работает грубый инвариант: если комнат N, то минимальное число соединений для связного графа — N−1 (дерево). Дверей меньше — данж гарантированно несвязен:
int rooms = CountRooms();
int doors = CountDoorTiles();
if (doors < rooms - 1)
return false; // математически несвязно, BFS не нужен
Конечно же, это нижняя оценка — двери могут вести в тупики, и BFS всё равно понадобится для финальной проверки. Но очевидные провалы убираем бесплатно.
Минимальная длина пути
Классика головоломок и платформеров: путь от старта до финиша должен быть не короче K шагов. BFS даст точный ответ, но Манхэттенское расстояние даёт бесплатную нижнюю оценку:
int manhattan = Mathf.Abs(start.x - end.x) + Mathf.Abs(start.y - end.y);
if (manhattan >= minPathLength)
return false; // реальный путь не короче манхэттена — а нам нужно именно короче
Ну и тут важно не перепутать направление неравенства. Манхэттен — *нижняя* граница для кратчайшего пути на сетке без диагоналей. Реальный путь будет длиннее или равен. Так что если манхэттен уже не дотягивает до минимума — дальше незачем смотреть.
Чтож, итог простой: брейк-радар — не замена BFS, а фильтр перед ним. Хорошая система генерации выстраивает радары по возрастанию стоимости — сначала O(1) по счётчикам, потом что-то дешёвое по периметру, и только потом полный обход. Моё решение тут точно не единственное, у каждой задачи свои инварианты, которые можно поймать заранее. Но принцип один: чем раньше выбросили плохой вариант — тем быстрее нашли хороший.
Буду рад обсудить в комментариях, какие радары используете вы.
#мат_геймдев #КодПодСкопой #процедурнаягенерация
🔥7👍2👏2❤1
Hitscan vs projectile: почему выбор механики стрельбы — это архитектурное решение
Рейлган в Quake попадает в момент нажатия — это raycast, один вызов, результат за кадр. Ракета — честный физический объект со своей скоростью: пока летит, противник уже не там, и целиться нужно в точку, где он окажется через ~400 мс.
Первый подход стал стандартом для тактических шутеров — CS, Valorant. Второй живёт в аркадных — Quake, UT, частично Apex. И это не вкусовщина: за выбором тянется целый хвост — lag compensation на сервере, recoil-паттерны, вся ощущаемая отзывчивость стрельбы.
На следующей неделе давайте разберём, когда какой выбирать и как это реализуется под капотом.
#мат_геймдев #шутеры #алгоритмы
Рейлган в Quake попадает в момент нажатия — это raycast, один вызов, результат за кадр. Ракета — честный физический объект со своей скоростью: пока летит, противник уже не там, и целиться нужно в точку, где он окажется через ~400 мс.
Первый подход стал стандартом для тактических шутеров — CS, Valorant. Второй живёт в аркадных — Quake, UT, частично Apex. И это не вкусовщина: за выбором тянется целый хвост — lag compensation на сервере, recoil-паттерны, вся ощущаемая отзывчивость стрельбы.
На следующей неделе давайте разберём, когда какой выбирать и как это реализуется под капотом.
#мат_геймдев #шутеры #алгоритмы
🔥12❤2
Квадратный спред: почему углы «едят» ваши пули
Наивная реализация:
Правильный вариант — полярные координаты:
Почему
В конце недели — полный разбор: cone, gaussian, детерминированные паттерны и когда равномерный диск вообще не нужен.
#мат_геймдев #шутеры #алгоритмы
Наивная реализация:
dx = Random(-spread, spread), dy — то же самое. Распределение равномерное, но форма — квадрат. А диагональ квадрата длиннее стороны в √2 раз, так что в угловых направлениях пуля улетает на spread * 1.41 вместо spread. Конус разброса физически не круглый — и это чувствуется.Правильный вариант — полярные координаты:
const angle = Math.random() * Math.PI * 2;
const radius = spread * Math.sqrt(Math.random());
const dx = Math.cos(angle) * radius;
const dy = Math.sin(angle) * radius;
Почему
sqrt? Без него точки кучкуются к центру — площадь кольца растёт с радиусом, а вероятность нет. sqrt это компенсирует и даёт равномерную плотность по всему диску.В конце недели — полный разбор: cone, gaussian, детерминированные паттерны и когда равномерный диск вообще не нужен.
#мат_геймдев #шутеры #алгоритмы
❤10🤔5🔥2
Cone-uniform vs Gaussian
Равномерный диск — это честно, но не реалистично. В жизни большинство пуль летит ближе к центру прицела, а не равномерно по всему конусу. Gaussian-распределение это отражает: пули кучкуются вокруг центра, хвосты редкие.
На практике разница такая: в CS-подобных играх первый выстрел — почти точный, разброс нарастает с темпом стрельбы. Это не равномерный диск и не чистый Gaussian — это детерминированный паттерн поверх него. Но базовый случайный спред лежащий в основе чаще всего именно Gaussian.
Собственно, выбор между uniform и Gaussian — это выбор ощущения. Uniform читается как «аркада», Gaussian — как «оружие с характером». Когда что уместно — разберём в конце недели, там же детерминированные паттерны и recoil-кривые.
#мат_геймдев #шутеры #алгоритмы
Равномерный диск — это честно, но не реалистично. В жизни большинство пуль летит ближе к центру прицела, а не равномерно по всему конусу. Gaussian-распределение это отражает: пули кучкуются вокруг центра, хвосты редкие.
На практике разница такая: в CS-подобных играх первый выстрел — почти точный, разброс нарастает с темпом стрельбы. Это не равномерный диск и не чистый Gaussian — это детерминированный паттерн поверх него. Но базовый случайный спред лежащий в основе чаще всего именно Gaussian.
function gaussianRandom(mean = 0, std = 1): number {
const u = 1 - Math.random();
const v = Math.random();
return mean + std * Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
}
const dx = gaussianRandom(0, spread);
const dy = gaussianRandom(0, spread);
Собственно, выбор между uniform и Gaussian — это выбор ощущения. Uniform читается как «аркада», Gaussian — как «оружие с характером». Когда что уместно — разберём в конце недели, там же детерминированные паттерны и recoil-кривые.
#мат_геймдев #шутеры #алгоритмы
👍14🔥3❤2
О стрельбе в играх по-простому: hitscan, projectile, recoil, lag compensation — шесть пластов под кнопкой выстрела
https://dev-math.ru/articles/shooting/
Шесть параграфов с интерактивами, формулами и кодом на C#. Если когда-нибудь в команде звучало «ну стрельбу-то сделаем за выходные», а потом неделями отлавливали «попал — не засчиталось», статья ровно про это. Жду 🔥 и репостов друзьям-геймдевам 😆
В статье о том, что под одним «нажал курок» лежит шесть независимых задач, и каждая решается по-своему:
▸ Hitscan vs projectile — архитектурный выбор: мгновенный луч (CS, Valorant) или физический снаряд (Quake, Kraber в Apex). За выбором тянется хвост: lag compensation, recoil, ощущаемая отзывчивость.
▸ Хитбоксы — почему индустрия стандартизировалась на капсулах, а не на полигональном меше. Valve в сентябре 2015 целиком переписали систему именно ради этого.
▸ Спред пули — почему два независимых random дают квадрат вместо круга, и как полярные координаты с √ это лечат.
▸ Recoil-паттерны — детерминированная таблица в духе AK-47, которую pro-игроки заучивают как стихи.
▸ Упреждение — банальное квадратное уравнение для AI-снайперов, решаемое за десять строк кода.
▸ Lag compensation — зачем сервер откатывает мир на 80–200 мс назад на каждом выстреле.
По дороге — интерактивы, в которых можно покрутить параметры руками, и сниппеты на C#.
Если соберём 200 🔥 — пишу продолжение по теме, которая больше всего зайдёт в комментариях (баллистика снайперок, penetration через стены, ricochet, damage falloff…).
#мат_геймдев #МатРазбор #шутеры #алгоритмы
https://dev-math.ru/articles/shooting/
Шесть параграфов с интерактивами, формулами и кодом на C#. Если когда-нибудь в команде звучало «ну стрельбу-то сделаем за выходные», а потом неделями отлавливали «попал — не засчиталось», статья ровно про это. Жду 🔥 и репостов друзьям-геймдевам 😆
В статье о том, что под одним «нажал курок» лежит шесть независимых задач, и каждая решается по-своему:
▸ Hitscan vs projectile — архитектурный выбор: мгновенный луч (CS, Valorant) или физический снаряд (Quake, Kraber в Apex). За выбором тянется хвост: lag compensation, recoil, ощущаемая отзывчивость.
▸ Хитбоксы — почему индустрия стандартизировалась на капсулах, а не на полигональном меше. Valve в сентябре 2015 целиком переписали систему именно ради этого.
▸ Спред пули — почему два независимых random дают квадрат вместо круга, и как полярные координаты с √ это лечат.
▸ Recoil-паттерны — детерминированная таблица в духе AK-47, которую pro-игроки заучивают как стихи.
▸ Упреждение — банальное квадратное уравнение для AI-снайперов, решаемое за десять строк кода.
▸ Lag compensation — зачем сервер откатывает мир на 80–200 мс назад на каждом выстреле.
По дороге — интерактивы, в которых можно покрутить параметры руками, и сниппеты на C#.
Если соберём 200 🔥 — пишу продолжение по теме, которая больше всего зайдёт в комментариях (баллистика снайперок, penetration через стены, ricochet, damage falloff…).
#мат_геймдев #МатРазбор #шутеры #алгоритмы
🔥44
This media is not supported in your browser
VIEW IN TELEGRAM
В Dreams на PS4 нет ни одного полигона. Совсем.
Взгляните на знаменитый Shadertoy «Happy Jumping» от Inigo Quilez — по экрану прыгает живая капля с глазами, мягкими тенями, AO. Никаких треугольников. Только SDF.
Это signed distance field и raymarching. Геометрия описана математически: для любой точки в пространстве функция возвращает то, как далеко до ближайшей поверхности.
Академическая штука для красивых демок? Конечно же нет. Всё это можно использовать в продакшене. Dreams от Media Molecule целиком построена на SDF — вся игра, все пользовательские ассеты. Doom Eternal считает через SDF-brushes свою gore-систему (есть отдельный Siggraph 2020 talk). Claybook делает на этом пластилиновую физику.
Шейдер персонажа Inigo Quilez выглядит круто, помещается в один небольшой файл, работает в браузере. Но у этого есть своя цена — и именно поэтому в играх raymarching чаще локальный эффект, чем основной рендер.
В статье на этой неделе разберём: что такое distance field, как работает sphere tracing, почему две сферы сливаются в каплю через одну функцию smin и откуда берутся "почти бесплатные" soft shadows. Плюс попробую сделать демки на WebGL чтобы можно было покрутить параметры самостоятельно 🔥
#мат_геймдев #МатРазбор #шейдеры #VFX
Взгляните на знаменитый Shadertoy «Happy Jumping» от Inigo Quilez — по экрану прыгает живая капля с глазами, мягкими тенями, AO. Никаких треугольников. Только SDF.
Это signed distance field и raymarching. Геометрия описана математически: для любой точки в пространстве функция возвращает то, как далеко до ближайшей поверхности.
Академическая штука для красивых демок? Конечно же нет. Всё это можно использовать в продакшене. Dreams от Media Molecule целиком построена на SDF — вся игра, все пользовательские ассеты. Doom Eternal считает через SDF-brushes свою gore-систему (есть отдельный Siggraph 2020 talk). Claybook делает на этом пластилиновую физику.
Шейдер персонажа Inigo Quilez выглядит круто, помещается в один небольшой файл, работает в браузере. Но у этого есть своя цена — и именно поэтому в играх raymarching чаще локальный эффект, чем основной рендер.
В статье на этой неделе разберём: что такое distance field, как работает sphere tracing, почему две сферы сливаются в каплю через одну функцию smin и откуда берутся "почти бесплатные" soft shadows. Плюс попробую сделать демки на WebGL чтобы можно было покрутить параметры самостоятельно 🔥
#мат_геймдев #МатРазбор #шейдеры #VFX
🔥12👍6
Как лепят миры из пластилина
В SDF-мире вся геометрия — это функции расстояния. Объединение двух объектов?
А хочется ртути. Слизи. Жидкого золота. Капли воды, которая стекает и сливается с другой. Нужно мягкое объединение.
Собственно, вот оно — polynomial smin:
Эта формула везде, где в кадре что-то текучее: лава и слизь во многих играх, любая «жидкая» геометрия в шейдерных демосценах. Конечно же, в больших AAA-катсценах жидкость чаще пекут в alembic-кэш — но это уже не процедурка, а проигрывание заранее посчитанной симуляции. У smin своя ниша.
smin выигрывает у alembic-кэшей везде где что-то меняется от ввода игрока в реальном времени. Боссы-слизни, которые делятся и сливаются. Кровь и слизь, динамически стекающиеся в лужи под ногами. Терраморфинг от взрывов, налипание снега. Жгуты энергии из руки игрока к цели. Запечённую симуляцию тут не подсунешь — форма зависит от того, что игрок делает прямо сейчас. А smin честно считает её на лету.
Основная проблема SDF и Raymarching — это производительность. И это главная причина почему SDF не вытеснили полигоны. Но с нынешними мощностями устройств эти техники применимы «локально». Когда полученная ими визуализация композится в сцену. Например, VFX заклинания или какие-то отдельные эффекты.
#мат_геймдев #МатРазбор #шейдеры
В SDF-мире вся геометрия — это функции расстояния. Объединение двух объектов?
min(a, b). Ставите две сферы рядом, берёте min — и получаете не каплю, а два камня, лежащих друг на друге. Жёсткий стык, шов на пиксель.А хочется ртути. Слизи. Жидкого золота. Капли воды, которая стекает и сливается с другой. Нужно мягкое объединение.
Собственно, вот оно — polynomial smin:
float smin(float a, float b, float k) {
float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
return mix(b, a, h) - k * h * (1.0 - h);
}
k — ширина шва. k=0 — жёсткое объединение, обычный min. k=0.3 — между сферами протягивается перешеек. k=1 — две сферы превращаются почти в каплю.Эта формула везде, где в кадре что-то текучее: лава и слизь во многих играх, любая «жидкая» геометрия в шейдерных демосценах. Конечно же, в больших AAA-катсценах жидкость чаще пекут в alembic-кэш — но это уже не процедурка, а проигрывание заранее посчитанной симуляции. У smin своя ниша.
smin выигрывает у alembic-кэшей везде где что-то меняется от ввода игрока в реальном времени. Боссы-слизни, которые делятся и сливаются. Кровь и слизь, динамически стекающиеся в лужи под ногами. Терраморфинг от взрывов, налипание снега. Жгуты энергии из руки игрока к цели. Запечённую симуляцию тут не подсунешь — форма зависит от того, что игрок делает прямо сейчас. А smin честно считает её на лету.
Основная проблема SDF и Raymarching — это производительность. И это главная причина почему SDF не вытеснили полигоны. Но с нынешними мощностями устройств эти техники применимы «локально». Когда полученная ими визуализация композится в сцену. Например, VFX заклинания или какие-то отдельные эффекты.
#мат_геймдев #МатРазбор #шейдеры
🔥12
Raymarch на 5 fps — это не GPU виноват. Это вы «шагаете» неправильно
У вас есть SDF и луч. Как найти, где луч упирается в поверхность? Самый очевидный ответ — идём вдоль луча маленькими шагами и проверяем, попали ли.
И вот ловушка. Шаг маленький — тысячи итераций на пиксель, шейдер выдает 5 fps. Шаг большой — луч проскакивает сквозь тонкие объекты, артефакты по всей сцене. Компромисс «либо медленно, либо неточно». А хочется оба.
Собственно, в чём идея. SDF возвращает расстояние до ближайшей поверхности. Значит, можно шагнуть ровно на это расстояние — и гарантированно не пройти насквозь.
Близко к поверхности шаги мелкие (поле говорит «осторожно, рядом»), в пустоте — большие («лети спокойно»). Обычно 30–60 итераций на всю сцену.
По сути, вся магия SDF держится именно на этой идее. Без sphere tracing формулы расстояний были бы просто красивой математикой без практического смысла.
#мат_геймдев #МатРазбор #шейдеры
У вас есть SDF и луч. Как найти, где луч упирается в поверхность? Самый очевидный ответ — идём вдоль луча маленькими шагами и проверяем, попали ли.
// плохо: фиксированный шаг
for (int i = 0; i < 1000; i++) {
if (sdf(p) < EPS) return p;
p += dir * 0.01;
}
И вот ловушка. Шаг маленький — тысячи итераций на пиксель, шейдер выдает 5 fps. Шаг большой — луч проскакивает сквозь тонкие объекты, артефакты по всей сцене. Компромисс «либо медленно, либо неточно». А хочется оба.
Собственно, в чём идея. SDF возвращает расстояние до ближайшей поверхности. Значит, можно шагнуть ровно на это расстояние — и гарантированно не пройти насквозь.
// хорошо: sphere tracing
for (int i = 0; i < 64; i++) {
float d = sdf(p);
if (d < EPS) return p;
p += dir * d; // ← шаг ровно SDF
}
Близко к поверхности шаги мелкие (поле говорит «осторожно, рядом»), в пустоте — большие («лети спокойно»). Обычно 30–60 итераций на всю сцену.
По сути, вся магия SDF держится именно на этой идее. Без sphere tracing формулы расстояний были бы просто красивой математикой без практического смысла.
#мат_геймдев #МатРазбор #шейдеры
🔥11❤1