LXF103:Стрелялка

Материал из Linuxformat.

Перейти к: навигация, поиск
Стрелялка за выходные

Содержание

Наш движок изнутри

ЧАСТЬ 3 Пользоваться инструментом, не понимая, как он работает – не дело для настоящего линуксоида. Разберитесь в механике Ingame вместе с Александром Супруновым – а заодно познакомьтесь с основами SDL.

Движок Ingame, которым мы пользовались на протяжении двух последних уроков, является набором оберток над функциями библиотеки Simple DirectMedia Layer (SDL), доступной для Linux, Windows, Mac OS X и множества других систем, включая даже AmigaOS. И сегодня мы попробуем разобраться в том, что происходило все это время «за кулисами».

SDL написана на языке C, поэтому все объекты, которыми она оперирует, представлены в виде структур. Имена этих структур начинаются с префикса SDL_, а центральное место среди них занимает SDL_Surface – это так называемая «экранная поверхность», на которой можно размещать изображения. Структура SDL_Surface имеет поля w и h, задающие высоту и ширину поверхности, а также поле format. Указатель на основной игровой экран, определенный в файле ingame.h, имеет тип SDL_Surface * и имя display.

Начало всех начал

Схема работы движка Ingame: вывод спрайтов и двойная буферизация.
Схема работы движка Ingame: вывод спрайтов и двойная буферизация.

Как вы уже знаете, каждая программа, использующая Ingame, начинается с вызова функции screen(). Ее прототип выглядит так:

void screen(int w, int h);

В первую очередь, screen() выполняет инициализацию SDL посредством вызова:

SDL_Init (SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO);

где объединенные при помощи операции «логическое ИЛИ» флаги имеют следующий смысл:

  • SDL_INIT_VIDEO – активировать возможность работы с графикой.
  • SDL_INIT_TIMER – активировать возможность использования встроенного в SDL таймера.
  • SDL_INIT_AUDIO – активировать возможность проигрывания звуковых данных.

Затем инициализируется поверхность display. Это делается следующим образом:

display = SDL_SetVideoMode (w, h, 0, SDL_SWSURFACE | SDL_ANYFORMAT);

Функция SDL_SetVideoMode() принимает в качестве параметров ширину, высоту экрана, количество бит на пиксель, а также различные ключи, в том числе:

  • SDL_SWSURFACE – предписывает использовать программную отрисовку графики. Режим нагружает центральный процессор, но совместим практически со всеми компьютерами.
  • Его антипод, SDL_HWSURFACE, задействует аппаратное ускорение, что может как повысить, так и понизить быстродействие конечной программы.
  • SDL_DOUBLEBUF – включает режим двойной буферизации, позволяет получить более «плавную» графику.
  • SDL_FULLSCREEN – активирует полноэкранный режим.

Прошу обратить внимание на третий параметр: количество бит на пиксель. Он может принимать значения 8, 16, 24, 32 или 0, что соответствует глубине цвета, установленной в системе по умолчанию. Последний вариант наиболее переносим. Однажды я наблюдал повреждение графики в SDL-игре, запущенной на Amiga Pegasos. Выяснилось, что причиной была жестко зашитая глубина цвета (16) – замена ее на 0 решила проблему.

Следующим шагом мы устанавливаем заголовок окна:

SDL_WM_SetCaption («Linux GAMES», NULL);

а затем инициализируем библиотеку SDL_ttf

TTF_Init();

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

fnt = TTF_OpenFont(«font.ttf», 20);

Функция TTF_OpenFont() принимает два аргумента (имя файла со шрифтом и размер в пунктах) и возвращает указатель на структуру TTF_Font, который мы сохраняем в глобальной переменной fnt.

С графикой разобрались; остался звук. За него отвечает библиотека SDL_mixer, которая инициализируется вызовом:

Mix_OpenAudio (44100, MIX_DEFAULT_FORMAT, 2, 2024);

Первый аргумент – частота дискретизации звука. Второй является стандартным ключом – просто запомните его. Далее указывается количество звуковых каналов (разумеется, «стерео») и размер буфера, отводимого под звуковые данные. Если вдруг в одно непрекрасное утро вы услышите, что звук начал «спотыкаться» – увеличьте последнее число, например, в два раза.

Две заключительных строки функции screen() –

frames = 0;
then = SDL_GetTicks();

имеют отношение к подсчету и ограничению FPS. Это необходимо для того, чтобы программа выполнялась с одинаковой скоростью на любых компьютерах. Мы инициализируем счетчик кадров frames и сохраняем текущее значение таймера в глобальной переменной then, имеющей тип Uint32.

Добавим игроков

