en
Feedback
Математика в Gamedev по-простому

Математика в Gamedev по-простому

Open in Telegram

Как на самом деле работают стрельба, толпа NPC, графика, физика тканей и свет в играх. Разборы на пальцах, рабочий код, интерактивы. dev-math.ru Сотрудничество: @it_bizdev

Show more
534
Subscribers
+324 hours
+47 days
+2630 days
Posts Archive
🚀 Velocity, acceleration, friction: Ньютон в аркадной игре 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. #мат_геймдев #МатРазбор #физика

🐛 #ОшибкаНедели: Is Kinematic — что это вообще значит Новички часто видят Is Kinematic и включают его, не понимая зачем. Разберём один раз и навсегда. Обычный Rigidbody: управляется физикой. Гравитация тянет вниз, AddForce работает, другие тела толкают его. Is Kinematic = true: объект управляется кодом/анимацией, не физикой. Гравитация игнорируется. AddForce не работает. Но он всё ещё участвует в коллизиях — отталкивает другие тела. ❌ Типичная ошибка: включить Is Kinematic у персонажа, потом удивляться, почему прыжок не работает. ✅ Когда нужен Kinematic: → Платформа, которая движется по скрипту → Дверь, управляемая анимацией → Враг, которого двигают по NavMesh, но у которого есть коллайдер → Объект, управляемый в FixedUpdate вручную Правило: Kinematic = «я сам двигаю объект», обычный = «физика двигает объект». #мат_геймдев #ОшибкаНедели #физика #rigidbody

🎯 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

⚙️ 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

🎮 3 игры с крутой процедурной генерацией — что под капотом Spelunky (2009) Уровень = 4×4 блока 10×8 клеток. Каждый блок — один из заранее нарисованных шаблонов (rooms), выбранный рандомно. Гарантия проходимости: один «опорный» маршрут через блоки всегда есть. Вывод: случайность + ручные шаблоны = игровой баланс сохранён. Dead Cells (2018) Уровни — не случайные, а сборные из готовых секций. Секции заранее дизайнерски проработаны, порядок и детали — рандомные. Вывод: меньше математики, больше контроля над feel. No Man's Sky (2016) Планеты, существа, растения — из параметрических «рецептов». Один seed → 18 квинтиллионов планет. Математика через noise-функции и L-системы. Вывод: глубокая процедурка требует огромных систем параметров. Общий урок: в хороших играх процедурка никогда не работает одна — всегда есть ручная работа, которая задаёт рамки. Даже когда я участвовал в разработке Vector 2, там были огромные Yaml генераторы задающие правила генерации уровней, чтобы рандом был интересным. #мат_геймдев #МатРазбор #процедурнаяГенерация

🌊 Wave Function Collapse: квантовая физика в генерации уровней В квантовой механике частица до измерения находится в суперпозиции — она одновременно может быть в нескольких состояниях. Как только ты её измеряешь, волновая функция «схлопывается» в одно конкретное. В алгоритме ровно та же метафора. Каждая клетка сетки до заполнения — это суперпозиция всех возможных тайлов: лес, вода, скала, дорога... Как только ты её «наблюдаешь» (выбираешь тайл) — она схлопывается в одно состояние. И это схлопывание мгновенно влияет на соседей — сужает их варианты. Точь-в-точь как квантовая запутанность. Берёшь набор тайлов и правила: «рядом с водой — только берег, рядом с берегом — трава». Потом заполняешь сетку, соблюдая эти правила. Результат: уровень выглядит как нарисованный вручную — потому что правила берутся из реального образца. Алгоритм в трёх шагах: 1. Наблюдение — выбери клетку с минимальной энтропией (меньше всего вариантов) и «схлопни» её. 2. Распространение — пройдись по соседям и удали варианты, которые теперь противоречат правилам. 3. Повтори — пока вся сетка не схлопнется. Физики здесь нет — это чистая метафора. Но название точное: именно так это и ощущается изнутри. Используют: Caves of Qud, Bad North, Townscaper. #мат_геймдев #МатРазбор #процедурнаяГенерация

🗺️ Два других способа генерировать уровни Комнатный 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 #мат_геймдев #МатРазбор #процедурнаяГенерация

🏰 Процедурная генерация: рандом с правилами «Процедурная генерация» — не просто случайные числа. Это рандом, ограниченный правилами так, чтобы результат был играбелен. Простейший пример — комнатный dungeon: Шаг 1. Создай сетку N×M, заполни стенами (1). Шаг 2. Попробуй разместить K случайных прямоугольников-комнат. Если прямоугольник не пересекается с уже размещёнными — добавь. Шаг 3. Соедини центры соседних комнат коридорами (L-образный тоннель). Шаг 4. Поставь старт в первую комнату, выход — в последнюю.

