<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="http://wiki.linuxformat.ru/wiki/skins/common/feed.css?303"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
		<id>http://wiki.linuxformat.ru/wiki/index.php?action=history&amp;feed=atom&amp;title=LXF103%3A%D0%A1%D1%82%D1%80%D0%B5%D0%BB%D1%8F%D0%BB%D0%BA%D0%B0</id>
		<title>LXF103:Стрелялка - История изменений</title>
		<link rel="self" type="application/atom+xml" href="http://wiki.linuxformat.ru/wiki/index.php?action=history&amp;feed=atom&amp;title=LXF103%3A%D0%A1%D1%82%D1%80%D0%B5%D0%BB%D1%8F%D0%BB%D0%BA%D0%B0"/>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/index.php?title=LXF103:%D0%A1%D1%82%D1%80%D0%B5%D0%BB%D1%8F%D0%BB%D0%BA%D0%B0&amp;action=history"/>
		<updated>2026-05-13T01:23:30Z</updated>
		<subtitle>История изменений этой страницы в вики</subtitle>
		<generator>MediaWiki 1.19.20+dfsg-0+deb7u3</generator>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/index.php?title=LXF103:%D0%A1%D1%82%D1%80%D0%B5%D0%BB%D1%8F%D0%BB%D0%BA%D0%B0&amp;diff=7108&amp;oldid=prev</id>
		<title>Yaleks: Новая: {{Цикл/Стрелялка}} == Наш движок изнутри == : ''ЧАСТЬ 3 Пользоваться инструментом, не понимая, как он работа...</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/index.php?title=LXF103:%D0%A1%D1%82%D1%80%D0%B5%D0%BB%D1%8F%D0%BB%D0%BA%D0%B0&amp;diff=7108&amp;oldid=prev"/>
				<updated>2009-03-01T13:30:30Z</updated>
		
		<summary type="html">&lt;p&gt;Новая: {{Цикл/Стрелялка}} == Наш движок изнутри == : &amp;#039;&amp;#039;ЧАСТЬ 3 Пользоваться инструментом, не понимая, как он работа...&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Новая страница&lt;/b&gt;&lt;/p&gt;&lt;div&gt;{{Цикл/Стрелялка}}&lt;br /&gt;
== Наш движок изнутри ==&lt;br /&gt;
: ''ЧАСТЬ 3 Пользоваться инструментом, не понимая, как он работает – не дело для настоящего линуксоида. Разберитесь в механике Ingame вместе с '''Александром Супруновым''' – а заодно познакомьтесь с основами SDL.''&lt;br /&gt;
&lt;br /&gt;
Движок Ingame, которым мы пользовались на протяжении двух&lt;br /&gt;
последних уроков, является набором оберток над функциями&lt;br /&gt;
библиотеки Simple DirectMedia Layer (SDL), доступной для Linux,&lt;br /&gt;
Windows, Mac OS X и множества других систем, включая даже AmigaOS.&lt;br /&gt;
И сегодня мы попробуем разобраться в том, что происходило все это&lt;br /&gt;
время «за кулисами».&lt;br /&gt;
&lt;br /&gt;
SDL написана на языке C, поэтому все объекты, которыми она оперирует, представлены в виде структур. Имена этих структур начинаются с&lt;br /&gt;
префикса SDL_, а центральное место среди них занимает SDL_Surface –&lt;br /&gt;
это так называемая «экранная поверхность», на которой можно размещать изображения. Структура SDL_Surface имеет поля w и h, задающие высоту и ширину поверхности, а также поле format. Указатель на&lt;br /&gt;
основной игровой экран, определенный в файле ingame.h, имеет тип&lt;br /&gt;
SDL_Surface * и имя display.&lt;br /&gt;
&lt;br /&gt;
=== Начало всех начал ===&lt;br /&gt;
[[Изображение:Img 103 96 1.jpg|thumb|300px|Схема работы движка Ingame: вывод спрайтов и двойная буферизация.]]&lt;br /&gt;
Как вы уже знаете, каждая программа, использующая Ingame, начинается с вызова функции screen(). Ее прототип выглядит так:&lt;br /&gt;
 void screen(int w, int h);&lt;br /&gt;
