Все, що ми робили до сьогодні, було пов'язано з підготовою вікна. Сьогодні пора нам продовжити розвивати наш фреймворк і спробувати щось намалювати. Оскільки переважна кількість ігор використовують попередньо створені ресурси (а саме графіка, звуки тощо), то ми сьогодні додамо можливість виводити в потрібному місці попередньо загатовлене зображення.
Поки для початку скористаємось нашим базовим класом. Додамо в списку приватних полів ще декілька:
//Тимчасово для тестування
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 окремі архіви до кожного епізоду), а весь процес можна подивитись на відео:
← попередня частина | наступна частина →
В наступному епізоді перейдемо до роботи з ігровими об'єктами, а це ще на крок ближче до створення повноцінної гри.
Немає коментарів:
Дописати коментар