Все, що ми робили до сьогодні, було пов'язано з підготовою вікна. Сьогодні пора нам продовжити розвивати наш фреймворк і спробувати щось намалювати. Оскільки переважна кількість ігор використовують попередньо створені ресурси (а саме графіка, звуки тощо), то ми сьогодні додамо можливість виводити в потрібному місці попередньо загатовлене зображення.
Поки для початку скористаємось нашим базовим класом. Додамо в списку приватних полів ще декілька:
//Тимчасово для тестування FTmpTexture : PSDL_Texture; //тут буде зображення FTmpSrcRect, FTmpDstRect : TSDL_Rect; //а це для визначення областей вихідного зображення і місця виведення
В конструкторі класу спробуємо заввантажити наше зображення. Для тестування я на популярному ресурсі завантажив деякі зображення, створені Kelvin Shadewing й доступні для використання по ліцензіях CC-BY-SA 4.0 та GPL 3.0, які дозволяють використовувати ці зображення безкоштовно з певними умовами. З текстом ліцензій можна ознайомитись за відповідними посиланнями.
Я скачав зображення "tux.png", яке розмістив в папці "assets" біля виконуваного файлу проекту.
Але є одна невелика проблема - бібліотека SDL 2.0 "з коробки" підтримує лише формат BMP, а зображення, які я хочу використати, збережені в форматі PNG, який має ряд переваг. На щастя, нічого конвертувати не доведеться, достатньо підключити в розділі uses ще один додатковий модуль - sdl2_image. За допомогою цього модуля можна скористатись офіційним розширенням бібліотеки, яке дозволяє використовувати обширний список графічних форматів і напряму їх використовувати. Про те, як користуватись цим розширенням, непогано описано в статті "Частина 7 - SDL2_image: Завантаження файлів зображень різних форматів", тому відразу приступлю до написання коду.
В кінці конструктора після ініціалізації всього необхідного спробуємо завантажити нашу текстуру:
//тимчасово - завантаження текстури з файлу FTmpTexture:=IMG_LoadTexture(FRenderer,'assets'+DirectorySeparator+'tux.png'); if FTmpTexture=nil then begin FErrorInfo:=SDL_GetError; FError:=true; WriteLn(FErrorInfo); end;
Наступним кроком спробуємо вивести зображення на екран. Логічно це зробити в методі Draw():
//тимчасово - вивести зображення SDL_RenderCopy(FRenderer,FTmpTexture,nil,nil);
І не забути в деструкторі коректно звільнити зайняту під текстуру пам'ять:
//тимчасова текстура SDL_DestroyTexture(FTmpTexture);
Виконуємо контрольний запуск проекту, і, якщо все зроблено правильно, в нашому вікні ми повинні отримати зображення, розтягнуте до розмірів вікна. Початок вже є, ми можемо вивести картинку в наше вікно, але для гри цього мало. При розробці ігор використовується одночасно велика кількість зображень, які виводяться в потрібний час в потрібному місці і навіть потрібним фрагментом. Зображення, яке я використав для прикладу - це так званий "спрайтшіт" - набір кадрів анімації персонажа. І щоб вивести цю анімацію, потрібно виводити певні фрагменти зображення один за одним.
Щоб спростити собі життя для зручного керування зображеннями в грі, напишемо ще один клас, який і буде займатись завантаженням та всією іншою рутиною включаючи малювання. Назвемо його TGoTextureManager і визначимось, чим він буде займатись.
Основна вимога - одночасно обробляти потрібну кількість зображень. Зручно буде мати доступ до них по унікальних назвах. Для цього зручно скористатись дженериком:
type TGoTextureMap = specialize TFPGMap;
Для його використання необхідно підключити модуль FGL, який є в стандартній комплектації Lazarus. Я описав відповідне приватне поле нашого менеджера текстур (нам непотрібно буде напряму звертатись до цього поля - весь механізм роботи з ним буде заховано "під капотом" менеджера текстур). Також нам потрібно буде конструктор та декструктор, де буде створюватись та знищуватись об'єкт карти текстур. А так як менеджер текстур буде використовуватись всередині нашого базового класу TGoEngine, то він теж буде як сінглтон, відповідно конструктор теж буде приватним.
constructor TGoTextureManager.Create; begin inherited Create; FTextureMap:=TGoTextureMap.Create; end; destructor TGoTextureManager.Destroy; var i: Integer; begin for i:= FTextureMap.Count-1 downto 0 do begin SDL_DestroyTexture(FTextureMap.Data[i]); FTextureMap.Delete(i); end; FreeAndNil(FTextureMap); inherited Destroy; end;
Наступною вимогою до менеджера текстур буде завантаження файлів зображень і додавання їх до карти разом з назвою, яка буде слугувати ключем доступу до зображення.
function TGoTextureManager.Load(aFileName: String; aID: String): Boolean; var TmpSurface : PSDL_Surface; TmpTexture : PSDL_Texture; begin Result := False; TmpSurface:=IMG_Load(PChar(aFileName)); if TmpSurface=nil then begin WriteLN( SDL_GetError(),' Error loading '+aFileName); Exit; end; TmpTexture:=SDL_CreateTextureFromSurface(GoEngine.FRenderer,TmpSurface); SDL_FreeSurface(TmpSurface); if TmpTexture<>nil then begin FTextureMap[aID]:=TmpTexture; Result:=True; end; end;
Також менеджер текстур повинен вміти намалювати в заданих координатах зображення, за потреби масштабуючи до потрібних розмірів та перевертаючи горизонтально/вертикально чи навіть одночасно горизонтально і вертикально:
procedure TGoTextureManager.Draw(aID: String; x, y, w, h: Integer; Flip: Integer); var srcRect, dstRect : TSDL_Rect; p : TSDL_Point; begin srcRect.x := 0; srcRect.y := 0; srcRect.w := w; dstRect.w := w; srcRect.h := h; dstRect.h := h; dstRect.x := x; dstRect.y := y; p.x:=0; p.y:=0; SDL_RenderCopyEx(GoEngine.FRenderer,FTextureMap[aId],@srcRect,@dstRect,0.0,@p,Flip); end;
Коли доведеться використовувати спрайтшіти і тайлмапи, потрібно буде намалювати відповідний кадр (будемо орієнтуватись на те, що всі кадри однакових розмірів і розміщені по рядках і стовбцях):
procedure TGoTextureManager.DrawFrame(aID: String; x, y, w, h: Integer; CurrentRow, CurrentFrame: Integer; Flip: Integer); var srcRect, dstRect : TSDL_Rect; begin srcRect.x := w*CurrentFrame; srcRect.y := h*CurrentRow; srcRect.w := w; dstRect.w := w; srcRect.h := h; dstRect.h := h; dstRect.x := x; dstRect.y := y; SDL_RenderCopyEx(GoEngine.FRenderer,FTextureMap[aId],@srcRect,@dstRect,0.0,nil,Flip); end;
В кінцевому варіанті в нас вийшов наступний клас:
TGoTextureManager = class private FTextureMap : TGoTextureMap; constructor Create; public function Load(aFileName : String; aID : String) : Boolean; procedure Draw(aID : String; x,y,w,h : Integer; Flip : Integer = SDL_FLIP_NONE); procedure DrawFrame(aID : String; x,y,w,h : Integer; CurrentRow, CurrentFrame : Integer; Flip : Integer = SDL_FLIP_NONE); destructor Destroy; override; end;
Тепер нам потрібно інтегрувати його в фреймворк. Для цього опишемо відповідне поле, яке буде використовуватись для доступу до менеджера текстур:
//Менеджер текстур property Texturemanager : TGoTextureManager read FTexturemanager;
Також нам потрібно створити екземпляр менеджера текстур, для цього в конструкторі фреймворка після створення візуалізатора і перед виділенням пам'яті для структури обробки подій додамо наступні рядки коду:
else begin FTexturemanager:= TGoTextureManager.Create; end;
І не забути в декструкторі фреймворка звільнити пам'ять, виділену під нього:
FreeAndNil(FTexturemanager);
Для тестування роботи менеджера текстур нам потрібно завантажити з диска файли зображень. Я зробив це в коді тестового проекту перед викликом ігрового циклу (поки що для тесту використав зображення, про які я згадував вище):
GoEngine.TextureManager.Load('assets'+DirectorySeparator+'tux.png','Tux'); GoEngine.TextureManager.Load('assets'+DirectorySeparator+'tuxfire.png','Tux Fire');
Для того, щоб можна було виводити потрібний кадр і створити анімацію, поки що тимчасово додамо два приватні поля в класі фреймворку:
fr,fc : Integer;
Ці поля будуть відповідати за номери рядка і стовпчика, в якому знаходиться потрібний кадр. Я попередньо відкрив зображення через графічний редактор і визначив, що розміри кадру будуть 32х32 пікселі, а кожен зі спрайтшітів, які я використав для демонстрації, має по 10 рядків, в кожному з яких по 8 зображень. Прорахунок номера кадра для тесту я описав в методі фреймворка GameLogic():
//тут буде прораховуватись ігрова логіка if fc >=7 then begin fc :=0; Inc(fr); if fr>=9 then fr:=0 else Inc(fr); end else Inc(fc); SDL_Delay(75);
Розрахунок затримки виводу наступного кадру я виставив виходячи зі швидкості анімації. В реальному проекті потрібно буде виконувати розрахунок такої затримки, цим ми займемось в одному з наступних епізодів. І, нарешті, саме цікаве - отримати анімацію на екрані. Для цього тимчасово в методі Draw() фреймворка після очистки вікна я написав наступні рядки коду:
//тимчасово - вивести зображення на екран TextureManager.DrawFrame('Tux', 0, 0, 32, 32, fr, fc); TextureManager.DrawFrame('Tux', 64, 0, 32, 32, 9-fr, 7-fc, SDL_FLIP_HORIZONTAL); TextureManager.DrawFrame('Tux Fire', 0, 64, 32, 32, fr, fc); TextureManager.DrawFrame('Tux Fire', 64, 64, 32, 32, 9-fr, 7-fc, SDL_FLIP_HORIZONTAL);
Пробуємо запустити на виконання, і, якщо все зроблено правильно, ми повинні побачити чотири анімованих пінгвіна.
Ну що ж, ми сьогодні написали багато коду і значно розширили можливості фреймворку для виводу зображень. В результаті ми отримали зручний менеджер текстур, який дозволяє всього в кілька рядків коду заванажити та вивести зображення або потрбний його фрагмент і легко додавати в гру потрібну кількість зображень.
Як для одного епізоду, це достатньо, код проекту можна скачати на Github (в розділі Releases окремі архіви до кожного епізоду), а весь процес можна подивитись на відео:
← попередня частина | наступна частина →
В наступному епізоді перейдемо до роботи з ігровими об'єктами, а це ще на крок ближче до створення повноцінної гри.
Немає коментарів:
Дописати коментар