Математика геймплея
Векторная математика и математика поворотов для геймплея в Unreal Engine — скалярное и векторное произведения, трансформы, мировое и локальное пространство, интерполяция, line trace и кватернионы.
15 вопросов
JuniorТеорияОчень частоДля чего используется скалярное произведение в геймплейном программировании?
Для чего используется скалярное произведение в геймплейном программировании?
Скалярное произведение двух нормализованных векторов даёт косинус угла между ними. В геймплее его используют, чтобы проверить, находится ли цель спереди (Dot > 0), внутри конуса обзора, или для проекции одного вектора на другой.
Типичные ошибки
- ✗Забыть нормализовать входные векторы — тогда результат не косинус, и из проверок остаётся только знак
- ✗Путать скалярное произведение (скаляр) с векторным произведением (вектор)
- ✗Принимать результат скалярного произведения за угол в градусах вместо косинуса
Уточняющие вопросы
- →Как преобразовать результат скалярного произведения в угол в градусах?
- →Почему знак скалярного произведения работает даже для ненормализованных векторов?
JuniorКодОчень частоКак выполнить line trace в C++ в Unreal Engine?
Как выполнить line trace в C++ в Unreal Engine?
Вызовите GetWorld()->LineTraceSingleByChannel(Hit, Start, End, Channel, Params). Он записывает первое блокирующее попадание в FHitResult и возвращает true, если что-то задето. Start и End — точки в мировом пространстве.
Типичные ошибки
- ✗Передавать вектор направления вместо абсолютной мировой точки
End - ✗Забыть добавить трассирующего актёра в
FCollisionQueryParamsи попасть в себя - ✗Читать поля
FHitResult, не проверив сначала булев результат вызова
Уточняющие вопросы
- →Как трассировать по нескольким объектам, а не останавливаться на первом блокере?
- →В чём разница между трассировкой по каналу и по типу объекта?
JuniorТеорияОчень частоВ чём разница между мировым и локальным пространством в Unreal?
В чём разница между мировым и локальным пространством в Unreal?
Мировое пространство — глобальная система координат, общая для всего уровня. Локальное пространство задаётся относительно трансформа родителя. Unreal переводит между ними через TransformPosition / InverseTransformPosition у FTransform.
Типичные ошибки
- ✗Сравнивать локальное смещение с мировыми позициями без предварительного преобразования
- ✗Считать, что локальная позиция компонента равна мировой без родителя — верно лишь для корня
- ✗Думать, что пространства различаются только единицами, а не началом координат и ориентацией
Уточняющие вопросы
- →Как преобразовать направление (а не позицию) между локальным и мировым пространством?
- →Что представляет относительный трансформ актёра в сравнении с мировым трансформом?
JuniorТеорияЧастоДля чего используется векторное произведение в геймплейном программировании?
Для чего используется векторное произведение в геймплейном программировании?
Векторное произведение двух векторов возвращает третий вектор, перпендикулярный обоим. В геймплее его используют для построения нормалей поверхности, получения правого вектора из forward и up, и чтобы понять, цель слева или справа.
Типичные ошибки
- ✗Путать векторное произведение (возвращает вектор) со скалярным (возвращает скаляр)
- ✗Считать векторное произведение коммутативным — перестановка операндов разворачивает результат
- ✗Ждать перпендикуляр при параллельных входных векторах — получится нулевой вектор
Уточняющие вопросы
- →Почему векторное произведение даёт нулевой вектор при параллельных входах?
- →Как по знаку компоненты векторного произведения определить лево или право?
JuniorТеорияЧастоКак вычислить направление от одного актёра к другому в Unreal?
Как вычислить направление от одного актёра к другому в Unreal?
Вычтите позицию источника из позиции цели и нормализуйте: (Target->GetActorLocation() - Source->GetActorLocation()).GetSafeNormal(). Результат — единичный FVector, направленный от источника к цели.
Типичные ошибки
- ✗Вычитание в неверном порядке —
Source - Targetуказывает от цели - ✗Забыть нормализовать, и длина вектора протекает расстоянием в дальнейшие вычисления
- ✗Использовать
Normalize()вместоGetSafeNormal()и упасть на почти нулевом векторе
Уточняющие вопросы
- →Почему
GetSafeNormal()предпочтительнееNormalize()для вектора направления? - →Как получить расстояние и направление за один проход?
JuniorКодЧастоКак двигать объект с постоянной скоростью из точки A в точку B?
Как двигать объект с постоянной скоростью из точки A в точку B?
Вычислите нормализованное направление (B - A).GetSafeNormal(), затем каждый кадр добавляйте к позиции Direction * Speed * DeltaTime. Умножение на DeltaTime делает скорость независимой от FPS; остановитесь, достигнув цели.
Типичные ошибки
- ✗Забыть
* DeltaTime, из-за чего скорость движения зависит от частоты кадров - ✗Использовать ненормализованное направление — скорость зависит от расстояния до B
- ✗Проскочить B, потому что шаг последнего кадра не ограничен оставшимся расстоянием
Уточняющие вопросы
- →Как обнаружить и точно встать на B в кадре, который иначе бы проскочил?
- →Чем движение с постоянной скоростью отличается от
VInterpToк B?
JuniorТеорияЧастоЧто такое Transform в Unreal Engine и из чего он состоит?
Что такое Transform в Unreal Engine и из чего он состоит?
FTransform объединяет три компонента: позицию (FVector), поворот (FQuat) и масштаб (FVector). Он описывает положение объекта в пространстве и умеет переводить точки между локальным и мировым пространством.
Типичные ошибки
- ✗Забывать, что трансформ включает масштаб, а не только позицию и поворот
- ✗Думать, что
FTransformхранит поворот углами Эйлера — внутри этоFQuat - ✗Композиция трансформов в неверном порядке — умножение трансформов не коммутативно
Уточняющие вопросы
- →Как объединить относительный трансформ ребёнка с мировым трансформом родителя?
- →Почему
FTransformхранит поворот как кватернион, а не какFRotator?
MiddleКодЧастоКак реализовать конус обзора врага с помощью скалярного произведения?
Как реализовать конус обзора врага с помощью скалярного произведения?
Нормализуйте forward врага и направление к цели, возьмите их скалярное произведение и сравните с cos(HalfAngle). Если Dot >= cos(HalfAngle), цель внутри конуса; затем добавьте line trace для проверки прямой видимости.
Типичные ошибки
- ✗Сравнивать скалярное произведение с углом в градусах вместо его косинуса
- ✗Забыть нормализовать оба вектора, из-за чего скалярное произведение не чистый косинус
- ✗Пропустить line trace, из-за чего враг «видит» цель сквозь стену
Уточняющие вопросы
- →Как добавить максимальную дистанцию обзора к этой проверке?
- →Почему после проверки конуса нужен трейс на прямую видимость?
MiddleТеорияЧастоЧем отличаются Lerp, InterpTo и движение через физику при перемещении объекта?
Чем отличаются Lerp, InterpTo и движение через физику при перемещении объекта?
Lerp смешивает по альфе 0–1, которой вы управляете. FMath::VInterpTo плавно движется к цели с заданной скоростью каждый кадр, независимо от FPS. Физическое движение прикладывает силы или скорость, и симуляция сама разрешает столкновения.
Типичные ошибки
- ✗Вызывать
Lerpс фиксированной альфой каждый кадр, ожидая сглаживания, не зависящего от FPS - ✗Забыть передать
DeltaTimeвVInterpTo, из-за чего скорость зависит от частоты кадров - ✗Двигать физически симулируемое тело через
SetActorLocationи бороться с симуляцией
Уточняющие вопросы
- →Как сделать движение на основе
Lerpнезависимым от частоты кадров? - →Когда движение через физику предпочтительнее
VInterpToдля персонажа?
MiddleКодЧастоКак найти объект под курсором мыши или в центре экрана?
Как найти объект под курсором мыши или в центре экрана?
Преобразуйте экранную точку в мировой луч через APlayerController::DeprojectScreenPositionToWorld, затем сделайте line trace вдоль него. Для курсора GetHitResultUnderCursor делает оба шага за один вызов и заполняет FHitResult.
Типичные ошибки
- ✗Принимать экранные пиксельные координаты за мировые точки без депроекции
- ✗Забыть, что для
GetHitResultUnderCursorнужен включённыйbShowMouseCursor - ✗Путать deproject (экран → мировой луч) и project (мир → экран)
Уточняющие вопросы
- →Как трассировать из точного центра экрана, а не из-под курсора?
- →Почему депроецированному лучу нужны и начало, и направление, а не только точка?
MiddleТеорияЧастоЧем кватернионы помогают с поворотами по сравнению с углами Эйлера?
Чем кватернионы помогают с поворотами по сравнению с углами Эйлера?
FQuat представляет поворот без gimbal lock и корректно компонуется умножением. Он поддерживает плавную интерполяцию по кратчайшему пути через Slerp. Unreal хранит повороты как FQuat внутри, а FRotator отдаёт для читаемости.
Типичные ошибки
- ✗Считать, что кватернионы страдают от gimbal lock — это проблема углов Эйлера
- ✗Считать умножение кватернионов коммутативным — порядок поворотов всё равно важен
- ✗Линейно интерполировать компоненты
FRotator, ожидая плавный поворот по кратчайшему пути
Уточняющие вопросы
- →В чём разница между
SlerpиLerpдля кватернионов? - →Почему Unreal всё равно отдаёт
FRotator, если хранит повороты какFQuat?
MiddleКодЧастоКак плавно поворачивать актёра к движущейся цели каждый кадр?
Как плавно поворачивать актёра к движущейся цели каждый кадр?
Постройте желаемый поворот через FRotationMatrix::MakeFromX(Direction).Rotator() или (Target - Self).Rotation(), затем плавно идите к нему через FMath::RInterpTo(Current, Desired, DeltaTime, Speed) и примените SetActorRotation.
Типичные ошибки
- ✗Резко ставить
SetActorRotationна поворот цели, полностью пропуская интерполяцию - ✗Забыть передать
DeltaTimeвRInterpTo, из-за чего скорость поворота зависит от FPS - ✗Линейно лерпить компоненты
FRotatorи поворачивать длинным путём после 180 градусов
Уточняющие вопросы
- →Как ограничить поворот, чтобы актёр менял только yaw и никогда pitch?
- →Почему
RInterpToпредпочтительнее линейной интерполяции компонентFRotator?
SeniorТеорияИногдаПочему наивный RInterpTo проскакивает цель при низком FPS и как сделать сглаживание независимым от FPS?
Почему наивный RInterpTo проскакивает цель при низком FPS и как сделать сглаживание независимым от FPS?
Линейный шаг RInterpTo равный Speed * DeltaTime устойчив, лишь пока он меньше 1; при низком FPS большой DeltaTime делает шаг больше оставшейся дистанции, поэтому цель проскакивается и идут колебания. Решение — экспоненциальное сглаживание: смешивать с коэффициентом 1 - exp(-Speed * DeltaTime), который остаётся в диапазоне 0..1 при любом шаге.
Типичные ошибки
- ✗Считать, что одной передачи
DeltaTimeдостаточно для независимости от FPS — линейный шаг всё равно расходится на больших дельтах - ✗Принимать проскок за баг заворачивания поворота, а не за неустойчивость явного шага Эйлера
- ✗Настраивать
Speedтак, чтобы выглядело верно на 60 FPS, и затем видеть колебания только на медленной машине
Уточняющие вопросы
- →Почему экспоненциальная форма
1 - exp(-Speed * DeltaTime)остаётся ограниченной при любомDeltaTime? - →Как разбиение на под-шаги с фиксированным тиком внутри одного кадра решает ту же неустойчивость?
SeniorДизайнИногдаВ очень большом открытом мире трассировки лучей и расстановка объектов становятся заметно нестабильными — точки попадания и позиции дрожат и прыгают, — стоит действию уйти достаточно далеко от начала координат, тогда как у самого начала всё незыблемо. Объясните, почему удаление от начала координат так ухудшает точность позиций, и опишите стратегии, которыми точность держат пригодной в мире намного большем, чем комфортно вмещает одно координатное пространство.
В очень большом открытом мире трассировки лучей и расстановка объектов становятся заметно нестабильными — точки попадания и позиции дрожат и прыгают, — стоит действию уйти достаточно далеко от начала координат, тогда как у самого начала всё незыблемо. Объясните, почему удаление от начала координат так ухудшает точность позиций, и опишите стратегии, которыми точность держат пригодной в мире намного большем, чем комфортно вмещает одно координатное пространство.
32-битный float имеет около 7 значащих цифр, поэтому вдали от начала координат шаг между значениями вырастает до сантиметров, и концы трассировок прилипают к этой грубой сетке, давая дрожание. Решения: смещать начало координат к игроку, делить мир на тайлы или использовать 64-битные координаты.
Типичные ошибки
- ✗Списывать дрожание на баг физики или столкновений, а не на рост шага float с расстоянием
- ✗Считать 64-битный мир бесплатным — Large World Coordinates стоит пропускной способности памяти и требует кода, учитывающего точность
- ✗Сместить начало координат, но забыть сдвинуть кэшированные мировые векторы, сетевые позиции или данные частиц
Уточняющие вопросы
- →Что ломается в геймплейном коде, когда начало координат смещается посреди сессии?
- →Как потоковая загрузка тайлов с локальными координатами на тайл полностью обходит проблему точности?
SeniorТеорияРедкоПочему nlerp отклоняется от постоянной угловой скорости и когда он всё равно лучше Slerp?
Почему nlerp отклоняется от постоянной угловой скорости и когда он всё равно лучше Slerp?
Nlerp линейно интерполирует четыре компоненты и нормализует — путь остаётся на единичной сфере, но скорость поворота неравномерна, максимальна у середины. Slerp держит постоянную угловую скорость, но стоит sin/acos. Nlerp всё равно выигрывает на малых углах и при скиннинге множества костей, где ошибка скорости незаметна.
Типичные ошибки
- ✗Думать, что nlerp оставляет результат вне единичной сферы — нормализация чинит длину; ошибка в скорости, а не в модуле
- ✗Считать, что nlerp и
Slerpидут по разным дугам — путь у них один и тот же кратчайший, отличается лишь темп вдоль него - ✗Применять
Slerpвезде ради корректности, не понимая, что ошибка скорости nlerp незаметна на малых покадровых дельтах
Уточняющие вопросы
- →Как растёт ошибка скорости nlerp по мере увеличения угла между двумя кватернионами?
- →Почему перед nlerp или Slerp нужно проверять знак скалярного произведения?