В этом уроке, как и обещал, я расскажу вам про такое понятие как «Сущности» (Entities). «Сущности» для всех игровых процессов это своего рода такие игровые объекты, которые могут взаимодействовать в какой-либо форме или каким-то способом друг с другом и с игровым миром. Примерами «Сущностей» могут служить монстры, которых вы встретите на своем нелегком пути, сундуки с сокровищами, которые вы можете открыть, монеты, которые можно собрать, стены, об которые можно убиться и т.д. Таким образом любой объект игрового мира который хоть как-то двигается, проявляет интерактивность, может быть представлен в виде этих «Сущностей».
Ну, скажем так: есть у вас какая-то «Гора», которая является частью вашей карты, стоит на месте и просто существует. Это не «Сущность». А вот если вы хотите заставить эту «Гору» перемещаться, падать на вашего героя (попросту — взаимодействовать с объектами игрового мира), то её естественно целесообразно представить в виде «Сущности». Автор решил разбить повествование на 3 статьи (и я его прекрасно понимаю, там очень много текста и кода, перевод займет уйму времени, так что ждите). В этом уроке будет всё довольно просто — мы создадим класс для работы с «Сущностями» и разберем его по косточкам. В следующем уроке узнаем как можно создать карту с помощью тайлсетов и управлять ей. Ну и в завершении цикла — подробный разбор процессов взаимодействия игровых объектов (обработка пересечений (ну или столкновений) «Сущностей» с другими «Сущностями» и с самой картой).
А пока, создайте, как вы это уже умеете, 2 файла CEntity.cpp и CEntity.h с таким содержимым:
#include <vector>
#include "CAnimation.h"
#include "CSurface.h"
class CEntity {
public:
static std::vector<CEntity*> EntityList;
protected:
CAnimation Anim_Control;
SDL_Surface* Surf_Entity;
public:
float X;
float Y;
int Width;
int Height;
int AnimState;
public:
CEntity();
virtual ~CEntity();
public:
virtual bool OnLoad(char* File, int Width, int Height, int MaxFrames);
virtual void OnLoop();
virtual void OnRender(SDL_Surface* Surf_Display);
virtual void OnCleanup();
};
Ну и соответственно:
#include "CEntity.h"
std::vector<CEntity*> CEntity::EntityList;
CEntity::CEntity() {
Surf_Entity = NULL;
X = Y = 0.0f;
Width = Height = 0;
AnimState = 0;
}
CEntity::~CEntity() {
}
bool CEntity::OnLoad(char* File, int Width, int Height, int MaxFrames) {
if((Surf_Entity = CSurface::OnLoad(File)) == NULL) {
return false;
}
CSurface::Transparent(Surf_Entity, 255, 0, 255);
this->Width = Width;
this->Height = Height;
Anim_Control.MaxFrames = MaxFrames;
return true;
}
void CEntity::OnLoop() {
Anim_Control.OnAnimate();
}
void CEntity::OnRender(SDL_Surface* Surf_Display) {
if(Surf_Entity == NULL || Surf_Display == NULL) return;
CSurface::OnDraw(Surf_Display, Surf_Entity, X, Y, AnimState * Width, Anim_Control.GetCurrentFrame() * Height, Width, Height);
}
void CEntity::OnCleanup() {
if(Surf_Entity) {
SDL_FreeSurface(Surf_Entity);
}
Surf_Entity = NULL;
}
Как обычно, настало время мне объяснить вам что тут всё-таки происходит. Я инкапсулировал от CApp 5 ранее использованных мною (и вами) методов (за исключением обработки событий — расскажу в следующем уроке как и обещал). Такой подход позволит нам «отделить зёрна от плевел» и обрабатывать «Сущности» более прозрачно и просто, нежели чем они были бы распиханы все в куче в главном (CApp) классе. Также мы сможем с легкостью обрабатывать и что-то другое нежели «Сущности». Вы уже обратили внимание на статический вектор EntityList? Он предназначен для хранения списка наших игровых сущностей и прямо доступен через CEntity::EntityList, всё потому что он статичен. Важно: я специально объявил EntityList именно в CEntity, поскольку такой подход позволит в будущем избежать циклических зависимостей. К примеру взаимодействие Карты с Сущностями, когда класс CMap объявлен как член CEntity и CEntity объявлен как член CMap (тут я сам немного не понял что автор имеет в виду), приведет к ошибке на этапе компиляции.
Итак, этот вектор будет хранить все наши игровые «Сущности» в виде ссылок на них (сделано с упором на будущее, когда от класса CEntity будут наследоваться другие классы). Так, например, если бы мы собирались сделать игру Megaman, у нас был бы класс CMegaMan наследованный от CEntity. И с помощью полиморфизма мы сможем хранить объекты класса CMegaMan в EntityList. Это и есть причина, по которой мы объявили вышеуказанные функции, как виртуальные, а некоторые члены класса защищенными.
У нашей «Сущности» есть общие для всех «Сущностей» параметры — координаты, размеры, поверхность для отрисовки картинки. Также имеется и метод для загрузки этой картинки, по умолчанию — просто прозрачная область. Лирическое отступление: не бойтесь изменять этот код, это не постулат, он не высечен из камня. Меняйте его так как вам вздумается, а если ошибетесь, оригинал всегда тут, никуда не денется!
В методе OnLoop у нас должны быть реализованы основные вычисления. Но пока мы оставим там только расчет кадров анимации. Также обратите внимание, что мы установили для анимации только переменную MaxFrames, а всё остальное оставили по умолчанию. Приглядимся к OnRender. Вместо того, чтобы делать тупо отрисовку на экран, я ввел параметр, чтобы стало возможным указывать любую поверхность, в которую мы бы хотели отрисовывать «Сущность». Таким образом вы можете даже накладывать отрисовку «Сущностей» друг на друга.
Ну и напоследок у нас OnCleanup, любимая моя очистка всего и вся (Чистилище по мне плачет), освободитель памяти, маленький эконом.
Как я уже говорил в самом начале — мы создали только базовую структуру класс для «Сущностей», и, не кривя душой, скажу что пока этот монстр мало чего умеет (но будет уметь в следующих уроках). Но заставить его заработать мы можем уже сейчас! Откроем
CApp.h и добавим парочку «Сущностей»:
#include "CEntity.h"
//... тут наш не изменяющийся код
private:
CEntity Entity1;
CEntity Entity2;
Загрузим их, прописав код в CApp_OnInit.cpp. (тут потребуются картинка с прошлого урока, вернее 2 её копии):
if(Entity1.OnLoad("./entity1.bmp", 64, 64, 8) == false) {
return false;
}
if(Entity2.OnLoad("./entity2.bmp", 64, 64, 8) == false) {
return false;
}
Entity2.X = 100;
CEntity::EntityList.push_back(&Entity1);
CEntity::EntityList.push_back(&Entity2);
Помните, как я говорил, что мы, в основном, инкапсулируем основные функции игры в классе сущностей? Теперь нужно их немножко повызывать. Редактируем CApp_OnLoop.cpp:
for(int i = 0;i < CEntity::EntityList.size();i++) {
if(!CEntity::EntityList[i]) continue;
CEntity::EntityList[i]->OnLoop();
}
Т.е. мы пробегаемся по нашему вектору с «Сущностями» и у каждой «Сущности» вызываем метод OnLoop (а также проводим небольшую проверку, дабы не нарваться на нулевой указатель). Осталось то же самое сделать и в CApp_OnRender.cpp:
for(int i = 0;i < CEntity::EntityList.size();i++) {
if(!CEntity::EntityList[i]) continue;
CEntity::EntityList[i]->OnRender(Surf_Display);
}
Очисточка, родимая, ну здравствуй:
CApp_OnCleanup.cpp
for(int i = 0;i < CEntity::EntityList.size();i++) {
if(!CEntity::EntityList[i]) continue;
CEntity::EntityList[i]->OnCleanup();
}
CEntity::EntityList.clear();
Последним штрихом (CEntity::EntityList.clear()) мы обнуляем вектор «Сущностей»… Компилируем, запускаем, любуемся!
Всем спасибо за внимание.
Ссылки на исходный код:
Ссылки на все уроки:
- Разработка игрового фрэймворка. Часть 1 — Основы SDL
- Разработка игрового фрэймворка. Часть 2 — Координаты и отображение
- Разработка игрового фрэймворка. Часть 3 — События
- Разработка игрового фрэймворка. Часть 4 — Крестики-Нолики
- Разработка игрового фрэймворка. Часть 5 — Анимация
- Разработка игрового фрэймворка. Часть 6 — Сущности
Автор: m0sk1t