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

Игра Tombstone


Мы начали с того, что вообще ничего не знали о VGA-карте. Сейчас мы знаем очень много. Поэтому я думаю, что было бы лучше, если бы вы написали маленькую двухмерную игру для закрепления пройденного материала. Чтобы вам в этом помочь, я написал демонстрацию и назвал ее Tombstone.

В этой демонстрации маленький ковбой ходит по городу с различной скоростью. У вас есть все инструменты, чтобы «дать» ему пистолет и "научить" стрелять. В PCX-файле на дискете вы найдете для этого все необходимые картинки. Вся программа за исключением функции Set_Mode() дана в Листин­ге 5.16. Прилинкуйте Set_Mode(), когда будете создавать исполняемый файл.

Листинг 5.16. Tombstone (TOMB.С).

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

ФАЙЛЫ

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

#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 <math.h>

#include <string.h>

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

#define ROM_CHAR_SET_SEG

0xF000 // сегмент описания символов в ПЗУ

#define ROM_CHAR_SET_OFF

0xFA6E // смещение, соответствующее

// описанию первого символа

#define VGA256            0х13

#define TEXT_MODE         0х03

#define PALETTE_MASK         ОхЗC6

#define PALETTE_REGISTER_RD 0x3C7

#define PALETTE_REGISTER_WR 0x3C8

#define PALETTE_DATA        0x3C9

#define SCREEN_WIDTH      (unsigned int)320

#define SCREEN_HEIGHT     (unsigned int)200

#define CHAR_WIDTH        8

#define CHAR_HEIGHT       8

#define SPRITE_MIDTH      24

#define SPRITE_HEIGHT     24

#define MAX_SPRITE_FRAMES 16

#define SPRITE_DEAD       0

#define sprite_alive      1

#define SPRITE_DYING      2                               

// СТРУКТУРЫ

ДАННЫХ ////////////////////////////////////////

typedef struct RGB_color_typ

{

unsigned char red;   // красная составляющая цвета (0-63)

unsigned char green; // зеленая составляющая цвета (0-63)




unsigned char blue; // синяя составляющая цвета (0-63)

} RGB_color, *RGB_color_ptr;

typedef struct pcx_header_typ

{ char manufacturer;

char version;

char encoding;

char bits_per_pixel;

int x,y;

int width,height;

int horz_res;

int vert_res;

char ega_palette[46];

char reserved;

char num_color_planes;

int bytes_per_line;

int palette_type;

char padding[58];

} pcx_header, *pcx_header_ptr;

typedef struct pcx_picture_typ

{

pcx_header header;

RGB_color palette[256];

char far *buffer;

} pcx_picture, *pcx_picture_ptr;

typedef struct sprite_typ

{

int x,y;            // текущая позиция спрайта

int x_old,y_old;    // предыдущая позиция спрайта

int width,height;   // размеры спрайта

int anim_clock;     // время анимации

int anim_speed;     // скорость анимации

int motion_speed;   // скорость движения

int motion_clock;   // время движения

char far *frames[MAX_SPRITE__FRAMES]; // массив указателей

// на образы

int curr_frame;                      // отображаемый фрейм

int num_frames;                      // общее число фреймов

int state;                           // статус спрайта

char far *background;               // фон под спрайтом

}sprite, *sprite_ptr;

// ВНЕШНИЕ ФУНКЦИИ /////////////////////////////////

extern Set_Mode(int mode);

// ПРОТОТИПЫ ///////////////////////////////////////

void Set_Palette_Register(int index, RGB_color_ptr color);

void Plot_Pixel_Fast(int x,int y,unsigned char color);

void PCX_Init(pcx_picture *image);

void PCX_Delete(pcx_picture *image);

void PCX_Load(char *filename, pcx_picture_ptr image, int enable_palette) ;

void PCX_Show_Buffer(pcx_picture_ptr image);

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

unsigned char far *video_buffer   = (char far *) 0xA0000000L;

unsigned int  far *video_buffer_w = (int  far *)0xA0000000L;

unsigned char far *rom_char_set   = (char far *)0xF000FA6EL;

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

