субота, 23 вересня 2017 р.

Розробка фреймворка для 2-Д відеоігор на основі мультимедійної бібліотеки SDL2 Частина 2. Перше вікно SDL2.


Розробка фреймворка для 2-Д відеоігор на основі мультимедійної бібліотеки SDL2

Частина 2. Перше вікно SDL2.

В попередній частині ми підготували усе необхідне для створення ігрового фреймворку на основі мультимедійної бібліотеки SDL2, а тепер почнемо роботу безпосередньо над самим фреймворком.
Оскільки сам термін “відеогра” передбачає виведення на екран, відповідно, основою для відеогри буде екран або якась його частина, у нашому випадку вікно. Спочатку я покажу тобі, яким способом можна отримати вікно за допомогою бібліотеки SDL2. Оскільки ми не будемо використовувати LCL, нам не потрібно нічого лишнього, тому в Lazarus створи новий проект типу “Проста програма”, і набери наступний код:
{******************************}program demo;{*********************************}
{*                                                                            *}
{*     Демонстрація створення пустого вікна за допомогою бібліотеки SDL2      *}
{*                                                                            *}
{******************************************************************************}
 {$mode objfpc}
uses sdl2;

 var SDLWindow   : PSDL_Window;
     SDLRenderer : PSDL_Renderer;
     SDLEvent    : PSDL_Event;
     isRun       : Boolean;

begin
   //спочатку пробуємо ініціалізувати бібліотеку
   if SDL_Init(SDL_INIT_EVERYTHING)>=0 then begin
      //якщо все пройшло добре - створюємо вікно
     SDLWindow:=SDL_CreateWindow('Game over!',SDL_WINDOWPOS_UNDEFINED,SDL_WINDOWPOS_UNDEFINED,640,480,SDL_WINDOW_SHOWN);
     //якщо вікно створене, потрібно створити рендерер
     if SDLWindow<>nil then begin
        SDLRenderer:=SDL_CreateRenderer(SDLWindow,-1,0);
        if SDLRenderer=nil then begin
           SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,'Error!',SDL_GetError,SDLWindow);
           Exit;
        end;
     end;
     New(SDLEvent);
     isRun:=true; //ознака виконання робочого циклу
     while isRun do begin
          //встановлюємо колір вікна в голубий
          SDL_SetRenderDrawColor(SDLRenderer,0,128,255,255);
          //очищаємо вікно
          SDL_RenderClear(SDLRenderer);
          //показуємо вікно на екрані
          SDL_RenderPresent(SDLRenderer);
          //відловлюємо подію закриття вікна
          if SDL_PollEvent(SDLEvent)=1 then begin
              if SDLEvent^.type_ = SDL_QUITEV then isRun:=false;
          end;
     end;
     //прибираємо за собою
     Dispose(SDLEvent);
     SDL_DestroyRenderer(SDLRenderer);
     SDL_DestroyWindow(SDLWindow);
     //зупиняємо бібліотеку
     SDL_Quit;
   end;
end.


Я не буду в деталях зупинятись на описі кожної функції бібліотеки SDL2, ця інформація доступна як в сирцях трансляції заголовочних файлів, так і на багатьох сайтах. Розглянемо лише що робить цей код.
Для створення вікна нам необхідні наступні змінні: для вікна, рендерера (це така штука, яка відповідає за малювання зображення), відловлювання подій SDL2 та ознака виконання робочого циклу.
Після ініціалізації усіх підсистем SDL2 створюємо спочатку вікно, тоді рендерер. У випадку, якщо не вдається створити рендерер, програма виводить відповідне повідомдення і перериває виконання.
Коли все пройшло успішно, ми маємо вікно і рендерер, який на ньому буде промальовувати все, що нам потрібно.
Для того, щоб вікно можна було закрити, ми мусимо відловити відповідну подію SDL2. Оскільки спеціальної функції для ініціалізації змінної для роботи з повідомленнями немає, потрібно просто виділити для цього пам’ять стандартною функцією Pascal New(). Встановленням ознаки виконання робочого циклу в true завершується розділ ініціалізації і починається безпосередньо робочий цикл.
Сам цикл буде тривати до того моменту, поки змінна isRun буде мати значення true. Все, що в нас вібувається з кожною ітерацією циклу - встановлення кольору для вікна, заливка вікна цим кольором і показ його на екрані, після чого йде відловлювання події SDL2 і перевірка чи це саме та подія, яка нам потрібна. І коли нарешті настає момент закриття вікна, ми відловлюємо відповідну подію SQL_QUITEV. Змінна isRun приймає значення false і виконання циклу перериваєьтся. Програма переходить до наступної стадії - очистка використаної пам’яті. Усі змінні очищаємо в порядку, оберненому до порядку їх створення: спочатку звільняємо пам’ять, виділену під вказівник змінної для відловлювання подій, тоді рендерер, вікно і зупиняємо усі підсистеми SDL2.
Варто пам’ятати: усі змінні, які ініціалізуються функціями типу SDL_CreateXXX(), звільняються відповідними функціями SDL_DestroyXXX() в порядку, оберненому до порядку їх ініціалізації. Ті змінні, пам’ять для яких виділялась явно функцією New(), повинні очишатись теж в оберненому порядку функцією Dispose().
Здається, тут все досить просто, але й близько не видно, яке це має відношення до ігрового фреймворку. Що ж, давай тепер розглянемо, за яким принципом побудовані усі відеоігри.

