Программирование игр для Windows. Советы профессионала

       

Вращение объектов


Для того чтобы вращать объект, мы должны повернуть его вокруг одной из координат. В двухмерной графике для этого обычно выбирается ось Z. Пока мы находимся в двухмерном мире, нас не беспокоит третье измерение - мы просто не придаем ему значения.

Если экран — это плоскость X-Y, то ось Z — это перпендикуляр к осям Х и Y. Таким образом, если мы описываем наши объекты относительно двухмерного мира, то у нас появляется возможность вращать их относительно оси Z,

Следующие формулы позволяют вращать произвольную точку (X,Y) отно­сительно оси Z:

new_x = x*cos(angle) - y*sin(angle) new_у = y*cos(angle) + y*sin(angle)

где angle — это угол, на который вы хотите повернуть точку. Кроме этого вам стоит помнить еще пару вещей:

§          Положительные углы имеют эффект вращения по часовой стрелке;

§          Отрицательные углы имеют эффект вращения против часовой стрелки.

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

Deg_To_Rad(deg) {pi*deg/180;}

Rad_To_Deg(rad) {180*rad/pi;}

Другими словами, это значит, что в круге 360 градусов или 2хPi радиан. Теперь нам нужно написать функцию для вращения объекта. Давайте просто используем формулы, не задумываясь о том, как и почему они работают. Функция в Листинге 4.7 делает именно то, что мы хотим.

Листинг 4.7. Вращение объекта.

void Rotate_0bject(object__ptr object, float angle)