В первую очередь, screen() выполняет инициализацию SDL посредством вызова:&lt;br /&gt;
 SDL_Init (SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO);&lt;br /&gt;
где объединенные при помощи операции «логическое ИЛИ» флаги&lt;br /&gt;
имеют следующий смысл:&lt;br /&gt;
* SDL_INIT_VIDEO – активировать возможность работы с графикой.&lt;br /&gt;
* SDL_INIT_TIMER – активировать возможность использования встроенного в SDL таймера.&lt;br /&gt;
* SDL_INIT_AUDIO – активировать возможность проигрывания звуковых данных.&lt;br /&gt;
Затем инициализируется поверхность display. Это делается следующим образом:&lt;br /&gt;
 display = SDL_SetVideoMode (w, h, 0, SDL_SWSURFACE | SDL_ANYFORMAT);&lt;br /&gt;
Функция SDL_SetVideoMode() принимает в качестве параметров&lt;br /&gt;
ширину, высоту экрана, количество бит на пиксель, а также различные&lt;br /&gt;
ключи, в том числе:&lt;br /&gt;
* SDL_SWSURFACE – предписывает использовать программную отрисовку графики. Режим нагружает центральный процессор, но совместим практически со всеми компьютерами.&lt;br /&gt;
* Его антипод, SDL_HWSURFACE, задействует аппаратное ускорение, что может как повысить, так и понизить быстродействие конечной программы.&lt;br /&gt;
* SDL_DOUBLEBUF – включает режим двойной буферизации, позволяет получить более «плавную» графику.&lt;br /&gt;
* SDL_FULLSCREEN – активирует полноэкранный режим.&lt;br /&gt;
Прошу обратить внимание на третий параметр: количество бит&lt;br /&gt;
на пиксель. Он может принимать значения 8, 16, 24, 32 или 0, что&lt;br /&gt;
соответствует глубине цвета, установленной в системе по умолчанию. Последний вариант наиболее переносим. Однажды я наблюдал&lt;br /&gt;
повреждение графики в SDL-игре, запущенной на Amiga Pegasos.&lt;br /&gt;
Выяснилось, что причиной была жестко зашитая глубина цвета (16) –&lt;br /&gt;
замена ее на 0 решила проблему.&lt;br /&gt;
&lt;br /&gt;
Следующим шагом мы устанавливаем заголовок окна:&lt;br /&gt;
 SDL_WM_SetCaption («Linux GAMES», NULL);&lt;br /&gt;
а затем инициализируем библиотеку SDL_ttf&lt;br /&gt;
 TTF_Init();&lt;br /&gt;
и загружаем шрифт, который будет использоваться в игре для вывода&lt;br /&gt;
различных сообщений&lt;br /&gt;
 fnt = TTF_OpenFont(«font.ttf», 20);&lt;br /&gt;
Функция TTF_OpenFont() принимает два аргумента (имя файла со&lt;br /&gt;
шрифтом и размер в пунктах) и возвращает указатель на структуру&lt;br /&gt;
TTF_Font, который мы сохраняем в глобальной переменной fnt.&lt;br /&gt;
&lt;br /&gt;
С графикой разобрались; остался звук. За него отвечает библиотека&lt;br /&gt;
SDL_mixer, которая инициализируется вызовом:&lt;br /&gt;
 Mix_OpenAudio (44100, MIX_DEFAULT_FORMAT, 2, 2024);&lt;br /&gt;