Ігровий алгоритм, або з чого складається відеогра

Незалежно від жанру, ігрового дизайну, сукупності використовуваних технологій, ігрова механіка будь-якої відеогри складається із взаємодії різних підсистем, таких як графіка, ігрова механіка і ввід корисувача. При цьому графічна підсистема може нічого не знати про логіку гри, яким чином відбувається введення користувачем, тощо. Ми можемо думати про структуру гри наступним чином:
Рис.1. Типовий алгоритм будь-якої гри.
Після одноразової ініціалізації усіх необхідних даних запускається ігровий цикл, який перевіряє ввід користувача, прораховує ігрову логіку і встановлює необхідні значення усіх об’єктів і змінних до того, як вивести графіку на екран.
Коли користувач вибирає завершити гру, ігровий цикл переривається, і гра переходить до стадії очищення даних та виходу.
Спробуємо розділити попередній код на окремі логічні блоки, які будуть відповідати за усі перечислені кроки:
{******************************}program demo_v2;{*********************************}
{*                                                                            *}
{*     Демонстрація створення пустого вікна за допомогою бібліотеки SDL2      *}
{*                                                                            *}
{******************************************************************************}
 {$mode objfpc}
uses sdl2;

 var SDLWindow   : PSDL_Window;
     SDLRenderer : PSDL_Renderer;
     SDLEvent    : PSDL_Event;
     isRun       : Boolean;

 function Initialize(aCaption : PChar; _height, _width : Integer) : Boolean;
 //ініціалізація SDL2 і створення вікна та рендерера
 begin
   result:=false;
   //спочатку пробуємо ініціалізувати бібліотеку
   if SDL_Init(SDL_INIT_EVERYTHING)>=0 then begin
      //якщо все пройшло добре - створюємо вікно
     SDLWindow:=SDL_CreateWindow(aCaption,SDL_WINDOWPOS_UNDEFINED,SDL_WINDOWPOS_UNDEFINED,_height,_width,SDL_WINDOW_SHOWN);
     //якщо вікно створене, потрібно створити рендерер
     if SDLWindow<>nil then begin
        SDLRenderer:=SDL_CreateRenderer(SDLWindow,-1,SDL_RENDERER_ACCELERATED);
        if SDLRenderer<>nil then begin
           New(SDLEvent);//потрібно виділити пам'ять для вказівника
           isRun:=true; //ознака виконання робочого циклу
           Result:=true;
           Exit;
        end;
     end;
   end;
 end;

procedure DoEvents;
begin
 //відловлюємо подію закриття вікна
  if SDL_PollEvent(SDLEvent)=1 then begin
     if SDLEvent^.type_ = SDL_QUITEV then isRun:=false;
  end;
end;

procedure Draw;
begin
  //встановлюємо колір вікна в бірюзовий
  SDL_SetRenderDrawColor(SDLRenderer,0,128,255,255);
  //очищаємо вікно
  SDL_RenderClear(SDLRenderer);
  //показуємо вікно на екрані
  SDL_RenderPresent(SDLRenderer);
end;

