воскресенье, 21 ноября 2010 г.

Немного о связке SDL + OpenGL

Сегодня, Я попытаюсь рассказать о том, как можно написать свою игру при ппомощи связки SDL + OpenGl. Для начала, заглянем в вики http://ru.wikipedia.org/wiki/Simple_DirectMedia_Layer и http://ru.wikipedia.org/wiki/OpenGL:


1) SDL (Simple DirectMedia Layer)-это свободная кроссплатформенная мультимедийная библиотека, реализующая единый программный интерфейс к графической подсистеме, звуковым устройствам и средствам ввода. Официально поддерживаются операционные системы: Linux, Windows, Windows CE, BeOS, Mac OS,*BSD, и т.д. Также, есть неофициальные поддержки AmigaOS, Dreamcast,Symbian OS и др. SDL API доступны для языков: C, C++,Vala, Erlang, Java, Lisp, Lua, Pascal, Python, Ruby и др. SDL создал Сэм Лантинга, будучи ведущим программистом компании Loki Entertainment Software. Работая над программой-эмулятором Macintosh для Microsoft Windows, он заметил, что многие куски кода без проблем могут работать на Linux. И он решил создать небольшую библиотеку, чтобы ей могли воспользоваться другие программисты - SDL распространяется под условиями лицензии GNU LGPL. В октябре 1997 был выпущен первый релиз версии 0.3, Позже, Loki Software вовсю использует эту библиотеку для портирования игр под Linux.



SDL можно рассматривать как тонкую прослойку, обеспечивающую поддержку для 2D-операций над пикселами, звука, доступа к файлам, обработки событий и т. п. Часто используется в дополнение к OpenGL.

SDL сам по себе довольно прост. Библиотека состоит из нескольких подсистем, таких как Video, Audio, CD-ROM, Joystick и Timer. В дополнение к этой базовой низкоуровневой функциональности, существует ряд стандартных библиотек, предоставляющих дополнительную функциональность:

SDL_image — поддержка различных растровых форматов

SDL_mixer — функции для организации сложного аудио, в основном, сведение звука из нескольких источников

SDL_net — поддержка сетевых функций

SDL_ttf — поддержка шрифтов TrueType

SDL_rtf — отрисовка текста в формате RTF

Оффициальный сайт библиотеки http://www.libsdl.org. Помимо самой библиотеки на сайте есть куча примеров и демонстрашек по использованию SDL.

2) OpenGL (Open Graphics Library — открытая графическая библиотека) — спецификация, определяющая независимый от языка программирования кросс-платформенный программный интерфейс для написания приложений, использующих двумерную и трёхмерную компьютерную графику. Включает более 250-ти функций для рисования сложных трёхмерных сцен из простых примитивов. Используется при создании компьютерных игр, САПР, виртуальной реальности, визуализации в научных исследованиях. На платформе Windows конкурирует с Direct3D.

Для создания простеньких 2д-игр можно использовать "сам по себе" - он быстр, не требует наличие видеодрайвера к видеокарте. Но количество возможностей работы с графикой сильно ограничены.В частности Мне не хватало возможности растянуть/сузить, увеличить/уменьшить, повернуть изображение. Этого можно было добится, подлючив дополнительные библиотеки, но тогда придется программе потребуются огромные вычислительные мощности на софтварный просчет, а следовательно - неизбежны тормоза. Потом, используя в качестве вывода графики не SDL а OpenGl, появляется возможность добавить к 2-ух мерной графике сложную 3-х мерную.