{

int index;       

float x_new, y_new,cs, sn;

// сначала вычислим синус и косинус угла

сs = cos(angle) ;

sn = sin(angle);

// поворачиваем каждую вершину на угол angle

for (index=0; index<object->num_vertices; index++)

{

x_new = object->vertices [index].x*cs-object->vertices[index].y*sn;

y_new = object->vertices [index].y*cs+object->vertices[index].x*sn;




// изменяем исходные координаты.на расчетные

object->vertices[index].x = x_new;

object->vertices[index].y = у_new;

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

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

Думаю, что надо кое-что объяснить. Я вычисляю заранее значения синуса и косинуса для данного угла. Зачем, спросите вы. Ответ прост — для скорости. Ибо заниматься вычислениями тригонометрических функций в процессе работы программы можно позволить себе только, имея математический сопроцессор.

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

Я хотел бы иметь поле астероидов различных размеров в количестве более 100 штук. И так, чтобы они могли вращаться. Для этого программа должна иметь следующую структуру:

Шаг 1. - Инициировать поле астероидов;

Шаг 2. - Стереть поле астероидов;

Шаг 3. - Трансформировать поле астероидов;

Шаг 4. - Нарисовать поле астероидов;

Шаг 5. - Перейти к Шагу 2, пока пользователь не нажмет на кнопку.

Чтобы сделать это проще, я добавил три новых поля к нашей структуре: одно для угла поворота и два - для скорости (целиком программа представлена в Листинге 4.8).

Листинг 4.8. Программа, которая рисует поле астероидов (FIELD.С).

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

#include <stdio.h>

#include <graph.h>

#include <math.h>

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

#define NUM_ASTEROIDS 10

#define ERASE 0

#define draw 1

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

//определяем структуру "вершина"

typedef struct vertex_typ

{

float x,y; // координаты точки на плоскости

} vertex, *vertex__ptr;

// структура объекта

typedef struct object_typ

{

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

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

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

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



float y_velocity;     // и y

float scale;          // коэффициент масштабирования

float angle;          // угол поворота

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 Scale_Object(object_ptr object,float scale)

{

int index;

// для всех вершин масштабируем координаты х и у

for (index = 0; index<object->num_vertices; index++)

{

object->vertices[index].x *= scale;

object->vertices[index].y *= scale;

}// end for index

// конец

функции ///////////////////////////////////////////

void Rotate_Object(object_ptr object, float angle)

{

int index;

float x_new, y_new,cs,sn;

// заранее вычислить синус и косинус

cs = cos(angle);

sn = sin(angle);

// поворачиваем каждую вершину на угол angle

for (index=0; index<object->num_vertices; index++)

{

x new = object->vertices[index].x * cs - object->vertices[index].y * sn;      

у new = object->vertices[index].y * cs + object->vertices[index].x * sn;

object->vertices[index].x = x_new;

object->vertices[index].y = y_new;

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

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

void Create_Field(void)

{

int index;

// формируем поле астероидов

for (index=0; index<NUM_ASTEROIDS; 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 + randO % 20;

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

asteroids[index].angle = (float) (-50+(float)(rand()%100))/100;



asteroids[index].vertices [0].x =4.0;

asteroids[index].vertices[0].у = 3.5;

asteroids[index].vertices[l].x=8.5;

asteroids[index].vertices[1].y = -3.0;

asteroids[index].vertices[2].x = 6;

asteroids[index].vertices[2].у = -5;

asteroids[index].vertices[3].x = 2;

asteroids[index].vertices[3].у =—3;

asteroids[index].vertices[4].x = -4;

asteroids[index].vertices[4].у = -6;

asteroids[index].vertices[5].x = -3.5;

asteroids[index].vertices[5].у =5.5;

// теперь масштабируем каждый астероид до нужного размера

Scale_Object((object_ptr)&asteroids [index],

asteroids[index].scale) ;

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

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

void Draw_Asteroids(int erase)

{

int index,vertex;

float xo,yo;

for (index=0; index<NUM_ASTEROIDS; 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[0].x),

(int)(yo+asteroids[index],vertices[0].y));

for (vertex=1; vertex<asteroids[index].num_vertices; vertex++)



_lineto((int)(xo+asteroids[index].vertlces[vertex].x),(int) (yo+asteroids[index].vertices [vertex].y));

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

по вершинам

// замыкаем

контур

_lineto((int)(xo+asteroids[index].vertices[0].x), (int)(yo+asteroids[index].vertices[0].y));

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

по астероидам

} // конец

функции                                      ///////////////////////////////////////////////////////////////////////////////////////////

void Translate_Asteroids()

{

int index;

for (index=0; index<NUM_ASTEROIDS; 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 ||  asteroids[index].yo < 40)

{

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

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

}

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

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

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

void Rotate_Asteroids(void)

{

int index;

for (index=0; index<NUM_ASTEROIDS; index++)

{

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

Rotate_0bject ((object_ptr) &asteroids [index],

asteroids[index].angle);

} // конец цикла 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);

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

// устанавливаем текстовый режим

_setvideomode( DEFAULTMODE);

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

Набрав, откомпилировав и запустив программу из Листинга 4.8, вы увидите на экране поле астероидов вместе со множеством разноцветных камней, отскакивающих от границ экрана (определение факта столкновения будет подробнее излагаться далее в этой главе, а также в главе одиннадцатой, «Алгоритмы, структуры данных и методология видеоигр»)

Теперь нам надо еще кое-что обсудить:

§          Первое, на что вы обратили внимание, это то, что образы слегка мелькают. Это связано с тем, что программа формирует изображение в тот же момент, когда происходит перерисовка экрана.

§          Экран — это множество линий, которые рисуются на мониторе слева направо и сверху вниз вашей видеокартой. Проблема заключается в том, что мы не можем изменять видеобуфер, пока на экране что-то рисуется. В пятой главе, «Секреты VGA-карт», мы обсудим рисование всего экрана целиком в отдельный буфер с последующим перемещением его в видеопамять;



§          Другая проблема, связанная с мерцанием, заключается в том, что мы используем графическую бибилотеку Microsoft С, которая не очень быстро работает. Вы должны понимать, что Microsoft не оптимизировал ее для высокой производительности и скорости работы;

§          Программа использует числа с плавающей запятой, которые впоследствии будут заменены числами с фиксированной запятой;

§          Вся программа совершенно неэффективна с точки зрения написания видеоигр. Все, что она делает, выполнено в классической, «книжной» манере. У разработчиков видеоигр есть правило номер 1: «Всегда есть способ сделать то, что кажется невозможным». Если б это было не так, то половина видеоигр вообще никогда не была бы написана, поскольку ПК не в состоянии обеспечить нужной производительности.

(Поверьте мне, DOOM — это лучший пример моим словам. Если бы я его не видел, то никогда не поверил бы, что такое возможно. Но, как мы знаем, DOOM существует, и это наилучший пример использования правильных методов для создания реального мира на компьютерном экране.)


Содержание раздела