void Blit_Char(int xc,int yc,char c,int color) {



// эта функция отображает символ на экране

// используя описание

// символов размером 8х8 точек, хранящееся в ПЗУ

int offset,х,у;

unsigned char data;

char far *work_char;

unsigned char bit_mask = 0х80;

// вычисляем смещение описания символа в ПЗУ

work_char = rom_charset + с * CHAR_HEIGHT;

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

offset = (ус << 8} + (ус << 6) + хс;

for (у=0; y<CHAR_HEIGHT; y++)

{

// сбросить битовую маску

bit_mask = 0х80;

for (x=0; x<CHAR_WIDTH; x++)

{ // если бит равен 1, рисуем пиксель

if ((*work_char & bit_mask))

video_buffer[offset+x] = color;

// сдвигаем

маску

bit_mask = (bit_mask>>1);

} // конец отрисовки строки

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

offset      += SCREEN_WIDTH;

work_char++;

} // конец рисования

} // конец

функции

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

void Blit_String(int x,int y,int color, char *string)

{ // функция отображает на экране передаваемую строку символов

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

// Для отображения символов вызывается функция blit_char

int index;

for (index=0; string[index]!=0; index++)

{

Blit_Char(x+(index<<3) ,y, string[index],color);

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

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

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

void Delay(int t)

{

float x = 1;

while(t—>0)

x=cos(x);

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

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

void Set_Palette_Register(int index, RGB_color_ptr color) {

// функция устанавливает элемент таблицы цветов задаваемый

// параметром index. Значения компонент цвета задаются полями

// структуры color.

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

_outp(PALETTE_MASK,Oxff);

// указываем номер изменяемого регистра

_outp(PALETTE_REGISTER_WR, index);

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

// один и тот же порт

_outp(PALETTE_DATA,color->red);

_outp(PALETTE_DATA,color->green);

_outp(PALETTE_DATA,color->blue);



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

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

void PCX_Init(pcx_picture_ptr image)

{

// функция выделяет память для загрузки PCX-файла

if ((image->buffer = (char far *)malloc (SCREEN_WIDTH * SCREEN_HEIGHT +1)));

printf("\ncouldn't allocate screen buffer");

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

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

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

{

// функция отображает на экране точку заданного цвета

// вместо умножения используется сдвиг

// пользуемся тем, что 320*у=256*у+64*у=у<<8+у<<6

video_buffer[ ((у<<8) + (у<<6)) + х] = color;

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

void PCX_Delete(pcx_picture_ptr image)

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

_ffree (image->buffer);

} // конец

функции

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

void PCX_Load(char *filename, pcx_picture_ptr image, int enable_palette}

{ // функция загружает PCX-файл и заполняет поля структуры

// pcx_picture, включая изображение (после декомпрессии),

// заголовок и палитру

FILE *fp;

int num_bytes,index;

long count;

unsigned char data;

char far *temp_buffer;

// открыть

файл

fp = fopen(filename,"rb");

// загрузить

заголовок

temp_buffer = (char far *)image;

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

{

temp_buffer[index] = getc(fp);

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

// загрузить данные и декодировать их в буфере

count=0;

while(count<=SCREEN_WIDTH * SCREEN_HEIGHT)

{ // получить первую часть данных

data = getc(fp);

// это RLE?

if (data>=192 && data<=255)

{ // сколько байт сжато?

num_bytes = data-192;

// получить значение цвета для сжатых данных

data = getc(fp);

// заполняем буфер полученным цветом

while(num_bytes-->0)

{

image->buffer[count++] = data;

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

} // конец обработки сжатых данных

else

{

// поместить значение цвета в очередную позицию



image->buffer[count++] = data;

} // конец обработки несжатых данных

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

// перейти в позицию, не доходя 768 байт от конца файла

fseek(fp,-768L,SEEK_END) ;

// загрузить

палигру

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

{

// красная

составляющая

image->palette[index].red = (getc(fp) >> 2);

// зеленая

составляющая

image->palette[index].green = (getc(fp) >> 2);

// синяя

составляющая

image->palette[index].blue = (getc(fp) >> 2) ;

} // конец

цикла for

fclose(fp);

// если установлен флаг enable_palette, установить новую палитру

if (enable_palette)

{

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

{

Set_Palette_Register(index,

(RGB_color_ptr)&image->palette[index]);

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

} // конец установки палитры

} // конец

функции

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

void PCX_Show_Buffer (pcx_picture_ptr image)

{ // функция копирует буфер, содержащий изображение из PCX-файла,

// в видеопамять

_fmemcpy(char far *)video_buffer,

 (char far *) image->buffer,SCREEN_WIDTH*SCREEN__HEIGHT);

} // конец

функции

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

void Sprite_Init(sprite_ptr sprite, int x,int y, int ac, int as,int mc,int ms)

{

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

int index;

sprite->x            = x;

sprite->y            = у;

sprite->x_old        = x;

sprite->y_old        = у;

sprite->width        = SPRITE_WIDTH;

sprite->height       = SPRITE_HEIGHT;

sprite->anim_clock   = ac;

sprite->anim_speed   = as;

sprite->motion_clock = me;

sprite->motion_speed = ms;

sprite->curr frame   = 0;

sprite->state        = SPRITE_DEAD;

sprite->num_frames   = 0;

sprite->background  = (char far *)malloc (SPRITE_WIDTH * SPRITE_HEIGHT+1);

// устанавливаем все указатели в значение NULL

for (index=0; index<MAX_SPRITE_FRAMES; index++) sprite->frames[index] = NULL;

} // конец

функции

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



void Sprite_Delete(sprite_ptr sprite)

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

int index;

_ffree(sprite->background) ;

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

for (index=0; index<MAX_SPRITE_FRAMES; index++) _ffree(sprite->frames(index]);

} // конец

функции

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

void PCX_Grap_Bitmap (pcx_picture_ptr image, sprite_ptr sprite, int sprite_frame, int grab_x, int grab_y)

{

// функция выделяет один кадр из буфера PCX-файла.

// Предполагается, что изображение размером 320х200 пикселей

// в действительности представляет собой массив 12х8 изображений

// каждое размерностью по 24х24 пикселя

int x_off,y_off, x,y, index;

char far *sprite_data;

//вначале выделяем память для размещения спрайта

sprite->frames[sprite_frame] = (char far *)malloc (SPRITE_WIDTH * SPRITE_HEIGHT);

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

sprite_data = sprite->frames[sprite_frame];

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

// вначале вычисляем, какое из изображений копировать.

// Помните: в действительности PCX-файл представляет собой массив

// из отдельных элементов размером 24х24 пикселя.

// Индекс (0,0) соответствует левому верхнему изображению,

// (11,7) - правому нижнему.

x_off

= 25 * grab_x + 1;

y_off = 25 * grab_y + 1;

// вычисляем начальный адрес

y_off

= y_off * 320;

for (y=0; y<SPRITE__HEIGHT; y++)

{

for (x=0; x<SPRITE_WrETH; x++)

{

// берем очередной байт и помещаем его в текущую позицию буфера

sprite_data[y*24 + х] = image->buffer [y_off + х_off + х];

} // конец копирования строки

// переходим к следующей строке y_off+=320;

} // конец копирования

// увеличиваем счетчик кадров sprite->num_frames++;

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

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

void Behind_Sprite(sprite_ptr sprite)

{

// функция сохраняет содержимое видеопамяти в той области,

// куда будет выведен спрайта

char far *work back;

int work_offset=0,offset,y;



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

work_back = sprite->background;

// вычисляем смещение в видеопамяти

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

for (y=0; y<SPRITE_HEIGHT; y++)

{

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

_fmemcpy((char far *)&work_back[work_offset], (char far *)&video_buffer[offset], SPRITE_WIDTH);

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

offset      += SCREEN_WIDTH;

work_offset += SPRITE_WIDTH;

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

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

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

void Erase_Sprite(sprite_ptr sprite)

{ // функция восстанавливает фон, сохраненный перед выводом спрайта

char far *work_back;

int work_offset=0,offset,y;

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

work_back = sprite->background;

// вычисляем смещение в видеобуфере

offset = (sprite->y_old << 8) + (sprite->y_old << 6} + sprite->x_old;

for (y=0; y<SPRITE_HEIGHT; y++)

{

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

_fmemcpy((char far *)&video_buffer [offset],(char far *)&work_back[work_offset], SPRITE_WIDTH);

// перейти к следующей строке

offset      += SCREEN_WIDTH;

work_offset += SPRITE_WIDTH;

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

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

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

void Draw_Sprite(sprite_ptr sprite)

{

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

// используется

сдвиг

char far *work sprite;

int work_offset=0,offset,x,y;

unsigned char data;

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

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

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

for (y=0; y<SPRITE_HEIGHT;y++)

{

for (x=0; X<SPRITE_WIDTH; X++)

{

// проверяем, не является ли пиксель "прозрачным" (с кодом 0),

// если

нет - рисуем

if ((data=work_sprite[work_offset+x]))

video_buffer[offset+x] = data;

} // конец вывода строки



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

offset      += SCREEN_WIDTH;

work_offset += SPRITE_WIDTH;

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

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

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

void main(void)

{

long index,redraw;                 

RGB_color color;

int frame_dir = 1;

pcx_picture town, cowboys;

sprite cowboy;

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

Set_Mode(VGA256);

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

Set_Screen_Pointers();

// загрузить

фон

PCX_Init((pcx_picture_ptr)&town) ;

PCX_Load( "town. pox", (pcx_picture_ptr)&town, 1) ;

PCX_Show_Buffer((pcx_picture_ptr)&town);

PCX_Delete((pcx_picture_ptr)&town);

// вывести на экран заставку игры

Blit_String(128, 24,50, "TOMBSTONE");

// загрузить

образы

PCX_Init((pcx_picture_ptr)&cowboys) ;

PCX_Load("cowboys.pcx", (pcx_picture_ptr) &cowboys,0) ;

// извлечь все образы из PCX-файла

Sprite_Init((sprite_ptr)&cowboy,SPRITE_WIDTH,100,0,7,0,3) ;

PCX_Grap_Bitmap ((pcx_picture_ptr) &cowboys, (sprite_ptr) &cowboy, 0, 0, 0);

PCX Grap_Bitmap( (pcx_picture_ptr) &cowboys,

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

PCX_Grap_Bitmap((pcx_picture_ptr)&cowboys,

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

PCX_Grap_Bitmap ((pcx_picture_ptr)&cowboys,

(sprite_ptr)&cowboy,3,1,0) ;

// очистить

память, выделенную

для загрузки PCX-файла PCX_Delete((pcx_picture_ptr)&cowboys);

Behind_Sprite((sprite_ptr)&cowboy);

Draw_Sprite ((sprite_ptr) &cowboy);

// главный

цикл

cowboy.state = SPRITE_ALIVE;

while

(!kbhit()) {

redraw = 0; // используется как флаг необходимости перерисовки

if (cowboy.state==SPRITE_ALIVE)

{

//не пора ли менять кадр?

if (++cowboy.anim_clock > cowboy.anim_speed)

{

// сбрасываем счетчик времени

cowboy.anim_clock

= 0;

if (++cowboy.curr_frame >= cowboy. num_frames)

{

cowboy.curr_frame = 0;

} // конец обработки достижения последнего кадра

redraw=1;



} // конец смены кадра

// проверяем не пора ли передвигать спрайт

if (++cowboy.motion_clock > cowboy.motion_speed)

{

// переустановить счетчик движения

cowboy.motion clock

=0;

// сохранить старую позицию

cowboy.x_old == cowboy.x;

redraw =1;

//передвинуть спрайт

if (++cowboy.x >= SCREEN_WIDTH-2*SPRITE_WIDTH)

{

Erase_Sprite((sprite_ptr)&cowboy);

cowboy.state = SPRITE_DEAD;

redraw         = 0;

} // конец обработки достижения последнего кадра

} // конец обработки движения спрайта

} // конец обработки ситуации "ковбой жив"

else

{ // пытаемся "оживить" ковбоя

if (rand()%100 == 0 )

{

cowboy.state      = SPRITE_ALIVE;

cowboy.x          = SPRITE_WIDTH;

cowboy.curr frame = 0;

cowboy.anim.speed   = 3 + rand()%6;

cowboy.motion_speed = 1 + rand()%3;

cowboy.anim_clock   = 0;

cowboy.motion_clock = 0;

Behind_Sprite{(sprite_ptr)&cowboy);

}                         

} // конец процесса "оживления"

// теперь состояние спрайта изменено

if (redraw)

{

// удалить спрайт в старой позиции

Erase_Sprite((sprite_ptr)&cowboy) ;

// сохранить фон в новой позиции

Behind_Sprite((sprite_ptr)&cowboy) ;

// нарисовать спрайт в новой позиции

Draw_Sprite((sprite_ptr)&cowboy);

// обновить старые координаты

cowboy.x_old = cowboy.x;

cowboy.у_old = cowboy.у;

} // конец перерисовки

Delay(1000);

} // конец главного игрового цикла

for(index=0; index <300000;index++,Plot_Pixel_Fast(rand()%320,

                                          rand()%200,0));

//Перейти обратно в текстовый режим

Set_Mode(TEXT_MODE);

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


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