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

       

Смещение мозаичных изображений


Одной из проблем, возникающей при выводе повторяющегося смещаемого изображения, является большой объем памяти, требуемый для размещения битовых карт. Практически, размер никакого отдельного изображения в D0b не может превышать 64К. Этого достаточно для изображений площадью 320х200 или 640х100 пикселей (как я уже говорил, в режиме 13h каждый пиксель занимает один байт). Но даже если бы вам и представилась возможность иметь изображения больших размеров, вы все равно очень скоро исчерпали бы память, поскольку в вашем распоряжении имеется максимум 640К.

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

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

Представьте себе смещающееся виртуальное изображение, состоящее из 5400х12 «кирпичиков», каждый из которых имеет размер всего 16х16 пикселей. Это означает, что площадь виртуального изображения составит 86400х192 пикселя, что намного больше, максимальных допустимых размеров любого отдельного изображения.

Единственным ограничением этого метода является то обстоятельство, что размер цельного образа полностью зависит от размеров отдельных «кирпичиков».


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

По практическим соображениям кирпичики должны иметь ширину, равную степени числа 2. То есть их размер по горизонтали должен составлять 2, 4, 16 и так далее вплоть до 320 пикселей в режиме 13h. Важность этих ограничений вы поймете позже.

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

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

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





Это свойство несколько упрощает построение мозаичного изображения. Таким образом, этот процесс состоит из трех шагов:

1.

Рисование первого (возможно, не полностью выведенного) «кирпичика»;



2.       Рисование нескольких (полностью выведенных) «кирпичиков»;

3.       Рисование последнего (возможно, не полностью выведенного) «кирпичика» .

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

Программа из Листинга 17.6 (TILES.С) демонстрирует моделирование мозаичных слоев. Самые ближние слои состоят из нескольких «кирпичиков». Определение виртуального изображения сохранено в файле TILEMAP.DAT, который представляет собой обычный ASCII-файл и обрабатывается во время инициализации. Цифры в файле представляют собой закодированные имена PCX-файлов. Обратите внимание, что код 0 зарезервирован для «прозрачного кирпичика». Рисунок 17.3 показывает небольшой пример мозаичного изображения.

Важным отличием между этой программой и демонстрационной программой двойного параллакса в Листинге 17.3 является добавление функции DrawTile().



Эта подпрограмма изображает «кирпичик» в указанном месте экрана. Два аргумента offset и width определяют соответственно начальный столбец и ширину для вывода не полностью помещающихся на экране «кирпичиков».

Для частично выведенных кирпичиков:

§          offset  -первый столбец, в котором будет нарисован «кирпичик»;

§          width - некоторое значение меньше ширины «кирпичика».

Для полностью выведенных «кирпичиков»:

§          offset - 0;

§          width - определяет ширину «кирпичика».

Программа из Листинга 17.6 также использует курсорные клавиши для управления движением и клавишу Esc для выхода. В демонстрационной программе на переднем плане появляется стена дома, составленная из отдельных «кирпичиков», а за ней видны хорошо известная горная гряда и линия неба на самом дальнем плане. Выполняется эта программа немного медленнее из-за использования в ней функции вывода мозаичного изображения, но только на пару кадров в секунду.


Листинг 17. 5 содержит файл заголовка для программы Построения мозаичного изображения, представленной в Листинге 17.6. В заголовке определены константы и прототипы функций для демонстрационной Программы.

Листинг 17.5. Заголовок мозаичного смещения (TILES.Н).

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

// прокрутки мозаичных изображений

#define NUM_TILES     17      // количество файлов,

// содержащих "кирпичики"

#define TILE_WIDTH    16      //ширина "кирпичиков"

#define TILE_HEIGHT   16      // высота "кирпичиков"

#define TILE_COLS     40      //ширина мозаичного изображения

#define TILE_ROWS      6      // высота мозаичного изображения

#define TILES_TOTAL   (TILE_COLS*TILE_ROWS)

#define TILES_PER_ROW (VIEW_WIDTH/TILE_WIDTH)

#define shift 4

ftifdef _cplusplus extern "C"

{

#endif

void ReadTiles(void);

void FreeTiles(void);

void ReadTileMap(char *);

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

void DrawTiles(int,int);

#ifdef __cplusplus

}

#endif

Поскольку программа из Листинга 17.6 практически повторяет 17.3, она приводится без комментариев.

Листинг 17.6. Демонстрационная программа мозаичного смещающегося слоя (TILES.C).

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <time.h>

#include <dos.h>

#include "paral.h"

#include "tiles.h"

char *MemBuf,

*BackGroundBmp,

*ForeGroundBnip,

*VideoRam;

PcxFile pcx;

int volatile KeyScan;

int frames=0,

PrevMode;

int background,

foreground, position;

char *tiles[NUM_TILES+l];

int tilemap[TILES_TOTAL] ;

void interrupt (*OldInt9)(void);

//        

//

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),l,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 & 0x3f;

abyte=fgetc(f);

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

}

}

else if(--nbytes ==0)

mode=NORMAL;

*p++=abyte;

}

fseek(f,-768L,SEEK_END);

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

p=pcx->pal;

for(i=0;i<768;i++) *p++=*p>>2;

fclose(f) ;

return PCX_OK;

}

//

void _interrupt NewInt9(void) {

register char x;

KeyScan=inp(Ox60);

x=inp(0х61) ;

outp(0x61,(x|0x80));

outp(0x61,x) ;

outp(0х20,0х20);

if(KeyScan == RIGHT__ARROW__REL ||

KeyScan == LEFT__ARROW_REL)

KeyScan=0;

}