procedure Cleanup;
begin
  //прибираємо за собою
  Dispose(SDLEvent);
  SDL_DestroyRenderer(SDLRenderer);
  SDL_DestroyWindow(SDLWindow);
  //зупиняємо бібліотеку
  SDL_Quit;
end;

begin
   //спочатку пробуємо ініціалізувати бібліотеку
   isRun:= Initialize('Game over! v.2.0',640,480);
   if not isRun then begin
           SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,'Error!',SDL_GetError,SDLWindow);
           Exit;
    end;
   while isRun do begin
     DoEvents;
     Draw;
   end;
   Cleanup;
end.


Коли ти запустиш цей код на виконання, то побачиш, що кінцевий результат не змінився, створюється те ж саме вікно, але тепер є окремі чітко виділені підпрограми для ініціалізації, обробки подій, рендерингу і очистки пам’яті. Це вже ближче до того, що нам потрібно. Не вистачає лише підпрограми для обчислення усієї ігрової логіки. Крім цього, такий підхід при розробці гри є не дуже гнучким, оскільки на кожну нову гру доведеться по новомо все це описувати. Це не наш метод, я хочу отримати в результаті фреймворк, який мені буде спрощувати написання коду і дозволить зосередитись безпосередньо на написанні гри, а не рутини по створенню вікон, обробці подій, виведенні на екран тощо. Для цього давай опишемо окремий клас, який стане основою для майбутнього фреймворку.
Оскільки ми вже впритул підійшли до написання фреймворку, потрібно придумати йому назву. Я пропоную “Game Over 2D Engine”, ти можеш придумати іншу назву.
Повернемось до нашого коду. Оскільки це буде базовий клас, я назвав його TGOEngine (скорочено від Game Over), і ось що в мене в результаті вийшло:
unit ugoengine;

{$mode objfpc}{$H+}

interface

uses sdl2;

type

 { TGOEngine }

 TGOEngine =class
  private
    fWindow   : PSDL_Window;
    fRenderer : PSDL_Renderer;
    fError    : Boolean; //якщо відбулась помилка
    fIsRun    : Boolean; //чи запущений ігровий цикл
  public
    constructor Create;
    destructor Destroy;override;

    procedure Initialize(aCaption : PChar; _height, _width : Integer);
    procedure DoEvents;
    procedure Update;
    procedure Draw;
  published
    property Error : Boolean read fError;
    property IsRunning : Boolean read fIsRun;
  end;

implementation

{ TGOEngine }

constructor TGOEngine.Create;
begin
  inherited Create;
  //ініціалізація SDL2 і створення вікна та рендерера
   begin
     fError:=true;
     //спочатку пробуємо ініціалізувати бібліотеку
     if SDL_Init(SDL_INIT_EVERYTHING)>=0 then begin
        //якщо все пройшло добре - створюємо вікно
       fWindow:=SDL_CreateWindow('Game over!',SDL_WINDOWPOS_UNDEFINED,SDL_WINDOWPOS_UNDEFINED,640,480,SDL_WINDOW_SHOWN);
       //якщо вікно створене, потрібно створити рендерер
       if fWindow<>nil then begin
          fRenderer:=SDL_CreateRenderer(fWindow,-1,0);
          if fRenderer<>nil then begin
             fError:=false;
             Exit;
          end;
       end;
     end;
   SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,'Error',SDL_GetError,fWindow);
   end;
end;

destructor TGOEngine.Destroy;
begin
   //прибираємо за собою
   SDL_DestroyRenderer(fRenderer);
   SDL_DestroyWindow(fWindow);
   //зупиняємо бібліотеку
   SDL_Quit;
   inherited Destroy;
end;

procedure TGOEngine.Initialize(aCaption : PChar; _height, _width : Integer);
begin
  //тут буде проходити ініціалізація даних
   fIsRun:=true;
end;

procedure TGOEngine.DoEvents;
var _Event : PSDL_Event;
begin
   New(_Event);
  //тут буде проходити обробка подій
   if SDL_PollEvent(_Event)=1 then begin
      case _Event^.type_ of
SDL_QUITEV: fIsRun:=false;
      end;//case
   end;
   Dispose(_Event);
end;

procedure TGOEngine.Update;
begin
  //тут буде оновлюватись поточний стан виконання
end;

