Как было сказано ранее, движение на основе синхронизации по времени определяется как расстояние движения, деленное на 1000 и умноженное на прошедшее время. Я использовал пример, когда пользователь нажал "вправо" на джойстике и его персонаж переместился вправо на заданное число единиц. А как насчет тех случаев, когда вы хотите перемещать объекты без участия пользователя? Например, предположим, игрок нажал кнопку и выпустил пули из большой пушки, которую нес с собой. Эти пули летят по определенной траектории с определенной скоростью. Вы можете установить скорости для каждой пули и не использовать траектории, но как насчет тех супер-пуль, которые могут атаковать через части уровня, по заранее заданной траектории?
Эти специальные случаи требуют установки координат движения заранее, и быстрых вычислений, для определения расположения объекта на этой траектории. А что насчет движения таких объектов как персонажи, платформы, рубильники? Вы угадали, использование траекторий является идеальным решением для всех этих проблем!
Я собираюсь рассказать о двух наиболее используемых типах траекторий -прямолинейных и криволинейных. Начну же свой рассказ с использования прямолинейных траекторий.
Следование прямолинейным траекториям
Прямолинейная траектория - это прямая. Перемещение происходит от начальной точки к конечной без остановок и поворотов. Прямая определяется двумя точками - началом и концом. Чтобы следовать прямолинейной траектории вам просто нужно двигаться по прямой из точки А в точку Б.
Для перемещения объекта по прямолинейной траектории вам необходимо, используя простые формулы, вычислить координаты точки, лежащей на прямой. Рис. 2.2 демонстрирует, что для расчета координатточки, лежащей на отрезке, используя скаляр (лежащий в пределах от 0 до 1), вам необходимо вычислить разность координат концевых точек отрезка, умножить ее на скаляр и добавить к результату координаты начальной точки.