Первый аргумент – частота дискретизации звука. Второй является стандартным ключом – просто запомните его. Далее указывается&lt;br /&gt;
количество звуковых каналов (разумеется, «стерео») и размер буфера,&lt;br /&gt;
отводимого под звуковые данные. Если вдруг в одно непрекрасное утро&lt;br /&gt;
вы услышите, что звук начал «спотыкаться» – увеличьте последнее&lt;br /&gt;
число, например, в два раза.&lt;br /&gt;
&lt;br /&gt;
Две заключительных строки функции screen() –&lt;br /&gt;
 frames = 0;&lt;br /&gt;
 then = SDL_GetTicks();&lt;br /&gt;
имеют отношение к подсчету и ограничению FPS. Это необходимо&lt;br /&gt;
для того, чтобы программа выполнялась с одинаковой скоростью на&lt;br /&gt;
любых компьютерах. Мы инициализируем счетчик кадров frames и&lt;br /&gt;
сохраняем текущее значение таймера в глобальной переменной then,&lt;br /&gt;
имеющей тип Uint32.&lt;br /&gt;
&lt;br /&gt;
=== Добавим игроков ===&lt;br /&gt;
Итак, экран готов – настало время загрузить спрайты. Для этих целей в&lt;br /&gt;
ingame.h используется структура WiHi, содержащая указатель на соответствующий объект SDL_Surface.&lt;br /&gt;
&lt;br /&gt;
По умолчанию резервируется место под 1900 объектов WiHi, причем все номера, начиная с 1000-го, используются движком для внутренних целей. При необходимости, можно увеличить число доступных&lt;br /&gt;
спрайтов, просто изменив размерность массива.&lt;br /&gt;
&lt;br /&gt;
Для загрузки спрайтов в формате BMP в Ingame предусмотрена&lt;br /&gt;
функция loadsprite(), принимающая в качестве аргументов номер ячейки (num), в которую будет загружена картинка и имя файла (name).&lt;br /&gt;
loadsprite() – обертка над двумя стандартными SDL-функциями: SDL_LoadBMP() и SDL_DisplayFormat().&lt;br /&gt;
&lt;br /&gt;
В принципе, здесь можно ограничиться всего одним вызовом:&lt;br /&gt;
 pic[num].tmp=SDL_LoadBMP(name);&lt;br /&gt;
но с точки зрения производительности рациональнее будет сразу же&lt;br /&gt;
преобразовать спрайт в пиксельный формат дисплея (например, если&lt;br /&gt;
оригинальная картинка использует 24 бита на пиксель, а на экране –&lt;br /&gt;
всего 16, глубина цвета должна быть понижена). В итоге тело функции&lt;br /&gt;
loadsprite() примет вид:&lt;br /&gt;
 pic[num].tmp=SDL_DisplayFormat(SDL_LoadBMP(name));&lt;br /&gt;
Функция sprite(num, x, y), как вы, надеюсь, помните, выводит&lt;br /&gt;
спрайт с номером num в точке с координатами (x,y). Происходит это&lt;br /&gt;
следующим образом: для изображения устанавливается «цветовой&lt;br /&gt;
ключ» (значение RGB, которое SDL будет считать прозрачным), а затем&lt;br /&gt;
спрайт просто переносится в нужную точку экрана.&lt;br /&gt;
&lt;br /&gt;
За прозрачность спрайта «отвечает» функция SDL_SetColorKey():&lt;br /&gt;
 SDL_SetColorKey(pic[num].tmp,SDL_SRCCOLORKEY | SDL_RLEACCEL,SDL_MapRGB(pic[num].tmp-&amp;gt;format,255,0,255));&lt;br /&gt;
