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

       

Рисование линий


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

Мы напишем две функции. Одна из них рисует горизонтальные линии слева направо, а другая — вертикальные сверху вниз. Рисунок 5.4 показывает, как они выглядят в видеобуфере.

Поскольку горизонтальные линии рисовать легче, то с них мы и начнем. Как видно из рисунка 5.4, горизонтальную линию можно получить, заполняя ряд пикселей в матрице 320х200. Чтобы это проделать, мы должны найти начальный адрес строки и заполнить ее значениями пикселей от начальной до конечной позиции. Для этого стоит использовать функцию rnemset. Это один из самых быстрых способов. Листинг 5,6 содержит код такой функции.

Листинг 5.6. Рисование горизонтальной линии.

void H_Line (int х1, int x2, int y, unsigned int color)

{

// функция рисует горизонтальную линию, используя memset()

// x2 должно быть больше х1

_fmemset ((char far *) (video_buffer + ((у << 8) + (у

<<6)) + х1), color, x2 - х1 + 1);

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

Следует кое-что запомнить:

§

Мы используем функцию _fmemset, поскольку она, в отличие от memset, корректно работает с дальними (FAR) указателями;

§          Мы вычисляем начальный адрес линии, и функция заполняет определенное количество байтов заданным значением цвета;



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

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


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

Листинг 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 <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_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-файлов.


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