О использовании библиотеки SDL с OpenGL прекрасно расжевано во 2-ом номере электронного журнала LinGameTech (http://lingametech.com). В частности - вот в этом номере (2-ой) : http://lingametech.com/index.php?option=com_weblinks&task=view&catid=15&id=12

Также, по работе именно c библиотекой SDL были можно почитать в статьях журнала LinuxFormat, в номерах N98 (http://wiki.linuxformat.ru/index.php/LXF98:%D0%A1%D1%82%D1%80%D0%B5%D0%BB%D1%8F%D0%BB%D0%BA%D0%B0) и N103 (http://wiki.linuxformat.ru/index.php/LXF103:%D0%A1%D1%82%D1%80%D0%B5%D0%BB%D1%8F%D0%BB%D0%BA%D0%B0). Много информации о разработке игр в linux с ипользованием SDL можно найти на сайте http://plg.lrn.ru/, например - вики о SDL на русском языке: http://plg.lrn.ru/wiki/%D0%97%D0%B0%D0%B3%D0%BB%D0%B0%D0%B2%D0%BD%D0%B0%D1%8F_%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0.

Я "учился" на статьях журанала LinuxFormat и на исходниках игрушки SDL-Ball (http://sdl-ball.sourceforge.net/) и какой-то там матери =)

Много от туда было позаимствовано:). И теперь, для того чтобы показать, как работать с этой связкой SDL+OpenGL в своих игровых проектах - я написал пример простенькой игры - понг:
http://narod.ru/disk/179861001/pong2.tar.gz.html.

Есть 2 ракетки - игрока и оппонента, также, есть мячик. Цель игры - набрать больше количество очков чем у оппонента, забивая голы и не давать забивать голы ему самому.

В статье я буду рассматривать в качестве операционной системы дистрибутив GNI/linux Ubuntu 10.04. Для компиляции и запуска примера необходимо установить следующие пакеты (должны быть в репозитории вашего дистрибутива):

libsdl-ttf2.0-0
libsdl-ttf2.0-dev
libsdl-mixer1.2
libsdl-mixer1.2-dev
libsdl-image1.2
libsdl-image1.2-dev

Возможно, имена пакетов от дистрибутива к дистрибутиву могут слегка изменятся.

Для компиляции в makefile манифесте добавляем библиотеки lGL, lGLU, lSDL_ttf, lSDL_image. он будет иметь следующий вид:
all:
gcc -o pong pong_adv.cpp `sdl-config --libs --cflags` -I/usr/include/GL -lGL -lGLU -lSDL_ttf -lSDL_image
Дальше, разберем исходный код моего примера pong_adv.cpp:
//Одна из основных библиотек С
#include <iostream>
//В исходный текст программы (pong_adv.cpp), соответственно, подключаем библиотеки: 
//библиотеки для работы с OpenGL
#include <GL/gl.h>
#include <GL/glu.h>

//библиотеки SDL
#include <SDL/SDL.h>
#include <SDL/SDL_ttf.h>
#include <SDL/SDL_image.h>
  
//Введем следущие переменные для работы с SDL
SDL_Surface *screen;//Экран - поверхность, некая 2d плоскость на которой будет происходить игровой действие
//для управления клавиатурой вводим
SDL_Event event;//для обработки событий, например на клавиатуре нажата клавиша
Uint8* keys;//для определения того какая конкретно клавиша нажата    

using namespace std;//подключаем пространство имён стандартной библиотеки

void randomize()
 {
  srand(time(NULL));//randomize;-включить счетчик случайных чисел <iostream.h>
 }

int random(int i)
 {
  return rand()%i+1;
 }
 
string IntToStr (int i)//перевод числа в строку-поправить
 {
    char buf[16];
    snprintf(buf, sizeof(buf), "%i", i);
    return string(buf);
 } 

//1) Функция для инициализации OpenGL средствами SDL.
bool init_sdl_opengl (int win_DX,//размеры экрана
                      int win_DY, 
                      int pal,//палитра 16 или 32 разрадная  
                      int full_screen)//на полный =1 экран или в окне =0
 {  
  //Проверка на возможность инициализация видео при помощи SDL
  //Если чтото не получилось (например не поддерживается заданный видео режим),
  //то выводим причину сбоя и заканчиваем работу программы
  if (SDL_Init(SDL_INIT_VIDEO)!=0) 
   {
    printf("Unable to initialize SDL: %s\n",SDL_GetError());
    return false;//функция свою задачу не выполнила (ложь)
}
  //Иначе
  if (SDL_Init(SDL_INIT_VIDEO)==0)
   {   
    //Включаем двойной буфер с OpenGL
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1);
    //И инициализируем флаг SDL_OPENGL (который говорит о том что SDL использует OpenGL).
    if (full_screen==0)//если оконный режим
       {screen=SDL_SetVideoMode(win_DX,win_DY,pal,SDL_OPENGL|SDL_RESIZABLE);}//выводим окно OpenGl на "полный экран"
    if (full_screen==1)//если полноэкранный режим
       {screen=SDL_SetVideoMode(win_DX,win_DY,pal,SDL_OPENGL|SDL_FULLSCREEN|SDL_RESIZABLE);}//экран OpenGl в окне   
    return true;//функция свою задачу выполнила (истина)
   }
  //Если не был установлен заданный видеорежим win_DX, win_DY, то выводим причину
  if (!screen)
   {
    printf("Unable to set video mode: %s\n",SDL_GetError());
    return false;//функция свою задачу не выполнила (ложь)
   }   
 }

//2) Следующая процедура не обязательна, но полезна: она необходима
//для перерисовки окна при изменении его габаритов win_DX и win_DY 
//(например в опциях игры). Для перерисовки экрана в новый размер 
//воспользуемся функцией glViewport 
//(http://opengl.gamedev.ru/doc/?func=glViewport),
//glViewport - задает область просмотра.
//При запуске программы в "оконном режиме", размеры такого окна можно 
//можно изменить при помощи мышки.
//но выводимая область при этом останется без изменения. 
//Эта процедура позимствована из исходников игры sdl-ball.
void resize_window (int win_DX, 
                    int win_DY)
 {
  GLfloat ratio;//соотношение win_DY/win_DY
  if (win_DY==0)//Защита от деления на ноль при win_DY=0
  win_DY = 1;
  //В таком случае соотношение будет равно
  ratio = (GLfloat) win_DX / (GLfloat) win_DY;
  //Устанавливаем наш новый вьюпорт
  glViewport(0,0,(GLsizei) win_DX,(GLsizei) win_DY);
  //изменяем проекционные матрицы и устнавливаем видимый объем
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(45.0f, ratio, 0.1f, 10.0f );//установка перспективы
  //Убеждаемся в том, что мы меняем вид модели, а не проекции (Make sure we're chaning the model view and not the projection)
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();//перезагрузка вида
 }

//3) Следущая функция нужна для нициализации 2д в OpenGl.
//Для этого будет использована функция glOrtho 
//(http://www.gamedev.ru/articles/?id=20114): 
//glOrtho()-устанавливает режим ортогонального (прямоугольного) проецирования. 
//Это значит, что изображение будет рисоваться как в изометрии. 6 параметров 
//типа GLdouble (или просто double): left, right, bottom, top, near, far
//определяют координаты соответственно левой, правой, нижней, верхней, ближней
//и дальней плоскостей отсечения, т.е. всё, что окажется за этими пределами, 
//рисоваться не будет. На самом деле эта процедура просто устанавливает 
//масштабы координатных осей.
void gl2dMode (int win_DX, 
               int win_DY)
 {
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(0, win_DX, win_DY, 0, -1, 1 );
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
 }
 
//4) Функция для загрузки текстуры из графического (jpg,png,bmp) 
//файла в переменную типа GLuint. Загрузка текстуры происходит с
//линейной фильтрацией GL_LINEAR (пиксели текстуры "размываются").
//Эту функцию лучше применять в тех случаях, когда текстура исользуется
//в качестве "тайлов" (элементов декораций уровня) или спрайтов с 
//незначительным содержанием "дырок в альфа канале". При использовании 
//линейной фильтрации Возможно появление артефактов на свободных участках
//и по краям текстуры. Эту функцию желательно использовать при инициализации.
bool load_LINEAR(string file, GLuint &tex)
 {
  SDL_Surface *temp = NULL;//временная SDL текстура
  GLint maxTexSize;
  GLuint glFormat = GL_RGBA;//по умолчанию, подразумеваем использование альфа канала в текстуре
  //если файл изображения имеет расширению jpg, то альфа канал не используется
if(file.substr(file.length()-3,3).compare("jpg") == 0)
{
    glFormat = GL_RGB;
   }
  
  //загружаем изображение во временную SDL текстуру temp
  temp = IMG_Load(file.data());
  
  //проверка 1 
  if(temp == NULL)//если изображение не удалось загрузить, то выводим причину
   {
    cout << "Apshipka managera textur: " << file << " : "<< SDL_GetError() << endl;
    SDL_FreeSurface(temp);//освобождаем память от временной SDL текстуры
    return false;//функция свою задачу не выполнила (ложь)
   }

  //проверка 2 - на превышение максимально допустимого размера текстуры SDL для использования ее в OpenGL
  glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize);
  if(temp->w > maxTexSize)//если есть превышение, то функция не срабатывает и пишем сообщение об ошибке
   {
    cout << "Apshipka managera textur: " << file << " texturka velika i shiroka." << endl;
    SDL_FreeSurface(temp);//освобождаем память от временной SDL текстуры
    return false;//функция свою задачу выполнила (ложь)
   }

  //Если все проверки пройдены без проблем, то генерим текстуру с изображением из SDL текстуры temp
  glGenTextures(1, &tex);//создаем 1 OpenGL текстуру с именем tex
  glBindTexture(GL_TEXTURE_2D, tex);
  
  //передаем изображение из SDL текстуре temp текстуре tex
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);//GL_LINEAR задает линейную фильтрацию
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
  glTexImage2D(GL_TEXTURE_2D, 0, glFormat, temp->w, temp->h, 0, glFormat, GL_UNSIGNED_BYTE, temp->pixels);
  
  SDL_FreeSurface(temp);//освобождаем память от временной SDL текстуры
  return true;//функция свою задачу выполнила (истина)
}

