Как создать трассу по кривой в GameMaker

Как построить кривую в редакторе GameMaker? Как с помощью этой кривой и текстуры дороги создать трассу? Нужно ли для этого знать тригонометрию? Как для этого использовать знания о векторах?

Как создать трассу по кривой в GameMaker

Всем привет! С вами программист Александр Кондырев. В этой статье я отвечу на вопросы, которые указаны выше.

Когда GameMaker был приобретён компанией YoYo Games, он получил новую IDE и стал называться GameMaker Studio. На днях коллега мне сообщил, что GameMaker Studio 2 теперь снова называется просто GameMaker. Я уже привык называть его GMS2, поэтому не заметил этого изменения. Исправляюсь и теперь снова называю его GameMaker или GM.

Когда я начинал программировать игры, я не понимал, как пользоваться кривыми.

Объекты, построенные по кривым, казались волшебством. Я тогда не осознавал одну важную концепцию: человек не может различить кривую линию от линии, разбитой на множество маленьких прямых отрезков. Эту концепцию я буду использовать в этом уроке. Мы построим кривую линию и разобьём её на прямые отрезки, чтобы на их основе создать трассу.

Для начала на бумаге я нарисовал кривую, по которой хочу построить дорогу.

Как создать трассу по кривой в GameMaker

В квадрате выделен фрагмент дороги, который я подробнее разберу на рисунке ниже.

Как создать трассу по кривой в GameMaker

Это увеличенный фрагмент кривой из квадрата. На рисунке видно, что я разбил этот фрагмент на десять кусочков одинаковой длины. Над и под каждым кусочком нарисованы квадраты. Каждый квадрат разделён пунктирной линией, показывая, что они состоят из треугольников. Эти фигуры хоть и близки к квадратам, но на самом деле это трапеции. Они сужаются там, где кривая вогнута, и расширяются там, где она выпукла.

На следующей схеме я рассмотрю более детально один фрагмент.

Как создать трассу по кривой в GameMaker

AB и BC — это два фрагмента кривой. Обращаю внимание, что на этой схеме я сократил количество треугольников на фрагмент кривой с четырёх до двух. Мой фрагмент дороги, построенный по кривой, будет состоять из треугольников A2A1B1 и B1B2A2. Чтобы их построить, мне нужно найти координаты точек A1, A2, B1, B2.

Теперь перейду в GM, чтобы построить там тестовую кривую.

Как создать трассу по кривой в GameMaker

Для этого в новом проекте добавляю кривую Path1 — в GameMaker они называются path или пути.

Как создать трассу по кривой в GameMaker

Открываю Room1 и создаю в этой комнате слой Path_1.

Как создать трассу по кривой в GameMaker

В этом слое делаю активной созданную кривую Path1.

Как создать трассу по кривой в GameMaker

Кривая может быть с открытым началом и концом, а может быть закрытой, когда конечная точка соединена с начальной. Для круговой трассы мне нужна закрытая кривая, поэтому в чекбоксе Closed ставлю галочку. Пути могут состоять из прямых линий или из кривых, касающихся этих прямых. Для плавных поворотов на трассе я выбираю второй вариант и нажимаю кнопку Smooth Curve. Далее расставляю точки и передвигаю их внутри комнаты, чтобы построить желаемую кривую. На скриншоте показываю получившуюся кривую.

Как создать трассу по кривой в GameMaker

Добавляю в проект Object1 и создаю его экземпляр в комнате. Дальше код буду писать в этом объекте.

Для отрисовки дороги я буду использовать буфер вершин. Для этого в событии Create я создаю формат вершин для этого буфера и сам буфер.

Как создать трассу по кривой в GameMaker

С третьей по седьмую строку я создаю формат вершин. В нём будут координаты вершины, текстурные координаты и цвет. Для этого примера цвет мне не нужен, да и координаты у вершины нужны только две — X и Y. Однако в этом руководстве я буду использовать стандартный шейдер GameMaker, поэтому оставляю такой формат вершины.

С девятой по тринадцатую строку я создаю вершинный буфер и переношу его в видеопамять с помощью функции vertex_freeze. Все эти строки уже были описаны в статье О трёхмерной графике. Добавлять рассчитанные вершины в буфер я буду между строками десять и двенадцать.

Теперь нужно рассчитать координаты точек A1, A2, B1, B2. Для этого введу несколько переменных.

Как создать трассу по кривой в GameMaker

