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

Вид материалаДокументы
Подобный материал:
1   2   3   4   5   6   7   8   9   ...   37
Листинг 4.9. Определение и умножение двух матриц.

// общая структура матрицы

typedef sruct matrix_typ

{

float elem[3][3]; // место для хранения матрицы

} matrix, *matrix_ptr;

void Mat_Mult3X3 (matrix_ptr matrix_1, matrix_ptr matrix_2, matrix_ptr result)

{

index i,j,k;

for(i=0; i<3; i++)

{

for (j=0; j<3; j++)

{

result[i][j] = 0; // Инициализация элемента

for(k=0; k<3; k++)

{

result->elem[i][j]+=matrix_1->elem[i][k] * matrix_2->elem[k][j];

} // конец цикла по k

} // конец цикла по j

} // конец цикла по i

} // конец функции

Перед выходом из этой функции мы имеем результат, сохраненный в переменной result.

Единичная матрица

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

Говоря попросту, нам нужно иметь матрицу размерностью mхn, которую назовем матрицей I. Умножая на нее любую другую матрицу, мы должны получить исходную. Этой матрицей будет квадратная матрица, по главной диагонали которой записаны единицы, а все остальные элементы равны нулю:

Если мы умножим матрицу А на матрицу I,

то результатом будет исходная матрица А;

А x I = A


Использование матриц в играх

Мы уже достаточно поговорили о матрицах. Теперь посмотрим, зачем они нам нужны и как их использовать в играх. В программе из Листинга 4.8 мы создали поле астероидов, которые перемещались, изменяли свой размер и вращались. Если мы представим наши объекты как набор матриц, то сможем использовать умножение матриц для их трансформации.

Прелесть матриц состоит в том, что вы можете объединить все операции в одну матрицу, описывающую перемещение, масштабирование и вращение.

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

Главная матрица перемещений

Главной матрицей перемещений будем называть такую матрицу, в которой x_translation и y_translation - это коэффициенты перемещения объекта по осям Х и Y. Вот как она выглядит:



Главная матрица масштабирования

Главная матрица масштабирования - это такая матрица, в которой scale_x и scale_y - это коэффициенты масштабирования объекта по координатам х и у.



Такая матрица позволяет выполнять неоднородное масштабирование — мы можем задать один масштаб по оси Х и другой — по Y. Таким образом, если мы хотим масштабировать объект однородно, то должны задать scale_x = scale_y.

Главная матрица поворотов

В главной матрице поворотов angle - это угол, на который вы хотите повернуть, объект:




Общая матрица масштабирования, поворотов и перемещений

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



Если вы теперь умножите вершины объекта на эту матрицу, то получите перемещенный, повернутый и масштабированный объект. Не слабо, а?

Компонент нормализации вершины

Я совсем упустил одну маленькую деталь, которую вы уже, наверное, заметили. «Как мы можем умножить вершину на матрицу размером 3х3?» Неплохой вопрос. Рад, что вы спросили. Чтобы выполнить это, мы должны изменить представление структуры вершин, добавив компонент нормализации. Компонент нормализации - это просто единица, добавленная в конец структуры, описывающей каждую вершину. Для этого нам надо чуть-чуть изменить исходные тексты в Листинге 4,4, в котором описаны вершины. Все это отражено в структуре данных в Листинге 4.10.

Листинг 4.10. Новая структура данных для вершин.

#define X_COMP 0

#define Y_COMP 1

#define N_COMP 2

typedef struct vertex_typ

{

float p[3];

) vertex, *vertex_ptr;

Как вы можете заметить, теперь мы превратили матрицу Р, списывающую вершину, в массив. Наша матрица преобразований имеет размер 3х3, и для умножения нам надо иметь матрицу размером 1х3. Матрица Р удовлетворяет этому условию.

Программа Астероиды с использованием матриц

