Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
DirectX. Продвинутая Анимация (2004) [rus].pdf
Скачиваний:
335
Добавлен:
16.08.2013
Размер:
8.39 Mб
Скачать

Синхронизация анимации идвижения

59

DWORD Keyframe2 = (Keyframe==3) ? Keyframe:Keyframe + 1;

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

DWORD TimeDiff = Keyframes[Keyframe2].Time -

Keyframes[Keyframe].Time;

//Убедиьтся, что существует разница во времени

//для исключения деления на 0

if(!TimeDiff)

TimeDiff=1;

float Scalar = (Time -Keyframes[Keyframe].Time)/TimeDiff;

Теперь у вас есть значение скаляра (находящегося в диапазоне от 0 до 1), который используется для интерполяции ключевых матриц преобразования. Чтобы облегчить работу с матрицами преобразований, они приводятся к типу D3DXMATRIX, так что D3DX может выполнить всю работу за вас.

// Вычислить разность преобразований D3DXMATRIX matInt = \

D3DXMATRIX(Keyframes[Keyframe2].matTransformation) - \ D3DXMATRIX(Keyframes[Keyframe].matTransformation);

matInt *= Scalar; // Scale the difference

//Прибавить масштабированную матрицу преобразования к матрице

//первого ключевого кадра.

matInt += D3DXMATRIX(Keyframes[Keyframe].matTransformation);

На данном этапе у вас есть корректно анимированная матрица преобразования, хранимая в matInt. Для просмотра результатов вашей работы установите матрицу matInt в качестве матрицы преобразования мира и визуализируйте ваш анимированный меш.

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

Перемещение, синхронизированное со временем

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

60

Глава2

Наиболее частое применение движения на основе времени находит место при перемещении объекта на определенное расстояние за определенное время. Например, предположим, что игрок переместил джойстик вправо, ваша игра реагирует на это, передвигая персонаж немного вправо, скажем, на 64 единицы за одну секунду, что эквивалентно 0.064 единицам за миллисекунду.

Используя небольшую функцию, вы можете вычислить количество единиц (вещественное число), на которое необходимо сдвинуть объект, основываясь на времени, прошедшем между кадрами.

float CalcMovement(DWORD ElapsedTime, float PixelsPerSec)

{

return (PixelsPerSec / 1000.0f * (float)ElapsedTime);

}

Как вы видите, функция CalculateMovement использует следующую формулу:

PixelsPerSec / 1000.0f * ElapsedTime;

Переменная PixelsPerSec содержит число единиц, на которое вы хотите переместить ваш объект за одну секунду. Величина 1000.0 означает 1000 миллисекунд. В основном, вы вычисляете количество единиц движения за миллисекунду. Наконец, вы умножаете скорость движения в миллисекундах на ElapsedTime для вычисления перемещения.

Эта разновидность движения, основанная на синхронизации по времени, очень проста, однако ее нельзя упускать. Знание принципов движения, основанного на синхронизации по времени, лежит в основе более сложных методов, таких как плавное движение объектов по заранее определенной траектории.

Движение вдоль траекторий

Как было сказано ранее, движение на основе синхронизации по времени определяется как расстояние движения, деленное на 1000 и умноженное на прошедшее время. Я использовал пример, когда пользователь нажал "вправо" на джойстике и его персонаж переместился вправо на заданное число единиц. А как насчет тех случаев, когда вы хотите перемещать объекты без участия пользователя? Например, предположим, игрок нажал кнопку и выпустил пули из большой пушки, которую нес с собой. Эти пули летят по определенной траектории с определенной скоростью. Вы можете установить скорости для каждой пули и не использовать траектории, но как насчет тех супер-пуль, которые могут атаковать через части уровня, по заранее заданной траектории?

Синхронизация анимации идвижения

61

Эти специальные случаи требуют установки координат движения заранее, и быстрых вычислений, для определения расположения объекта на этой траектории. А что насчет движения таких объектов как персонажи, платформы, рубильники? Вы угадали, использование траекторий является идеальным решением для всех этих проблем!

Я собираюсь рассказать о двух наиболее используемых типах траекторий - прямолинейных и криволинейных. Начну же свой рассказ с использования прямолинейных траекторий.

Следование прямолинейным траекториям

Прямолинейная траектория - это прямая. Перемещение происходит от начальной точки к конечной без остановок и поворотов. Прямая определяется двумя точками - началом и концом. Чтобы следовать прямолинейной траектории вам просто нужно двигаться по прямой из точки А в точку Б.

Для перемещения объекта по прямолинейной траектории вам необходимо, используя простые формулы, вычислить координаты точки, лежащей на прямой. Рис. 2.2 демонстрирует, что для расчета координатточки, лежащей на отрезке, используя скаляр (лежащий в пределах от 0 до 1), вам необходимо вычислить разность координат концевых точек отрезка, умножить ее на скаляр и добавить к результату координаты начальной точки.

Начало траектории

 

 

 

Конец траектории

Скаляр = 0.0

Скаляр = 0.25

Скаляр = 0.5

Скаляр = 0.75

Скаляр = 1.0

Рис. 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 единицах от начала траектории.

62

Глава2

Для определения координат, используя в качестве измерения трехмерные единицы, с помощью функции D3DXVec3Length вьиисляется длина траектории и делится на заданное положение для получения скаляра, использовавшегося в предыдущих вычислениях.

