🗺️ Два других способа генерировать уровни
Комнатный dungeon — самый простой метод.
Вот ещё два, которые дают другой визуальный стиль.
BSP (Binary Space Partitioning)
Делит пространство пополам рекурсивно, как оригами.
Каждая «половинка» становится комнатой. Переходы между ними — двери.
Результат: более прямоугольные, «архитектурные» уровни.
Используют: Nethack, классические roguelike.
Cellular Automata (клеточные автоматы)
Заполни сетку случайными стенами/полом (50/50).
Потом повтори правило N раз:
«Если у клетки ≥ 5 соседей-стен — она стена, иначе — пол»
Результат: органичные пещеры, плавные очертания.
Используют: Dwarf Fortress, Don't Starve для биомов.
Выбор метода зависит от стиля игры:
→ Dungeon / RPG → комнаты или BSP
→ Survival / Open world → cellular automata
#мат_геймдев #МатРазбор #процедурнаяГенерация
Комнатный dungeon — самый простой метод.
Вот ещё два, которые дают другой визуальный стиль.
BSP (Binary Space Partitioning)
Делит пространство пополам рекурсивно, как оригами.
Каждая «половинка» становится комнатой. Переходы между ними — двери.
Результат: более прямоугольные, «архитектурные» уровни.
Используют: Nethack, классические roguelike.
Корень → [левая половина] [правая половина]
↓ ↓
[комн.] [комн.] [комн.] [комн.]
Cellular Automata (клеточные автоматы)
Заполни сетку случайными стенами/полом (50/50).
Потом повтори правило N раз:
«Если у клетки ≥ 5 соседей-стен — она стена, иначе — пол»
Результат: органичные пещеры, плавные очертания.
Используют: Dwarf Fortress, Don't Starve для биомов.
// Один шаг клеточного автомата
for (int x = 1; x < w-1; x++)
for (int y = 1; y < h-1; y++)
{
int walls = CountWallNeighbors(x, y);
nextGrid[x,y] = walls >= 5 ? 1 : 0;
}
Выбор метода зависит от стиля игры:
→ Dungeon / RPG → комнаты или BSP
→ Survival / Open world → cellular automata
#мат_геймдев #МатРазбор #процедурнаяГенерация
🔥6❤1
🌊 Wave Function Collapse: квантовая физика в генерации уровней
В квантовой механике частица до измерения находится в суперпозиции — она одновременно может быть в нескольких состояниях. Как только ты её измеряешь, волновая функция «схлопывается» в одно конкретное.
В алгоритме ровно та же метафора.
Каждая клетка сетки до заполнения — это суперпозиция всех возможных тайлов: лес, вода, скала, дорога... Как только ты её «наблюдаешь» (выбираешь тайл) — она схлопывается в одно состояние. И это схлопывание мгновенно влияет на соседей — сужает их варианты. Точь-в-точь как квантовая запутанность.
Берёшь набор тайлов и правила: «рядом с водой — только берег, рядом с берегом — трава». Потом заполняешь сетку, соблюдая эти правила. Результат: уровень выглядит как нарисованный вручную — потому что правила берутся из реального образца.
Алгоритм в трёх шагах:
1. Наблюдение — выбери клетку с минимальной энтропией (меньше всего вариантов) и «схлопни» её.
2. Распространение — пройдись по соседям и удали варианты, которые теперь противоречат правилам.
3. Повтори — пока вся сетка не схлопнется.
Физики здесь нет — это чистая метафора. Но название точное: именно так это и ощущается изнутри.
Используют: Caves of Qud, Bad North, Townscaper.
#мат_геймдев #МатРазбор #процедурнаяГенерация
В квантовой механике частица до измерения находится в суперпозиции — она одновременно может быть в нескольких состояниях. Как только ты её измеряешь, волновая функция «схлопывается» в одно конкретное.
В алгоритме ровно та же метафора.
Каждая клетка сетки до заполнения — это суперпозиция всех возможных тайлов: лес, вода, скала, дорога... Как только ты её «наблюдаешь» (выбираешь тайл) — она схлопывается в одно состояние. И это схлопывание мгновенно влияет на соседей — сужает их варианты. Точь-в-точь как квантовая запутанность.
Берёшь набор тайлов и правила: «рядом с водой — только берег, рядом с берегом — трава». Потом заполняешь сетку, соблюдая эти правила. Результат: уровень выглядит как нарисованный вручную — потому что правила берутся из реального образца.
Алгоритм в трёх шагах:
1. Наблюдение — выбери клетку с минимальной энтропией (меньше всего вариантов) и «схлопни» её.
2. Распространение — пройдись по соседям и удали варианты, которые теперь противоречат правилам.
3. Повтори — пока вся сетка не схлопнется.
Физики здесь нет — это чистая метафора. Но название точное: именно так это и ощущается изнутри.
Используют: Caves of Qud, Bad North, Townscaper.
#мат_геймдев #МатРазбор #процедурнаяГенерация
🔥11
🎮 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