void GenerateDungeon(int seed)
{
    var rng = new SeededRandom(seed);
    var rooms = new List<RectInt>();

    for (int i = 0; i < MAX_ROOMS; i++)
    {
        int w = rng.Next(MIN_SIZE, MAX_SIZE);
        int h = rng.Next(MIN_SIZE, MAX_SIZE);
        int x = rng.Next(1, width  - w - 1);
        int y = rng.Next(1, height - h - 1);
        var room = new RectInt(x, y, w, h);

        if (!rooms.Any(r => r.Overlaps(room)))
        {
            CarveRoom(room);
            if (rooms.Count > 0)
                CarveCorridors(rooms.Last(), room, rng);
            rooms.Add(room);
        }
    }
}
Секрет «не сломанного» уровня: правила ограничивают, рандом заполняет. Именно это делает Spelunky, Enter the Gungeon, Dead Cells — каждый запуск свой, но всегда проходимый. #мат_геймдев #МатРазбор #процедурнаяГенерация

🌱 Seed-рандом: один номер — весь мир Minecraft. Ты вводишь число при создании мира и получаешь конкретный ландшафт. Через год вводишь то же число — тот же ландшафт. Это seed. Seed — это просто начальное значение для PRNG. Пока генератор тот же, последовательность всегда идентична. Практический паттерн: отдельный генератор для каждой системы

public class SeededRandom
{
    private System.Random rng;
    public int Seed { get; private set; }

    public SeededRandom(int seed)
    {
        Seed = seed;
        rng = new System.Random(seed);
    }

    public int Next(int min, int max) => rng.Next(min, max);
    public float NextFloat()          => (float)rng.NextDouble();
}

// Каждый регион карты — свой генератор:
var dungeonRng  = new SeededRandom(worldSeed ^ 0xDEAD);
var lootRng     = new SeededRandom(worldSeed ^ 0xBEEF);
var enemyRng    = new SeededRandom(worldSeed ^ 0xCAFE);
XOR с разными константами гарантирует, что генераторы идут по разным дорожкам, но оба воспроизводимы при известном worldSeed. Три применения: → Процедурные карты — один seed = один мир → Реплеи — записываешь только ввод игрока + seed, а не весь стейт → Отладка — нашёл баг? Скажи seed разработчику, он воспроизведёт #мат_геймдев #МатРазбор #рандом

🎲 Откуда берётся «случайность» в компьютере? Спойлер: её нет. Компьютер генерирует числа по формуле — детерминированно, шаг за шагом. Такая последовательность называется псевдослучайной (PRNG). Простейший PRNG — линейный конгруэнтный генератор (LCG):
next = (a * current + c) % m
Три магических константы a, c, m — и каждый вызов выдаёт «новое» число. Пример на пальцах (a=1664525, c=1013904223, m=2^32):

uint state = 42; // начальное значение (сид)

uint NextRandom()
{
    state = 1664525u * state + 1013904223u;
    return state;
}
Вызови три раза — получишь три разных числа. Вызови снова с state=42 — те же три. Почему это важно для геймдева: → Ты контролируешь «случайность» через начальное значение (seed) → Один и тот же seed = один и тот же мир / один и тот же дроп → Это основа воспроизводимых миров, реплеев и читов через save-scumming Unity использует System.Random под капотом — тот же принцип, чуть сложнее. Godot — свой Mersenne Twister. Но идея одна: формула + стартовое число. Существуют ГСЧ, основанные на сборе энтропии — они «более случайные», и главная задача таких генераторов — быть непредсказуемыми. Применяются они там, где взлом или предсказуемость недопустимы: в криптографии, защите данных, онлайн-сервисах. Это область весьма сложных идей и алгоритмов: получить по-настоящему случайное, равновероятно распределённое число от 0 до 1 — задача совсем не тривиальная. Вообще важно понимать природу случайности — это абстрактное понятие, придуманное человеком. Я когда-то задумывался: бросить монетку 50 на 50 — спорное утверждение. Ведь по сути невозможно поставить чистый эксперимент, который бы это достоверно доказал. Так что под случайностью мы скорее понимаем непредсказуемость. И псевдослучайность по примеру выше работает как настоящая случайность ровно до тех пор, пока ты не знаешь, как она устроена. Если тема интересна — расскажу про ТГСЧ и КГПСЧ: генераторы, которые используются в защите данных и криптографии. Ну и хорошими постами делись с друзьями — «нам нужно больше золота подписчиков». #мат_геймдев #МатРазбор #рандом