Рис. 2.2. Вы можете определить точку траектории, используя скаляр, который является частью полной длины траектории с началом в 0 и концом в 1
// Определим, начальную и конечную точки прямолинейной траектории // Scalar = положение для вычисления (от 0 до 1) D3DXVECTOR3 vecStart = D3DXVECTOR3(0.0f, 0.0f, 0.0f); D3DXVECTOR3 vecEnd = D3DXVECTOR3(10.0f, 20.0f, 30.0f); D3DXVECTOR3 vecPos = (vecEnd - vecStart) * Scalar + vecStart;
Если вы установите Scalar равным 0.5, то vecPos будет иметь координаты 5.0, 10.0, 15.0, которые являются серединой траектории. Теперь предположим, что вы не хотите использовать скаляр. Что если использовать вместо него трехмерные единицы? Например, предположим, что вместо использования значения скаляра 0.5, вы хотите узнать координаты точки, расположенной в 32 единицах от начала траектории.
Для определения координат, используя в качестве измерения трехмерные единицы, с помощью функции D3DXVec3Length вьиисляется длина траектории и делится на заданное положение для получения скаляра, использовавшегося в предьщупшх вычислениях.
Например, для получения координат точки, лежащей в 32 единицах от начала траектории, вы можете использовать следующий код:
// Pos = положение (в трехмерных единицах) точки на траектории, // которую вы хотите определить
// Определение начальной и конечной точки прямолинейной траектории D3DXVECTOR3 vecStart = D3DXVECTOR3(0.0f, 0.0f, 0.0f); D3DXVECTOR3 vecEnd = D3DXVECTOR3(10.0f, 20.0f, 3 0.0f);
// Вычисляем длину траектории
float Length = D3DXVec3Length(&(vecEnd-vecStart));
// Вычисляем скаляр, деля pos на len float Scalar = Pos / Length;
// Используем скаляр для вычисления координат
D3DXVECTOR3 vecPos = (vecEnd - vecStart) * Scalar + vecStart;
Теперь, когда вы можете вычислять расположение любой точки, лежащей на траектории, можно использовать эти знания для перемещения объекта вдоль траектории. Предположим, вы хотите, применяя теорию движения, основанного на синхронизации по времени, переместить объект из одной точки в другую за 1000 миллисекунд. Следующий код (вызываемый один раз за кадр), поможет вам выполнить это, бесконечно перемещая объект из начальной точки в конечную.
// vecPoints[2] = Начальная и конечная координаты траектории // Используйте этот код каждый кадр для перемещения объекта // вдоль прямолинейной траектории на основании текущего времени float Scalar = (float)(timeGetTime() % 1001) / 1000.0f; D3DXVECTOR3 vecPos = (vecPoints[1] - vecPoints[0]) * \
Scalar + vecPoints[0]; // используйте координаты vecPos.x, vecPos.y, and vecPos.z для объекта
Передвижение по криволинейным траекториям
В вашей игре траектории не обязательно будут прямолинейными. Ваши объекты могут двигаться по изящным криволинейным траекториям, как например, когда персонаж ходит по кругу. Определить гладкий округлый путь, используя прямые линии практически невозможно, так что вам необходимо разработать второй тип траекторий, который сможет использовать кривые. Надо заметить, что не все типы кривых могут быть использованы. Помните, что это продвинутая анимация, - мы занимаемся великими делами, так что лучше всего нам подойдут кубические кривые Безье! Как показано на рис. 2.3, для определения кубической кривой Безье необходимы четыре точки (две концевые и две промежуточные).
Рис. 2.3. Кубическая кривая Безье использует четыре точки для определения направления движения от начала к концу
Как вы можете видеть, кубическая кривая Безье не просто кривая - она может выгибаться и крутиться множеством различных способов. Управляя этими четырьмя контрольными точками, вы можете создавать действительно полезные траектории для использования в своих проектах. Принцип работы кубических кривых Безье прост, но вот реализовать его практически достаточно сложно.
Чтобы понять теорию использования кубических кривых Безье, посмотрите на рис. 2.4, на котором показано построение кривой по четырем контрольным точкам.
Разбиение линий, соединяющих точки кривой, служит для визуальной помощи и для сглаживания кривой. Чем больше дополнительных разбиений каждой линии вы сделаете, тем глаже будет выглядеть результирующая кривая. Чтобы увидеть, как будет выглядеть кривая, заданная точками, вам необходимо соединить все разбиения с каждой стороны линий как показано на рис. 2.5
Хотя мы и изобразили кривую, компьютер так делать не будет, и нам это не поможет узнать координаты точки, лежащей на кривой. Нам нужно отыскать способ вычисления точных координат любой точки, лежащей на кривой. Таким образом, вы сможете делать с координатами все что угодно - от рисования кривой до вычисления координат расположения вашего объекта на криволинейной траектории! Вот формула для вычисления координат лежащих на кривой:
С(8)=Р0*(1-8)3+Р1*3*8*(1-8)2+Р2*3*82*(1-8)+Р3*83
В этой формуле контрольные точки задаются как Р0, Р1, Р2 и Р3, которые являются начальной, первой промежуточной, второй промежуточной и конечной точками соответственно. Результирующая точка, лежащая на кривой, определена как С(8), где 8 - это
Рис. 2.4. Вы определяете кубическую кривую Безье, соединяя четыре контрольные точки и разбивая соединяющее их линии в заданном соотношении. Каждое разбиение нумеруется для дальнейшего использования

