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

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

Kanalga Telegram’da o‘tish

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

Ko'proq ko'rsatish
443
Obunachilar
Ma'lumot yo'q24 soatlar
+177 kunlar
+9130 kunlar
Postlar arxiv
Брейк-радары в процедурной генерации — выбрасываем плохой вариант раньше, чем запустить BFS Сталкивались с тем, что процедурн
Брейк-радары в процедурной генерации — выбрасываем плохой вариант раньше, чем запустить BFS Сталкивались с тем, что процедурный генератор карт подвисает на пару секунд прямо посреди загрузки? Вроде всё логично: набросали вариант, прогнали 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) по счётчикам, потом что-то дешёвое по периметру, и только потом полный обход. Моё решение тут точно не единственное, у каждой задачи свои инварианты, которые можно поймать заранее. Но принцип один: чем раньше выбросили плохой вариант — тем быстрее нашли хороший. Буду рад обсудить в комментариях, какие радары используете вы. #мат_геймдев #КодПодСкопой #процедурнаягенерация

Кватернионы через зеркала: как одно геометрическое наблюдение убивает магию формулы q·v·q⁻¹ https://dev-math.ru/articles/rota
Кватернионы через зеркала: как одно геометрическое наблюдение убивает магию формулы 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 — каждое со своими плюсами и минусами, без объявления любимчика заранее. #мат_геймдев #МатРазбор #кватернионы #графика

📡 Raycast, SphereCast, BoxCast — собственно, какой кастер выбрать? Сталкивались с тем, что выпущенный снаряд пролетает сквозь угол колонны, а персонаж намертво застревает в дверном проёме шириной с него самого? Корень обеих бед — неправильно выбранный кастер. Давайте разберём, чем 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% случаев работает. #мат_геймдев #КодПодСкопой #физика

🕳️ Sweep test и CCD: когда объект «проваливается» сквозь стену Снаряд летит быстро. Кадр 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. Включай только там, где нужно. #мат_геймдев #МатРазбор #физика #туннелирование

❓ Загадка недели Ты написал платформер. Нажимаешь стрелку — персонаж идёт. Отпускаешь — он продолжает скользить. Код выглядит вот так:

velocity.x += Input.GetAxis("Horizontal") * speed;
transform.position += velocity * Time.deltaTime;
Почему персонаж не останавливается мгновенно? Ответ: 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); ``` #мат_геймдев #МатРазбор #физика

Сделал викторину по математике геймдева https://dev-math.ru/quiz/ Давно хотел собрать в одном месте те вещи, которые постоянно всплывают на собесах и в рабочих задачах. Начал с короткого списка для себя — и затянуло. Да и захотелось прикольно сделать :) Если коротко — вопросы про то, что реально ломается в проде: почему персонаж дёргается на больших координатах, когда Lerp превращается в «телепорт», зачем кватернионы, если есть углы Эйлера, и десятки подобных вещей. Если найдёте косяки — кидайте в комменты, поправлю. Ставим 🔥 и делитесь с друзьями, если зашло. Интересно кто куда пройдет. Постарался всё сделать кайфово, в ностальгической эстетике. #мат_геймдев #викторина

Драматургия через математику: как одна формула превращает генератор карт в дизайнера уровней https://habr.com/ru/articles/1026506/ Вы просили что-то посложнее. Ну держитесь 🙂 Страшные слова, формулы, интерактивное демо. Я заморочился. Так что жду огоньков и репостов друзьям😆 Классический алгоритм Wave Function Collapse создаёт технически валидные, но драматургически «мёртвые» карты, потому что не различает пространство: для него вход, босс-арена и выход равнозначны. Я предлагаю добавить в формулу выбора клетки семантический множитель f(W(x,y)), где W — поле весов, задающее важность каждой зоны: ключевые точки коллапсируют первыми и через propagation определяют структуру всей карты. Разбив поле W на слои (опасность, награда, направление к выходу, нарратив) и добавив модуляцию вероятности тайлов в зависимости от позиции, дизайнер получает инструмент, где задуманная идея уровня воспроизводится в каждом прогоне, а конкретная геометрия остаётся уникальной. #мат_геймдев #МатРазбор #алгоритмы

🎲 Демо к статье: WFC + Entropy Bias Рисуете тепловую карту — получаете уровень. - W_d — плотность врагов - W_r — распределение лута - W_e — точки входа/выхода Алгоритм решает CSP с весами из вашей карты. Коллапс волновой функции смещается энтропийным биасом под заданные зоны. 👉 Открыть мини-апп по кнопке 🖥 С ПК в фулскрине удобнее.

🚀 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 дважды — получаем отражение. #мат_геймдев #МатРазбор #физика #векторы