Векторна математика

Вступ

Ця стаття — це короткий вступ до лінійної алгебри та опис способів її використання в розробці ігор. Лінійна алгебра — це наука про вектори та їх використання. Використовувати їх можна як в 2D, так і в 3D й Godot робить це дуже часто. Для того, щоб бути добрим програмістом, розуміти векторну математику просто необхідно.

Примітка

Ця стаття — це не підручник з лінійної алгебри. Ми лише розглянемо, як їх можна використовувати в прив'язці до розробки ігор. Для ширшого розуміння математики, погляньте на https://www.khanacademy.org/math/linear-algebra

Система координат (2D)

У двовимірному просторі, координати описуються за допомогою горизонтальної (x) та вертикальної (y) осей. Певна точка в просторі описується як пара чисел. Наприклад: (4,3).

../../_images/vector_axis1.png

Примітка

Якщо ви раніше не працювали з комп'ютерною графікою, то вам може здатись дивним, що вісь y напрямлена вниз а не вгору, як зазвичай, в підручниках з математики. Проте, такий підхід поширений серед більшости графічних комп'ютерних програм.

Будь-яка точка на площині може бути визначена як пара чисел у такий спосіб. А ще, ми можемо уявляти точку (4,3) як зміщення від точки (0,0), або початкової точки. Намалюймо стрілку, яка тягнеться від початкової точки до нашої:

../../_images/vector_xy1.png

Це вектор. Вектор представляє багато корисної інформації. Крім того, що точка знаходиться в (4, 3), ми також можемо думати про це як про кут θ (тета) і довжину (або величину) m. У цьому випадку стрілка є вектором позиції - вона позначає положення в просторі відносно початку координат.

Варто зазначити, що вектор описує лише відносний напрямок та відносне зміщення. Вектори не мають такої штуки, як положення. Дві стрілки на зображенні нижче — це той самий вектор:

../../_images/vector_xy2.png

Обидва вектори позначають точку яка лежить на 4 одиниці вправо та на 3 одиниці вниз від деякої початкової точки. Не важливо де ви намалюєте вектор, він завжди буде позначати відносний напрямок та зміщення.

Дії над векторами

Ви можете описувати вектор як пару координат x та y, або як пару з кута й модуля. Але, зазвичай, програмісти використовують координатну форму. Наприклад, в Godot, початок координат знаходиться в верхньому лівому куті екрану. А тому, щоб розмістити 2D вузол під назвою Node2D на 400 пікселів правіше й на 300 пікселів нижче, потрібно використати такий код:

$Node2D.position = 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

Додавання векторів

Коли додаються або віднімаються два вектори, додаються і їхні відповідні компоненти:

var c = a + b  # (2, 5) + (3, 1) = (5, 6)

Суму векторів можна зобразити додавши один вектор, до кінця іншого:

../../_images/vector_add1.png

Сума a + b дає такий же результат як і b + a.

Множення на скаляр

Примітка

Вектори представляють як напрямок, так і величину. Значення, яке представляє лише величину, називається скаляром. Скаляри використовують тип float у Godot.

Вектор можна помножити на скаляр:

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)
../../_images/vector_mult1.png

Примітка

Множення вектора на додатний скаляр не змінює його напрямок, змінює лише його величину. Множення на від’ємний скаляр призводить до вектора в протилежному напрямку. Ось як ви масштабуєте вектор.

Практичне застосування

Погляньмо на два найпоширеніші способи використання суми та різниці векторів.

Пересування

Вектор може представляти будь-яку величину з величиною та напрямком. Типовими прикладами є: положення, швидкість, прискорення та сила. На цьому зображенні космічний корабель на кроці 1 має вектор положення «(1, 3)» і вектор швидкості «(2, 1)». Вектор швидкості показує, на яку відстань рухається судно за кожен крок. Ми можемо знайти позицію для кроку 2, додавши швидкість до поточної позиції.

../../_images/vector_movement1.png

Порада

Швидкість вимірює зміну положення за одиницю часу. Нове положення визначається шляхом додавання швидкості, помноженої на час, що минув (тут приймається за одну одиницю, наприклад, 1 с) до попереднього положення.

У типовому сценарії двовимірної гри ви матимете швидкість у пікселях на секунду та помножите її на параметр дельта (час, що минув з попереднього кадру) з _process() або _physics_process() зворотні виклики.