procedure TGOEngine.Draw;
begin
  if fError then Exit;
  //встановлюємо колір вікна в бірюзовий
  SDL_SetRenderDrawColor(fRenderer,0,128,255,255);
  //очищаємо вікно
  SDL_RenderClear(fRenderer);
  //показуємо вікно на екрані
  SDL_RenderPresent(fRenderer);
end;


end.


А ось код основної програми:
{******************************}program demo_v3;{*********************************}
{*                                                                            *}
{*     Демонстрація створення пустого вікна за допомогою бібліотеки SDL2      *}
{*                                                                            *}
{******************************************************************************}
 {$mode objfpc}{$H+}

uses ugoengine;

var TestEngine : TGOEngine;

begin
   //створюємо екземпляр класу
   TestEngine:=TGOEngine.Create;
   //ініціалізуємо початкові дані
   TestEngine.Initialize('Game Over v.3.0',640,480);
   while TestEngine.IsRunning do begin;
    TestEngine.DoEvents;
    TestEngine.Update;
    TestEngine.Draw;
   end;
   TestEngine.Free;
end.


Як бачиш, використання класу дозволяє не думати про рутину створення вікна і зосередитись лише над тим, що ти хочеш отримати в результаті. Проте цей клас ще не забезпечує усієї потрібної роботи з вікном і рендерером. Спробуємо його вдосконалити в наступній частині.

неділя, 17 вересня 2017 р.

Розробка фреймворка для 2-Д відеоігор на основі мультимедійної бібліотеки SDL2 Частина 1. Підготовка системи.

Розробка фреймворку для 2-Д відеоігор на основі мультимедійної бібліотеки SDL2

Частина 1. Підготовка системи.

В Інтернеті на різних сайтах по програмуванню є дуже багато інформації щодо мультимедійної бібліотеки SDL2, але вся вона має два суттєвих недоліки з точки зору розробки відеоігор мовою програмування Object Pascal:
  1. Більшість такої інформації приводиться з використанням мови програмування C++, а дійсно якісна документація доступна лише англійською мовою. Але далеко не всі програмісти на потрібному рівні володіють англійською, як і не всі пишуть на C++/.
  2. Серед усієї доступної інформації по використанню SDL2 переважаюча більшість лише описує можливості самої бібліотеки з наведенням абстрактних прикладів. Ніби й нічого, але як з усім цим зробити конкретну гру?
Своїм циклом статей про розробку фреймворку для 2-Д відеоігор з кількома прикладами готових ігор я спробую заповнити цей недолік інформації.
Що я спробую охопити:
  • підготовка і настройка системи для використання SDL2
  • створення фреймворка і розробка на основі нього декількох відеоігор різних жанрів
Що я не буду розглядати:
  • основи програмування на Object Pascal - такий об’єм інформації виходить далеко за рамки кількох статей.
  • усі тонкощі використання функцій SDL2 і офіційних розширень цієї бібліотеки - документації про це вже і так є достатньо.
Необхідні інструменти:

Підготовка системи

Якщо в тебе ще немає встановленого компілятора FPC та середовища Lazarus, саме час їх встановити. Зрештою, можна використовувати лише сам компілятор, але в Lazarus набагато зручніше працювати з проектами. Проте це моя суб’єктивна думка, і ти можеш користуватись тими інструментами, якими зручно саме тобі.
Спочатку тобі потрібно встановити саму бібліотеку SDL2. Розгляну процес встановлення по черзі для кожної операційної системи.

Встановлення SDL2 в ОС Linux

Я користуюсь Linux Mint, тому все описане нижче буде відноситись саме до цієї системи. В інших системах Linux усі необхідні пакети встановлюються аналогічно. За більш детальними роз’ясненнями можна звернутись до документації відповідного дистрибутиву.

Рис.1. Запуск менеджера пакунків Synaptic в Linux Mint.
Отже, тобі будуть потрібні пакунки з самою бібліотекою та її офіційними розширеннями, а також відповідні пакунки для розробки - без них неможливо буде лінкеру зібрати програму, яка використовує SDL2. Тож запускай Synaptic (Рис.1), вводь пароль і в вікні пошуку вводь “libsdl2” - як результат, ти побачеш відібрані усі необхідні пакунки. Відмічай ті, що на Рис.2 відмічені зеленим і натискуй кнопку “Застосувати”. Через кілька хвилин усі необхідні компоненти вже будуть встановлені, і твій дистрибутив буде готовий до розробки програм з використанням цієї надзвичайної мультимедійної бібліотеки.
Рис.2. Пакунки необхідні для розробки з використанням усіх можливостей SDL2.

