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

Вид материалаДокументы

Содержание


Техника созданий параллакса
Подобный материал:
1   ...   29   30   31   32   33   34   35   36   37

^ ТЕХНИКА СОЗДАНИЙ ПАРАЛЛАКСА

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

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

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

Еще кое-что о режиме 13h

Прежде чем заняться разработкой первой программы, реализующей параллакс давайте еще раз поговорим об избранном видеорежиме. Как мы уже обсуждали в пятой главе «Секреты VGA-карт», наиболее популярным видеорежимом для программирования игр является режим 13h. Причиной номер 1 является его совместимость с различными микросхемами VGA. Другая причина, вероятно заключается в простоте программирования. Все нижеследующее является кратким обзором приемов программирования VGA в режиме 13h. Для более глубокого обзора необходимо вернуться к пятой главе. С другой стороны, если вы в совершенстве овладели программированием VGA-карт, то без ущерба можете пропустить этот раздел.

Режим 13h поддерживает разрешение 320х200 пикселей при одновременном отображении 256 цветов, где каждый пиксель представлен одним байтом. Следовательно, видеобуфер имеет размер 64000 байт. Эти 64К отображаются в область памяти, начиная с адреса А000:0000.

Все, что требуется для изображения пикселя в режиме 13h, это записать в видеобуфер байт. Проще всего это сделать, используя следующий код:

char far *VideoMem =MK_FP(0xA000,0) ;

Чтобы изобразить отдельный пиксель в позиции (200,100) с цветовым значением равным 2, вам необходимо только рассчитать смещение байта относительно начала видеопамяти. Смещение вычисляется путем умножения Y-координаты на 320 (поскольку в строке 320 пикселей) и добавлением Х-координаты:

PixelOffset = у * 320 + х;

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

VideoMem[PixelOffset] = 2;

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

Например, следующий фрагмент копирует 320 байт из массива Scr в видеобуфер:

memcpy(VideoMem, Scr, 320);

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

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

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

Примечание по поводу демонстрационной программы

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

Внимание

Все программы в этой главе написаны на Borland C++ 3.1 и Турбо ассемблере 3.1. Однако все примеры на Си были написаны с максимальной осторожностью, без привлечения особенностей Borland С. Так что они должны легко компилироваться любыми трансляторами C/C++ без внесения значительных изменений. Программы на ассемблере писались с использованием ключа IDEAL. Обратите внимание, что они не используются в демонстрационном примере, приведенном в этой главе, поскольку здесь же приведены их аналоги на Си.

Первый шаг

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

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

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

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

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

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



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

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

Затем обе части изображения выводятся на экран. Делается это так. Начиная с левой границы экрана, рисуется правая логическая часть изобраяжения:
  • Сначала выводится строка пикселей изображения, начиная со столбца определенного переменной LeftHalf. Количество рисуемых писелей равно общей ширине изображения минус LeftHalf;
  • Продолжайте рисование пикселей от левого края изображения до столбца LeftHalf.

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

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

// нарисовать левую половину изображения

memcpy(Screen+320-LeftHalf, Bitmap, LeftHalf) ;

// нарисовать „правую часть изображения

memcpy(Screen,Bitmap+LeftHalf,320-LeftHalf) ;

где Screen - указатель на видеопамять, LeftHalf - ширина логической левой части изображения, a Bitmap - указатель на битовую карту изображения, загруженную в память. Этот процесс повторяется для каждой строки развертки изображения.

Каждый раз, когда вы увеличиваете значение LeftHalf на единицу, вы должны убедиться, что оно не превышает общей ширины изображения:
  • Если LeftHalf больше ширины изображения, присвойте ей значение, равное единице;
  • Если LeftHalf меньше единицы, присвойте ей значение, равное общей ширине изображения.

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

Листинг 17.1 - это файл заголовка PARAL.H, в котором содержатся объявления различных констант, структур данных, а также прототипы функций, используемых в демонстрационной программе из Листинга 17.2 (PARAL.C).

Листинг 17.1. Файл заголовка демонстрационной программы циклического скроллинга (PARAL.H).

//

//Paral.h - данный заголовок определяет константы и структуры

//данных, используемые в демонстрационной программе

// параллакса

#define KEYBOARD 0х09 //

//Коды клавиатуры для прерывания INT 9h

#define RIGHT_ARROW_PRESSED 77

#define RIGHT_arrow_rel 205

#define LEFT_ARROW_PRESSED 75

#define LEFT_ARROW_REL 203

#define ESC_PRESSED 129

