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

         

Повороты объектов


Хотя мы и не собираемся вращать многочисленные растровые изображения, для полноты картины познакомимся с основными принципами этого процесса.

Вращение битовых объектов до сих пор еще не до конца исследованная область. Инженеры и программисты и по сей день пытаются найти наиболее эффективные алгоритмы для этой операции. Проблема заключается в том, что битовый образ - это конгломерат сотни, если не тысячи пикселей и чтобы повернуть образ, нам надо правильно произвести поворот каждой из этих точек, Обычно это связано с довольно сложными математическими преобразованиями, которые выполняются на персональном компьютере не очень-то быстро. Вы можете спросить: «А как же это сделал Крис Роберте в игре Wing Commander?» Очень просто: он заранее получил все возможные повороты всех космических кораблей с помощью программы моделирования, а потом просто занес их в гигантскую таблицу. Единственная операция, которая могла бывыполняться долго ~ масштабирование, тоже была произведена заранее, а результаты также занесены в таблицу. Я всегда действую подобным же образом и вам советую заранее создать все варианты поворотов ваших изображений, используя один из двух пакетов, либо Deluxe Paint фирмы Electronic Arts или, если вам нравится тратить деньги, 3D Studio фирмы AutoDesk. Затем поместите их в таблицу, и пусть ваша программа извлекает изображения из этой таблицы, используя угол поворота, как индекс. Отрицательная сторона этого метода - не очень рациональное использование памяти. Хранение 32 или 64 битных образов для всех возможных объектов отъедает существенный кусок памяти. Однако можно использовать частичные а не полные таблицы. Например, вы можете поместить в таблицу только кадры для одного квадранта, а для других симметричных квадрантов генерировать изображения уже во время игры. Так как поворот вокруг осей Х и Y делается очень просто — такой метод используется очень часто.

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


Эта программа, которую я назвал AFIELD.С, передвигает астероид по трехмерному звездному полю, масштабируя его по мере приближения или удаления оигрока. Листинг 7.11 содержит текст этой программы.

Листинг 7.11. Трехмерный астероид (AFIELD.С).

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

#include <io.h>

#include <conio.h>                                       

#include <stdio.h>

#include <stdlib.h>

#include <dos.h>



#include <bios.h>

#include <fcntl.h>

#include <memory.h>

#include <malloc.h>

#include <math.h>

#include <string.h>

#include "graph0.h" // включаем нашу графическую библиотеку

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

#define NUM_STARS 30

// СТРУКТУРЫ ///////////////////////////////////////////////

typedef struct star_typ

{

int x,y;    // позиция звезды

int vel;    // скорость звезды по координате х

int color; // цвет звезды

} star,*star_ptr;

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

star stars[NUM_STARS]; // звездное поле

sprite object;

 pcx_picture ast_cells;

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