Отражение векторов - Интерактив https://wreath-violet-94758009.figma.site/ Я пока думаю в каком формате делать всякие интерактивные штуки чтобы было проще воспринимать формулы и код. Гифки там или интерактивные сайты. Для последнего надо бюджет на тачку и домен для этого дела выделить, а пока не до того) Экспериментально из любопытства собрал в ИИ фигмы интерактив объясняющий пост выше. Как решение задачи стоящей не совсем подходит, но в целом инструмент забавный кстати говоря. #мат_геймдев #МатРазбор #физика #векторы

📊 Вектор отражения: как математика делает отскок Мяч летит в стену. Как посчитать направление после отскока? Интуиция подсказывает: «отразить угол». Но угол — это арктангенс, градусы, неудобно. Есть формула, которая работает через векторы: R = V - 2 * (V · N) * N Где: V — вектор направления до удара N — нормаль поверхности (перпендикуляр к стене, единичный вектор) R — вектор направления после отскока

Vector2 Reflect(Vector2 velocity, Vector2 normal)
{
    // normal должен быть нормализован
    float dot = Vector2.Dot(velocity, normal);
    return velocity - 2f * dot * normal;
}

// Примеры нормалей:
// Пол (горизонтальная поверхность) → normal = Vector2.up    (0, 1)
// Стена справа                     → normal = Vector2.left  (-1, 0)
// Наклонная платформа 45°          → normal = new Vector2(-0.707f, 0.707f)

// Использование:
ballVelocity = Reflect(ballVelocity, wallNormal);
В Unity это уже встроено:

ballVelocity = Vector2.Reflect(ballVelocity, wallNormal);
Но понимать формулу важно — потому что Reflect работает для любого угла. Не только для горизонтальных/вертикальных стен, но и для наклонных поверхностей, движущихся платформ, рикошетов. Откуда формула? V · N — это «насколько вектор V направлен вдоль N». 2 * (V · N) * N — это та часть V, которую нужно «отзеркалить». Вычитаем её из V дважды — получаем отражение. #мат_геймдев #МатРазбор #физика #векторы

📊 Circle-коллайдер: почему он лучше AABB для снарядов AABB — быстрый, но у него слабое место: он прямоугольный. Пуля, мяч, персонаж в top-down — у них нет углов. AABB даёт ложные столкновения в углах, которых визуально нет. Circle решает это элегантно. Проверка столкновения двух окружностей — одно вычитание и одно сравнение:

bool CirclesOverlap(Vector2 posA, float rA, Vector2 posB, float rB)
{
    float dx = posA.x - posB.x;
    float dy = posA.y - posB.y;
    float distSq = dx * dx + dy * dy;       // квадрат расстояния
    float radiusSum = rA + rB;
    return distSq < radiusSum * radiusSum;   // без корня — быстрее
}
Никакой тригонометрии. Корень не нужен — сравниваем квадраты. Когда выбирать Circle: • снаряды (пули, фаерболы) — форма симметрична • мячи, монеты, бонусы — очевидно • персонажи в top-down — нет чёткой «передней» стороны Когда выбирать AABB: • платформы, стены, тайлы — прямоугольные по природе • хитбоксы, которые должны «прижиматься» к форме спрайта На практике движки используют их вместе: AABB — быстрая первичная проверка (broad phase), Circle или Polygon — точная (narrow phase). #мат_геймдев #МатРазбор #физика #коллайдеры

🤔 Загадка: почему быстрый персонаж проваливается сквозь пол? Коллайдеры настроены правильно. На низкой скорости всё работает. Но разогнать персонажа — и он прошивает платформу насквозь. Пауза — подумай секунду. ... Ответ: Tunneling (туннелирование). Физика в играх дискретная: каждый кадр объект перемещается на velocity * deltaTime. Если объект за один кадр перемещается дальше, чем толщина коллайдера — движок просто не замечает пересечения. Объект был над полом, стал под ним. Столкновения не было. Три решения: 1. Continuous collision detection (CCD) — движок проверяет весь путь, не точку. Дорого по производительности. 2. Ограничить максимальную скорость так, чтобы за кадр объект не мог пролететь сквозь коллайдер. 3. Raycast в направлении движения перед шагом — рукопашная CCD. Знал о туннелировании раньше? 👇 #мат_геймдев #физика #коллайдеры #ОшибкаНедели