Итак, экран готов – настало время загрузить спрайты. Для этих целей в ingame.h используется структура WiHi, содержащая указатель на соответствующий объект SDL_Surface.

По умолчанию резервируется место под 1900 объектов WiHi, причем все номера, начиная с 1000-го, используются движком для внутренних целей. При необходимости, можно увеличить число доступных спрайтов, просто изменив размерность массива.

Для загрузки спрайтов в формате BMP в Ingame предусмотрена функция loadsprite(), принимающая в качестве аргументов номер ячейки (num), в которую будет загружена картинка и имя файла (name). loadsprite() – обертка над двумя стандартными SDL-функциями: SDL_LoadBMP() и SDL_DisplayFormat().

В принципе, здесь можно ограничиться всего одним вызовом:

pic[num].tmp=SDL_LoadBMP(name);

но с точки зрения производительности рациональнее будет сразу же преобразовать спрайт в пиксельный формат дисплея (например, если оригинальная картинка использует 24 бита на пиксель, а на экране – всего 16, глубина цвета должна быть понижена). В итоге тело функции loadsprite() примет вид:

pic[num].tmp=SDL_DisplayFormat(SDL_LoadBMP(name));

Функция sprite(num, x, y), как вы, надеюсь, помните, выводит спрайт с номером num в точке с координатами (x,y). Происходит это следующим образом: для изображения устанавливается «цветовой ключ» (значение RGB, которое SDL будет считать прозрачным), а затем спрайт просто переносится в нужную точку экрана.

За прозрачность спрайта «отвечает» функция SDL_SetColorKey():

SDL_SetColorKey(pic[num].tmp,SDL_SRCCOLORKEY | SDL_RLEACCEL,SDL_MapRGB(pic[num].tmp->format,255,0,255));