Рис. 2.5. Вы можете нарисовать кубическую кривую Безье, выделив линии, соединяющие пронумерованные разбиения значение скаляра (или времени) в диапазоне от 0 до 1, который определяет смещение точки, координаты которой мы хотим определить, вдоль кривой.
Значение s=0 определяет начальную точку, в то время как s=1 определяет конечную точку. Любое значение s от 0 до 1 определяет положение между двумя концевыми точками. Таким образом, для вычисления координат середины кривой задайте s=0.5. Положению в четверти кривой соответствует s=0.25 и так далее.
Для упрощения можно создать функцию, параметрами которой были бы четыре контрольных точки (вектора) и скаляр, которая будет возвращать вектор, содержащий координаты точки, лежащей на заданной кривой. Назовем функцию CubicBezierCurve и воспользуемся следующим прототипом для ее определения.
void CubicBezierCurve(D3DXVECTOR3 *vecPoint1, // начальная точка D3DXVECTOR3 *vecPoint2, // промежуточная точка 1 D3DXVECTOR3 *vecPoint3, // промежуточная точка 2 D3DXVECTOR3 *vecPoint4, // Конечная точка float Scalar, D3DXVECTOR3 *vecOut)
{
Теперь приготовтесь записать формулу кубической кривой Безье в программном коде, заменяя соответствующие переменные векторами контрольных точек и скаляром.
// C(s) =
*vecOut = \
// Р0 * (1 - s)3 +
(*vecPoint1)*(1.0f-Scalar)*(1.0f-Scalar)*(1.0f-Scalar) + \ // P1 * 3 * s * (1 - s)2 +
(*vecPoint2)*3.0f*Scalar*(1.0f-Scalar)*(1.0f-Scalar) + \ // P2 * 3 * s2 * (1 - s) +
(*vecPoint3)*3.0f*Scalar*Scalar*(1.0f-Scalar) + \
// P3 * s3
(*vecPoint4)*Scalar*Scalar*Scalar;
}
Вот и все! Теперь вы можете вычислять координаты точки, лежащей на кубической кривой Безье, задавая контрольные точки и скаляр. Возвращаясь к примеру с кривой, вы можете параметрически найти ее середину, вызвав функцию CubicBezierCurve:
D3DXVECTOR3 vecPos;
CubicBezierCurve(&D3DXVECTOR3(-50.0f, 25.0f, 0.0f), \ &D3DXVECTOR3(0.0f, 50.0f, 0.0f), \ &D3DXVECTOR3(5 0.0f, 0.0f, 0.0f), \ &D3DXVECTOR3(2 5.0f, -50.0f, 0.0f), \ 0.5f, &vecPos) ;
Итак, вы можете использовать координаты, возвращаемые функцией CubicBezierCurve (содержащиеся в векторе vecPos), для перемещения объекта в игре. Постепенно изменяя скаляр от 0 до 1 (с течением заданного времени), вы двигаете объект от начала траектории к ее концу. Например, для перемещения по криволинейной траектории за 1000 миллисекунд вы можете использовать следующий код:
// vecPoints[4]=начальная/промежуточная 1,промежуточная 2 и конечная точки // Каждый кадр использует данный код для расположения объекта на кривой //используя текущее время D3DXVECTOR3 vecPos;
float Scalar = (float)(timeGetTime() % 1001) / 1000.0f; CubicBezierCurve(&vecPoints[0] , &vecPoints[1] , \
&vecPoints[2], &vecPoints[3], \
Scalar, &vecPos) ;
// Используйте координаты vecPos.x, vecPos.y, and vecPos.z для объекта
Все это хорошо, но использовать скаляр немного неудобно при работе с реальными трехмерными единицами измерения. Я имею в виду, как узнать, какой скаляр использовать, если вы хотите переместить объект на 50 единиц вдоль кривой? Есть ли способ вычислить длину кривой, чтобы использовать ее, как и прямые линии?
Странно, но нет. Нет простого способа вычисления длины кривой Безье. Так или иначе, можно аппроксимировать длину, используя простые вычисления. Предполагая, что четыре контрольные точки кривой обозначены как p0, p1, р2, и р3, можно найти расстояние между точками р0 и p1, p1 и р2, р2 и р3, разделить результат пополам и добавить расстояние между р0 и р3 (также деленное пополам). В виде кода это будет выглядеть так:
// р[4] = четыре контрольные точки - координаты векторов float Length01 = D3DXVec3Length(&(p[1]-p[0])); float Length12 = D3DXVec3Length(&(p[2]-p[1])); float Length23 = D3DXVec3Length(&(p[3]-p[2])); float Length03 = D3DXvec3Length(&(p[3]-p[0])); float CurveLength = (Length01+Length12+Length23) * 0.5f + \ Length0 3 * 0.5f;
Переменная CurveLength будет содержать приблизительную длину кривой. Вы будете использовать значение CurveLength таким же образом, как и с вычислениями в прямолинейных траекториях для преобразования количества единиц в скаляр для вычисления точных координат точки, лежащей на кривой.
// Pos = положение на кривой (от 0-CurveLength) float Scalar = Pos / CurveLength;
CubicBezierCurve(&vecPoints[0], &vecPoints[1], \ &vecPoints[2] , &vecPoints[3] , \ Scalar, &vecPos);
Как вы можете видеть, кубические кривые Безье не слишком трудно использовать. Все формулы простые, и я оставляю детали вычислений для математических книжек (или такой хорошей книги как Kelly Dempski's "Рассмотрение кривых и поверхностей" - смотрите приложение А "Книги и веб ссылки"). На данный момент для меня главное - сделать работающий проект игры. Говоря об этом, давайте посмотрим, что можно сделать с новоприобретенными знаниями использования прямолинейных и криволинейных траекторий для созданий маршрутов.
Определение маршрутов
Траектория сама по себе приносит не очень много пользы; бывают случаи, когда вам необходимо соединить вместе несколько траекторий, по которым должен двигаться объект. Я говорю о сложных траекториях, которые частично прямолинейные, а частично криволинейные. По сути, мы больше не обсуждаем траектории, а переходим к следующей теме: маршруты!
Как вы можете видеть на рис. 2.6, маршрут - это набор траекторий, соединенных между собой концевыми точками.

Рис. 2.6. Вы можете создавать сложные маршруты, используя набор прямолинейных и криволинейных траекторий. Как вы можете здесь видеть, траекториям не обязательно соединяться для завершения маршрута
Как вы уже догадываетесь, маршрут может быть определен, используя массив траекторий. Создав основную структуру траектории, вы можете хранить информацию прямолинейных и криволинейных траекторий в одной структуре. Хитрым ходом является поиск общностей этих двух типов траекторий и их обособление. Например, и прямолинейные, и криволинейные траектории имеют начало и конец. Поэтому мы можем определить в основной структуре траектории два набора координат, соответствующих ее начальной и конечной точкам, как показано:
typedef struct {
D3DXVECTOR3 vecStart, vecEnd;
} sPath;
Единственной разницей между этими двумя типами траекторий является что криволинейная траектория имеет две дополнительные точки. Добавление двух дополнительных векторов к ранее определенной структуре sPath дает ей возможность прекрасно справиться с хранением этих двух дополнительных контрольных точек.
typedef struct {
D3DXVECTOR3 vecStart, vecEnd; D3DXVECTOR3 vecPoint1, vecPoint2;
} sPath;
Теперь единственное, что отсутствует в структуре sPath, - это флаг, определяющий тип траектории - прямолинейная или криволинейная. Добавьте переменную типа DWORD и перечисляемое объявление для определения типа траектории.
enum { PATH_STRAIGHT = 0, PATH_CURVED }; typedef struct { DWORD Type;
D3DXVECTOR3 vecStart, vecEnd; D3DXVECTOR3 vecPoint1, vecPoint2;
} sPath;
После этого вам просто нужно будет хранить значение PATHSTRAIGHT или PATHCURVED в переменной sPath::Type - для определения типа хранимых данных: начальную и конечную точку для прямолинейных траекторий, начальную, конечную и две промежуточные точки для криволинейных.
Создать массив структур sPath очень легко, а насколько просто заполнить этот массив данными траектории (как показано на рис. 2.7) показывает следующий код.
sPath Path[3] = {
{ PATH_STRAIGHT, D3DXVECTOR3(-5 0.0f, 0.0f, 0.0f), \ D3DXVECTOR3(-50.0f, 0.0f, 25.0f), \ D3DXVECTOR3(0.0f, 0.0f, 0.0f), \ D3DXVECTOR3(0.0f, 0.0f, 0.0f) }, \ { PATH_CURVED, D3DXVECTOR3(-50.0f, 0.0f, 25.0f), \ D3DXVECTOR3(0.0f, 0.0f, 50.0f), \ D3DXVECTOR3(5 0.0f, 0.0f, 0.0f), \ D3DXVECTOR3(25.0f, 0.0f, -50.0f) }, \

Рис. 2.7. Комбинация двух прямолинейных и одной криволинейной траектории образует сложный маршрут
{ РАТН_8ТРА10НТ, Б3БХ¥ЕСТОР3(25.0г, 0.0г, -50.0г), \ Б3БХ¥ЕСТОР3(-50.0г, 0.0г, 0.0г), \ Б3БХ¥ЕСТОР3(0.0г, 0.0г, 0.0г), \ Б3БХ¥ЕСТОР3(0.0г, 0.0г, 0.0г) } \
Конечно же вы не должны вручную записывать маршруты в ваших проектах; более правильным было бы использование внешнего источника, такого как .X файл для хранения данных вашего маршрута.
⇐Перемещение, синхронизированное со временем || Оглавление || Создание анализатора маршрутов .X файла⇒