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

Первый шаг


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

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

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

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

Запомните положение логической границы, разделяющей изображение на две части. Полученное число будет соответствовать ширине левой части изображения. Теперь назовем это число 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 <stdio.h>

#include<stdlib.h>

#include<string.h>

#include <time.h>

#include<dos.h>

#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<pcx->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<Height;i++)

{

// нарисовать левую половину битовой карты в правой половине буфера menicpy(dest+ScrollSplit,bmp,VIEW_WIDTH-ScrollSplit) ;

// нарисовать правую половину битовой карты в левой половине буфера memcpy(dest,bmp+VIEW_WIDTH-ScrollSplit,ScrollSplit);

bmp+=VIEW_WIDTH;

dest+=VIEW_WIDTH;

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

// Функция рисует смещающиеся слои

void DrawLayers()

{

OpaqueBlt(BackGroundBmp,0,100,background);

}

// Функция, обеспечивающая анимацию изображения.

// Наиболее критичная по времени выполнения.

// Для оптимизации как эту функцию, так и процедуры,

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

// (В среднем это увеличивает производительность на 30%)

void AnimLoop()

{

while(KeyScan != ESC_PRESSED)    // цикл, пока не нажата ЕSС

{

switch(KeyScan)                 // обработать нажатую клавишу

{

case RIGHT_ARROW_PRESSED:      // нажата правая стрелка

background-=1;      // скроллировать фон на 2

// пикселя влево

if(background < 1)           // еще не конец образа?

background+=VIEW_WIDTH;    // ...тогда, можно смещать

// фон дальше

break;

case LEFT ARROW_PRESSED:       // нажата левая стрелка

background+=1;               // скроллировать фон на 2

// пикселя вправо

if(background > VIEW_WIDTH-1) // еще не конец образа

background-=VIEW_WIDTH;     // ...тогда можно смещать

// фон дальше

break;

default:                        // обработать все остальные

// клавиши break;

} DrawLayers();

memcpy(VideoRam,MemBuf,MEMBLK); // копировать MemBuf в

// VGA-память

frames++;

} }

// Функция осуществляет полную инициализацию



void Initialize()

{

InitVideo();            // установить режим 13h

InitKeyboard();         // установить собственный обработчик

// прерываний клавиатуры

if(!InitBitmaps())      // прочитать битовые образы

Cleanup();            // освободить память

printf("\nError loading bitmaps\n");

exit(l);

} }

// функция восстанавливает исходное состояние системы

void Cleanup()

{

RestoreVideo();       // восстановить VGA

RestoreKeyboard();    // восстановить вектор клавиатуры

FreeMem();            // освободить память

}

// Начало основной программы

int main()

{

clock_t begin, fini;

Initialize();

begin=clock();         // получить "тики" часов при старте

AnimLoop();            // начать анимацию изображения

fini=clock();          // получить "тики" часов в конце

Cleanup();             // освободить память

printf("Frames: %d\nfps: %gf\n", frames,

(float)CLK_TCK*frames/(fini-begin));

return 0;

}


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