Встановлення SDL2 в ОС Windows

Оскільки я вже досить давно не використовую в роботі ОС Windows, приведу лише загальні рекомендації.
Саму бібліотеку можна скачати на офіційному сайті, потрібно лише вибрати правильну розрядність твоєї системи (32 або 64 біти). Офіційні розширення можна скачати за посиланням, нам потрібні будуть лише наступні:
  • SDL_image
  • SDL_mixer
  • SDL_net
  • SDL_ttf
Усі скачані архіви тобі потрібно розпакувати, і результат скопіювати в папку c:\Windows\System32 (незалежно від розрядності твоєї системи), якщо ти маєш відповідні права доступу. Якщо ж немає можливості скопіювати файли в систему, ти можеш їх скопіювати просто до виконуваного файла твоєї програми - цього буде достатньо.

Настройка Lazarus на використання SDL2

Для того, щоб ти зміг використовувати усі можливості SDL2 у своїх програмах, тобі потрібно скачати трансляцію заголовочних файлів для компілятора FPC. Знайти їх можна у розділі “Bindings” офіційного сайту SDL (взагалі, там доступні трансляції для різних мов програмування, але тобі потрібно лише для Pascal). Я користуюсь Pascal SDL 2 - на відміну від Bare Game, тут є також трансляція усіх офіційних доповнень та збережено коментарі з детальними описами структур даних, констант та функцій SDL2 та офіційних розширень.
Після того, як скачаєш архів, тобі потрібно його розпакувати і пояснити компілятору, де шукати ці файли. Особисто мені для будь-якого нового проекту зручно, коли “мухи від котлет окремо”, тому в мене наступна структура каталогів проекту - в папці проекту є три вкладені папки: bin, lib, src. В папку bin в мене компілюється виконуваний файл проекту, в папці lib - проміжні результати компіляції (з врахуванням операційної системи і архітеркури процесора), а в папці src - сирці самого проекту. Таким чином, щоб почистити результари збирання проекту і перенести його на інший комп’ютер, мені достатньо очистити папку lib і в папці bin видалити виконуваний файл (або декілька файлів). Для цього в кореневій папці проекту для кожної ОС можна створити відповідний скрипт для автоматизації цього процесу.
Оскільки я використовую Lazarus, в настройках проекту потрібно вказати що саме мені необхідно. Настройки, які відрізняються від стандартних, я виділив червоним на рис.3.
Рис.3. Настройки проекту Lazarus.
Отже, в розділі “Інші файли” потрібно прописати шлях до трансляції заголовочних файлів, вихідну теку модулів додати перед шляхом “../” (це означає піднятись на рівень вище), аналогічно в розділі “Назва цільового файлу” теж додати “../bin/” (що означає піднятись на один рівень вище і зайти в папку “bin”). Опція “Застосовувати умови” означає, що ім’я виконуваного файлу буде формуватись відповідно до стандарту операційної системи. Так, для проекту з назвою demo в ОС Windows буде створено файл demo.exe, в ОС Linux - demo (без розширення, оскільки тут можливість запустити файл на виконання залежить лише від атрибуту), а в OS X - demo.app.
Ну що ж, усе необхідне для роботи ми вже підготували, в наступній частині статті я покажу тобі типову програму з використанням SDL2, ми розглянемо типовий алгоритм будь-якої відеогри і спробуємо разом це все адаптувати для ігрового фреймворку.

пʼятниця, 15 вересня 2017 р.

Перезавантаження...

Всім привіт! Я довго обдумував, як краще мені викладати матеріали на мій блог, і нарешті вирішив зробити перезавантаження , що уроки буду формувати і публікувати по мірі можливості, наскільки мені буде вистачати часу працювати над ними. Але регулярно буду публікувати різні цікаві матеріали на тему програмування і не тільки, частина з яких потім можливо буде використано для уроків. Таким чином, хоча б раз на тиждень блог буде доповнюватись новими матеріалами, і обіцяю, що ні плагіату, ні копіпасту з інших сайтів у мене не буде. Вже на цих вихідних викладу початок циклу по використанню чудової мультимедійної бібліотеки SDL2, а точніше - почну розробку фреймворка для створення ігор.