//5) Следущая функция полностью аналогична предыдущей, за иключением того, 
//что изображение передается в текстуру без линейной фильтрации. 
//Как следствие, - в текстуре артефакты отсутствуют, 
//зато присутствует пикселизация при малой детализации исходного
//изображения. В "своих" 2д играх Я исользую эту функцию и 
//для спрайтов, и для тайлов.
bool load_NEAREST(string file, GLuint &tex)
 {
  SDL_Surface *temp = NULL;//временная SDL текстура
  GLint maxTexSize;
  GLuint glFormat = GL_RGBA;//по умолчанию, подразумеваем использование альфа канала в текстуре

  if(file.substr(file.length()-3,3).compare("jpg") == 0)//если изображение содержит расширению jpg - альфы нет
   {
    glFormat = GL_RGB;
   }
  
  //загружаем изображение во временную SDL текстуру temp
  temp = IMG_Load(file.data());
  
  //проверка 1 
  if(temp == NULL)//если изображение не удалось загрузить, то выводим причину
   {
    cout << "Apshipka managera textur: " << file << " : "<< SDL_GetError() << endl;
    SDL_FreeSurface(temp);//освобождаем память от временной SDL текстуры
    return false;//функция свою задачу не выполнила (ложь)
   }

  //проверка 2 - напрвышает максимально допустимого размера SDL текстуры для использования ее в OpenGL
  glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize);
  if(temp->w > maxTexSize)//если есть превышение, то функция не срабатывает и пишем сообщение об ошибке
   {
    cout << "Apshipka managera textur: " << file << " texturka velika i shiroka." << endl;
    SDL_FreeSurface(temp);//освобождаем память от временной SDL текстуры
    return false;//функция свою задачу не выполнила (ложь)
   }

  //Если все проверки пройдены без проблем, то генерим текстуру с изображением из SDL текстуры temp
  glGenTextures(1, &tex);//создаем 1 OpenGL текстуру с именем tex
  glBindTexture(GL_TEXTURE_2D, tex);
  
  //передаем изображение из SDL текстуре temp текстуре tex
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);//GL_NEAREST - текстура загружается как есть
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);//без фильтрации. Осторожно - пиксели! :)
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
  glTexImage2D(GL_TEXTURE_2D, 0, glFormat, temp->w, temp->h, 0, glFormat, GL_UNSIGNED_BYTE, temp->pixels);
  
  SDL_FreeSurface(temp);//освобождаем память от временной SDL текстуры
  return true;//функция свою задачу выполнила (истина)
}