#define UP_ARROW_PRESSED 72

#define UP_ARROW_REL 200

#define DOWN_ARROW_PRESSED 80

#define down_arrow_rel 208

#define VIEW_WIDTH 320

#define VIEW_HEIGHT 150

#define MEMBLK VIEW_WIDTH*VIEW HEIGHT

#define TRANSPARENT 0 // цветовые коды

#define TOTAL_SCROLL 320

enum (NORMAL, RLE},;

enum (FALSE,TRUE};

typedef struct

{

char manufacturer; /* Всегда 0 */

char version; /* Всегда 5 для 256-цветных файлов */

char encoding; /* Всегда 1 */

char bits_per_pixel;

/* Должно быть равно 8 для 256-цветных файлов */

int xmin, ymin; /* Координаты левого верхнего угла */

int xmax,ymax; /* Высота и ширина образа */

int hres; /* Горизонтальное разрешение образа */

int vres; /* Вертикальное разрешение образа */

char palettel6[48];

/* палитра EGA; не используется для 256-цветных файлов */

char reserved; /* зарезервировано */

char color planes; /* цветовые планы */

int bytes_per_line;

/* количество байт в каждой строке пикселей */

int palette_type;

/* Должно быть равно 2 для цветовой палитры */

char filler[58]; /* Не используется */

} PcxHeader;

typedef struct

{

PcxHeader hdr;

char *bitmap;

char pal[768] ;

unsigned imagebytes,width,height;

} PcxFile;

#define PCX_MAX_SIZE 64000L enum {PCX_OK,PCX_NOMEM,PCX_TOOBIG,PCX_NOFILE};

#ifdef __cplusplus

extern "C" {

#endif

int ReadPcxFile(char *filename,PcxFile *pcx);

void _interrupt NewInt9(void) ;

void RestoreKeyboard(void);

void InitKeyboard(void);

void SetAllRgbPalette(char *pal);

void InitVideo (void);

void RestoreVideo(void);

int InitBitmaps(void); void FreeMem(void);

void DrawLayers(void);

void AnimLoop(void);

void Initialize(void);

void CleanUp (void) ;

void OpaqueBIt (char*, int, int, int) ;

void TransparentBit(char *,int,int,int) ;

#ifdef __cplusplus

} #endif

Программа из Листинга 17.2 (PARAL.C) демонстрирует повторяемое смещающееся изображение. Движущаяся картинка показывает облачное небо под солнцем. Хотя изображение и выглядит непрерывно меняющимся, но на самом деле оно неподвижно.

Наиболее важной частью программы является функция OpaqueBIt(). Она выводит левую и правую части изображения в буфер системной памяти, основываясь на значении LeftHalf. Когда построение закончено, содержимое буфера копируется на экран.

Запустив оттранслированную программу, используйте курсорные клавиши «влево» и «вправо» для изменения направления скроллинга. Для выхода из программы нажмите Esc. При этом она вычислит и покажет скорость анимации кадра. На машине с процессором 386SX/25 скорость выполнения составила около 35 кадров в секунду при размерах демонстрационного окна 320х100 Пикселей.

Листинг 17.2 Демонстрационная программа повторяемого смещения.

#include

#include

#include

#include

#include

#include "paral.h"

char *MemBuf, // указатель на буфер памяти

*BackGroundBmp, // указатель на скрытую битовую карту

*VideoRam; // указатель на память VGA

PcxFile pcx; // структура PCX-файла

int volatile KeyScan; // изменения клавиатурного обработчика

int frames=0, // количество нарисованных кадров

PrevMode; // сохраняет исходный видеорежим

int background;

void _interrupt (*OldInt9)(void); // указатель на клавиатурный

// обработчик BIOS

// Данная процедура загружает 256-цветный PCX-файл

int ReadPcxFile(char *filename,PcxFile *pcx)