Я уже устал от разговоров — давайте что-нибудь напишем. К примеру, перепишем нашу программу из Листинга 4.8. Вы должны ее помнить. Мы ее переделаем и используем в ней матрицы. Листинг 4.11 показывает эту программу. Она называется «Супер Астероиды».

Листинг 4.11. Супер Астероиды (FIELD.C).

// ВКЛЮЧАЕМЫЕ ФАЙЛЫ ///////////////////////////////////////////

#include

#include

#include

//ОПРЕДЕЛЕНИЯ ///////////////////////////////////////////////

#define NUM__ASTEROIDS 10

#define ERASE 0

#define DRAW 1

#defineX_COMP 0

#define Y_COMP 1

#define N_COMP 2

// СТРУКТУРЫ ДАННЫХ ////////////////////////////////////

// новая структура, описывающая вершину

typedef struct vertex_typ

{

float p[3]; // единичная точка в двумерном пространстве

//и фактор нормализации

} vertex, *vertex_ptr;

// общая структура матрицы

typedef struct matrix_typ

{

float elem[3] [3]; // массив для хранения элементов

// матрицы

} matrix, *matrix_ptr;

// определение структуры "объект",.

typedef struct object_typ

{

int num_vertices; // количество вершин в объекте

int color; // цвет объекта

float xo,yo; // позиция объекта

float x_velocity; // скорости пермещения по осям Х float y_velocity;

// и Y matrix scale; // матрица масштабирования

matrix rotation; // матрицы поворота и перемещения

vertex vertices[16]; // 16 вершин

} object, *object_ptr;

// ГЛОБАЛЬНЫЕ.ПЕРЕМЕННЫЕ /////////////////////////////////

object asteroids [NUM_ASTEROIDS] ;

// ФУНКЦИИ ////////////////////////////////////////////

void Delay(int t)

{

// функция выполняет некоторую задержку

float x = 1;

while(t—>0)

x=cos(x) ;

} // конец функции

///////////////////////////////////////////////////////

void Make_Identity(matrix_ptr i)