//Говорят, для текстур желательно использовать файлы с изображением размерами по 
//высоте и ширине равные 2 в степени n. Это связано с особенностями работы видеопамяти. 
//На видеокартах nvidia я глюков особо не замечал, но на видеокартах от Intel (встроенные)  
//"неправильные" изображения в текстуру просто не загружаются. Вместо спрайта
//рисуется только залитый цветом полигон.

//6) Процедура для отрисовки затекстурированного полигона в координатах x,y; 
//размером dX, dY; повернутом на delta градусов. относительно верхней левой точки,
//либо - относительно центра. Эту процедуру удобно использовать для рисования фонов
//или спрайтов/тайлов, текстура которых использует все исходное изображение целиком.
void DrawTXT (float x,
              float y,
              float dX,
              float dY,
              float delta,
              int center)
 {
  glEnable(GL_TEXTURE_2D); 
  glLoadIdentity();
  glTranslatef(x,y,0);//задаем местоположение
  glRotatef(delta,0,0,-1);//поворачиваемся на delta градусов
  
  //Если поворот объекта происходит относительно его центра, то смещаем ось координат на 0.5dX и 0.5dY
  if (center==1) {glTranslatef(-dX/2,-dY/2,0);}
  
  //Рисуем затекстурированный полигон, текстурные координаты glTexCoord2i задаются в %/100 
  glBegin(GL_QUADS);
   glTexCoord2i(0,0); glVertex2f(0, 0 );// Верхний левый угол
   glTexCoord2i(0,1); glVertex2f(0, dY);//Нижний левый угол
   glTexCoord2i(1,1); glVertex2f(dX,dY);//Нижний правый угол 
   glTexCoord2i(1,0); glVertex2f(dX,0 );//Верхний правый угол
  glEnd();
  
  glLoadIdentity();
 }

//7) Процедура для рисования полигона с текстурой, взятой "кусочком" из другой
//(исходной) текстуры. Как бы, вырезаем необходимый элемент из одного большого
//изображения. Я использую эту процедуру для рисования анимированных спрайтов,
//где каждый кадр для анимации берется из одного файла-картинки.
void _DrawTXT (float W,//ширина большой текстуры, в пикселях
               float H,//высота большой текстуры, в пикселях
               float t_x,//координаты верхнего левого угла "кусочка" 
               float t_y,//в большой текстуре, в пикселях
               float t_dx,//размер вырезаемого "кусочка", в пикселях
               float t_dy,

               float x,//аналогично предыдущей процедуре
               float y,
               float dX,
               float dY,
               float delta,
               int center)
 {
  glEnable(GL_TEXTURE_2D); 
  glLoadIdentity();
  glTranslatef(x,y,0);
  glRotatef(delta,0,0,-1);
    
  if (center==1) {glTranslatef(-dX/2,-dY/2,0);}//смещаем по центре

  glBegin(GL_QUADS);
   glTexCoord2f((t_x/W),(t_y/H));               glVertex2f(0, 0); //Верхний левый угол
   glTexCoord2f((t_x/W),((t_y+t_dy)/H));        glVertex2f(0, dY);//Нижний левый угол
   glTexCoord2f(((t_x+t_dx)/W),((t_y+t_dy)/H)); glVertex2f(dX,dY);//Нижний правый угол
   glTexCoord2f(((t_x+t_dx)/W),(t_y/H));        glVertex2f(dX, 0);//Верхний правый угол
  glEnd();

  glLoadIdentity();
 }

//Так как для текстур нужны изображения "правильных размеров" (2 в степени n, см.выше), 
//лучше всего использовать _DrawTXT если текстура "не квадратная".

//8) Для создания надписей, необходимо сначало создать текстовую текстуру. 
//Следущая процедура этим и занимается. Сперто из исходников sdl-bаll и не 
//совсем допилена, но как-то работает. В общем, здесь без подробных комментариев:)
void GlTxt(int sdl_dx,//координаты
           int sdl_dy,
           TTF_Font *font,
           SDL_Color textColor,
           const char text[],
           GLuint texture)
 {
  SDL_Surface *temp,*tempb;
  int w,h;
  Uint32 rmask, gmask, bmask, amask;
  #if SDL_BYTEORDER == SDL_BIG_ENDIAN
   rmask = 0xff000000;
   gmask = 0x00ff0000;
   bmask = 0x0000ff00;
   amask = 0x000000ff;
  #else
   rmask = 0x000000ff;
   gmask = 0x0000ff00;
   bmask = 0x00ff0000;
   amask = 0xff000000;
  #endif

  if(font == NULL)
   {
    cout << SDL_GetError() << endl;;
    SDL_FreeSurface(temp);
    SDL_FreeSurface(tempb);
   }

  temp = TTF_RenderText_Blended(font, text, textColor);
  SDL_SetAlpha(temp, 0, 0);

  tempb = SDL_CreateRGBSurface(0, sdl_dx, sdl_dy, 32, rmask,gmask,bmask,amask);

  TTF_SizeUTF8(font,text, &w,&h);

  SDL_Rect src,dst;
  src.x=0;
  src.y=0;
  src.w=sdl_dx;//w; - криво но работает
  src.h=sdl_dy;//h;

  dst.x=0;
  dst.y=0;
  dst.w=sdl_dx;//w;
  dst.h=sdl_dy;//h;

  SDL_BlitSurface(temp, &src, tempb, &dst);
  glBindTexture(GL_TEXTURE_2D, texture);
  gluBuild2DMipmaps(GL_TEXTURE_2D, 
                    GL_RGBA, 
                    tempb->w, 
                    tempb->h, 
                    GL_RGBA, 
                    GL_UNSIGNED_BYTE, 
                    tempb->pixels);

  SDL_FreeSurface(temp);
  SDL_FreeSurface(tempb);
 }

//9) Теперь, напишем процедуру, которая использует предыдущую (8) для вывода текста
//на экран. Текст будет выводится, как полигон с нанесенной на него текстовой текстурой.
//Все входные параметры аналогичны DrawTXT, за исключением: fonts - используемый фонт и 
//text - сам выводимый текст.
void DrawText(float x,
              float y,
              float dX,
              float dY,
              TTF_Font *fonts,//используемый *.ttf фонт
              string text,//сам выводимый текст
              float delta,
              int center)
 {
  
  SDL_Color txtColorWhite = {255,255,255};//цвет текста, по умолчанию - белый
  
  //Если нужно использовать другой цвет для текста, то лучше окрашивать "текстовый полигон"
  //перед его выводом на экран (см.ниже)
  
  GLuint t_txt;//временная текстовая текстура  
  glGenTextures(1, &t_txt);//генерим текстовую текстуру
  GlTxt(dX,
        dY,
        fonts,
        txtColorWhite,
        text.c_str(),
        t_txt);

  //Рисуем полигон с текстовой текстурой
  glBindTexture( GL_TEXTURE_2D, t_txt);
  DrawTXT(x,
          y,
          dX,
          dY,
          delta,
          center);
          
  glDeleteTextures(1, &t_txt);//удаляем использованную текстуру  
 }
        
//Для рисования "хвоста" из системы частиц подключаем этот модуль,
//в данном примере - особо не нужен. 
#include "effects.h"
  
