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

«Animotion»


Animation (animation (мультипликация )+ motion (движение)) — это придуманное мною слово, которое должно войти в словарь терминов компьютерных игр. (Дальше я тоже буду выдумывать слова, но они уже не будут так хороши как это.) Оно описывает точное слияние анимации (мультипликации) и движения.

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

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

«Animotion» абсолютно необходим, если вы хотите, чтобы движение -ходьба, бег или прыжки — выглядели реалистично. Иначе мультипликационные объекты выглядят неестественно. Мы не можем просто в цикле менять мульти пликационный кадр и одновременно передвигать объект на произвольное расстояние. Мы должны рассмотреть каждый кадр и определить, на сколько в действительности следует перемещать объект для этого кадра. Затем мы создаем таблицу выбора для движения, в которой в качестве индекса используем номера кадров. В этой таблице будут содержаться величины перемещений, которые и будут использоваться при оживлении картинки.

На рисунке 7.6 изображены мультипликационные кадры движения худого человечка, которого-мы назовем «человечек-палка».

(У «человечка-палки» серьезные проблемы со зрением — он имеет только один глаз.) Для движения человечка у нас будет 12 мультипликационных кадров. Для каждого из кадров я прикинул и задал величину перемещения, при котором движение становится действительно похожим на прогулку. Немного помучавшись, я получил для каждого мультипликационного кадра значения, приведенные в таблице 7.3.




Таблица 7.3. Таблица перемещений.



Кадр





Значение



Кадр



Значение



Кадр



Значение



1



17



5



3



9



6



2



0



6



0



10



2



3



6



7



17



11



3



4



2



8



0



12



0

Чтобы показать на примере, насколько «animotion» превосходит по качеству постоянное движение, я создал программу в которой игрок, нажимая на клавишу пробел, может выбирать между ними. Нашим «подопытным кроликом» снова будет беззащитный «человечек-палка». Мы собираемся поместить его в действительно ужасный городской квартал и при этом у него не будет ничего, кроме пары ног, пары рук и пары глаз. (Ой, извините! Глаз-то всего один.) Теперь посмотрите, как он разгуливает по экрану. Затем нажмите клавишу пробела, чтобы перейти с постоянного движения к «animotion». Вы увидите большую разницу. Текст программы приведен в Листинге 7.7, она также использует функции из GRAPH0.C, поэтому не забудьте скомпоновать программу с этим модулем.



Листинг 7.7. Демонстрация «animotion» (STICK.С).

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

#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 VEL_CONST -1  // флаг постоянной скорости перемещения

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

unsigned int far *clock = (unsigned int far *)0x0000046C;

// указатель на внутренний таймер 18.2 "тик"/с

sprite object;

pcx_picture stick_cells, street_cells;

// таблица выбора содержит величины перемещения для каждого

// мультипликационного кадра, чтобы движение было более реалистичным

int object_vel = {17,0,6,2,3,0,17,0,6,2,3,0};

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



void Timer (int clicks)

