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

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

  Все выпуски  

Программирование игр в Linux выпуск 3


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

Программирование игр в Linux: OpenGL для графики 2D-игры

Часть 3

Здравствуйте, уважаемые читатели. Несмотря на столь долгое молчание, мы наконец-то снова вместе. В прошлых выпусках мы рассматривали основы рисования 2D-графики используя OpenGL. Начиная с этого выпуска мы будем писать несложный движок, на основе которого вы сможете написать простую игру. Все примеры из прошлых выпусках были написаны на языке Си, но для нашего движка больше подходит С++. В выпусках рассылки я врядли смогу научить вас программировать на этом языке, поэтому предполагается, что каждый читатель имеет хотя бы небольшой опыт в С++. Мы начнем с разработки класса изображения, поскольку мы с вами уже затрагивали эту тему.

С точки зрения движка, любая картинка - это ресурс. В игре могут быть совершенно разные ресурсы: изображения, звуки, скрипты и многое другое. Их особенность в том, что они занимают какое-то количество памяти. Очень часто необходимо использовать один и тот же ресурс несколько раз. Чтобы не дублировать один и тот же ресурс, необходимо загрузить его один раз и использовать его неограниченное количество раз (например через указатель).
И так, общими для всех ресурсов (применительно к нашему движку) части таковы: Имя ресурса, по которому мы сможем его загрузить, либо обратиться к нему; Метод загрузки load() - каждый ресурс должен быть загружен из файла. Так как ресурсы имеют общие части, можно вынести их в базовый класс:
class Resource
{
public:
        Resource(const std::string& name) : name_(name), is_loaded_(false) {};
        virtual ~Resource() {};
        
        const std::string& get_name() const { return name_; };
        bool is_loaded() const { return is_loaded_; };
        virtual void load() = 0;
        
protected:
        std::string name_;
        bool is_loaded_;

};
Этот класс описывает только интерфейс для ресурсов всех типов, поскольку является абстрактным. Для добавления поддержки ресурса любого типа, нужно отнаследовать конкретный класс от базового Resource. Это сделано для того, чтобы в дальнейшем мы смогли бы оперировать с ресурсами, используя базовые методы (такие как load), потому что указатель на каждый отнаследованный от Resource класс можно привести к базовому типу и наоборот. Более подробно мы рассмотрим эту тему в следующем выпуске.

Теперь, как и планировалось, попробуем разработать конкретный ресурс - изображение. Для начала было бы неплохо понять, что именно мы подразумеваем под изображением, какие свойства и методы должен иметь производный класс Image.
В каждой 2D-игре есть такое понятие как спрайт, с которым практически все из вас должны быть хорошо знакомы. Спрайты бывают анимированные и статические. Помимо спрайтов, изображения используются в роли фона. Чтобы иметь поддержку дял всех типов игровых объектов, будь то статический, анимированный спрайт или задний фон, мы будем рассматривать ресурс "Изображение" как набор кадров (фреймов). Для статических спрайтов или фона в таком наборе будет всего один кадр, а в анимациях их будет больше.
Таким образом, изображение должно представлять собой набор кадров, номера которых начинаются с верхнего левого угла, продолжаются сверху вниз по колонкам и заканчиваются в правом нижнем углу. Для понимания этого взгляните на схему расположения кадров в разных изображениях:

 1) Анимация из 9 кадров:  2) Анимация из 3 кадров:  3) Анимация из 3 кадров:  4) Статическая картинка:
         [1 4 7]                                             [1]                   
         [2 5 8]                   [1 2 3]                   [2]                         [1]
         [3 6 9]                                             [3]
Цифрами обозначен порядок следования кадров внутри изображения. Размер каждого кадра должен быть одинаковым - это очень важно. При загрузке такого изображения, нам нужна информация о количестве кадров, а также о количестве рядов и колонок. Следовательно, для примеров выше информация будет такая:
  • 1) Рядов: 3; Колонок: 3; Всего кадров: 9
  • 2) Рядов: 1; Колонок: 3; Всего кадров: 3
  • 3) Рядов: 3; Колонок: 1; Всего кадров: 3
  • 4) Рядов: 1; Колонок: 1; Всего кадров: 1
На основе этих данных можно делать класс:
class Image : public Resource
{
public:
        Image(const std::string& name, int rows, int cols, int frames = 0);
        virtual ~Image();
        
        virtual void load();
        
        int get_rows() const { return rows_; };
        int get_cols() const { return cols_; };
        int get_frames() const { return frames_; };

        int get_width() const { return width_; };
        int get_height() const { return height_; };
        
        void draw(int frame, int x, int y, bool flip = false, float xscale = 1.0, float yscale = 1.0);
        
protected:
        int rows_, cols_, frames_;
        int width_, height_;
        GLuint* textures_;
};
В классе содержится информация, о которой мы говорили чуть выше, а также размеры одного кадра. Для каждого кадра создается отдельная OpenGL текстура (переменная textures_). Функция draw принимает в качестве параметров номер кадра, которые требуется нарисовать, а также координаты левого верхнего угла кадра. Координаты для рисования не хранятся внутри класса, потому что это всего лишь ресурс, который может быть доступен в разных частях программы (через указатели например) и везде необходимо будет нарисовать кадр изображения в разных позициях. Но зато используя класс Image можно будет создать полноценный класс Sprite, который и будет содержать информацию о своем местоположении.
Также метод draw может принимать и другие параметры отрисовки: flip - перевернуть изображение, xscale и yscale - коэффициенты масштабирования. Эти аргументы имеют параметры по умолчанию для того чтобы не прописывать их постоянно, ведь в большинстве случаев не требуется менять масштаб и переворачивать изображение. Можно было бы реализовать отражение картинки не только сверху вниз, но и слева направо. Думаю, многие из вас успешно справятся с этой задачей к следующему выпуску.
Поскольку реализация функции draw очень простая, имеет смысл немного рассказать как работает load, так как она более сложная и объемная. Нет смысла объяснять каждую строчку кода, поэтому я расскажу схему работы этой функции, а детали реализации вы сможете посмотреть в исходном коде.
Механизм работы таков:
  • Загружаем все изображение в SDL_Surface при помощи SDL_image.
  • Поскольку нам уже известны параметры изображения (переданные в конструкторе и полученные из самого изображения), далее в цикле вырезаем из всей картинки нужный кадр в новый SDL_Surface при помощи обычного SDL_BlitSurface.
  • Если размер кадра не pot - масштабируем его при помощи gluScaleImage и создаем OpenGL-текстуру точно так же, как описано в предыдущем выпуске.
Таким образом имеем набор OpenGL-текстур с кадрами из изображения (или одну текстуру для статического изображения). Индексация начинается с нуля (то есть для обращения к самому первому кадру используем индекс 0).
Использовать наш класс очень просто: нужно создать объект, выполнить метод load и рисовать его.

 // Создаем объект для изображения вида [1 2 3]
 Image* img = new Image("sprite.png", 1, 3);
 img->load();
 
 // Рисуем первый кадр в точке (10, 40)
 img->draw(0,  10, 40);

Итак, мы сделали первый шаг в написании нашего игрового 2D движка. В следующий раз мы будем разрабатывать специальный объект, который будет отвечать за ресурсы. В этом выпуске не очень подробно рассматривалась реализация тех или иных методов, а описаны лишь идеи и интерфейсы. Для более подробного изучения обязательно загрузите исходный код к уроку. Если у вас все же возникнут вопросы, то их можно задать на нашем форуме.
Не может не огорчать тот факт, что рассылка выходит с большими перерывами. Будем надеяться, что они будут не слишком длинными. Также, в очередной раз хотел бы обратиться к читателям: очень важно получать ваши отзывы. Нравится ли вам материал рассылки, каков уровень вашего мастерства, что бы вы хотели видеть в будущих выпусках и многое другое помогут нам вместе сделать рассылку лучше.
До скорых встреч!


http://subscribe.ru/
http://subscribe.ru/feedback/
Подписан адрес:
Код этой рассылки: comp.soft.prog.plg
Отписаться

В избранное