📊 AABB — прямоугольник против прямоугольника без тригонометрии AABB (Axis-Aligned Bounding Box) — прямоугольник, стороны которого параллельны осям координат. Никакого поворота. Проверка столкновения двух AABB — четыре сравнения чисел:

struct AABB
{
    public float minX, maxX, minY, maxY;
}

bool Overlaps(AABB a, AABB b)
{
    return a.maxX > b.minX &&  // a не левее b
           a.minX < b.maxX &&  // a не правее b
           a.maxY > b.minY &&  // a не ниже b
           a.minY < b.maxY;    // a не выше b
}
Никакого корня квадратного, никаких синусов. Четыре сравнения. Это причина, по которой большинство игровых движков используют AABB для первичной проверки (broad phase) даже у сложных объектов. Минус: не работает если объект повёрнут. Для поворота — OBB или SAT. Но для большинства платформеров и top-down игр AABB хватает с головой. #мат_геймдев #МатРазбор #физика #коллайдеры

👾 Физика столкновений: зачем разбираться, если движок всё делает сам? Коллайдеры Unity или Godot — это удобно. Ставишь Box Collider, игра работает. Но рано или поздно случается одно из двух: • Персонаж проваливается сквозь пол на высокой скорости • Хитбоксы не совпадают с визуалом и игроки злятся • Нужна кастомная физика — платформер кастомной физикой top-down с трением, пинбол-механика У меня было такое, что мне это пригодилось для кастомных рейкастов в VR, когда нужно было переключаться по тысячам вершин графа которые движутся в пространстве. Тогда понимание математики за коллайдерами — не академизм, а рабочий инструмент. На этой неделе разбираем: — AABB: самый быстрый прямоугольный коллайдер — Circle: идеален для снарядов и мячей — Вектор отражения: отскок без магии #мат_геймдев #МатРазбор #физика

📋 Итог блока «Рандом» — что мы разобрали и где это использовать За прошлую неделю — три концепции, которые покрывают основные задачи с дропом предметов в геймдеве: Weighted Random Простой и читаемый. Выбор по умолчанию для лутбоксов, таблиц дропа, спавна мобов. Alias Method O(1) после предподготовки. Когда вызовов тысячи в секунду. Binary Search + кеш Компромисс. Быстрее обычного, проще Alias. На следующей неделе открываем новую тему — Физика столкновений. Поехали 👇 #мат_геймдев #ДайджестМесяца #рандом

🚨 Ошибка недели: почему твой weighted random врёт Классическая ловушка — веса заданы через float и суммируются с погрешностью:

float[] weights = { 0.1f, 0.3f, 0.6f }; // кажется, сумма = 1.0
// На самом деле: 0.99999994f из-за float-арифметики
Если ты делишь на сумму для нормализации — погрешность накапливается. Последний элемент может никогда не выпасть или выпадать чуть реже. Два способа починить: 1. Используй int-веса — целые числа не теряют точность:

int[] weights = { 60, 30, 10 }; // вместо 0.6f, 0.3f, 0.1f
2. Последний элемент — всегда fallback, а не результат сравнения:

for (int i = 0; i < weights.Length; i++)
    if (roll < cumulative[i]) return i;
return weights.Length - 1; // если погрешность съела край — возвращаем последний
Маленькая деталь — но именно такие баги живут в проде годами и порой приводят к непредсказуемым последствиям. #мат_геймдев #ОшибкаНедели #рандом

Да, я только понял, что забыл комменты привязать XD В следующих постах будет)

🎮 Weighted Random в играх, которые ты знаешь Эта механика везде — просто под разными названиями: 🃏 Gacha-игры (Genshin Impact, Honkai) Ставки в 0.6% на 5★ — это weighted random с очень маленьким весом редкого предмета. Плюс «pity system» — счётчик, который повышает вес при неудаче. ⚔️ Diablo / Path of Exile Аффиксы предметов тянутся из пула с разными весами. Редкий аффикс просто имеет вес 1 против 1000 у обычного. 🌍 Minecraft Структуры биомов, дроп мобов, содержимое сундуков — всё таблицы весов. В коде это буквально называется loot tables. 🎰 Pokémon Скрытые способности (Hidden Ability) — 1 шанс из 150. Шансы на природу — равные, но можно изменить через синхронизацию. Суть одна — ты уже знаешь как это работает под капотом. #мат_геймдев #геймдизайн #рандом #МатРазбор