Флаг SDL_SRCCOLORKEY указывает, что «цветовой ключ» (последний аргумент функции) следует считать прозрачным, SDL_RLEACCEL включает RLE-оптимизацию (при этом группы одинаковых пикселей кодируются по принципу число_пикселей X значение, что ускоряет копирование), а вызов SDL_MapRGB() возвращает значение «ключа» в требуемом формате (напомню, что Ingame считает прозрачными пиксели цвета (255,0,255).

Собственно копирование спрайта осуществляется функцией SDL_BlitSurface():

SDL_BlitSurface(pic[num].tmp,0,display,&shadow);

Первый аргумент (pic[num]. tmp) – исходная поверхность, второй – область исходного изображения, подлежащая копированию (мы передаем здесь NULL, что означает «вся поверхность»). Остальные два параметра имеют тот же смысл для целевой поверхности. shadow – переменная типа SDL_Rect, представляющая собой прямоугольник; координаты левого верхнего угла которого как раз равны x и y.

Как сказать: «Game over»?

Помимо спрайтов, на экране время от времени нужно отображать и текстовые сообщения. Для этих целей в Ingame предназанчена функция print(сообщение, координата_x, координата_y). Она начинается с определения двух переменных

SDL_Color color = {255,255,255,0};
SDL_Rect dest= {(Sint16)x, (Sint16)y,0,0};

Первая из них, типа SDL_Color, задает цвет символов (белый), а вторая определяет точку, в которой будет выводиться сообщение. Текст надписи растеризуется на служебной поверхности (помните, все спрайт-слоты выше 1000 заняты Ingame для внутренних нужд) функцией TTF_RenderText_Blended():

pic[1000].tmp = TTF_RenderText_Blended(fnt, txt, color);

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

Остается только скопировать сообщение на экран при помощи уже известной нам функции SDL_BlitSurface():

SDL_BlitSurface( pic[1000].tmp, NULL, display, &dest );

и освободить память:

SDL_FreeSurface( pic[1000].tmp );

Движущая сила

Итак, библиотека инициализирована, спрайты отрисованы; настало время заставить их двигаться. За это и многое другое отвечает функция fx(), реализующая основной цикл игры. Она требует предварительного объявления ряда глобальных переменных:

SDL_Event event;
Uint8* keys;

event – специальная переменная событийного типа (зачем она нужна, будет ясно ниже), а keys содержит номера нажатых клавиш.

Мы также вводим ограничение FPS 75-ю кадрами в секунду.

#define FPS_LIMIT 75

Функция fx() отслеживает нажатие клавиш, ограничивает количество кадров, выводимых на экран монитора, очищает его перед отрисовкой очередной сцены, организует механизм «велосити» для движения объектов и меняет местами основной и теневой экраны, то есть реализует двойную буферизацию изображения. Разберем эти действия по шагам.

fx() начинается с опроса SDL на предмет произошедших событий. Этим занимается функция SDL_PollEvent(), которая принимает указатель на переменную событийного типа и возвращает TRUE, если что-то произошло. После этого мы можем изучить поле event.type, чтобы понять, что именно случилось, и отреагировать надлежащим образом:

while (SDL_PollEvent(&event))
{
if (event.type==SDL_QUIT)
{
GAME=0;
SDL_Quit();
}
if (event.type==SDL_KEYDOWN)
{
if(event.key.keysym.sym==SDLK_ESCAPE)
{
GAME=0;
SDL_Quit();
}
}
}

В первую очередь обрабатываются события, сигнализирующие о необходимости выхода из игры: системное SDL_QUIT (оно генерируется, если, например, пользователь щелкнул кнопку закрытия окна) и нажатие на клавишу Escape (событие типа SDL_KEYDOWN, код клавиши при этом сохраняется SDL в поле event.key.keysym.sym). Доступные коды клавиш перечислены в файле SDL/SDL_keysym.h, там же определены и константы наподобие SDLK_ESCAPE. Кстати, можно реагировать не на нажатие, а на отпускание клавиши – такое событие будет иметь тип SDL_KEYUP.

Затем наступает черед обработки нажатия на управляющие клавиши: «вправо», «влево», «огонь» и т.п. Здесь можно было бы воспользоваться тем же приемом, что и для Escape, но есть одно «но»: пользователь должен иметь возможность нажать на несколько клавиш одновременно (скажем, «влево» и «вверх» или «вверх» и «огонь»). Для таких целей SDL предоставляет в наше распоряжение функцию SDL_GetKeyState(), которая возвращает указатель на массив значений типа Uint8, представляющий собой текущее состояние клавиатуры в целом. Исходя из того, что именно было нажато, мы устанавливаем переменные-флаги (LEFT, RIGHT и т.д.), а также изменяем значение «велосити».

keys=SDL_GetKeyState(NULL);
if(keys[SDLK_LEFT])
{
LEFT=1; vel=velocity;
}
else
LEFT=0;

Аналогично поступаем и с другими управляющими клавишами.

Далее в игру вступает механизм «велосити», который был достаточно подробно рассмотрен в LXF100/101, и, наконец, отрабатывает система ограничения FPS:

++frames;
now = SDL_GetTicks();
if ( now > then ) {
fps= (int)((double)frames*1000)/(now-then);
}
if ( fps > FPS_LIMIT ) {SDL_Delay(1000/FPS_LIMIT);}

Мы увеличиваем счетчик кадров, получаем текущее значение таймера и вычисляем FPS. Если полученный результат превышает заданный предел, функция SDL_Delay() приостанавливает работу программы на необходимый промежуток времени.

В качестве завершающего аккорда функция fx() меняет местами теневой и экранный буферы:

SDL_Flip(display);

делая видимым все, что было нарисовано на данной итерации цикла, и очищает вспомогательный экран, заливая его черным цветом при помощи непрямого вызова функции SDL_FillRect().

Музыка и звук

Работа со звуком в Ingame по своей сути похожа на работу со спрайтами. Здесь также определяются структуры SiHi и MiHi, являющиеся обертками для указателей на Mix_Chunk (звуковой эффект) и Mix_Music (музыкальная композиция), соответственно. Структуры объединяются в массивы, насчитывающие 500 и 100 элементов. Для загрузки эффекта из файла формата WAV функция loadsound() вызывает Mix_LoadWAV(), которая, в свою очередь, принимает имя WAV-файла в качестве единственного параметра. Для воспроизведения эффекта используется функция sound(), эквивалентная вызову:

Mix_PlayChannel (-1, sn[num].tmp, 0);

-1 является указанием использовать первый доступный канал, 0 – число повторений, означающее, что звук будет проигран один раз.

С музыкой все обстоит ненамного сложнее. Соответствующие функции являются обертками над Mix_LoadMUS() и Mix_PlayMusiс(). Интерес представляет лишь функция stopmusic(), вызывающая

Mix_FadeOutMusic(1000);

для плавного затухания звука в течение 1 секунды.

Что дальше?

Наш краткий экскурс в SDL подошел к концу. Конечно, мы не рассмотрели и десятой части возможностей этой замечательной библиотеки, но сделали главное – разобрались в том, как работает движок Ingame. Теперь, когда на нашей карте не осталось белых пятен, вы можете сами решать, куда двигаться дальше. Хотите – развивайте ingame.h: исходный код распространяется по лицензии GPL; хотите – напишите с его помощью собственную игру. Возможен так-же третий вариант – разберитесь в деталях с SDL и создайте что-нибудь с нуля. Главное, если у вас получится что-то стоящее – не забудьте сообщить нам об этом!

Личные инструменты
  • Купить электронную версию
  • Подписаться на бумажную версию