Указування на ціль

В цій ситуації, ви маєте танка, який хоче направити свою гармату на робота. Різниця положень танка й робота дасть нам вектор що вказує від танка, до робота.

../../_images/vector_subtract2.webp

Порада

Щоб знайти вектор, що вказує від A до B, використовуйте B - A.

Одиничні вектори

Вектор з довжиною 1 називається одиничним вектором. Їх ще іноді називають напрямними векторами чи нормалями. Одиничні вектори корисні, коли необхідно знати про напрямок без довжини.

Нормалізація

Нормалізація вектора означає зменшення його довжини до 1 зі збереженням його напрямку. Це робиться шляхом ділення кожного з його компонентів на його величину. Оскільки це така поширена операція, Godot надає для цього спеціальний метод normalized():

a = a.normalized()

Попередження

Оскільки нормалізація передбачає ділення на довжину вектора, ви не можете нормалізувати вектор довжини 0. Спроба зробити це зазвичай призведе до помилки. Однак у GDScript спроба викликати метод normalized() для вектора довжини 0 залишає значення недоторканим і уникає помилки.

Відбиття

Вектори часто використовуються для того, щоб позначати нормалі. Вектор нормалі — це одиничний вектор, спрямований перпендикулярно до якоїсь поверхні. Він вказує на сторону, в яку повернута ця поверхня. Нормалі використовуються при обчисленні освітлення, зіткнень та інших операцій, зв'язаних із поверхнями.

Наприклад, уявімо, що ми маємо кулю, що рухається і ми хочемо, щоб вона відбилася від стіни чи іншого об'єкта:

../../_images/vector_reflect1.png

Нормаль поверхні має значення (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)

Скалярний добуток

Скалярний добуток — це одне з найважливіших понять у векторній математиці. Проте, його часто не розуміють. Скалярний добуток — це операція, яка бере два вектори, та повертає скаляр. На відміну від векторів, які мають модуль та напрямок, скаляр — це просто число, яке має лише модуль.

Є дві формули для обчислення скалярного добутку:

../../_images/vector_dot1.png

і

../../_images/vector_dot2.png

Математична нотація ||A|| представляє величину вектора A, а Ax означає x компонент вектора A.

Однак у більшості випадків найпростіше використовувати вбудований метод dot(). Зверніть увагу, що порядок двох векторів не має значення:

var c = a.dot(b)
var d = b.dot(a)  # These are equivalent.

Скалярний добуток найбільш корисний, коли використовується з одиничними векторами, завдяки чому перша формула зводиться лише до cos(θ). Це означає, що ми можемо використовувати скалярний добуток, щоб сказати нам щось про кут між двома векторами:

../../_images/vector_dot3.png

Якщо множаться одиничні вектори, то результат буде в межах від -1 (180°) до 1 (0°).

Напрямок

Ми можемо використати це для того, щоб дізнатись, чи дивиться один об'єкт на іншого. На схемі нижче, гравець p намагається уникати зомбі A і B. Припустімо, поле зору зомбі має кут 180°, чи можуть вони побачити гравця?

../../_images/vector_facing2.png

Зелені стрілки 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!")

Векторний добуток

Як і скалярний добуток, векторний добуток — це операція над двома векторами. Але результат векторного добутку — це ще один вектор, який направлений перпендикулярно до двох інших. Його довжина залежить від кута між ними. Якщо вектори паралельні — то результатом векторного добутку буде нульовий вектор.

../../_images/vector_cross1.png ../../_images/vector_cross2.png

Ось так обчислюється векторний добуток:

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)

З Godot ви можете використовувати вбудований метод Vector3.cross():

var c = a.cross(b)

Перехресний добуток не є математично визначеним у 2D. Метод Vector2.cross() є широко використовуваним аналогом 3D перехресного добутку для 2D векторів.

Примітка

Для векторного добутку важливий порядок: 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

Указування на ціль

У розділі про скалярний добуток ми побачили, як він може бути використаний для знаходження кута між векторами. Цього не достатньо для повороту в 3D. Нам також потрібно знати навколо якої осі обертатись. Ми можемо знайти її знайшовши векторний добуток між вектором який дивиться уперед і тим, що направлений на ціль. Отриманий перпендикуляр і буде віссю обертання.

Додаткова інформація

Дізнатись більше про використання векторів у Godot можна в наступних статтях: