Векторная математика
Введение
Этот урок - короткое и практичное введение в линейную алгебру, применяемую в разработке игр. Линейная алгебра изучает векторы и их использование. Векторы могут применяться в 2D и 3D разработке и Godot использует их интенсивно. Разработчику игр требуется хорошее понимание векторной алгебры чтобы стать сильным в этой области.
Примечание
Этот урок — не учебник по линейной алгебре. Мы рассмотрим только то, что применяется в разработке игр. Для более широкого взгляда на математику, смотрите https://www.khanacademy.org/math/linear-algebra
Системы координат (2D)
В 2D пространстве, координаты определены использованием горизонтальной оси (x) и вертикальной оси (y). Определённая позиция в 2D пространстве записывается как пара значений, таких как (4, 3).
Примечание
Если вы новичок в компьютерной графике, вам может показаться странным, что положительная ось y указывает вниз, а не вверх, как вас учили в школе. Тем не менее, в приложениях компьютерной графики это встречается повсеместно.
Любая точка на 2D плоскости может быть таким образом определена парой чисел. Вы также можете рассматривать (4, 3) как смещение от точки (0, 0), или точки начала координат. Нарисуем стрелку от начала координат до заданной точки:
Это вектор. Вектор предоставляет множество полезной информации. Помимо сообщения нам, что точка расположена в (4, 3), мы можем также представить это в виде угла θ и длины (величины, модуля) m. В данном примере стрелка — это вектор позиции — он обозначает позицию в пространстве относительно начала координат.
При рассмотрении векторов крайне важно иметь в виду, что они представляют только относительные направление и величину (модуль). Вектору нельзя приписать определённой позиции. Два следующих вектора идентичны:
Оба вектора представляют точку 4 единицами правее и 3 единицами ниже стартовой точки. Неважно, где на плоскости вы нарисуете вектор, он всегда представляет относительное направление и величину.
Операции над векторами
Вы можете применять любой из методов (задание координат x и y или угла с длиной) для определения вектора, но обычно программисты используют координаты. Для примера, в Godot начало координат — это верхний-левый угол экрана, так что для перемещения 2D узла с именем Node2D на 400 пикселей вправо и 300 вниз используйте следующий код:
$Node2D.position = Vector2(400, 300)
var node2D = GetNode<Node2D>("Node2D");
node2D.Position = new Vector2(400, 300);
Godot поддерживает типы Vector2 и Vector3 для 2D и 3D соответственно. Математические правила, рассказанные в этой статье, применяются к обоим типам, и везде, где мы ссылаемся на методы Vector2 в ссылке на класс, вы также можете ознакомиться с их аналогами Vector3.
Доступ к полям
К отдельным компонентам вектора можно обращаться непосредственно по имени.
# Create a vector with coordinates (2, 5).
var a = Vector2(2, 5)
# Create a vector and assign x and y manually.
var b = Vector2()
b.x = 3
b.y = 1
// Create a vector with coordinates (2, 5).
var a = new Vector2(2, 5);
// Create a vector and assign x and y manually.
var b = new Vector2();
b.X = 3;
b.Y = 1;
Сложение векторов
Когда два вектора складываются или вычитаются, складываются соответствующие компоненты:
var c = a + b # (2, 5) + (3, 1) = (5, 6)
var c = a + b; // (2, 5) + (3, 1) = (5, 6)
Мы также можем посмотреть визуально на добавление второго вектора к концу первого:
Отметьте что сложение a + b даёт такой же результат что и b + a.
Скалярное перемножение
Примечание
Векторы представляют как направление, так и величину. Значение, представляющее только величину, называется скаляром. в Godot, скаляры используют тип float.
Вектор может быть умножен на скаляр:
var c = a * 2 # (2, 5) * 2 = (4, 10)
var d = b / 3 # (3, 6) / 3 = (1, 2)
var e = d * -2 # (1, 2) * -2 = (-2, -4)
var c = a * 2; // (2, 5) * 2 = (4, 10)
var d = b / 3; // (3, 6) / 3 = (1, 2)
var e = d * -2; // (1, 2) * -2 = (-2, -4)
Примечание
Умножение вектора на положительный скаляр не изменяет его направление, а только величину. Умножение на отрицательный скаляр приводит к получению вектора в противоположном направлении. Вот как вы масштабируете вектор.
Практические применения
Давайте посмотрим на два обычных приёма использования для векторного сложения и вычитания.
Движение
Вектор может представлять любую величину, имеющую модуль и направление. Типичные примеры: положение (position), скорость (velocity), ускорение (acceleration) и сила (force). На этом изображении космический корабль на шаге 1 имеет вектор положения (1, 3) и вектор скорости (2, 1). Вектор скорости показывает, насколько далеко корабль перемещается на каждом шаге. Мы можем найти положение для шага 2, прибавив скорость к текущему положению.
Совет
Скорость измеряет изменение положения за единицу времени. Новое положение определяется путём прибавления скорости, умноженной на прошедшее время (в данном случае предполагается, что оно равно одной единице, например, 1 s), к предыдущему положению.
В типичных сценариях 2D игр, у вас есть скорость в пикселях в секунду, и вы умножаете её на параметр delta (время, прошедшее с предыдущего кадра) из вызовов _process() или _physics_process().
Направление в сторону цели
В этом примере вы, управляя танком, хотите направить дуло на робота. Вычитание позиции танка из позиции робота даёт вектор, направленный от танка к роботу.
Совет
Чтобы найти вектор, направленный от A к B, используйте B - A.
Единичные векторы
Вектор с длиной, равной 1, называется единичным вектором. Они также иногда называются векторами направления или нормалями. Единичные векторы полезны, когда вам нужно сохранить направление без учёта длины.
Нормализация
Normalizing (Нормализация) вектора означает уменьшение его длины до 1 с сохранением направления. Это достигается делением каждого из его компонентов на его модуль. Поскольку эта операция встречается так часто, Godot предоставляет для этого специальный метод normalized():
a = a.normalized()
a = a.Normalized();
Предупреждение
Поскольку нормализация подразумевает деление на длину вектора, вектор длины 0 нормализовать невозможно. Попытка сделать это обычно приводит к ошибке. Однако в GDScript попытка вызвать метод normalized() для вектора длины 0 оставляет значение неизменным и позволяет избежать ошибки.
Отражение
Обычный пример использования единичных векторов - определение нормалей. Векторы нормалей - это единичные векторы, перпендикулярные к поверхности, определяющей их направление. Обычно они используются в обработке света, столкновений и других операциях с поверхностями.
Например, представьте что вы движете шар, который вы хотите отражать от стен или других объектов:
Нормаль поверхности имеет значение (0, -1), поскольку это горизонтальная поверхность. При столкновении мяча мы берём его оставшееся движение (количество оставшихся после удара о поверхность) и отражаем его с помощью нормали. В Godot для этого есть метод bounce(). Вот пример кода для вышеприведённой диаграммы с использованием CharacterBody2D:
var collision: KinematicCollision2D = move_and_collide(velocity * delta)
if collision:
var reflect = collision.get_remainder().bounce(collision.get_normal())
velocity = velocity.bounce(collision.get_normal())
move_and_collide(reflect)
KinematicCollision2D collision = MoveAndCollide(_velocity * (float)delta);
if (collision != null)
{
var reflect = collision.GetRemainder().Bounce(collision.GetNormal());
_velocity = _velocity.Bounce(collision.GetNormal());
MoveAndCollide(reflect);
}
Скалярное произведение
Результат скалярного произведения очень важный аспект в векторной алгебре, но его также часто плохо понимают. Скалярное произведение это операция над двумя векторами которая возвращает скаляр. В отличии от вектора, который содержит длину и направление, скаляр содержит только длину.
Формула скалярного произведения имеет две распространённых формы:
и
Математическая запись ||A|| представляет величину вектора A, а Ax означает компоненту x вектора A.
Однако в большинстве случаев проще всего использовать встроенный метод dot(). Обратите внимание, что порядок векторов не имеет значения:
var c = a.dot(b)
var d = b.dot(a) # These are equivalent.
float c = a.Dot(b);
float d = b.Dot(a); // These are equivalent.
Скалярное произведение наиболее полезно при использовании с единичными векторами, что позволяет сократить первую формулу до cos(θ). Это означает, что мы можем использовать скалярное произведение, чтобы получить информацию об угле между двумя векторами:
При использовании единичных векторов результат всегда будет находиться между -1 (180°) и 1 (0°).
Направление взгляда
Мы можем использовать этот факт для обнаружения, что объект смотрит в направлении другого объекта. На диаграмме ниже, игрок P пытается избежать взгляда зомби A и B. Могут ли зомби его увидеть если их угол обзора равен 180° ?
Зелёные стрелки fA и fB — это единичные векторы, указывающие направление взгляда зомби, а синий полукруг — его поле зрения. Для зомби A мы находим вектор направления AP, указывающий на игрока, используя P - A, и нормализуем его. Однако в Godot есть вспомогательный метод для этого, называемый direction_to(). Если угол между этим вектором и вектором направления взгляда меньше 90°, то зомби видит игрока.
В коде это бы выглядело как:
var AP = A.direction_to(P)
if AP.dot(fA) > 0:
print("A sees P!")
var AP = A.DirectionTo(P);
if (AP.Dot(fA) > 0)
{
GD.Print("A sees P!");
}
Векторное произведение
Также как и скалярное произведение, векторное произведение это операция над двумя векторами. Но в результате векторного произведения вы получаете вектор с направлением перпендикулярным обоим исходным векторам. Его длина зависит от их относительного угла. Если два вектора параллельны, в результате вы получите нулевой вектор.
Векторное произведение вычисляется так:
var c = Vector3()
c.x = (a.y * b.z) - (a.z * b.y)
c.y = (a.z * b.x) - (a.x * b.z)
c.z = (a.x * b.y) - (a.y * b.x)
var c = new Vector3();
c.X = (a.Y * b.Z) - (a.Z * b.Y);
c.Y = (a.Z * b.X) - (a.X * b.Z);
c.Z = (a.X * b.Y) - (a.Y * b.X);
С Godot вы можете использовать встроенный метод Vector3.cross():
var c = a.cross(b)
var c = a.Cross(b);
Векторное произведение математически не определено в двумерном пространстве. Метод Vector2.cross() — это широко используемый аналог трёхмерного векторного произведения для двумерных векторов.
Примечание
В векторном произведении, порядок аргументов важен. a.cross(b) не даёт такого же результата что и b.cross(a). Получаемые векторы имеют противоположные направления.
Расчитывание нормалей
Одно из распространённых применений векторных произведений — поиск нормали к плоскости или поверхности в трёхмерном пространстве. Если у нас есть треугольник ABC, мы можем использовать вычитание векторов для нахождения двух рёбер AB и AC. Используя векторное произведение, AB × AC получаем вектор, перпендикулярный обоим рёбрам: нормаль к поверхности.
Здесь показана функция для вычисления нормали треугольника:
func get_triangle_normal(a, b, c):
# Find the surface normal given 3 vertices.
var side1 = b - a
var side2 = c - a
var normal = side1.cross(side2)
return normal
Vector3 GetTriangleNormal(Vector3 a, Vector3 b, Vector3 c)
{
// Find the surface normal given 3 vertices.
var side1 = b - a;
var side2 = c - a;
var normal = side1.Cross(side2);
return normal;
}
Направление на цель
В скалярном произведении выше, мы видели как оно может использоваться для нахождения угла между двумя векторами. Однако для 3D этого недостаточно. Мы также должны знать вокруг какой оси нужно осуществлять вращение. Мы можем найти её выполняя векторное произведение текущего направления взгляда и направлением цели. В результате мы получим перпендикулярный вектор оси вращения.
Дополнительная информация
Для большей информации об использовании векторной алгебры в Godot, смотрите следующие статьи: