Отправляет email-рассылки с помощью сервиса Sendsay

Программирование игр в Linux

  Все выпуски  

Programming Linux Games 02


Информационный Канал Subscribe.Ru


Programming Linux Games

Выпуск 2 нормальный (28.12.2003)


"Если б все было так сложно,
мы б давно уже все сдохли..."
Народная мудрость

Вступительное слово:

Здравствуйте, уважаемые подписчики! Вас уже целых 40 человек :-)! После долгого перерыва наконец нашлись силы сделать второй выпуск, а по сути первый нормальный. Прежде всего, я искренне поздравляю всех с наступающим Новым Годом. На самом деле, мне особой радости в новогоднюю ночь не предвидится, ибо работа... Все желающие, если таковые найдутся, могут составить мне компанию на нашем IRC канале: серверы RusNet (irc.primorye.ru) канал #emu или #plg.
Теперь немного о новостях нашего сайта. Зарегистрировал сайт в банерной сети Game Community Network. Поэтому исчез наш привычный так называемый логотип, а появились так называемые заглушки-залепы... Одним словом, в банерную сеть нас еще пока не приняли, т.к. модерируют... Уже как неделю модерируют, хотя утверждают, что эта процедура делается буквально в течение дня. Но не будем о грустном!
Второе событие, самое важное, отправил первый майлстоун Sven Bomwollen for Linux на тестирование в Phenomedia AG. То еще событие! Исправил багу буквально за пару часов до отправки! Хотя сидел до этого неделю не спамши и не жрамши. Отправил точно по графику! Планов не нарушил. По сему вывод напрашивается: все должно выйти точно в сроки, а именно 1 марта 2004 года.
Пытливый, как говорится, пользователь уже заметил, что сменилась тема опроса на сайте. Наш опрос с трудом можно назвать точным, но все же кое-какие выводы сделать можно. На сей раз мне приспичило узнать, сколько денег не жалко за хорошую игру под Linux. Как говорится, ждем ваших счастливых историй.
А в остальном все тихо - мирно, как говорится, по-старому. Наше дружное комьюнити в лице меня, Юры Stalkerg и еще двух-трех человек по прежнему жарко дискутирует на страницах форума, я изредка добавляю ссылки и новости и мечтаю доделать раздел с документацией и Downloads. Посетителей мало, обстановка почти домашняя... А может так оно лучше?

Искренне ваш, E$h

Сегодня в номере:

  • Для самых маленьких юных натуралистов: Дебют нового цикла статей для начинающих игроделателей. Мы опять возвращаемся к теме зоофилии, на этот раз в сторону курочек и садо-мазо. Для тех кто в танке: первый опыт - Sven Bomwollen for Linux.
  • Наболевшие вопросы и неприятные моменты: Интересные вопросы и ответы с нашего форума, различных мэйл-листов, да и мало ли еще откуда!
  • Портирование: реальные страхи. Портирование игр с Win32 в Linux... Что может быть страшнее? Другие ОС для меня неактуальны ибо не имею.
  • Обзор новых ссылок и проектов. За то долгое время после выхода пилотного номера рассылки я успел поднакопить несколько интересных ссылок. О них и пойдет речь.

Для самых маленьких: делаем игру вместе

Вместе мы будет создавать простенькую, неказистую, ненужную, но самую настоящую игру. Прежде чем завалить вас килобайтами кода, необходимо разобрать несколько моментов.
Момент 1: о чем будет игра? В стародавние времена были устройства под названием Atari. И могли они играть игру то ли Frogger, то ли еще как-то... Суть игры - перейти дорогу, наводненную автомобилями. Мы управляем маленьким беззащитным цыпленком. Нужно быть осторожным - жалко же если его задавит машина.
Момент 2: как делать? Будем использовать великий и могучий язык Си и не менее могучую библиотеку SDL. К разочарованию большинства начинающих, игра будет 2D. Но до трехмерных миров мы еще успеем дожить.
Момент 3: что мы имеем? Иметь мы будем маленькую добрую курочку и много-много злых автомобилей. Автомобили будут неуправляемые, то есть управлять ими мы не будем. Иначе это был бы уже автосимулятор. Неуправляемые автомобили мчатся по шоссе с различной скоростью и в разных направлениях.
Желающие могут посмотреть скриншот здесь.
И так, начинаем делать. Заведите себе папку вроде chicken/. В этой папке будет пока всего один файл chicken.c - наш исходник. Для начала нужно написать функцию main, но думаю вы не на столько маленькие, чтобы не знать как.
#include <stdlib.h>
#include "SDL.h"