void Star_Field(void) {

static int star_first=1; // Эта функция создает трехмерное звездное поле

int index;

// проверяем, следует ли нам создать звездное поле,

//то есть первый ли раз вызвана функция

if (star_first)

{ // обнуляем флаг первого вызова

star_first=0;

// инициализируем все звезды

for (index=0; index<NUM STARS; index++)

{ // инициализируем для каждой звезды позицию, скорость и цвет

stars[index].х     = rand()%320;

stars[index].у     = rand()%180;

// определяем плоскость звезды

switch(rand()%3){

case 0: // плоскость 1 - самая далекая

{

// установка скорости и цвета

stars[index].vel = 2;

stars[index].color.= 8;

} break;

case 1: // плоскость 2 - среднее расстояние

{

stars[index].vel = 4;

stars[index].color = 7;

) break;

 case 2://плоскость 3 - самая близкая



{

stars[index].vel = 6;

stars[index].color = 15;

} break;

} // конец оператора switch

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

//конец оператора if else                                  

{ // это не первый вызов, поэтому делаем рутинную работу -

// стираем, двигаем, рисуем                        

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

{                                 

if ((stars[index].x+=stars[index].vel) >=320 ) stars[index].x = 0;

// рисуем

Plot_Pixel_Fast_D(stars[index].x,stars[index].y, stars[index].color);     

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

} // конец оператора else

} // конец Star_Field                         

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

void Scale_Sprite(sprite_ptr sprite,float scale)    

{

// эта функция масштабирует спрайт, рассчитывая число дублирования

// исходных пикселей, необходимое для получения требуемого размера

char far *work_sprite;

int work_offset=0,offset,x,у;

unsigned char data;          

float y_scale_index,x_scale_step,y_scale_step,x_scale_index;

// берем первый пиксель исходного изображения

y_scale_index = 0;

// рассчитываем

дробный шаг

y_scale_step = sprite_height/scale;

x_scale_step = sprite_width /scale;

// для простоты даем указателю на спрайт новое имя

work_sprite = sprite->frames[sprite->curr_frame];

// расчет смещения спрайта в видеобуфере

offset = (sprite->y << 8) + (sprite->y << 6) + sprite->x;

// построчно масштабируем спрайт

for (у=0; y<(int) (scale); у++)

{

// копируем следующую строчку в буфер экрана

x_scale_index=0;

for (x=0; x<(int)scale; x++)

{

// проверка на прозрачность пикселя

//(то есть равен ли он 0), если нет - прорисовываем пиксель

if ((data=work_sprite[work_offset+(int)x_scale_index]))

double_buffer[offset+x] = data;

x_scale_index+=(x_scale_step) ;

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

//используя дробный шаг приращения определяем

// следующий пиксель исходного изображения

у_scale_index+=y_scale_step;

// переходим к следующей строке видеобуфера



//и растрового буфера спрайта

offset     += SCREEN_WIDTH;

work_offset = sprite_width*(int)(y_scale_index) ;

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

} // конец Scale Sprite ////////////////////////////////////////////////////////////

void Clear_Double_Buffer(void)

{ // эта функция очищает дублирующий буфер

// несколько грубо, зато работает

_fmemset(double_buffer, 0, SCREEN_WIDTH * SCREEN_HEIGHT + 1);

} // конец Clear_Double_Buffer

// ОСНОВНАЯ ПРОГРАММА ///////////////////////////////////////

void main(void)

{

int index,

done=0,dx=5,dy=4,ds=4;

float scale=5;

// установка видеорежима 320х200х256

Set_Mode(VGA256) ;

// установка размера спрайта

sprite_width

= sprite_height = 47;

// инициализация файла PCX, который содержит

// мультипликационные кадры

PCX_Init((pcx_picture_ptr)&ast_cells) ;

// загрузка файла PCX, который содержит мультипликационные кадры

PCX_Load("asteroid.рсх", (pcx_picture_ptr)&ast_cells,1) ;

// резервируем память под дублирующий буфер

Init_Double_Buffer() ;

Sprite_Init((sprite_ptr)&object,0,0,0,0,0,0) ;

// загрузка кадров вращающегося астероида

PCX_Grap_Bitmap((pcx_picture_ptr)&ast_cells,

(sprite_ptr)&object,0,0,0);

PCX_Grap_Bitmap ((pcx_picture_ptr) &ast_cells,

(sprite_ptr)&object,1,1,0} ;

PCX_Grap_Bitmap((pcx_picture_ptr)&ast_cells,

(sprite_ptr)&object,2,2,0) ;

PCX_Grap_Bitmap((pcx_picture_ptr)&ast_cells,

(sprite_ptr)&object,3,3,0) ;

PCX_Grap_Bitmap ((pcx_picture_ptr) &ast_cells,

(sprite_ptr)&object,4,4,0);

PCX_Grap_Bitmap((pcx_picture_ptr)&ast_cells,

(sprite_ptr)&object,5,5,0) ;

PCX_Grap_Bitmap((pcx_picture_ptr)&ast_cells, (sprite_ptr)&object,6,0,1);

PCX_Grap_Bitmap({pcx_picture_ptr)&ast_cells, (sprite_ptr)&object,1,1,1) ;

// позиционирование объекта в центре экрана

object.curr_frame =0;

object.x          = 160-(sprite width>>1);

object.у          = 100-(sprite_height>>1) ;

// очистка

дублирующего буфера

Clear_Double_Buffer();



// вывол

масштабированного спрайта

Scale_Sprite((sprite_ptr)&object,scale) ;

Show_Double_Buffer(double_buffer) ;

// главный цикл

while (!kbhit())

{ // масштабируем астероид

scale+=ds;

// не слишком ли велик или мал астероид?

if (scale>100 |1 scale < 5)

{

ds=-ds;

scale+=ds;

} // конец if

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

object.x+=dx;

object.y+=dy;

// коснулся ли астероид края экрана?

if ((object.x + scale) > 310 || object.x < 10)

{

dx=-dx;

object.x+=dx;

} // конец if

if ((object.у + scale) > 190 || object.у < 10) {

dy=-dy;

object.y+=dy;

} // конец if

// поворот астероида на 45 градусов

if (++object.curr_frame==8) object.curr_frame=0; // очистка дублирующего буфера

Clear_Double_Buffer();

// прорисовка звезд

Star_Field() ;

// масштабируем спрайт и выводим его в дублирующий буфер

Scale_Sprite((sprite_ptr)&object,scale) ;

// выводим дублирующий буфер на экран

Show_Double_Buffer (double_buffer);

} // конец оператора while

// удаляем файл PCX

PCX_Delete ((pcx_picture_ptr) &ast_cells);

// возврат в текстовый режим

Set_Mode (TEXT_MODE) ;

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


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