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

       

Битовое отсечение


Битовое отсечение означает вырезание растрового изображения краями экрана или другими границами. Мы не будет обсуждать здесь общие проблемы отсечения. Нас интересует только прямоугольный или оконный тип отсечения. Даже в играх наподобие Wolfenstein или DOOM используется только прямоугольное отсечение границами экрана или прямоугольниками, находящимися внутри экранной области. На рисунке 7.3 показан пример отсечения растрового изображения.

Каким же образом осуществляется отсечение прямоугольного изображения? И, прежде всего, нужно ли оно нам вообще? На этот вопрос можно ответить и «да» и «нет». Если во время игры изображения ваших объектов и персонажей никогда не выходят за границы экрана, то применять отсечение не обязательно. В этом случае необходимо только выполнять логический контроль за тем, чтобы при движении или трансформации объектов образ никогда не выходил за пределы экрана.

Однако, это не всегда допустимо. Например, в трехмерном DOOM'e монстры часто видны на игровом экране только частично. Скажем, видна только правая половина его тела. Это значит, что его левая часть должна быть отброшена при выводе образа и следует применить какой-либо алгоритм битового отсечения.

Мы всегда должны отсекать растровое изображение в тех случаях, когда оно заезжает за пределы экрана (или за пределы других установленных нами границ). Например, в трехмерной игре Wolfenstein (равно как и в DOOM) играющий может менять размер окна с изображением, но картинка никогда не  будет выходить за пределы этого окна, так как она отсекается его краями.

Как же мы можем произвести отсечение изображения? Существует два пути:

§          Мы можем проверять каждый отдельный пиксель, находится ли он внутри отображаемой области. Такое отсечение называется отсечением пространства образа. Другими словами, мы обрабатываем каждый из пикселей внутри всего образа и принимаем решение, рисовать его или нет. Эта техника не принимает во внимание геометрические свойства объекта.