Во второй строке я создаю массив lines_array для хранения координат рассчитанных вершин. Называю его lines , потому что вершины в него будут сохраняться попарно, и таким образом они будут составлять линии. В третьей строке сохраняю значение 64 в переменную width , которая обозначает ширину трассы. А в четвёртой строке нахожу половину этой длины и сохраняю её в переменную width_half . В пятой строке я создаю переменную pth и присваиваю ей значение созданного пути Path1 . В шестой строке присваиваю значение 8 переменной part_size . Я хочу, чтобы примерно столько пикселей было в каждом фрагменте пути. Чем больше пикселей будет в каждом фрагменте, тем более угловатой будет дорога. Чем меньше пикселей в каждом фрагменте, тем плавнее будет дорога, но для её отрисовки потребуется больше треугольников. Я говорю "примерно" о длине фрагмента, потому что не знаю длины всей кривой, и необязательно эта длина будет кратной восьми.

В седьмой строке с помощью функции path_get_length я получаю и сохраняю длину кривой в переменной path_length . В восьмой строке я целочисленно делю длину кривой на длину фрагмента, чтобы получить количество фрагментов в кривой. В девятой строке делю длину кривой на количество фрагментов, чтобы скорректировать длину каждого фрагмента. В десятой строке я делю размер фрагмента на ширину трассы, чтобы найти этот размер в текстурных координатах, и сохраняю его в переменную tex_frac .

Далее я буду использовать цикл, чтобы пройтись по всем фрагментам кривой, рассчитать координаты A1, A2, B1, B2 и сохранить их в массив.

Вот ещё раз приведу схему трапеции с искомыми вершинами.

Как создать трассу по кривой в GameMaker

В приведённом ниже коде я вычисляю вектор AB. С его помощью нахожу вектор, находящийся слева от него AA1, и вектор, находящийся справа от него AA2. Используя эти векторы, я вычисляю координаты искомых точек A1 и A2, после чего сохраняю их в массив. В последующих шагах цикла я повторяю эту процедуру для всех остальных фрагментов кривой.

Как создать трассу по кривой в GameMaker

Детальнее разберём приведённый код. С четырнадцатой по девятнадцатую строку я вычисляю координаты точек A и B. Это делается с помощью функций path_get_x и path_get_y . Этим функциям нужно передать кривую и значение от нуля до единицы. Ноль обозначает начало кривой, единица — её конец. Чтобы найти нужное значение, требуется разделить индекс на количество частей. В цикле я прохожу по всем рассчитанным точкам, поэтому индекс каждой точки соответствует итератору i . В рамках каждого шага цикла для расчёта вектора нужно найти координаты двух точек, соответственно индекс второй точки будет i+1 .

С двадцатой по двадцать четвёртую строку я вычисляю вектор, нахожу его длину и нормализую его. Длина нормализованного вектора всегда равна единице. Чтобы найти вектор, нужно из координат точки B вычесть координаты точки A. С помощью функции point_distance вычисляется длина вектора. Затем координаты вектора делятся на его длину для нахождения единичного вектора.

С двадцать пятой по тридцать вторую строку происходит вычисление левого и правого векторов. Угол между единичным вектором и искомыми векторами всегда равен 90 градусам, поэтому здесь нет необходимости использовать тригонометрические формулы. Я меняю местами координаты единичного вектора и к одной из координат добавляю знак минуса. Таким образом находятся левый и правый единичные векторы. Я умножаю их на половину ширины трассы, чтобы получить необходимую длину.

С тридцать третьей по тридцать седьмую строку я прибавляю найденные векторы к координатам точки A, чтобы получить координаты искомых точек A1 и A2. Полученные значения сохраняю в массив lines_array .

Для отображения трассы я нарисовал текстуру размером 16x16 пикселей. В настройках нужно обязательно поставить галочку в чекбоксе Separate Texture Page, иначе GameMaker на этапе компиляции добавит эту текстуру в общую текстурную страницу. Если это произойдёт, то расчёты, приведённые в этом руководстве, использовать не получится, и придётся действовать другим методом.

Как создать трассу по кривой в GameMaker

Дальше нужно добавить вершины в буфер. Кроме пространственных координат мне понадобятся текстурные координаты. Нарисую ещё одну схему:

Как создать трассу по кривой в GameMaker

На этой схеме изображён квадрат, который представляет текстуру. Квадрат разбит пунктирными линиями на 8 одинаковых отрезков. У верхнего левого угла текстуры координаты (0,0), у правого нижнего — (1,1). В центре текстуры координаты (x=0,5, y=0,5). Ранее я рассчитал переменную tex_frac . Это значение представляет собой расстояние по горизонтали между двумя пунктирными линиями.

В следующем фрагменте я заканчиваю построение буфера вершин.

Как создать трассу по кривой в GameMaker