{

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

// 32-битовое значение этого таймера находится по адресу 0000:046Сh

unsigned int now;

// получаем текущее время

now = *clock;

//Ожидаем до истечения указанного периода времени.

// Заметьте, что каждый "тик"' имеет длительность примерно в 55 мс

while(abs(*clock - now) < clicks)() {}

// конец Timer

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

void main(void) {

int index, done=0,

vel_state=VEL_CONST;

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

Set_Mode(VGA256);

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

sprite_width = 32;

sprite_height =64;

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

файла PCX, который

содержит изображение

улицы

PCX_Init((pcx_picture_ptr)&street_cells) ;

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

PCX__Load("street.pcx", (pcx_picture_ptr)&street_cells,1} ;

PCX_Show_Buffer((pcx_picture_ptr)&street_cells) ;

// используем буфер PCX

как дублирующий

double_buffer = street_cells.buffer;

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

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

файла PCX, который

содержит кадры

спрайта

PCX_Init((pcx_picture_ptr)&stick_cells);

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

PCX_Load("stickman.pcx", (pcx_picture_ptr) &stick_cells,1) ;

// выбираем 6 кадров

движения

PCX_Grap_Bitmap((pcx_picture_ptr)&stick_cells,

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

PCX_Grap_Bitmap((pcx_picture_ptr)&stick_cells,

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

PCX_Grap_Bitmap( (pcx_picture_ptr) &stick_cells,

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

PCX_Grap_Bitmap((pcx_picture_ptr)&stick_cells,

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

PCX_Grap_Bitmap((pcx_picture_ptr)&stick cells,

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

PCX_Grap_Bitmap( (pcx_picture_ptr) &stick_cells, (sprite_ptr) &object, 5, 5, 0);

PCX_Grap_Bitniap ( (pcx_picture_ptr) &stick_cells,(sprite_ptr)&object,6, 0,1);



PCX_Grap_Bitmap((pcx_picture_ptr)&stick_cells,

(sprite_ptr)&object,7, 1,1);

PCX_Grap_Bitmap ( (pcx_picture_ptr) &stick_cells,

(sprite_ptr)&object,8, 2,1) ;

PCX_Grap_Bitmap((pcx_picture_ptr)&stick_cells,

(sprite_ptr) &object, 9, 3,1);

PCX_Grap_Bitmap((pcx_picture_ptr)&stick_cells,(sprite_ptr)&object,10,4,1);

PCX_Grap_Bitmap((pcx_picture_ptr)&stick_cells,

(sprite_ptr)&object,11,5,1);

// файл stickman.pcx больше не нужен

PCX_Delete((pcx_picture_ptr)&stick_cells);

// настраиваем параметры человечка

object.x        = 10;

object.у        = 120;

object.curr_frame = 0;

// сохраняем

фон

Behind_Sprite((sprite_ptr)&object);

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

while(!done)

{

// стираем спрайт

Erase_Sprite((sprite_ptr)&object) ;

// увеличиваем номер кадра на единицу

if (++object.curr_frame > 11) object.curr_fcame = 0;

// перемещаем спрайт, используя или постоянную скорость,

// или

таблицу выбора

if (vel_state==VEL_CONST)

{

object.x+=4;

}

// конец if

else

/         {

// используем номер кадра для определения величины

// перемещения по таблице выбора

object.x += object_vel[object.curr_frame];

} // конец else

}

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

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

 Во-первых, если мы будем полагаться на определенную аппаратуру, наши программы станут аппаратнозависимыми и не будут работать на картах VGA с меньшей памятью;



 Во-вторых, прокрутка в режиме 13h существенно сложнее, чем прокрутка в режимах EGA, так как видеопамять в этом случае не разбита на несколько плоскостей. Эта означает, что мы не можем использовать в данном режиме аппаратную прокрутку, таким же образом как в режимах EGA.

Таким образом, мы не будем использовать для прокрутки аппаратное обеспечение персонального компьютера. Мы будем это делать программно, применяя блочное копирование из памяти в видеобуфер. Используя этот метод, мы будем абсолютно независимыми от аппаратного обеспечения, следовательно наши программы будут более гибкими.

Прокрутку целого экрана или его части можно осуществлять двумя путями:

§          Можно нарисовать все пространство игры в обширном буфере памяти. Однако шесть предварительно нарисованных экранов займут 6х64000 байтов, то есть 384К. Это довольно большие потери памяти. В любом случае, как мы только что говорили, мы должны будем сделать воображаемое окно, перемещающееся по этому буферу и отображать все, что в него попадает, на экран;                                                          |

§          Второй метод более медленный, но требует меньше памяти. Он основан на генерации изображения «на лету». Под выражением "на лету" я подразумеваю, что мир будет представляться с помощью иначе структурированных данных - например, в виде двухмерной матрицы, где каждая ячейка 8х8 пикселей ставится в соответствие растровому изображению части игрового пространства. Такой ячеистый мир в шесть экранов будет занимать всего 6000 байт. При перемещении окна по ячейкам матрицы, на экране будут воспроизводиться соответствующие растровые изображения.

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


Это как раз и происходит из- за применения второго метода. Мы должны принести в жертву либо время, либо память. Выбор как всегда за вами.

Чтобы показать вам пример скроллинга, я написал программу, которая создает игровое пространство размером 640х100 пикселей. Я двигаю окно по изображению мира игры и передаю его содержимое в середину экрана. В этой игре мир состоит из звезд и гористого горизонта. (Этот пейзаж немного напоминает игру Defender.) Перемещая с помощью клавиатуры окно просмотра вправо и влево, вы можете прокрутить весь пейзаж. Листинг 7.8 содержит текст этой программы, которая называется DEFEND.С.

Листинг 7.8. Пример прокрутки (DEFEND.C).

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

#include <stdio.h>

#include <math.h>

#include <graph.h>

#inciude <malloc.h>

#include <memory.h>

#include <string.h>

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

#define SCREEN_WIDTH      (unsigned int)320

#define SCREEN_HEIGHT     (unsigned int)200

// ГЛОБАЛЬНЫЕ

ПЕРЕМЕННЫЕ

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

unsigned char far *video_buffer = (char far *)0xA0000000L;//указатель

на видеобуфер

unsigned char far *double_buffer = NULL;

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

void Show_View_Port(char far *buffer,int pos)

{     

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

unsigned int y,double_off, screen_off;

// нужно переместить 100 строк, перемещаем их построчно

for (y=0; у<100; у++) {

// расчет начального смещения дублирующего буфера

//у

* 640 +pos

double_off = ((у<<9) + (у<<7) + роs);

// расчет начального смещения в видеобуфере

// у * 320 + 80

screen_off = (((у+50)<<8) + ((у+50)<<6) + 80);

// перемещение данных

_fmemmove ((char far *)&video_buffer[screen off],

(char far *)&double_buffer[double_off],160);

} // конец

цикла for

} // конец Show View_Port ////////////////////////////////////////////////////////////////



void Plot_Pixel_Fast_D2(int x,int y,unsigned char color)

{

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

// экрана размером 640х100 пикселей

// учтем, что 640*у = 512*у + 128*у = у<<9 + у<<7

double_buffer[ ((у<<9) + (у<<7)) + х] = color;

} // конец Plot_Pixel_Fast_D2 ////////////////////////////////////////////////////////////

void Draw_Terrain(void) {

// эта функция рисует ландшафт в дублирующем буфере

// размером 640х100 пикселей

int х,у=70,index;

// очистка

памяти

_fmemset(double_buffer,0,(unsigned int}640*(unsigned int)100);

// рисуем

звезды

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

{

Plot_Pixel_Fast_D2(rand()%640,rand()%70,15);

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

//рисуем горы

for (x=0; x<640; х++)

{

// расчет смещения

y+=-1 + rand()%3;

// проверяем, находятся ли горы в приемлемых границах

if (y>90) у=90;

else

if (y<40) у=40;

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

Plot_Pixel_Fast_D2 (x,y, 10);

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

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

void main(void)

{

int done=0,sx=0;

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

_setvideomode(_MRES256COLOR);

_settextposition(0,0);

printf("Use < > to move. Press Q to quit.");

// рисуем небольшое окно

_setcolor(l);

_rectangle(_GBORDER, 80-1,50-1,240+1,150+1) ;

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

double_buffer=(char far *)_fmalloc(SCREEN_WIDTH*SCREEN_HEIGHT+1);

Draw_Terrain() ;

Show_View_Port(double_buffer,sx) ;

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

while (!done)

{// не Нажал ли игрок клавишу?

if (kbhit())

switch(getch())

{

case ',': // переместить окно влево, если это возможное

{

sх-=2;

if (sx<0)

sx=0;

} break;

case '.':// переместить окно вправо если это возможно

{                                                     

sx+=2;

if (sx > 640-160)

sx=640-160;

} break;

case 'q': // игроку

надоело?

{

done=1; } break;

} // конец

оператора

switch

// копируем окно просмотра на экран

Show_View_Port(double_buffer,sx);      

_settextposition(24,0);

printf("Viewport position = %d ",sx);

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

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

// восстановление видеорежима

_setvideomode(_DEFAULTMODE);   

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

Если вы похожи на меня, то, вероятно, захотите написать игру типа Defender. Отлично! Отложите книгу — и вперед. Не беспокойтесь, я подожду. По крайней мере, попытайтесь в своей программе заставить джойстик управлять полетом корабля.                                                      

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


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