Она очень медлительна. (И я подозреваю, что именно такой метод применила фирма Microsoft в своей графике, потому что я никогда не видел блиттинг, работающий более медленно, чем в Microsoft's Graphics Library!) Посему данным методом мы пользоваться не будем;

§          Мы можем учитывать такие геометрические свойства объекта, как его размер, его форма и его положение по отношению к области отсечения. Этот способ называется отсечением области объекта.

Мы воспользуемся вторым приемом. Он довольно прост, по крайней мере, в том виде, в котором я собираюсь показать его на примере. Мы, как всегда, хотим до&тичь максимума производительности и эффективности. А так как отсечение замедляет блиттинг, нужно постараться свести потери времени к минимуму.

Во-первых, мы должны провести предварительный тест и выяснить: находится ли объект внутри ограниченной области? Например, пусть наш объект 16х16 и он находится в точке (1000,1000). Экран ограничен. Поскольку экран имеет размеры 320х200, совершенно ясно, что этот образ окажется невидим, и никакое отсечение здесь не поможет. Таким образом, мы должны проверять каждый объект на предмет того, будет ли он виден хотя бы частично. Алгоритм 7.1 выполняет данный тест.

Алгоритм 7.1. Тест видимости объекта.

// Пусть объект размером Width x

Height



находится в точке (х,у).

//

Размер экрана - Screen Width x Screen Height.

// Для каждого объекта производим следующую операцию:

if (X+Width>0 and X<Screen_Width and Y+Height>0 and Y<Screen_Height)

then

полностью или частично видим

goto отсечение

else

нет, не видим

goto следующий объект

Алгоритм 7.1 не особенно эффективен. Однако он выполняет свою функцию: если объект полностью или хотя бы частично должен появиться на экране, то нужно выполнить отсечение, после чего можно вывести изображение на экран. В противном случае проверяется следующий объект.

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


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

Если же растровое изображение больше экрана хотя бы по одному измерению, то идея «вылетает в трубу». Так обычно бывает, когда мы масштабируем спрайт, имитируя приближение игрока к объекту. Но пока мы будем предполагать, что спрайты всегда меньше игрового поля экрана.

Если вы изучали функцию Draw_sprite() пятой главы или другие ее модификации, то, должно быть, заметили, что в ней выполняется вложенный цикл FOR, в котором битовый образ объекта рисуется по столбцам. Каждый столбец начинается с Х-коордипаты спрайта и спускается вниз по координате Y. Таким образом, мы должны изменить механизм этого цикла так, чтобы принять во внимание новые отправные и конечные координаты (х,у) и изменить исходное местоположение спрайта, если одна из координат выходит за пределы экрана. Не забывайте о том, что если координата Х или Y (либо обе) выходят за пределы экрана, это еще не значит, что ваш объект вообще невидим. На рисунке 7.4 изображен иллюстрирующий это пример.

Давайте изменим функцию Draw_sprite(), чтобы отсечь данные, не попадающие на экран. Новая версия будет работать на 5 процентов медленнее, однако она всегда будет корректно рисовать спрайты. Листинг 7-3 содержит ее исходный текст.



Листинг 7.3. Новая функция Draw_Sprite() с отсечением.

void Draw_Sprite_Clip(sprite_ptr sprite)

{

// Эта функция рисует спрайт на экране и отсекает области,

// выходящий за границы экрана. Функция написана в

// демонстрационных целях.

char far *work_sprite;

int work_offset=0,offset,x_off,x,y,xs,ys,xe,ye, clip_width, clip_heigth;

unsigned char data;    

// Получить указатель на спрайт

xs=sprite->x;

ys=sprite->y;



// Расчет координат прямоугольника, описывающего спрайт

xe=xs+sprite_width-1;

ye=ys+sprite_height-1;

// Проверка полной невидимости спрайта

// ( то есть лежит ли он полностью за пределами экрана)

if((xs>=SCREEN_WIDTH) || (ys>==SCREEN HEIGHT) | |

(xs<=(-sprite_width))||(ys<=(-sprite_height)))

{

return;

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

// Спрайт частично видим, следовательно, необходимо

// рассчитать рисуемую область

// Отсечение по координате Х

if(xs<0)

xs=0;

else

if(xe>=SCREEN_WIDTH) xe=SCREEN_WIDTH-l;

//Отсечение по координате Y

if(ys<0)

уs=0;

else

if(ye>=SCREEN_HEIGHT) ye=SCREEN_HEIGHT-1;

// Расчет новой высоты и ширины

clip_width=xe-xs+l;

clip height=ye-ys+l;

// Расчет рабочего смещения на основе нового начального

// значения

координаты

Y

work_offset=(sprite->y-ys)*sprite_width;

x_off=(xs-sprite->x);

// Построение усеченного спрайта

// Для упрощения дадим указателю на спрайт новое имя

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

// Расчет смещения,спрайта в видеобуфере

offset = (ys<< 8) + (ys<< 6) + sprite->xs;

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

{

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

for (х=0; x<clip_width; x++)

(

// Проверка пикселя на "прозрачность" (то есть на 0);

// если пиксель "непрозрачный" - рисуем

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

double_buffer[offset+x+x_off] = data;

} // конец цикла по X

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

// и в растровом буфере спрайта

offset      += SCREEN_WIDTH;

work_offset += sprite_width;

} // конец цикла по У

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

Как и к двум первым листингам этой главы, к Листингу 7.3 надо относиться достаточно скептически. Эта программа, конечно, делает то, что ей и положено, однако не учитывает при этом контекста. А контекст — это все! Например, в вашей игре все картинки могут располагаться посередине. Тогда в отсечении у вас просто отпадет необходимость. Возможно, ваш объект всегда будет двигаться по горизонтали, в этом случае не потребуется выполнять вертикальное отсечение и вы сможете провести оптимизацию части предварительной обработки.Иными словами, всегда пытайтесь создавать ваши функции для конкретной ситуации и для определенной игры. В данном случае передо мной стояла несколько иная задача — объяснить вам суть проблемы. Если вы поняли идею, то, потратив определенное время и силы, найдете свое правильное решение.


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