//

void RestoreKeyboard(void) {

_dos_setvect(KEYBOARD,OldInt9);

}

//

void InitKeyboard(void)

{

Oldlnt9=_dos_getvect(KEYBOARD) ;

_dos_setvect(KEYBOARD,Newlnt9);

}

//

void SetAllRgbPalettefchar *pal)

{

struct SREGS s;

union REGS r;

segread(&s) ;

s.es=FP_SEG((void far*)pal);

r.x.dx=FP_OFF((void far*)pal);

r.x.ax=0xl012;

r.x.bx=0;

r.x.cx=256;

int86x(0xl0,&r,&r,&s) ;

}

//

void InitVideo()

{

union REGS r;

r.h.ah=0x0f;

int86(0xl0,&r,&r); PrevMode=r.h.al;

r.x.ax=0xl3;

int86(0xl0,&r,&r);

VideoRam=MK_FP(0xa000,0);

}                                     

//

void RestoreVideo()

{

union REGS r;

r.x.ax=PrevMode;

int86(0xl0,&r,&r) ;

}

//

int InitBitmaps()

{

int r;

background=foreground=l;

r=ReadPcxFile("backgrnd.pcx",&pcx) ;

if(r != PCX_OK) return FALSE;

BackGroundBnip=pcx.bitmap ;

SetAllRgbPalette(pcx.pal); ,

r=ReadPcxFile("foregrnd.pcx",&pcx);

if(r != PCX_OK) return FALSE;

ForeGroundBmp=pcx.bitmap;

MemBuf=malloc(MEMBLK) ;

if(MemBuf == NULL) return FALSE;

memset(MemBuf,0, MEMBLK) ;

return TRUE;

) //

void FreeMem()



{

free(MemBuf);

free(BackGroundBmp) ;

free(ForeGroundBmp) ;

FreeTiles(};

}

//

void DrawLayers()

{ OpaqueBlt(BackGroundBmp,0,100,background) ;

TransparentBIt(ForeGroundBmp,50,100,foreground) ;

DrawTiles(position,54) ;

}

//

void AnimLoop() {

while(KeyScan != ESC_PRESSED)

{

switch(KeyScan)

{ case RIGHT_ARROW_PRESSED:

position+=4;

if(position > TOTAL_SCROLL) {

position=TOTAL_SCROLL;

break;

}

background-=1;

if(background < 1)

background+=VIEW_WIDTH;

foreground-=2; if(foreground < 1)

foreground+=VIEW_WIDTH;

break;

case LEFT_ARROW_PRESSED:

position-=4;

if(position < 0) {

position=0;

break;

}

background+=1;

if(background > VIEW_WIDTH-1) background-=VIEW_WIDTH;

foreground+=2 ;

if (foreground > VIEW_WIDTH-1) foreground-=VIEW_WIDTH;

break;

default:

break;

} DrawLayers();

memcpy(VideoRam,MemBuf,MEMBLK) ;

frames++;

} }

//

void Initialize()

{

position=0;

InitVideo(} ;

InitKeyboard();

if(!InitBitmaps())

{

Cleanup();

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

exit(l);

} ReadTileMap("tilemap.dat");

ReadTiles();

}

//               void Cleanup() {

RestoreVideo() ;

RestoreKeyboard();

FreeMem();

}

void ReadTiles(void)

{

PcxFile pcx;

char buf[80];

int i,result;

tiles[0]=NULL;

for(i=l;i<=NUM_TILES;i++)

{

sprintf(buf,"t%d.pcx",i);

result=ReadPcxFile(buf,&pcx);

if(result != PCX_OK) ( printf("\ nerror reading file: %s\n",buf);

exit(1);

} tiles[i]=pcx.bitmap;

} }

void FreeTiles() { int i;

for(i=0;i<NUM_TILES;i++) free(tiles[i]) ;

}

void ReadTileMap(char *filename)

{

int i;

FILE *f;

f=fopen(filename,"rt") ;

for (i=0; i<TILES__TOTAL; i-H-) {

fscanf(f,"%d",&(tilemap[i])) ;

}

fclose(f);

}

//

void DrawTile(char *bmp,int x,int y,int offset, int width)

{

char *dest;

int i;

if(bmp == NULL) return;

dest=MemBuf+y*VIEW_WIDTH+x;

bmp+=offset;

for(i=0;i<TILE_HEIGHT;i++) {

memcpy(dest,bmp,width);

dest+=VIEW_WIDTH;

bmp+=TILE_WIDTH;

} }

//

void DrawTiles(int VirtualX,int Starty)

{

int i,x,index,offset,row,limit;

index=VirtualX>>SHIFT;

offset=VirtualX - (index<<SHIFT) ;

limit=TILES_PER_ROW;

if(offset==0)

limit--;

for(row=Starty;row<Starty+TILE_HEIGHT*TILE_ROWS;

row+=TILE_HEIGHT) {

x=TILE_WIDTH-of£set;

DrawTile(tiles[tilemap[index]],0,row,offset,

TILE_WIDTH-offset);

for(i=index+l;i<index+limit;i++)

{

DrawTile(tiles [tilemap [i]], x, row, 0, TILE_WIDTH) ;

x+=TILE_WIDTH;

} DrawTile(tiles [tilemap[i] ] ,x, row, 0,offset);

index+=TILE_COLS;

}

}

//

int main() { clock_t begin,fini;

Initialize() ;

begin=clock();

AnimLoop() ;

fini=clock() ;

Cleanup() ;

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

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

return 0;

)


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