{

// функция формирует единичную матрицу

i->elem[0][0] = i->elem[l][1] = i->elem[2][2] = 1;

i->elem[0][1] = i->elem[l][0] = i->elem[l][2] = 0;

i->elem[2][0] = i->elem[01[2] = i->eiem[2][1] = 0;

} // конец функции ///////////////////////////////////////////////////////

void Clear_Matrix(matrix_ptr m) {

// функция формирует нулевую матрицу

m->е1еm[0][0] = m->elem[l][1] = m->е1еm[2][2] = 0;

m->elem[0][1] = m->elem[l] [0] = m->е1еm[1] [2] = 0;

m->elem[2][0] = m->elem[0] [2] = m->elem[2][1] = 0;

} // конец функции

///////////////////////////////////////////////////////

void Mat_Mul (vertex_ptr v, matrix_ptr m)

{

// функция выполняет умножение матрицы 1х3 на матрицу 3х3

// элемента. Для скорости каждое действие определяется "вручную",

// без использования циклов. Результат операции - матрица 1х3

float x new, у new;

x_new=v->p[0]*m->elem[0][0] + v->p[1]*m->elem[1][0] + m->elem[2][0];

y_new=v->p[0]*m->elem[0][1] + v->p[1]*m->elem[1][1] + m->elem[2][1];

// N_COMP - всегда единица

v->p[X_COMP] = x_new;

v->p[Y_COMP] = y_new;

} // конец функции

///////////////////////////////////////////////////////

void Scale_Object_Mat(object_ptr obj)

{

// функция выполняет масштабирование объекта путем умножения на

// матрицу масштабирования

int index;

for (index=0; indexnum_vertices; index++)

{

Mat_Mul ((vertex_ptr) &obj->vertices [index],

(matrix_ptr)&obj->scale) ;

} // конец цикла for

} // конец функции

///////////////////////////////////////////////////////

Rotate_Object_Mat(object_ptr obj)

{

// функция выполняет.поворот объекта путем умножения на

// матрицу поворота

int index;

for (index=0; indexnum_yertices; index++)

{

Mat_Mul((vertex_ptr)&obj->vertices[index],(matrix_ptr)&obj->rotation) ;

} // конец цикла for

} // конец функции ///////////////////////////////////////////////////////

void Create_Field(void)

{

int index;

float angle,c,s;


// эта функция создает поле астероидов

for (index=0; index
{

asteroids[index].num vertices = 6;

asteroids [index] .color = 1 + rand() % 14; //астероиды

// всегда видимы

asteroids[index].xo = 41 + rand() % 599;

asteroids[index].yo = 41 + rand() % 439;

asteroids [index].,x_velocity = -10 +rand() % 20;

asteroids[index].y_velocity = -10 + rand() % 20;

// очистить матрицу

Make_Identity((matrix_ptr)&asteroids[index].rotation) ;

// инициализировать матрицу вращений

angle = (float) (- 50 + (float) (rand ()\ % 100)) / 100;

c=cos(angle);

s=sin(angle);

asteroids[index].rotation.elem[0][0] = с;

asteroids[index].rotation.elem[0][1] = -s;

asteroids[index].rotation.elem[l][0] = s;

asteroids[index].rotation.elem[l][1] = с;

// формируем матрицу масштабирования

// очистить матрицу и установить значения коэффициентов

Make_Identity((matrix ptr)&asteroids[index].scale);

asteroids[index].scale.elem[0][0] = (float) (rand() % 30) / 10;

asteroids[index].scale.elem[1][1] = asteroids[index].scale.elem[0][0];

asteroids[index].vertices[0].p[X_COMP] = 4.0;

asteroids[index].vertices[0].p[Y_COMP] = 3.5;

asteroids[index].vertices[0].p[N_COMP] = l;

asteroids[index].vertices[1].p[X_COMP] = 8.5;

asteroids[index].vertices[l].p[Y_COMP) = -3.0;

asteroids[index].vertices[1].p[N_COMP] = l;

asteroids[index].vertices[2].p[X_COMP] = 6;

asteroids[index].vertices[2].p[Y_COMP] = -5;

asteroids[index].vertices[2].p[N_COMP] = l;

asteroids[index].vertices[3].p[X_COMP] = 2;

asteroids[index].vertices[3].p[Y_COMP] = -3;

asteroids[index].vertices[3].p[N_COMP] = l;

asteroids[index].vertices[4].p[X_COMP] = -4;

asteroids[index].vertices[4].p[Y_COMP] = -6;

asteroids[index].vertices[4].p[N_COMP] = 1;

asteroids[index].vertices[5].p[X_COMP] = -3.5;

asteroids[index].vertices[5].p[Y_COMP] = 5.5;

asteroids[index],vertices[5].p[N_COMP] = 1;

// теперь масштабировать астероиды

Scale_Object_Mat((object_ptr)&asteroids[index]);

} // конец цикла for

} // конец функции

///////////////////////////////////////////////////////

void Draw Asteroids (int erase)

{

int index,vertex;

float xo,yo;

// эта функция в зависимости от переданного флага рисует или стирает

// астероиды

for (index=0; index
{

// нарисовать астероид

if (erase==ERASE)

_setcolor(0);

else

_setcolor(asteroids[index].color);

// получаем позицию объекта

xo = asteroids[index].xo;

yo = asteroids [index].yo;

// перемещаемся в позицию первой вершины астероида

_moveto((int)(xo+asteroids[index]-vertices[О].p[X_COMP]), (int)(yo+asteroids[indexl.vertices[0].p[Y_COMP])) ;

for (vertex=l; vertex
{

_lineto((int)(xo+asteroids[index].vertices[vertex].p[X_COMP]), (int)(yo+asteroids[index].vertices[vertex].p[Y_COMP])) ;

} // конец цикла по вершинам

_lineto((int)(xo+asteroids[index].vertices[0],p[X_COMP]), (int)(yo+asteroids[index].vertices[0].p[Y_COMP]));

} // замкнуть контур объекта

} // конец функции

///////////////////////////////////////////////////////

void Translate_Asteroids(void)

{

// функция перемещает астероиды

int index;

for (index=0; index
{

// перемещать текущий астероид

asteroids[index].xo += asteroids[index].x velocity;

asteroids[index].yo += asteroids[index].y_velocity;

// проверка на выход за границы экрана

if (asteroids[index].xo > 600 || asteroids[index].xo < 40)

{

asteroids[index].x_velocity = -asteroids[index].x_velocity;

asteroids[index].xo += asteroids[index].x_velocity;

}

if (asteroids[index].yo > 440 1) asteroids[index].yo < 40)

{

asteroids[index].y_velocity = -asteroids[index].y_velocity;

asteroids[index].yo += asteroids[index].у_velocity;

)

} // конец цикла for

} // конец функции

////////////////////////////////////////////////////////

void Rotate__Asteroids()

{

int index;

for (index=0; index
{

// вращать текущий астероид

Rotate_Object_Mat((object_ptr)&asteroids[index]);

} // конец цикла for

} // конец функции

///////////////////////////////////////////////////////

void main(void)

{

// перевести компьютер в графический режим

_setvideomode(_VRES16COLOR); // 640х480, 16 цветов

// инициализация

Create_Field();

while(!kbhit())

{

// стереть поле

Draw_Asteroids(ERASE) ;

// преобразовать поле Rotate_Asteroids();

Translate_Asteroids() ;

// нарисовать поле

Draw_Asteroids(DRAW);

// немного подождем...

Delay(500);

)

// перевести компьютер в текстовый режим

_setvideomode(_DEFAULTMODE);

} // конец функции


Вам потребуется время, чтобы изучить эту программу. Уделите внимание способу, которым астероиды масштабируются и поворачиваются. Если вы сравните время исполнения программ из Листингов 4.8 и 4.11, то не найдете никакой разницы. Если же вы потратите время и поместите все повороты, масштабирование и перемещение в одну матрицу, то программа из Листинга 4,11 будет работать значительно быстрее.

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

К вашему сведению

Запомните, что все это мы делаем для того, чтобы научиться писать видеоигры, особенно трехмерные. И делаем мы это постепенно. Если я вам сразу покажу, как писать игры типа Wolfenstein 3-D или DOOM, то мы многое потеряем в тактике и философии разработки и создания игр.

Если же вы настоящий разработчик игр, то уже в данный момент должны думать,о том, как переделать те программы, которые я дал, в настоящую игру. Не беспокойтесь! Мне и в голову не приходит сказать: «В качестве упражнения перепишите программу из Листинга 4.11 в настоящую игру». Мы напишем игру вместе, но чуть позже. Лучше поговорим о том, какие изменения нам надо внести, чтобы игра получилась.

Если вы хотите узнать, как я бы переделывал Листинг 4.8 в настоящую игру, то я бы сделал следующее:
  • Оснастил программу всем необходимым для выполнения миссии. Ведь нужно иметь маневренный космический корабль, несколько ракет для уничтожения астероидов, а чтобы игра приняла «товарный» вид, нужна система подсчета очков, заставка и, конечно, возможность выхода из программы. Кроме перечисленного потребуется создать систему оповещения о столкновениях с астероидами (такая система будет рассмотрена чуть позже, в этой же главе);
  • Далее я подготовил бы свой корабль. Для этого описал бы объект, который позаимствовал, например, из игры Star Trek;
  • Написал бы подпрограмму для рисования и уничтожения корабля (типа функции DrawAsteroids из Листинга 4.11);
  • Разработал бы функции, позволяющие игроку управлять кораблем;
  • Написал бы функции, которые, в зависимости от состояния клавиатуры, вращали корабль по или против часовой стрелки. Это я сделал бы, изменяя величину angles в структуре, описывающей объект корабля;
  • Потом мне понадобился бы способ передвижения корабля. Для этого я бы изменял значения x_velocity и y_velocity в соответствующих функциях:

x_veiocity = cos(angle)*speed

y_velocity = sin(angle)*speed
  • где скорость изменялась бы при нажатии на определенные клавиши;
  • Чтобы ввести корабль в игру, я поместил бы вызов функции стирания всех графических объектов - корабля и астероидов - в одно и то же место. Затем поместил бы функцию движения, которая опрашивает клавиатуру, в середину программы (между вызовом функций стирания и рисования);
  • В самый конец цикла вставил бы функцию рисования корабля.

Вот, собственно, и все. Выглядит не очень сложно. Посмотрим, что дальше.

Основы контроля столкновений

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

Это значит, что нам надо сделать зримым соударение космического корабля с астероидом. Правильнее всего будет поступить так:

  1. Забыть о реальных границах астероида и корабля и создать виртуальную граничную область вокруг каждого объекта исключительно для тестовых целей. Не надо ее рисовать, достаточно, чтобы она присутствовала внутри программы. Рисунок 4.10 показывает, что я имею в виду.
  2. Если мы увидим, что граничная область нашего судна вошла в граничную область астероида, мы можем сказать, что наш корабль уничтожен. Это довольно просто сделать: достаточно сравнить каждую вершину области с вершиной другой области. Если есть вхождение, значит произошло столкновение.
  3. Теперь, когда столкновение зафиксировано, нужно произвести на экране какой-нибудь красочный эффект: 500-тонный корабль с двигателем на антивеществе врезается в астероид. Например, вспышки случайных точек. Это будет неплохо смотреться.
  4. Подсчет очков прост: создайте переменную score и печатайте ее на экране каждый раз, проходя через главный цикл. Вы можете использовать функцию _outtext () из библиотеки Си.

Кроме этого, нам надо набирать очки. Значит, нужно иметь оружие. Например, корабль в игре может использовать фотонные торпеды — маленькие точки. Чтобы их получить, нам надо:

  1. Назначить одну из клавиш (лучше Пробел) для стрельбы.
  2. Когда будет обнаружено нажатие на клавишу, определить несколько переменных, в которых запомнить направление движения корабля (miss_xv и miss_yv).
  3. Затем запомнить в двух переменных исходную позицию ракеты, то есть координаты центра корабля (miss_x и miss_y).
  4. Присвоить переменной life значение 100.
  5. В цикле начать перемещение ракеты параллельно курсу корабля и каждый раз сравнивать координаты торпеды с границами астероидов. Если столкновение обнаружено, увеличить счетчик очков и удалить астероид.
  6. Уменьшить значение переменной life, и если она стала равна нулю, то уничтожить ракету и дать возможность игроку выстрелить следующий раз.

ИТОГ

Эта глава могла бы оказаться в сотни раз больше. У меня действительно была задача провести краткий курс двухмерной графики. Мы узнали много новых концепций и терминов. Теперь мы знаем, как производить простые преобразования объектов и точек на плоскости. Мы также узнали о матрицах (даже несколько больше, чем нужно) и как они могут быть использованы в компьютерной графике и видеоиграх для упрощения геометрических преобразований и придания им более компактной формы. В конце мы даже увидели поле астероидов на экране. Я уверен, что у вас есть много вопросов. Наверное, даже больше, чем в начале изучения этой книги. Я постараюсь ответить на них в дальнейшем. Но прошу вас помнить, что не существует простых и быстрых правил для написания игр. Это не сортировка массива. Нам, как программистам, нужен набор различных трюков для реализации наших идей, а посему продолжим работу.