{

long i;

int mode=NORMAL,nbytes;

char abyte,*p;

FILE *f;

f=fopen(filename,"rb") ;

if(f==NULL)

return PCX_NOFILE;

fread(&pcx->hdr,sizeof(PcxHeader),1,f) ;

pcx->width=1+pcx->hdr.xmax-pcx->hdr.xmin;

pcx->height=1+pcx->hdr.ymax-pcx->hdr.ymin;

pcx->imagebytes= (unsigned int) (pcx->width*pcx->height);

if(pcx->imagebytes > PCX_MAX_SIZE)

return PCX_TOOBIG;

pcx->bitmap= (char*)malloc (pcx->imagebytes);

if(pcx->bitmap == NULL)

return PCX_NOMEM;

p=pcx->bitmap;

for(i=0;i
imagebytes;i++)

{

if (mode == NORMAL)

{

abyte=fgetc(f);

if((unsigned char)abyte > Oxbf)

{

nbytes=abyte & Ox3f;

abyte=fgetc(f);

if(--nbytes > 0) mode=RLE;

}

}

else if(--nbytes == 0) mode=NORMAL;

*p++=abyte;

}

fseek(f,-768L,SEEK_END); // извлечь палитру из PCX-файла

fread(pcx->pal,768,1,f) ;

p=pcx->pal;

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

*p++=*p >>2;

fclose(f) ;

return PCX_OK; // успешное завершение

}

// Это новый обработчик прерывания 9h. Он позволяет осуществлять

// мягкий скроллинг. Если обработчик BIOS не будет запрещен,

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

// буфера клавиатуры и очень неприятному звуку из динамика.

void _interrupt NewInt9(void)

{

register char x;

KeyScan=inp(0х60); // прочитать символ с клавиатуры

x=inp(0x61); // сообщить клавиатуре, что символ обработан

outp(0x61,(х|0х80)) ;

outp(0х61,x);

outp (0х20,0х20} ; // сообщить контроллеру

// прерываний о завершении прерывания

if(KeyScan == RIGHT ARROW REL || // проверить кнопки

KeyScan == LEFT_ARROW_REL)

KeyScan=0;

}

// Функция восстанавливает прежний обработчик прерываний клавиатуры

void RestoreKeyboard(void) {

_dos_setvect (KEYBOARD, OldInt9); // восстановить прежний вектор

}

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

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

//в программе.

void InitKeyboard(void)

{

OldInt9=_dos_getvect(KEYBOARD); // сохранить вектор BIOS

_dos_setvect (KEYBOARD,NewInt9);// инсталлировать новый вектор

}

// Функция вызывает видео BIOS и заполняет все необходимые регистры

// для работы с палитрой, задаваемой массивом раl[]

void SetAllRgbPalette(char *pal)

{

struct SREGS s;

union REGS r;

segread(&s); // получить значение сегмента

s.es=FP_SEG((void far*)pal); // ES указывает на pal

r.x.dx=FP_OFF((void far*)pal); // получить смещение pal

r.x.ax=0xl012; // int l0h, функция 12h

// (установка регистров DAC)

r.x.bx=0; // первый регистр DAC

r.x.cx=256; // количество регистров DAC

int86x(0х10,&r,&r,&s); // вызвать видео BIOS }

// Функция устанавливает видеорежим BIOS 13h

// это MCGA-совместимый режим 320х200х256 цветов

void InitVideo()

{

union REGS r ;

r.h.ah=0x0f; // функция BIOS Ofh

int86(0х10,&r,&r); // вызывать видео BIOS

PrevMode=r.h.al; // сохранить текущий видеорежим

r.x.ax=0xl3; // установить режим 13h

int86(0х10,&r,&r); // вызвать видео BIOS

VideoRam=MK_FP(0xa000,0) ; // создать указатель на видеопамять

}

// Функция восстанавливает изначальный видеорежим

void RestoreVideo() {

union REGS r;

r.x.ax=PrevMode; // восстановить начальный видеорежиы

int86(0xl0,&r,&r); // вызвать видео BIOS

}

// Функция загружает битовые карты

int InitBitmaps()

{

int r;

background=l;

r=ReadPcxFile("backgrnd.pcx",&pcx); // прочитать битовую карту

if(r != РСХ_ОК) // выход при возникновении ошибки return FALSE;

BackGroundBmp=pcx.bitmap; // сохранить указатель битовой

// карты

SetAllRgbPalette(pcx.pal); // установить палитру VGA

MemBuf=malloct(MEMBLK); // выделить память под буфер

if(MemBuf == NULL) // проверить на ошибки при

// выделении памяти

return FALSE;

memset(MemBuf,0,MEMBLK); // очистить

return TRUE; // Порядок!

}

// Функция освобождает выделенную программой память

void FreeMem()

{

free(MemBuf);

free(BackGroundBmp);

}

// функция рисует прокручиваемую битовую карту, не содержащую

// прозрачных пикселей; для скорости используется функция memcpyO;

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

// разбивается на две части

void OpaqueBlt(char *bmp,int StartY,int Height,int ScrollSplit)

{

char *dest;

int i;

dest=MemBuf+StartY*320; // вычисляем начальную позицию буфера

for(i=0;i