Линейная алгебра!
в програмировании игр можно и, я считаю, нужно использовать линейную алгебру, это упрощает выполнение кода, и его понимание.
да, тригонометрия работает, но тригонометрические функции медленее, этого незаметно, но если мы будем вызывать 2000 тригонометрических функций, то, я думаю, будет видно снижение производительности.
линейная алгебра, это работа с векторами, матрицами и другим, правда я расскажу здесь только о векторах.
за основу брал
https://habrahabr.ru/post/131931/
я буду показывать вычисления, необходимые в 2d игре основанные на векторах, использовать их буду для положений объектов и направлений.
в bgt есть класс vector, который содержит в себе необходимые методы, но я их тут опишу.
примечание. проверил скорость выполнения тригонометрических функций, они на одну десятую быстрее чем считывание из сохранённых значений. чем это объяснить? наверно в bgt тригонометрические ффункции не вычисляют, а тоже берут значения из кэша.
примечание 2. получается нет смысла искать скорость выполнения кода переходя на линейную алгебру, но во-первых, я мог ошибаться в тестировании, а во-вторых, работа с векторами более понятна и предоставляет много возможностей.
сложение, чтобы сложить, складываем компоненты вектора. (3, 3) плюс (4, 4) равно (7, 7).
в bgt просто vector1 = vector2 + vector3;
я сложение использую в совершении движения объектом, да и много где ещё необходимо складывать векторы.
вычитание. чтобы вычесть один вектор из другого нужно вычесть компоненты вектора из соответствующих компонентов другого вектора. (3, 3) минус (4, 4) равно (-1, -1)
в bgt vector1 = vector2 - vector3;
вычитание я использую в получении вектора направления от одного объекта к другому.
Умножение на скаляр, это умножение на число, каждый компонент умножаем на определённое число. (3, 3) умножить на 5 равно (15, 15);
в bgt можно кратко vector1 *= 5;
я ещё не использовал, но можно например для сопротивления, ну например в воде делать шаги меньше, например мы идём на север, вектор движения (0, 1), умножаем на число сопротивления 0.8, получаем (0, 0.8), а вообще этот приём хорошо использовать в гонках, или для самолётов, космических кораблей, где меняется скорость и есть различного рода ускорения и сопротивления.
длина вектора. можно использовать для вычисления растояния, вычисляется по теореме Пифагора, допустим мы в точке (10, 10), а монстр - (14, 13), вычитаем из положения монстра наше положение, получаем (4, 3), берём длину этого вектора, получаем 5. корень квадратный из суммы квадратов сторон, 4 в квадрате плюс 3 в квадрате равно 25, корень из 25 равно 5.
в bgt (vector1 - vector2).length();
использую для получения громкости звука разных объектов, которые надо слышать, конечно это пригодиться и при вычислениях силы урона по отношению к монстрам и прочего.
Нормализация вектора. своими словами, сокращение вектора до длины равной единицы, необходимо много где и очень важное действие. чтобы вычислить нормализованный вектор надо компоненты вектора разделить на его длину. например, мы в точке (10, 10), к нам идёт монстр, он находится на точке (14, 13), он должен сделать шаг по направлению к нам, шаг его равен конечно же единице, вычисляем направление и нормализуем вектор (10, 10) минус (14, 13) равно (-4, -3) берём длину этого вектора и делим компоненты вектора направления, получаем (-4 делить 5, -3 делить 5) равно (-0.8, -0.6), теперь к местоположению монстра прибавляем получившийся вектор, и вот первый шаг сделан.
в bgt vector new_mob_position = mob_position + (mob_position - position) / (mob_position - position).length(); // где mob position - положение монстра, а position - наше положение
пока не использую, до монстров ещё не дошёл, но сложностей здесь нет. препятствия надо будет проверять, но это уже другая тема
сколярное произведение векторов. умножаем компоненты и складываем их, казалось бы, для чего? позволяет нам вычислять углы направления, ниже покажу функцию, где я это вычисляю, правда тут используется функция аркосинус, как видно без тригонометрии не обойтись, но всего одна функция при вычислениях направления, например для выстрела попал или не попал, в остальном тригонометрия не нужна.
в bgt не знаю как вычислить сколярное произведение, поэтому беру вручную перемножаю иксы векторов и складываю с произведением игриков. если кто вкурсе, отпишитесь, буду благодарен.
как писал, вычисление углов у меня готово. осталось применять на практике.
Векторное произведение. это не сложное действие, позволяет поворачивать вектора направления на 90 кратные градусы, описывать это не буду, можете почитать в статье на хабре.
я написал функцию поворота на любой градус, зачем нам ещё функция, которая будет по 90 поворачивать?
Базисный вектор. вот эта тема очень интересная и сложноватая, дело в том, что у нас обе оси имеют направления, ось икс это (1, 0), а игрик - (0, 1), это и есть базисные вектора, нужно нам это для того, чтобы поворачивать карту, вернее вычислять другие позиции объектов в зависимости от нашего направления, ну например если я смотрю направнои нахожусь на точке (1,1), то объект который находится в точке(4, 4) должен оказаться в точке (4, -4), это объяснять подробно не буду, но в ниже описаных кодах, я это покажу.
советую изучить базисные вектора, мы этим крутим мир, а главное после того как разберётесь, оно оказывается совсем не сложным.
а теперь код.
я создал полностью с нуля свой sound_pool, и модуль math.bgt, в последнем находятся функции вычисления, у самтупи это был rotation.bgt. в sound_pool я покажу 2 способа вычисления новой точки объекта в зависимости от нашего поворота, а в math я покажу всё.
начнём с math.bgt
#include "direction_vectors.bgt"; // подключается модуль в котором объявлен константный массив из нормализованых векторов направления, я их вычислил их 360, и беру из кэша, чтобы не вычислять каждый раз
const double PI = 3.1415926535897932384626433832795; // число PI
const int decimal_places = 2; // величина округления, использую в расчёте расстояния, если клетка это метр на метр, то расстояние с точностью до сантиметра, этого достаточно
// ниже константы для поворотов, я их хотел убрать, но оставил пока, честно говоря они не нужны
const int north=0;
const int northeast=45;
const int east=90;
const int southeast=135;
const int south=180;
const int southwest=225;
const int west=270;
const int northwest=315;
// функция возвращает новое положение, нужно для движения
vector move(vector this_vector, int dir) { // принимаем позицию, и направление в градусах, возвращаем новый вектор
if (dir < 0 or dir > 359) { dir = normaly_dir(dir); } // если градусы за пределами 0..359, то вычисляем градус, например если dir будет 500 то нормализуется до 140 градусов.
return this_vector + direction_vectors[dir];// прибавляем к позиции, вектор из массива с индексом dir. никаких синусов
}
// функция поворота направо на определённый градус
int turn_right(int dir, int turn_step) { // принимаем направление в градусах и количество градусов на который надо повернуть, возвращаем новое направление в градусах
dir += turn_step;
if (dir < 0 or dir > 359) { dir = normaly_dir(dir); } // нормализуем направление и тут, вдруг вышли за пределы 0..359
return dir;
}
// аналогичная функция с поворотом налево
int turn_left(int dir, int turn_step) {
dir -= turn_step;
if (dir < 0 or dir > 359) { dir = normaly_dir(dir); }
return dir;
}
// вычисляем дистанцию между двумя точками
double get_distance(vector v1, vector v2) { // принимаем 2 вектора местоположений, возвращаем число с плавающей запятой округлённое до двух чисел после запятой
return round((v1 - v2).length(), decimal_places); // вычли вектор из вектора, взяли длину получившегося вектора и округлили
}
// функция вычисляет направление из одной точки на другую
int get_angle(vector v1, int dir, vector v2) { // принимает позицию и направление объекта и позицию куда надо вычислить направление, возвращает число в градусах
vector vec_dir = normaly_vector(v2 - v1); // вычислили вектор направления и нормализовали его
if (dir < 0 or dir > 359) { dir = normaly_dir(dir); } // если градусы за пределами 0..359, то нормализовали направление в градусах
int a = round(arc_cosine(round(direction_vectors[dir].x * vec_dir.x + direction_vectors[dir].y * vec_dir.y, 4)) / PI * 180, 0); // вычислили по формуле угол, и округлили до 4 знаков после запятой, однако угол в 180 градусов а не в 360
if (direction_vectors[dir].y * vec_dir.x - direction_vectors[dir].x * vec_dir.y < 0) a = 360 - a; // если угол отрицательный, то вычисляем его отнимая от 360, тем самым получаем чёткий угол направления на точку
if (a < 0 or a > 359)
return normaly_dir(a);
else
return a;
}
// функция нормализации градусов
int normaly_dir(int dir) {
dir %= 360;
if (dir < 0) dir += 360;
return dir;
}
// функция нормализации вектора
vector normaly_vector(vector v) {
return v / v.length();
}
пока что всё в этом модуле.
в sound_pool я вычисляю позицию объекта относительно нас двумя способами
1. тригонометрический
double a=normaly_dir(dir)*PI/180; // вычислили угол в радианах
double x = (sound_vector.x - listener_vector.x) * cosine(a) - (sound_vector.y - listener_vector.y) * sine(a);
double y = (sound_vector.y - listener_vector.y) * cosine(a) + (sound_vector.x - listener_vector.x) * sine(a);
где sound_vector - вектор положения объекта, listener_vector - вектор слушателя, dir - число в градусах на сколько повёрнут слушатель
работает как оказалось достаточно быстро, 250000 итераций на слабом железе меньше секунды
2. векторный
int dir2 = normaly_dir(90 - dir); // взяли базисный вектор оси x, вернее повёрнутый базисный вектор оси x относительно слушателя
double x = (sound_vector.x - listener_vector.x) * direction_vectors[dir2].x + (sound_vector.y - listener_vector.y) * direction_vectors[dir2].y * (-1);
double y = (sound_vector.x - listener_vector.x) * direction_vectors[dir2].y + (sound_vector.y - listener_vector.y) * direction_vectors[dir2].x;
также вычислили x и y точки относительно нас., но работает немножко дольше чем тригонометрия, что удивительно.
впринципе всё, в целом могу сказать что не смотря на то, что тригонометрия работает в bgt быстро, я за векторы, они в bgt есть, с ними работать легко, дело за вами по какому пути будете идти, но большинство игр как я понимаю используют линейную алгебру.