Здесь я снова прохожу циклом по всем точкам и формирую из них на каждом шаге по два треугольника, составляющие трапецию. С пятьдесят первой по шестьдесят первую строку я извлекаю из массива две соседние линии. Для удобства сохраняю координаты линий в отдельные переменные ax1, ay1, ax2, ay2, bx1, by1, bx2, by2 . По текущему индексу i я получаю первую линию, а по индексу i+1 — следующую. Обратите внимание на конструкцию в строке пятьдесят два: % — это оператор "modulo" или "mod", который возвращает остаток от деления. Почему эта конструкция используется? В последней итерации цикла i+1 выйдет за пределы длины массива, что вызвало бы ошибку. Но оператор modulo возвращает 0 в этом случае, и ошибки не происходит. Это даёт первую линию из массива, которая как раз нужна для последнего значения. Раньше я не использовал эту конструкцию и прибегал к дополнительным условиям или другим решениям, что усложняло код и его чтение.

С шестьдесят второй по семидесятую строку я добавляю в буфер первый треугольник A1B1A2 . C семьдесят второй по восьмидесятую строку с буфер добавляется второй треугольник B1B2A2 .

Добавление вершин в буфер было подробно описано в статье О трёхмерной графике.

Обычно для текстурных координат вместо X и Y принято использовать U и V соответственно. Координата V для точек A1 и B1 всегда будет равна нулю, а для точек A2 и B2 — единице. Это можно увидеть на схеме выше. Координата U рассчитывается умножением текущего индекса i или j на длину фрагмента текстуры tex_frac . Можно заметить, что значения U будут превышать единицу в какой-то момент, но благодаря включению функции gpu_set_texrepeat(true) такие значения допустимы, потому что текстура будет повторяться во все стороны. На скриншоте буфер road_buff выделен синим цветом. Это означает, что я убрал слово var при объявлении этой переменной, чтобы она была доступна не только в событии Create , но и в Draw .

Осталось нарисовать созданный буфер на экране. Ниже представлен код из события Draw.

Как создать трассу по кривой в GameMaker

Во второй строке я сохраняю указатель на текстуру в переменную tex . Его возвращает функция sprite_get_texture , которой передаётся название спрайта и кадр, для которого нужно получить указатель на текстуру. У меня один кадр, поэтому значение кадра равно нулю.

В третьей строке я включаю повтор текстуры, как упоминалось выше. В четвёртой строке отключаю фильтрацию. Это не обязательно, но я использую пиксельную текстуру и не хочу, чтобы её цвета интерполировались. В шестой строке с помощью функции vertex_submit я рисую буфер вершин на экране. Этой функции передаётся буфер вершин road_buff , pr_trianglelist означает, что в переданном буфере вершины описывают треугольники, а tex — указатель на текстуру.

Запускаем компиляцию и видим отрисованную трассу на экране.

Как создать трассу по кривой в GameMaker

В этой статье я построил трассу по кривой в GameMaker и отобразил её на экране. В процессе я выяснил, что для построения нужны базовые знания о векторах и совсем не требуются знания тригонометрии. Статья получилась значительно длиннее предыдущих. Напишите в комментариях, показалась ли она вам слишком длинной или, наоборот, в самый раз.

Спасибо, что дочитали статью до конца. Надеюсь, она вам понравилась, и вы узнали что-то новое. Если это так, поставьте сердечко — возможно, тогда о ней узнает больше людей.

Если вам понравилась эта статья, также рекомендую ознакомиться с другими материалами о программировании в GameMaker:
(1) О шейдерах

(2) О трёхмерной графике. Часть 1 из 2
(3) О трёхмерной графике. Часть 2 из 2

Подписывайтесь на меня, чтобы не пропускать новые публикации. Спасибо за ваше внимание!

55
7 комментариев

Не особо понятно для кого эта статья
Для тех кто в теме разбирается - она бесполезна
Для тех кто не разбирается - "ну надо сделать так и так потому что ну так надо, код скопируете"

Ну и практичность такого метода для построения трассы как сказал комментатор выше и правда спорная

1
Ответить

Уф. Посмотрел прошлую часть серии статей, там же расчёт на новичьков идёт, не?
Любой новичьек в осадок выпадет после стены кода что ты вывалил

Собственно большая часть проблемы это такие "гайды" где учат не программировать и понимать что вообще происходит, а копировать код и подкручивать рандомные переменные пока не заработает в твоём случае

1
Ответить

Я когда-то не понимал как сделать прямой объект кривым с помощью кривой. Эта статья для тех кто тоже не понимает как это сделать.

Ответить

Сам делал то же самое на UE. Чтобы по трассе было интересно ехать, она должна строиться иначе. Грубо говоря, нужно сначала построить произвольную ломаную кривую, а затем сгладить каждый угол с помощью куска окружности произвольного радиуса. Так строятся реальные дороги и гоночные трассы.

1
Ответить