Например, для получения координат точки, лежащей в 32 единицах от начала траектории, вы можете использовать следующий код:

//Pos = положение (в трехмерных единицах) точки на траектории,

//которую вы хотите определить

//Определение начальной и конечной точки прямолинейной траектории D3DXVECTOR3 vecStart = D3DXVECTOR3(0.0f, 0.0f, 0.0f);

D3DXVECTOR3 vecEnd = D3DXVECTOR3(10.0f, 20.0f, 30.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 для объекта

Передвижение по криволинейным траекториям

В вашей игре траектории не обязательно будут прямолинейными. Ваши объекты могут двигаться по изящным криволинейным траекториям, как например, когда персонаж ходит по кругу. Определить гладкий округлый путь, используя прямые линии практически невозможно, так что вам необходимо разработать второй тип траекторий, который сможет использовать кривые. Надо заметить, что

Синхронизацияанимацииидвижения

63

не все типы кривых могут быть использованы. Помните, что это продвинутая анимация, - мы занимаемся великими делами, так что лучше всего нам подойдут кубические кривые Безье! Как показано на рис. 2.3, для определения кубической кривой Безье необходимы четыре точки (две концевые и две промежуточные).

Рис. 2.3. Кубическая кривая Безье использует четыре точки для определения направления движения от начала к концу

Как вы можете видеть, кубическая кривая Безье не просто кривая - она может выгибаться и крутиться множеством различных способов. Управляя этими четырьмя контрольными точками, вы можете создавать действительно полезные траектории для использования в своих проектах. Принцип работы кубических кривых Безье прост, но вот реализовать его практически достаточно сложно.

Чтобы понять теорию использования кубических кривых Безье, посмотрите на рис. 2.4, на котором показано построение кривой по четырем контрольным точкам.

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

Хотя мы и изобразили кривую, компьютер так делать не будет, и нам это не поможет узнать координаты точки, лежащей на кривой. Нам нужно отыскать способ вычисления точных координат любой точки, лежащей на кривой. Таким образом, вы сможете делать с координатами все что угодно - от рисования кривой до вычисления координат расположения вашего объекта на криволинейной траектории! Вот формула для вычисления координат лежащих на кривой:

C(s)=P0*(1-s)3+P1*3*s*(1-s)2+P2*3*s2*(1-s)+P3*s3

В этой формуле контрольные точки задаются как Р0, P1, P2 и Р3, которые являются начальной, первой промежуточной, второй промежуточной и конечной точками соответственно. Результирующая точка, лежащая на кривой, определена как C(s), где s - это

64

Глава2

Рис. 2.4. Вы определяете кубическую кривую Безье, соединяя четыре контрольные точки и разбивая соединяющее их линии в заданном соотношении. Каждое разбиение нумеруется для дальнейшего использования

Рис. 2.5. Вы можете нарисовать кубическую кривую Безье, выделив линии, соединяющие пронумерованные разбиения

Синхронизация анимации идвижения

65

значение скаляра (или времени) в диапазоне от 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(50.0f, 0.0f, 0.0f), \ &D3DXVECTOR3(25.0f, -50.0f, 0.0f), \ 0.5f, &vecPos);

66

Глава2

Итак, вы можете использовать координаты, возвращаемые функцией 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 + \ Length03 * 0.5f;

Переменная CurveLength будет содержать приблизительную длину кривой. Вы будете использовать значение CurveLength таким же образом, как и с вычислениями в прямолинейных траекториях для преобразования количества единиц в скаляр для вычисления точных координат точки, лежащей на кривой.

// Pos = положение на кривой (от 0-CurveLength) float Scalar = Pos / CurveLength; CubicBezierCurve(&vecPoints[0], &vecPoints[1], \

&vecPoints[2], &vecPoints[3], \ Scalar, &vecPos);

Синхронизацияанимацииидвижения

67

Как вы можете видеть, кубические кривые Безье не слишком трудно использовать. Все формулы простые, и я оставляю детали вычислений для математических книжек (или такой хорошей книги как Kelly Dempski's "Рассмотрение кривых и поверхностей" - смотрите приложение А "Книги и веб ссылки"). На данный момент для меня главное - сделать работающий проект игры. Говоря об этом, давайте посмотрим, что можно сделать с новоприобретенными знаниями использования прямолинейных и криволинейных траекторий для созданий маршрутов.

Определение маршрутов

Траектория сама по себе приносит не очень много пользы; бывают случаи, когда вам необходимо соединить вместе несколько траекторий, по которым должен двигаться объект. Я говорю о сложных траекториях, которые частично прямолинейные, а частично криволинейные. По сути, мы больше не обсуждаем траектории, а переходим к следующей теме: маршруты!

Как вы можете видеть на рис. 2.6, маршрут - это набор траекторий, соединенных между собой концевыми точками.

Рис. 2.6. Вы можете создавать сложные маршруты, используя набор прямолинейных и криволинейных траекторий. Как вы можете здесь видеть, траекториям не обязательно соединяться для завершения маршрута

Как вы уже догадываетесь, маршрут может быть определен, используя массив траекторий. Создав основную структуру траектории, вы можете хранить информацию прямолинейных и криволинейных траекторий в одной структуре. Хитрым

Соседние файлы в предмете Программирование на C++