//Если проводить анологию с "паскалем", то эта функция, как-бы, главный "BEGIN" 
int main()
 {  
  //Задаем основные переменные
  int win_DX=1024,//параметры экрана, размеры в пикселях
      win_DY=768,
      pal=32,//палитра в битах
      full_screen=0,//программу запускаем в окне
      done=0;//отвечает за окончание работы основного цикла программы
      
  //Создаем окно  
  init_sdl_opengl(win_DX,win_DY,pal,full_screen);
  resize_window(win_DX,win_DY);  
  
  //Текст в загаловке окна и текст отображаемый в панеле
  SDL_WM_SetCaption("Pong title window","pong - example");
  //Иконка приложения (в опенбоксе не отображается)
  SDL_WM_SetIcon(SDL_LoadBMP("icon.bmp"),NULL);

  //Инициализируем 2д
  gl2dMode(win_DX,win_DY);
  
  //Задаем цвет заднего фона (R,G,B,alpha) - черный
  glClearColor(0,0,0,0);
  
  //Включаем режим текстурирования
  glEnable(GL_TEXTURE_2D);
  
  //Включаем использование "альфа-канала"
  glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
  glEnable(GL_BLEND);

  //Инициируем и загружаем шрифт
  TTF_Init();  
  TTF_Font *fonts;
  fonts = TTF_OpenFont("font1.ttf", win_DY/16);//открываем фонт
  
  //Включаем счетчик случайных чисел
  randomize();

  Init_Flame();//Инициализируем систему частиц, необязательная часть программы
  
  //Отсюда начинается сама игра
  //Задаем переменные
  float player_x,player_y,//координаты игрока
        player_dx,player_dy,//габариты игрока 
        player_a,//шаг игрока при передвижении по оси х
        
        enemy_x,enemy_y,//все тоже самое для оппонента
        enemy_dx,enemy_dy,
        enemy_a,
        
        ball_x,ball_y,//координаты мячика
        ball_dx,ball_dy,//габариты мячика
        ball_a,ball_b,//шаг при перемещении мячика по осям x и y
        ball_vector,ball_teta,//полярные координаты вектор направления движения и угол
        ball_delta;//угол вращения мячика вокруг своей оси
                       
  int player_score,enemy_score;//очки, соответственно игрока и оппонента
  
  //Текстуры
  GLuint back,//задний фон
         sprite;//спрайты
         
  //Загрузка текстур
  load_LINEAR("back.jpg", back);//задний фон
  load_LINEAR("spr.png", sprite);//игрове спрайты         
        
  //Значения переменных при старте программы для игрока
  //Размеры задаются в относительных величинах для того чтобы масштабироваться при
  //различных размерах окна win_DX, win_DY. По умолчанию, размеры win_DX=1024, 
  //win_DY=768
  player_x=win_DX/2;//=512
  player_y=win_DY-win_DY/10;
  player_dx=win_DX/6.83;//=150;
  player_dy=win_DY/25.6;//=30; 
  player_a=win_DX/341.3;//=3;
  player_score=0;
  
  //Для оппонента
  enemy_x=win_DX/2;//=512
  enemy_y=0+win_DY/10;
  enemy_dx=win_DX/6.83;//=150;
  enemy_dy=win_DY/25.6;//=30; 
  enemy_a=win_DX/512;//=2;
  enemy_score=0;
  
  //Для мячика
  ball_x=win_DX/2;//=512
  ball_y=win_DY/2;//=512
  ball_dx=win_DX/40.96;//=25
  ball_dy=win_DY/30.72;//=25
  ball_vector=random(7);//скорость перемещения мячика вдоль вектора направления движения
  ball_teta=random(360)*(1)*(3.142/180);//угол движения в градусах
  if (ball_teta==0) {ball_teta=1;}
  
  //Преобразование полярных координат в соответствующие проекции на оси x и y
  ball_a=ball_vector*cos(ball_teta);
  ball_b=ball_vector*sin(ball_teta);   
  ball_delta=0;

  //Начинается главный цикл программы, за выход из него отвечает переменная done
  while(done == 0)
   {
 //Обрабатываем события
    while(SDL_PollEvent(&event))
     {
      //Если пвыходим из SDL то по происходит закрытие всей программы (при нажатии на кнопку закрытия окна тоже самое)
      if (event.type==SDL_QUIT) {done=1;}
      
      //Если единичный случай нажатия на клавиатуру
      if (event.type==SDL_KEYDOWN)
       {
  //Выход из программы по нажатию кнопки ESC   
        if (event.key.keysym.sym==SDLK_ESCAPE) {done=1;}
       }      
     }//конец обработки событий

    //Управление с клавиатуры игроком. Учитывается постоянное удержание кнопок,
    //а не единичное нажатие
    keys = SDL_GetKeyState(NULL);
    
    if (keys[SDLK_LEFT]) {player_x=player_x-player_a;}//перемещение игрока влево
    if (keys[SDLK_RIGHT]) {player_x=player_x+player_a;}//перемещение игрока вправо
    
    //Ограничение на перемещение игрока и оппонента внури экрана
    if (player_x>=win_DX-0.7*player_dx) {player_x=win_DX-0.7*player_dx;}
    if (player_x<0+0.7*player_dx) {player_x=0+0.7*player_dx;}
   
    if (enemy_x>=win_DX-0.7*enemy_dx) {enemy_x=win_DX-0.7*enemy_dx;}
    if (enemy_x<0+0.7*enemy_dx) {enemy_x=0+0.7*enemy_dx;} 
        
    //"АИ оппонента"-ракетка оппонента стремится отразить удар, бегая за мячом
    if (ball_x>enemy_x) {enemy_x=enemy_x+enemy_a;}
    if (ball_x<enemy_x) {enemy_x=enemy_x-enemy_a;}

    //Мячик движется в 2д плоскоти, благодаря изменению 2-х координат
    ball_x=ball_x+ball_a;
    ball_y=ball_y+ball_b;
   
    //Мячик крутится вокруг своей оси
    ball_delta=ball_delta+5;
    if (ball_delta>=360) {ball_delta=0;}
       
    //Мячик отскакивает от боковых стенок-проверка осуществляеться только по оси X   
    if (ball_x>win_DX-0.5*ball_dy or
        ball_x<0+0.5*ball_dy)
      {   
    ball_a=-ball_a;//изменяем шаг передвижения на противоположный  
   }
   
    //Если мячик вылетает верх или низ экрана - значит ктото комуто забил гол
    if (ball_y>win_DY-0.5*ball_dy or
        ball_y<0+0.5*ball_dy) 
     {
   //В зависимости кто кому забил гол, тому начисляются очки 
   //Если это низ экрана, то очко зарабатывает опонент
   if (ball_y>win_DY-0.5*ball_dy) {enemy_score=enemy_score+1;}
   //Если это верх экрана - то игрок.
   if (ball_y<0+0.5*ball_dy) {player_score=player_score+1;}
      
      //И мячик респавнится с параметрами как при старте
      ball_x=win_DX/2;
      ball_y=win_DY/2;
      ball_dx=win_DX/40.96;//25
      ball_dy=win_DY/30.72;//25
      ball_vector=random(7);//скорость перемещения мячика вдоль вектора направления движения
      ball_teta=random(360)*(1)*(3.142/180);//угол движения в градусах
      if (ball_teta==0) {ball_teta=1;}
      
      //Преобразование полярных координат в соответствующие проекции на оси x и y
      ball_a=ball_vector*cos(ball_teta);
      ball_b=ball_vector*sin(ball_teta);   
      ball_delta=0;
     }   
       
    //Взаимодействие мячика с ракеткой игрока 
    //Простое отражение от верхней центральной части ракетки и
    if (player_x-0.4*player_dx<ball_x+0.5*ball_dx and
        player_x+0.4*player_dx>ball_x-0.5*ball_dx and
        player_y-0.5*player_dy<ball_y+0.5*ball_dy and
        player_y+0.5*player_dy>ball_y-0.5*ball_dy)
     {
   ball_b=-ball_b;
   //Симуляция передачи импульса мячику, если игрок движется
   if (keys[SDLK_LEFT]) {ball_a=ball_a-0.5*player_a;}
   if (keys[SDLK_RIGHT]) {ball_a=ball_a+0.5*player_a;}   
     }  
     
    //Отражение от левого бока ракетки
    if (ball_a>0 and
        ball_x+0.5*ball_dx>player_x-0.6*player_dx and
        ball_x+0.5*ball_dx<player_x-0.4*player_dx and
        ball_y+0.5*ball_dy>player_y-0.5*player_dy and
        ball_y-0.5*ball_dy<player_y+0.5*player_dy)
     {
      ball_a=-ball_a;
     }   
     
    //Отражение от правого бока ракетки
    if (ball_a<0 and
        ball_x-0.5*ball_dx<player_x+0.6*player_dx and
        ball_x-0.5*ball_dx>player_x+0.4*player_dx and
        ball_y+0.5*ball_dy>player_y-0.5*player_dy and
        ball_y-0.5*ball_dy<player_y+0.5*player_dy)
     {
      ball_a=-ball_a;
     }   
     
    //Аналогично записываем взаимодействие мячика с ракеткой оппонента
    //Простое отражение
    if (enemy_x-0.4*enemy_dx<ball_x+0.5*ball_dx and
        enemy_x+0.4*enemy_dx>ball_x-0.5*ball_dx and
        enemy_y-0.5*enemy_dy<ball_y+0.5*ball_dy and
        enemy_y+0.5*enemy_dy>ball_y-0.5*ball_dy)
     {ball_b=-ball_b;}  
                
    //Отражение о левого бока ракетки
    if (ball_a>0 and
        ball_x+0.5*ball_dx>enemy_x-0.6*enemy_dx and
        ball_x+0.5*ball_dx<enemy_x-0.4*enemy_dx and
        ball_y+0.5*ball_dy>enemy_y-0.5*enemy_dy and
        ball_y-0.5*ball_dy<enemy_y+0.5*enemy_dy)
     {ball_a=-ball_a;}   
     
    //Отражение о правого бока ракетки
    if (ball_a<0 and
        ball_x-0.5*ball_dx<enemy_x+0.6*enemy_dx and
        ball_x-0.5*ball_dx>enemy_x+0.4*enemy_dx and
        ball_y+0.5*ball_dy>enemy_y-0.5*enemy_dy and
        ball_y-0.5*ball_dy<enemy_y+0.5*enemy_dy)
     {ball_a=-ball_a;}   
     
    //Рисуем задний фон
    glColor4f(1,1,1,1);
    glBindTexture(GL_TEXTURE_2D,back);//glBindTexture устанавливает соответствующую текстуру
    DrawTXT(0,0,win_DX,win_DY,0,0);
        
 //Выводим набранные очки в верхнем левом углу экрана
 glColor4f(0,0,1,1);//рисуем текст синим цветом
    DrawText(0,//x
             0,//y
          win_DX/8,//dX
          win_DY/16,//dY
             fonts,//font
             IntToStr(player_score)+":"+IntToStr(enemy_score),
             0,//delta
          0);//center 
    glColor4f(1,1,1,1);          
            
    //Рисуем мячик                  
    glColor4f(1,1,1,1);
    glBindTexture(GL_TEXTURE_2D,sprite);
    _DrawTXT(256,
             256,
             1,
             1,
             100,
             100,
             ball_x,
             ball_y,
             ball_dx,
             ball_dy,
             ball_delta,1);
    glColor4f(1,1,1,1);            
                        
    //Рисуем ракетку игрока одним цетом
    glColor4f(0,1,1,1);
    glBindTexture(GL_TEXTURE_2D,sprite);
    _DrawTXT(256,
             256,
             0,
             103,
             150,
             30,
             player_x,
             player_y,
             player_dx,
             player_dy,
             0,1);             
    glColor4f(1,1,1,1);
    
    //Рисуем ракетку оппонента другим цветом
    glColor4f(1,0,1,1);
    glBindTexture(GL_TEXTURE_2D,sprite);
    _DrawTXT(256,
             256,
             0,
             103,
             150,
             30,
             enemy_x,
             enemy_y,
             enemy_dx,
             enemy_dy,
             0,1);             
    glColor4f(1,1,1,1);
    
    //Рисуем огненный след от мяча. Необязательная часть программы - понты:)
    //----//
    Draw_Flame(win_DX,
               win_DY,
               60,//flame_kolvo
               ball_x,//X,
               ball_y,//Y,
               0.0007,//flame_A
               4,//flame_B
               7,//flame_C
               3, //flame_D
               0.000004,//flame_E
               0.003,//flame_F
               0.0029,//flame_G
               0.15,//flame_H
               0.15,//flame_I
               3,//flame_J
               0.01,//x_amp
               6,//y_amp
               0.07,//y_n
               230,// flame_init_life,
               100,// flame_init_life_rnd,
               0.8,//+0.3*temp_c,//c_R
               0.1,//c_G
               0.8,//c_B
               1,
               1);//изменения в c_G

    glColor4f(1,1,1,1);
    glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_TEXTURE_2D);
    //----//
    
    //Задержка, если не обходима
    //SDL_Delay(10);
                         
 SDL_GL_SwapBuffers();//Обновляем экран
   }
   
  //Отключаем SDL по окончанию работы
  SDL_Quit();
 }


