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

Вид материалаДокументы
Подобный материал:
1   2   3   4   5   6   7   8   9   10   ...   37
Листинг 5.7. Рисование вертикальной линии.

void V_Line(int yl,int y2,int x,unsigned int color)

{

//рисуем вертикальную линию (у2 больше yl)

unsigned int line offset,

index;

// вычисляем начальную позицию

line_offset = ((y1<<8) + (y1<<6)) + x;

for (index=0; index<=y2-y1; index++)

{

video_buffer[line_offset] = color;

line_offset+=320;

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

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

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

Функция V Line несколько длиннее Н Line, поскольку она сама производит все адресные вычисления. Эта функция фантастически эффективна - вспомните пример использования сдвига вместо умножения (кстати, если вы до сих пор не поняли смысл двоичного сдвига, не отчаивайтесь - в восемнадцатой главе, «Техника оптимизации», мы это подробно изучим).

Прежде чем перейти к следующей теме, я хочу дать вам замечательную программу, которая создает новую палитру и выводит ее на экран, используя функции рисования вертикальных линий. В ней встречается уже известная функция Set_Mode() , описанная во второй главе, поэтому здесь я не включил ее исходный код. При желании вы можете взять его из второй главы. Более того, эта функция объявлена в программе как EXTERNAL, так что ее можно просто прилинковать. Листинг 5.8 содержит необходимый код программы Show_Palette.

Примечание

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


Листинг 5.8. Создание и отображение цветовой палитры (PALDEMO.C).

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

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include


// определения /////////////////////////////////////

#define ROM_CHARSET_SEG 0xF000

#define ROM_CHAR_SET_OFF 0xFA6E

#define VGA256 0x13

#define TEXT_MODE 0х03

#define PALETTE_MASK ОхЗc6

#define PALETTE_REGISTER_RD.Ox3c7 #define PALETTE_REGISTER_WR 0x3c8

#define PALETTE_DATA 0x3c9

#define SCREEN_WIDTH (unsigned int)320

#define SCREEN_HEIGHT (unsigned int)200

// структуры данных////////////////////////////////////////

// структура, сохраняющая RGB

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;

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

extern Set_Mode(int mode);

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

void Set_Palette Register(int index, RGB_color_ptr color);

void Get_Palette_Register(int index, RGB_color_ptr color);

void Create_Cool__Palette();

void V_Line(int y1,int y2,int x,unsigned int color);

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

// указатель на начало видеопамяти (для операций с байтами)

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

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

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

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

void Set_Palette_Register (int index, RGB_color_ptr color)

{

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

// цветов. Номер регистра задается переменной index, структура

// color содержит значения красной, зеленой и синей составляющих

// цвета

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

// регистра палитры

_outp(PALETTE_MASK,Oxff);

// какой из регистров мы хотим обновить?

_outp(PALETTE_REGISTER_WR, index) ;

// теперь обновляем RGB

_outp(PALETTE_DATA,color->red);

_outp(PALETTE_DATA,color->green);

_outp(PALETTE_DATA,color->blue);

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

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

void Get_Palette_Register(int index, RGB_color_ptr color)

{

// эта функция читает данные элемента таблицы цветов и помещает их

// в поля структуры color

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

_outp(PALETTE_MASK,Oxff);

// сообщаем VGA, какой из регистров мы будем читать

_outp(PALETTE_REGISTER_RD, index);

// читаем данные

color->red = _inp(PALETTE_DATA);

color->green = _inp(PALETTE_DATA);

color->blue = _inp(PALETTE_DATA);

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

void Create_Cool_Palette(void) {

// эта функция создает палитру, содержащую по 64 оттенка серого,

// красного, зеленого и синего цветов

RGB_color color;

int index;

// проходим по элементам таблицы цветов и создаем 4 банка

// по 64 элемента

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

{

// оттенки серого

color.red = index;

color.green = index;

color.blue = index;

Set_Palette_Register(index, (RGB_color_ptr)&color);

// оттенки красного

color.red = index;

color.green = 0;

color.blue = 0;

Set_Palette_Register(index+64, (RGB_color_ptr)&color) ;

// оттенки зеленого

color.red = 0;

color.green = index;

color.blue = 0;

Set_Palette_Register(index+128, (RGB_color_ptr)&color);

// оттенки синего

color.red = 0;

color.green = 0;

color.blue = index;

Set_Palette_Register(index+192, (RGB_color_ptr)&color);

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

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

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

void V_Line(int y1,int y2,int x,unsigned int color)

{

// рисуем вертикальную линию у2 > yl

unsigned int line_offset, index;

// вычисляем начальную позицию

line_offset = ((y1<<8) + (y1<<6)} + x;

for (index=0; index<=y2-y1; index++)

{

video_buffer[line_offset] = color;

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

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

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

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

void main(void)

{

int index;

RGB_color color,color_1;

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

Set_Mode(VGA256) ;

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

Create_Cool_Palette();

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

for (index=0; index<320; index++) V_Line(0,199,index,index);

// ждем реакции пользователя

while(!kbhit())

{

Get_Palette_Register(0,(RGB_color_ptr)&color 1) ;

Get_Palette_Register(0,(RGB_color_ptr)&color_l);

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

{

Get_Palette_Register(index+l,(RGB_color_ptr)&color);

Get_Palette__Register(index+l, (RGB_color_ptr)&color) ;

Set Palette Register(index,(RGB color_ptr)&color) ;

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

Set_Palette_Register(255,(RGB_color_ptr)&color_1);

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

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

Set_Mode(TEXT_MODE);

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

Программа из Листинга 5.8 создает новую палитру, которая содержит 64 оттенка всех основных цветов, включая серый. Затем, она разделяет каждый цвет вертикальными линиями и после этого перемешивает их.

Вроде, хватит об этом. Теперь стоит поговорить о том, как целиком прочитать файл с образом. Начнем с формата PCX-файлов.

Графический формат PCX

В индустрии компьютерной графики существует так много стандартов, что само слово «стандарт» уже потеряло свой первоначальный смысл. Сегодня существует несколько наиболее известных стандартов: PCX, GIF, RGB, TGA, TIF и многие другие. Нам интересен формат PCX потому, что сегодня он является самым распространенным.

Файл в формате PCX представляет собой закодированное представление изображения. Кодирование необходимо для уменьшения размера файла, поскольку только один образ 320х200 пикселей уже займет 64К памяти. Рисованные объекты обладают большой цветовой избыточностью, и это обстоятельство используется для сжатия изображения.



Примечание

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

Давайте посмотрим на рисунок 5.5 (файл CH19\WARINTR2.PCX на дискете). Это копия экрана из игры Warlock. Как вы можете заметить, там не слишком много цветов. Более того, на нем присутствует множество больших, одинаково окрашенных областей.

Как правило, в экранных изображениях используется лишь ограниченное число цветов. Так почему бы не подсчитать количество пикселей одинакового цвета и не сохранить их как целые группы, вместе с позицией и цветом. Это можно сделать. В общем, подобная технология, правда, в несколько усовершен­ствованном виде, и применена в PCX-формате. Для сжатия информации этот формат использует так называемое RLE-кодирование. При этом изображение обрабатывается построчно без учета расположения пикселей по вертикали.

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



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

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

Файл формата PCX состоит из трех секций:
  • Первая секция PCX-файла длиной 128 байт содержит различную служеб­ную информацию;
  • Вторая секция — это данные сжатого образа, которые могут оказаться любой длины;
  • Третья секция размером в 768 байт содержит цветовую палитру, если она есть. В нашем случае она будет присутствовать, поскольку мы используем 256-цветный режим 13h. Эти 768 байт хранят значения RGB от 0 до 255.

Суммируя вышесказанное, можно нарисовать структуру PCX-файла (рис. 5.7).

Получение информации из заголовка несложно: достаточно прочитать первые 128 байт и отформатировать их в соответствии со структурой, представленной в Листинге 5-9.




Листинг 5.9. Структура заголовка PCX-файла.

typedef struct pcx_header_typ

{

char manufacturer; // всегда 10

char version; // 0 - версия 2.5 Paintbrush

// 2 - версия 2.8 с палитрой

// 3 - версия 2.8 без палитры

// 5 - версия 3.0 или старше

char encoding; // всегда 1 - RLE кодирование

char bits_per_pixel;// количество бит на пиксель

// для нашего случая – 8

int x,y; // координаты верхнего левого угла изображения

int width,height; // размеры изображения

int horz_res; // количество пикселей по горизонтали

int vert_res; // количество пикселей по вертикали

char ega_palette[48]; // EGA-палитра. Ее можно игнорировать,

char reserved; // ничего значимого

char num_color_planes; // количество цветовых плоскостей

//в изображении

int bytes_per_line; // количество байт на одну строку

int palette_type; // не беспокойтесь об этом

char padding[58]; // ссылка на палитру в конце файла

} pcx_header, *pcx_header_ptr;


Последнюю часть PCX-файла также довольно легко обработать:
  • Необходимо установить указатель на конец файла;
  • Передвинуться вверх на 768 байт;
  • Прочитать 768 байт как палитру.

Конечно, я упустил кое-какие детали обработки PCX-файла, но сделал это лишь для того, чтобы лучше передать смысл производимых действий. Сейчас же нас больше должен занимать способ декодирования средней части, где находится само изображение. Именно отсюда начинаются сложности, поскольку процедура декомпрессии не очень проста и очевидна.
  • Если код прочитанного байта принадлежит множеству 192...255, то мы вычитаем из него 192 и используем полученный результат, как количество повторений следующего байта;
  • Если код прочитанного байта лежит в диапазоне от 0 до 191, то мы используем его как байт данных, то есть помещаем его в битовую карту без изменений.

Если вы достаточно внимательны, то можете спросить: «А как же быть с пикселями, имеющими значения от 192 до 255? Интерпретируются ли они как RLE-цепочки?» Да, и гениальное решение этого вопроса состоит в том, что такие значения кодируются не одним, а двумя байтами. Например, если требуется поместить в файл значение 200, то сначала нужно записать число 193 (192-1) как количество повторений, а потом — 200. Посмотрим на рисунок 5.8, чтобы увидеть пример декомпрессии.

Теперь настало время написать программу, реализующую чтение файл формата PCX. Она получилась весьма неплохой. Листинг 5.10 даст вам возможность убедиться в этом самостоятельно.

Листинг 5.10. Программа чтения файла формата PCX.

// размеры экрана

#define SCREEN_WIDTH 320

#define SCREEN_HEIGHT 200

// структура для хранения данных PCX файла

typedef struct pcx_picture_typ

{

pcx_header header; // заголовок файла (длина 128 байт)

RGB_color palette[256]; // палитра

char far *buffer; // буфер для размещения изображения

// после декомпрессии

} pcx_picture, *pcx_picture_ptr;

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
{

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);

// повторяем байты в буфере num_bytes раз

while(num_bytes-->0)

{

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

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

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

else

{

// помещаем данные в следующую позицию буфера

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

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

} // конец чтения байт изображения

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

fseek(fp,-768L,SEEK_END);

// читаем палитру и загружаем ее в регистры VGA

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

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

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

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

Функция выполняет именно те действия, которые-мы с вами уже обсуждали и ничего больше:
  • Открывает PCX-файл;
  • Читает заголовок;
  • Загружает PCX-файл и декомпрессирует все 64000 пикселей;
  • Загружает цветовую палитру.

В общем, все это несложно. А вот что делать с картинками, которые больше, чем целый экран? Ответ прост: можно декодировать только маленький кусочек, скажем 24 на 24 пикселя.

Я создал для вас заготовку CHARPLATE.PCX, которую вы найдете на прилагаемом диске. Если вы посмотрите на него, то увидите множество маленьких белых квадратов. Вы можете использовать этот шаблон для рисования ваших игровых персонажей в этих квадратиках.

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

Возникает вопрос: «Как редактировать PCX-файлы в режиме 320х200х 256?» Для этого можно воспользоваться такими условно-бесплатными программами как VGA-Paint или Pro-Paint. Тем не менее, я надеюсь, что самые расторопные читатели уже давно пользуются копией Electronic Art's Deluxe Paint & Animation. Это одна из самых классных программ для рисования на ПК. Она корректно работает с режимом 320х200х256 и имеет множество полезных функций для преобразования и анимации изображения.

Побитовое копирование изображения (бит-блиттинг)

Термин бит-блиттинг (bit blitting) — означает процесс перемещения группы битов (образа) из одного места экрана в другое. В играх для ПК нас интересует перемещение образа из области хранения вне экрана в область видеобуфера. Давайте посмотрим на рисунок 5.9, чтобы уяснить сущность этой операции.



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

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

Спрайты

Вы можете спросить: «Что такое спрайт?». Знаете, есть такой газированный напиток... Снова шучу. На самом деле спрайты - это такие маленькие объектики, которые находятся на игровом поле и могут двигаться. Этот термин

прижился с легкой руки программистов фирмы Atari и Apple в середине, 70-х годов. Теперь поговорим о спрайтах и их анимации. В будущем мы еще вернемся к этой теме в седьмой главе, «Продвинутая битовая графика и специальные эффекты. Именно с этой мыслью я создал несколько небольших спрайтов, которые мы будем использовать в дальнейшем.

Спрайты - это персонажи в играх для ПК, которые могут без труда перемещаться по экрану, изменять цвет и размер. Все это звучит как мечта программиста. Но надо помнить, что в IBM-совместимых компьютерах нет спрайтов! Во нормальных компьютерах существует аппаратная поддержка спрайтов. Такие машины как Atari, Amiga, Commodore и последние модели Apple имеют эту возможность, а вот ПК - нет. Поэтому мы вынуждены делать это самостоятельно.

М-да. Нам будет чем заняться.

Конечно, мы не станем заниматься разработкой аппаратной поддержки спрайтов. Все, что нам нужно, это понять, каким образом помещать образ на экран, сохраняя при этом возможность его перемещений и видоизменений. Поскольку спрайт — это довольно сложный объект, то стоит подумать о том, как это реализовать на программном уровне. Мы вовремя об этом заговорили:. вспомните разработку игры «Астероиды».

Вот что нам надо:
  • Мы должны уметь извлекать матрицу пикселей из загруженного РСХ-образа и сохранять ее в буфере, связанном со спрайтом;
  • Более того, хотелось бы считывать сразу несколько образов из PCX-файла и загружать их в массив, связанный с одним спрайтом. Это позволит нам оптимизировать программу по скорости выполнения.

Рисунок 5.10 показывает последовательность кадров, которые приводят в движение ковбоя. Мы воспользуемся ею позже.



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

Давайте на этом Месте остановимся и поговорим чуть-чуть об анимации. В играх для ПК применяется два способа обновления экрана:
  • Мы можем перерисовывать весь экран целиком, как это сделано; в игре Wolfenstein 3D;
  • Можно перерисовывать лишь участки экрана.

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

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



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