/* Наш главный экран: */
SDL_Surface * screen;

int main(int argc, char * argv[])
{
  /* Инициализируем SDL: */
  if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0)
  {
    printf("Error initing SDL:  %s\n",
     SDL_GetError());
    exit(1);
  }

  /* При выходе из программы вызвать SDL_Quit: */
  atexit(SDL_Quit);

  /* Создаем экран: */
  screen = SDL_SetVideoMode(640, 480, 16, 0);

  if (screen == NULL)
  {
    printf("Error: Can't open window!  %s\n",
     SDL_GetError());
    exit(1);
  }
  
  /* Флаг выхода из программы: */
  int done = 0;
  
  /* Главный игровой цикл: */
  while(done == 0)
  {
  
    /* Очередь событий: */
    SDL_Event event;
  
    /* Цикл событийЖ */
    while ( SDL_PollEvent(&event) )
    {
      
      if ( event.type == SDL_QUIT )
      { 
        /* событие выхода */
 done = 1; 
      } 
      
      /* Если нажата какая-нибудькнопка на клавиатуре */
      if ( event.type == SDL_KEYDOWN )
      {
        if ( event.key.keysym.sym == SDLK_ESCAPE )
 { 
          /* если нажата Esc, то выходим */
   done = 1; 
        } 
      }
      
    } /* Цикл событий */
   
    /* Здесь происходит отрисовка графики */
   
  } /* Главный цикл */
  
  return 0;
  
}
Ужас на первый неопытный взгляд... Этот кусок кода есть ни что иное, как каркас нашей будущей игры. Давайте понемногу разберемся.
Сначала мы должны инициализировать SDL:

  /* Инициализируем SDL: */
  if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0)
  {
    printf("Error initing SDL:  %s\n",
     SDL_GetError());
    exit(1);
  }
В данном случае мы инициализируем подсистемы видео и звука (на будущее). В случае ошибки пишем в чем проблемы (SDL_GetError) и выходим из программы. Далее в обязательном порядке создаем окно:
  /* Создаем экран: */
  screen = SDL_SetVideoMode(640, 480, 16, 0);

  if (screen == NULL)
  {
    printf("Error: Can't open window!  %s\n",
     SDL_GetError());
    exit(1);
  }
Заметьте, что screen у нас глобальная переменная (указатель на структуру SDL_Surface), которая по совместительству является нашим окном (грубо говоря). Также проверяем на ошибки и с криками выходим из программы, если таковые нашлись.
Затем мы проникаем в главный цикл и болтаемся там пока переменная done равна нулю. При определенных обстоятельствах мы присваиваем этой переменной другое значение (1), чтобы выйти из главного цикла и вообще из программы. В нашем случае такое обстоятельство наступает при нажатии на клавишу Esc или при нештатном событии SDL_QUIT (это нас по большей части не касается). Отслеживать события нам помогает переменная SDL_Event event - которая является сложной структурой. Во время каждого прохода цикла (итерация) в нее помещается свежая информация о произошедших событиях. А мы их потом оттуда извлекаем и проверяем. К примеру при событии SDL_KEYDOWN (нажата была какая-то кнопка) мы проверяем какая же это была все-таки кнопка: если Esc, то выходим из программы. Сюда мы будем вставлять еще проверки для других клавиш.
Для начала должно хватить. Попробуйте собрать программу (она уже рабочая) так:
gcc -o chicken chicken.c sdl-config --cflags --libs
Внимание!!! Все кавычки обратные: там где знак ~ (возле цифры 1). Это важно. Готовая программа будет показывать свой черный экран, пока не нажмете Esc.
Если вам что-нибудь не понятно, то попробуйте спрашивать на нашем форуме и почитать (на сайте в разделе с документацией) учебник "Познакомьтесь, SDL". Для тех, кому и после этого ничего не понятно рекомендую научиться программировать на языке Си.