В итоге:
   Для создания окна размерами win_DX, win_DY, с иконкой и надписями средствами SDL и OpenGL в нашей игре достаточно написать:


  //Создаем окно  
  init_sdl_opengl(win_DX,win_DY,pal,full_screen);
  resize_window(win_DX,win_DY);  
  
  //Текст в загаловке окна и текст отображаемый в панеле
  SDL_WM_SetCaption("Pong title window","pong - example");
  //Иконка приложения (в опенбоксе не отображается)
  SDL_WM_SetIcon(SDL_LoadBMP("icon.bmp"),NULL);

Если, заметили какие-либо ошибки, пожалуйста - дайте мне знать))

3 комментария:

  1. using namespace std;
    подключает пространство имён стандартной библиотеки.
    вообще-то хватило бы просто using std::string, using std::cout, using std::endl. Чуть быстрее бы компилялось и вообще не комильфо строка "using namespace std". В цпп не так и страшно, главное, что бы в хедерах не было этого.

    Для перевода строки в число можно было i/o streams использовать.

    //экран OpenGl на полный экран
    //При запуске программы в оконном режиме -окно можно мышкой растягивать мышкой,
    //если изображение содержит расширению jpg - альфы нет

    return 0;//и функция возвращает 0 (ложь), т.е функцию свою задачу выполнить не может
    return 1;//и функция возвращает 1 (истина), т.е функцию свою задачу выполнла
    return false/true; и нафиг коммент с implicit cast левым

    //проверка 2 - напрвышает максимально допустимого размера SDL текстуры для использования ее в OpenGL

    //Если поворот происходит относительно центре, то смещаемся еще немного на 0.5dX b 0.5dY

    //Отражение о праого бока ракетки

    Ну и ещё отступы поправить, местами просто жесть :)
    Хоть на вкус и цвет фломастеры разные, лично мне, не совсем этот стиль отступов нравится.

    Плюс всякие магические числа можно было бы и убрать.

    Запятых там доброй дюжины не хватает, но я уже забил на это дело.

    ОтветитьУдалить
  2. elfenlaid, спасибо. поправил :)

    ОтветитьУдалить
  3. Как выводить true type кирилицу этой связкой?

    ОтветитьУдалить