Флаг SDL_SRCCOLORKEY указывает, что «цветовой ключ» (последний аргумент функции) следует считать прозрачным, SDL_RLEACCEL&lt;br /&gt;
включает RLE-оптимизацию (при этом группы одинаковых пикселей&lt;br /&gt;
кодируются по принципу число_пикселей X значение, что ускоряет&lt;br /&gt;
копирование), а вызов SDL_MapRGB()&lt;br /&gt;
возвращает значение «ключа» в требуемом формате (напомню, что Ingame&lt;br /&gt;
считает прозрачными пиксели цвета&lt;br /&gt;
(255,0,255).&lt;br /&gt;
&lt;br /&gt;
Собственно копирование спрайта осуществляется функцией&lt;br /&gt;
SDL_BlitSurface():&lt;br /&gt;
 SDL_BlitSurface(pic[num].tmp,0,display,&amp;amp;shadow);&lt;br /&gt;
Первый аргумент (pic[num].&lt;br /&gt;
tmp) – исходная поверхность, второй –&lt;br /&gt;
область исходного изображения, подлежащая копированию (мы передаем&lt;br /&gt;
здесь NULL, что означает «вся поверхность»). Остальные два параметра имеют тот же смысл для целевой поверхности. shadow – переменная типа SDL_Rect, представляющая собой прямоугольник; координаты левого верхнего&lt;br /&gt;
угла которого как раз равны x и y.&lt;br /&gt;
&lt;br /&gt;
=== Как сказать: «Game over»? ===&lt;br /&gt;
Помимо спрайтов, на экране время&lt;br /&gt;
от времени нужно отображать и текстовые сообщения. Для этих целей&lt;br /&gt;
в Ingame предназанчена функция&lt;br /&gt;
print(сообщение, координата_x, координата_y). Она начинается с определения двух переменных&lt;br /&gt;
 SDL_Color color = {255,255,255,0};&lt;br /&gt;
 SDL_Rect dest= {(Sint16)x, (Sint16)y,0,0};&lt;br /&gt;
Первая из них, типа SDL_Color, задает цвет символов (белый), а&lt;br /&gt;
вторая определяет точку, в которой будет выводиться сообщение.&lt;br /&gt;
Текст надписи растеризуется на служебной поверхности (помните, все&lt;br /&gt;
спрайт-слоты выше 1000 заняты Ingame для внутренних нужд) функцией TTF_RenderText_Blended():&lt;br /&gt;
 pic[1000].tmp = TTF_RenderText_Blended(fnt, txt, color);&lt;br /&gt;
Назначение ее аргументов, думаю, должно быть ясно из их&lt;br /&gt;
имен. Можно также использовать более скоростной вариант – TTF_RenderText_Solid(), но он проигрывает _Blended() по красоте вывода.&lt;br /&gt;
&lt;br /&gt;
Остается только скопировать сообщение на экран при помощи уже&lt;br /&gt;
известной нам функции SDL_BlitSurface():&lt;br /&gt;
 SDL_BlitSurface( pic[1000].tmp, NULL, display, &amp;amp;dest );&lt;br /&gt;
и освободить память:&lt;br /&gt;
 SDL_FreeSurface( pic[1000].tmp );&lt;br /&gt;
&lt;br /&gt;
=== Движущая сила ===&lt;br /&gt;
Итак, библиотека инициализирована, спрайты отрисованы; настало&lt;br /&gt;
время заставить их двигаться. За это и многое другое отвечает функция fx(), реализующая основной цикл игры. Она требует предварительного объявления ряда глобальных переменных:&lt;br /&gt;
 SDL_Event event;&lt;br /&gt;
 Uint8* keys;&lt;br /&gt;
event – специальная переменная событийного типа (зачем она нужна,&lt;br /&gt;
будет ясно ниже), а keys содержит номера нажатых клавиш.&lt;br /&gt;
&lt;br /&gt;
Мы также вводим ограничение FPS 75-ю кадрами в секунду.&lt;br /&gt;
 #define FPS_LIMIT 75&lt;br /&gt;
Функция fx() отслеживает нажатие клавиш, ограничивает количество кадров, выводимых на экран монитора, очищает его перед&lt;br /&gt;
отрисовкой очередной сцены, организует механизм «велосити» для&lt;br /&gt;
движения объектов и меняет местами основной и теневой экраны,&lt;br /&gt;
то есть реализует двойную буферизацию изображения. Разберем эти&lt;br /&gt;
действия по шагам.&lt;br /&gt;
&lt;br /&gt;
fx() начинается с опроса SDL на предмет произошедших событий.&lt;br /&gt;
Этим занимается функция SDL_PollEvent(), которая принимает указатель на переменную событийного типа и возвращает TRUE, если&lt;br /&gt;
что-то произошло. После этого мы можем изучить поле event.type,&lt;br /&gt;
чтобы понять, что именно случилось, и отреагировать надлежащим&lt;br /&gt;
образом:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot;&amp;gt;while (SDL_PollEvent(&amp;amp;event))&lt;br /&gt;
{&lt;br /&gt;
if (event.type==SDL_QUIT)&lt;br /&gt;
{&lt;br /&gt;
GAME=0;&lt;br /&gt;
SDL_Quit();&lt;br /&gt;
}&lt;br /&gt;
if (event.type==SDL_KEYDOWN)&lt;br /&gt;
{&lt;br /&gt;
if(event.key.keysym.sym==SDLK_ESCAPE)&lt;br /&gt;
{&lt;br /&gt;
GAME=0;&lt;br /&gt;
SDL_Quit();&lt;br /&gt;
}&lt;br /&gt;
}&lt;br /&gt;
}&amp;lt;/source&amp;gt;&lt;br /&gt;
В первую очередь обрабатываются события, сигнализирующие&lt;br /&gt;
о необходимости выхода из игры: системное SDL_QUIT (оно генерируется, если, например, пользователь щелкнул кнопку закрытия&lt;br /&gt;
окна) и нажатие на клавишу Escape (событие типа SDL_KEYDOWN,&lt;br /&gt;
код клавиши при этом сохраняется SDL в поле event.key.keysym.sym).&lt;br /&gt;
Доступные коды клавиш перечислены в файле SDL/SDL_keysym.h,&lt;br /&gt;
там же определены и константы наподобие SDLK_ESCAPE. Кстати,&lt;br /&gt;
можно реагировать не на нажатие, а на отпускание клавиши – такое&lt;br /&gt;
событие будет иметь тип SDL_KEYUP.&lt;br /&gt;
&lt;br /&gt;
Затем наступает черед обработки нажатия на управляющие&lt;br /&gt;
клавиши: «вправо», «влево», «огонь» и т.п. Здесь можно было бы&lt;br /&gt;
воспользоваться тем же приемом, что и для Escape, но есть одно&lt;br /&gt;
«но»: пользователь должен иметь возможность нажать на несколько клавиш одновременно (скажем, «влево» и «вверх» или «вверх» и&lt;br /&gt;
«огонь»). Для таких целей SDL предоставляет в наше распоряжение&lt;br /&gt;
функцию SDL_GetKeyState(), которая возвращает указатель на массив&lt;br /&gt;
значений типа Uint8, представляющий собой текущее состояние клавиатуры в целом. Исходя из того, что именно было нажато, мы устанавливаем переменные-флаги (LEFT, RIGHT и т.д.), а также изменяем&lt;br /&gt;
значение «велосити».&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot;&amp;gt;keys=SDL_GetKeyState(NULL);&lt;br /&gt;
if(keys[SDLK_LEFT])&lt;br /&gt;
{&lt;br /&gt;
LEFT=1; vel=velocity;&lt;br /&gt;
}&lt;br /&gt;
else&lt;br /&gt;
LEFT=0;&amp;lt;/source&amp;gt;&lt;br /&gt;
Аналогично поступаем и с другими управляющими клавишами.&lt;br /&gt;
&lt;br /&gt;
Далее в игру вступает механизм «велосити», который был достаточно подробно рассмотрен в [[LXF100-101:Стрелялка|LXF100/101]], и, наконец, отрабатывает&lt;br /&gt;
система ограничения FPS:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot;&amp;gt;++frames;&lt;br /&gt;
now = SDL_GetTicks();&lt;br /&gt;
if ( now &amp;gt; then ) {&lt;br /&gt;
fps= (int)((double)frames*1000)/(now-then);&lt;br /&gt;
}&lt;br /&gt;
if ( fps &amp;gt; FPS_LIMIT ) {SDL_Delay(1000/FPS_LIMIT);}&amp;lt;/source&amp;gt;&lt;br /&gt;
Мы увеличиваем счетчик кадров, получаем текущее значение таймера и вычисляем FPS. Если полученный результат превышает заданный предел, функция SDL_Delay() приостанавливает работу программы на необходимый промежуток времени.&lt;br /&gt;
&lt;br /&gt;
В качестве завершающего аккорда функция fx() меняет местами&lt;br /&gt;
теневой и экранный буферы:&lt;br /&gt;
 SDL_Flip(display);&lt;br /&gt;
делая видимым все, что было нарисовано на данной итерации цикла,&lt;br /&gt;
и очищает вспомогательный экран, заливая его черным цветом при&lt;br /&gt;
помощи непрямого вызова функции SDL_FillRect().&lt;br /&gt;
&lt;br /&gt;
=== Музыка и звук ===&lt;br /&gt;
Работа со звуком в Ingame по своей сути похожа на работу со спрайтами. Здесь также определяются структуры SiHi и MiHi, являющиеся&lt;br /&gt;
обертками для указателей на Mix_Chunk (звуковой эффект) и Mix_Music (музыкальная композиция), соответственно. Структуры объединяются в массивы, насчитывающие 500 и 100 элементов. Для загрузки эффекта из файла формата WAV функция loadsound() вызывает&lt;br /&gt;
Mix_LoadWAV(), которая, в свою очередь, принимает имя WAV-файла&lt;br /&gt;
в качестве единственного параметра. Для воспроизведения эффекта&lt;br /&gt;
используется функция sound(), эквивалентная вызову:&lt;br /&gt;
 Mix_PlayChannel (-1, sn[num].tmp, 0);&lt;br /&gt;
-1 является указанием использовать первый доступный канал,&lt;br /&gt;
0 – число повторений, означающее, что звук будет проигран один раз.&lt;br /&gt;
&lt;br /&gt;
С музыкой все обстоит ненамного сложнее. Соответствующие&lt;br /&gt;
функции являются обертками над Mix_LoadMUS() и Mix_PlayMusiс().&lt;br /&gt;
Интерес представляет лишь функция stopmusic(), вызывающая&lt;br /&gt;
 Mix_FadeOutMusic(1000);&lt;br /&gt;
для плавного затухания звука в течение&lt;br /&gt;
1 секунды.&lt;br /&gt;
&lt;br /&gt;
=== Что дальше? ===&lt;br /&gt;
Наш краткий экскурс в SDL подошел к концу. Конечно, мы не рассмотрели и десятой&lt;br /&gt;
части возможностей этой замечательной&lt;br /&gt;
библиотеки, но сделали главное – разобрались в том, как работает движок Ingame.&lt;br /&gt;
Теперь, когда на нашей карте не осталось&lt;br /&gt;
белых пятен, вы можете сами решать, куда&lt;br /&gt;
двигаться дальше. Хотите – развивайте&lt;br /&gt;
ingame.h: исходный код распространяется&lt;br /&gt;
по лицензии GPL; хотите – напишите с его&lt;br /&gt;
помощью собственную игру. Возможен так-же третий вариант – разберитесь в деталях с&lt;br /&gt;
SDL и создайте что-нибудь с нуля. Главное,&lt;br /&gt;
если у вас получится что-то стоящее – не&lt;br /&gt;
забудьте сообщить нам об этом!&lt;/div&gt;</summary>
		<author><name>Yaleks</name></author>	</entry>

	</feed>