Наболевшие вопросы и неприятные моменты

За короткий промежуток времени накопилось несколько вопросов, которые мы как-то пытались решать. Вопросов немного, так как я практически не получал от вас отзывы по первому выпуску, да и собственно вопросов по нему не должно было возникнуть.
Первая наболевшая проблема: зеркальный блиттинг, то есть зеркальное преобразование сурфейсов в SDL. Штатных средств для этого естественно нет, т.к. SDL достаточно низкоуровневая библиотека, на основе которой каждый может создать что-то свое. Я пытался выяснить этот вопрос в листе рассылки SDL, но в ответ умные буржуи раздались пространственными размышлениями о высоких материях и решили, что такую фичу включать в библиотеку нерулез... Но мы, как говорится, не лыком шиты. Взял я и написал себе две функции: одна зеркально отражает слева направо, а вторая сверху вниз. Функции создают новый SDL_Surface - зеркальную копию оригинала, который передается в параметрах.
Ниже представлен код функции mirror_hor, которая возвращает зеркально отраженную сверху вниз копию своего параметра. Эти функции работают при любой глубине цвета, но оригинальный сурфейс (src) нужно приводить в display format, например: SDL_Surface *src = SDL_DisplayFormat(SDL_LoadBMP(file)). Хотя, если вы серьезно работаете с 2D SDL, то должны делать это всегда - скорость отличается в разы! Но будьте внимательны: функция SDL_DisplayFormat создает новый сурфейс. Это можно забыть и тогда у вас очень быстро кончится оперативная память и своп :-)
// --------------------------------------------
SDL_Surface* mirror_vert(SDL_Surface* src)
{

   SDL_Surface* temp = SDL_CreateRGBSurface(SDL_SWSURFACE, src->w, src->h,
                                             src->format->BitsPerPixel,
                                             src->format->Rmask,
                                             src->format->Gmask,
                                             src->format->Bmask,
                                             src->format->Amask);
 
  char *pTemp, *pSrc;
  int i;
  int Bpp = temp->format->BytesPerPixel;
  
  SDL_LockSurface(temp);
  SDL_LockSurface(src);
  
  pSrc = (char*)src->pixels;

   /* VERT MIRROR */
   pTemp = (char*)temp->pixels + (temp->h-1)*temp->pitch;
   for(i=0; i <= src->h; i++)
   {
     memcpy((void*)pTemp,(void*)pSrc, temp->pitch);
     pTemp-=temp->pitch;
     pSrc+=temp->pitch;
   }

  SDL_UnlockSurface(src);
  SDL_UnlockSurface(temp);

  return temp;
}
Тут в принципе не сложно, просто помните, что на 1 пиксель приходится SDL_Surface->format->BytesPerPixel байт, а pitch показывает размер (в байтах) одной строки. Например если цвет 16 бит, то получаем 2 байта на пиксель (1 байт = 8 бит). Зная это, мы просто берем строку пикселей сверху src и переносим их вниз temp (или dest). Рекомендую внимательнейшим образом проштудировать официальную документацию по SDL, в особенности структуру SDL_Surface.
А вот код второй функции, которая отражает слева направо.
// --------------------------------------------
SDL_Surface* mirror_hor(SDL_Surface* src)
{

   SDL_Surface* temp = SDL_CreateRGBSurface(SDL_SWSURFACE, src->w, src->h,
                                             src->format->BitsPerPixel,
                                             src->format->Rmask,
                                             src->format->Gmask,
                                             src->format->Bmask,
                                             src->format->Amask);

 
  char *pTemp, *pSrc;
  int i, j;
  int Bpp = temp->format->BytesPerPixel;
  
  SDL_LockSurface(temp);
  SDL_LockSurface(src);
  
   pSrc = (char*)src->pixels;

   /* HOR MIRROR */
   pTemp = (char*)temp->pixels + (temp->w-1)*Bpp;
   for(i=0; i< src->h; i++)
   {
     for(j=0; j <= src->w; j++)
     {
       memcpy((void*)pTemp, (void*)pSrc, Bpp);
       pTemp-=Bpp;
       pSrc+=Bpp;
     }
     pTemp+=temp->pitch*2;
   }

  SDL_UnlockSurface(temp);
  SDL_UnlockSurface(src);

  return temp;
}
Здесь два цикла: в первом берем строку пикселей и во втором цикле копируем каждый пиксель в temp слева направо. Если кто-нибудь захочет полностью разобраться в работе функций, то рекомендую нарисовать сначала на бумаге :-)
Вторая наболевшая проблема: как сделать качественный скроллинг. В своем труде (учебнике по SDL) я посвятил этому вопросу целую главу. Там реализован side-scroller средствами чистого SDL. В этом есть и свои проблемы: это очень медленно, т.к. не используется аппаратное ускорение. Если использовать расширение иксов DGA2 (только полный экран), то могут быть проблемы в разных дистрибутивах (у некоторых только из-под рута, у других вообще никак). Другой, я бы сказал, наиболее удачный выход - использовать OpenGL для 2D графики. В качестве спрайтов можно использовать PNG с альфа каналом. Для скролла нужно просто изменять текстурные координаты полигона! Это очень просто и результат впечатляет! Хороший пример есть в NeHe (http://nehe.gamedev.net). Скоро я возможно набросаю простую библиотеку для 2Д спрайтов под OpenGL ибо надо :-) А пока желаю успешных экспериментов, благо не химики.
На сегодняшний момент интересных проблем более не имею, но с удовольствием приму ваши. Вроде как приму на себя ваши проблемы... Только вы сильно не увлекайтесь.

Портирование: реальные страхи

В этом разделе я начинаю большой рассказ с примерами на тему портирования программ. Тема, на мой взгляд, полезная и нужная. Много людей считает, что портирование легче, чем разработка с нуля. Так ли это - судить не мне, но по личному опыту скажу твердое нет. В любом случае, есть множество неприятных моментов и прежде всего, изучать чужой код, особенно если он плохо написан, - сущее наказание. Для начала давайте посмотрим что же такое портирование. Я выделил несколько типов:

  • Операционные системы: Win32 в Linux и т.п.
  • Версии ОС: MacOS 9 в MacOS X
  • Базы данных: MS-SQL в Oracle
  • Язык программирования: Pascal в C++
  • Библиотеки: MFC в Qt
  • Технологии: COM в CORBA
  • Средства разработки: VC++ в KDevelop
  • Web-платформы: IIS/ASP в Apache/PHP
Можно долго размышлять на отвлеченные темы по портированию. К примеру, процесс состоит из нескольких фаз: решение общих вопросов, выбор средств разработки для целевой платформы, изучение кода, портирование отдельных модулей и т.п. Это важные вопросы, но они зависят от проекта. Иногда допускается непонимание кода, а просто механическое переписывание некоторых участков. Но это удел либо профессионалов, либо единиц несложных проектов. В большинстве случаев необходимо полностью разобраться в оригинальном коде. Еще полезно иметь возможность экспеиментировать над оригинальным кодом, чтобы лучше разобраться.
Я буду публиковать свои заметки по портированию Win32 в Linux. Если у вас есть свои истории или вы портируете в другие платформы (или с других), то буду безумно рад разделить с вами авторство данной колонки! Для начала разберемся с некоторыми несложными моментами Win32 to Linux.
1. PostMessage или SendMessage По своей портируемой сути они одинаковы. Эти функции помещают специальные сообщения в системную очередь. В Win32 они могут использоваться даже для пересылки сообщений другим приложениям. Далее любое окно должно обрабатывать эту очередь сообщений, посланных ему, и специальным образом реагировать на них. Вот прототип функции:
BOOL PostMessage (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
hWnd - дескриптор окна (как бы глобальный номер окна)
message - специальное сообщение (например WM_CLOSE)
wParam и lParam - параметры, передаваемые вместе с сообщением. Так можно передать какие-нибудь данные из функции в главное окно.
Вы уже конечно догадались, что для портирования этой функции больше всего подходит система событий SDL (внимательно ознакомьтесь с документацией по SDL_Event). Этот механизм легко позволяет создавать свои собственные сообщения, которые с легкостью можно обработать в главном цикле!
SDL_Event* ue = new SDL_Event;
ue->type = SDL_USEREVENT;
ue->user.code = YOUR_MESSAGE_ID;
ue->user.data1 = 0;
ue->user.data2 = 0;
SDL_PushEvent(ue);
delete ue;
Здесь мы создаем новое событие, которому присваиваем тип SDL_USEREVENT (стандартный тип), в поле user.code мы храним уникальный собственный номер события, который потом обрабатываем. Вместо lParam и wParam мы имеем void *data1 и void *data2 - универсальный тип для хранения любых данных.
   while ( SDL_PollEvent(&event) )
   { 

     if ( event.type == SDL_USEREVENT )
     {
  switch(event.user.code)
  {
  // обработка собственных событий
  }
     }
   }
Как видим, все элементарно. Безусловно, я разобрал не все моменты, связанные с этими функциями, но приведенный выше пример я реально использую в проекте.
2. INI файлы казалось бы, что может быть проще? Ан не совсем... Win32 имеет в составе API функции для работы с INI файлами. Создание своей библиотеки для работы с любыми INI файлами дело непростое. Для этого лучше всего использовать чужие разработки, а именно libini (http://libini.sourceforge.net). Разбирать мы эту библиотеку не будем, ибо несложная. Распространяется под лицензией GNU GPL, а не под обычной для библиотек LGPL, что не позволяет включить ее в коммерческий проект. Другой вариант - использовать средства lex и bison для парсинга таких файлов. Этим делом я не занимался, но говорят легко. Еще можно ковырять исходники Samba - ее конфигурационные файлы очень похожи по синтаксису на INI. И, наконец, вы можете настроить программу для чтения только вашего INI файла (как собственно я и сделал) используя, например std::fstream из STL или функции семейства scanf.
3. Портирование GUI приложений MFC. Совершенно случайно я набрел на интересную статью с сайта IBM, в которой наглядно расписан процесс портирования GUI программы, использующей MFC в программу на wxWindows. При ознакомлении с этой библиотекой в глаза бросается поразительное сходство с MFC! Буквально необходимо заменить суффикс C на wx! Это, естественно, отличное средство для портирования например редакторов карт игры и т.п. Описывать этот процесс я тоже не буду. Но затронул этот вопрос, потому что делается все просто и эффективно.
В следующих выпусках мы обязательно продолжим разговор о портировании. Если у вас есть свои заметки, обязательно присылайте!

Обзор новых ссылок и проектов

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

  • http://frustum.org - воистину сайт! Целая коллекция демок нашего соотечественника. Все примеры для OpenGL и под Linux. Естественно с исходниками. Уже просматривая скриншоты, быстро текут слюни. В общем, немедленно идите и посмотрите! Только дочитайте рассылку.
  • http://blender3d.ru - киньте камень в того, кто говорит, что Blender3D плохой! Наконец-то ребята объединились и создали официальный русскоязычный сайт этой замечательной программе. На сайте есть много документации, ссылок на документацию (на русском языке), куча плагинов, в т.ч. для экспорта моделей в различные форматы. Есть несколько потрясающих моделей, созданных ребятами. Пожелаем им удачи!
  • http://portinggurus.org - некое сообщество, предположительно из Индии, решило создать полезный ресурс. Еще многое не сделано, но они меня заверили (по e-mail), что "фсио будет в шоколаде". Ах да! Сайт англоязычный что, надеюсь, не проблема.
  • http://linux-sound.org - я обещал в прошлом выпуске найти какие-нибудь редакторы звука для Linux. И обещание свое сдержал. На этом сайте есть ВСЕ, что может пришодиться для создания звуковых данных игры: трекеры, драм-машины, сэмплы и т.п. Точнее, там ссылки на другие сайты.
Как всегда, смотрите обновления ссылок в соответствующем разделе нашего сайта.

Заключительное слово

На этом поставим... нет не точку, а всего лишь запятую, потому как не за горами новый выпуск, который вы, уважаемые подписчики, уже ждете с нетерпением. С удовольствием приму ваши замечания, предложения и т.п. А пока позволим себе расслабиться и получать удовольствие, празднуя Новый 2004 год.
Да преумножится многократно ваш код, уважаемые подписчики!


Рассылку выпускал E$h (bbroth@plg.lrn.ru); Сайт рассылки: http://plg.lrn.ru; Периодичность: не менее 2-х раз в месяц.
 


http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться
Убрать рекламу

В избранное