SDL游戏教程第六课 实体 (Entities)
本系列教程来自Dev Hub。
在这个新的教程里, 像我以前许诺的那样,我们将要一起动手去创建实体。实体,对于所有的游戏的用途是作为能够以任何方式、形式、和结构交互事物。一些例子可以一个怪兽或者一个你可以的打开的宝箱。在这种意义上,几乎所有游戏可以动的东西都是实体。一块地图一部分的石头,不能移动,不是一个实体。但是如果你想因为一些原因要让石头移动,那么我们希望他成为一个实体。这个教程会被分成3个不同的教程。第一个教程,就是你现在读的这一个,将会涉及到一个基本的实体类结构。下一个教程我们将会稍微改变一点去建立一个地图类(Map class)通过材质(tileset)。最后一个教程,让很多人都有困惑的,将会涉及到实体和地图之间的碰撞检测,实体和实体间的碰撞检测。
更新:
- 修正了下面的类,使之有一个虚析构函数。(感谢 Andras)
让我们通过创建连个新的叫做CEntity.cpp 和 CEntity.h的文件来开始吧。打这个开头文件加入下面的代码:
#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();
};
现在,打开cpp文件并且加入下面的代码:
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;
}
OK,现在我来做一些基本的解释吧。我在这里做的事情是封装这我开始的课程提到的5个基本的组件(component) (包括事件(Events), 将会在后面的课程中被使用到) 这使我们能够处理游戏里的实体更容易,而不是和游戏中的其他东西聚集到主类CApp中。这也会是我们处理其他东西的方法。第一件你也许会注意到的东西是一个静态的向量(static vector)对象,名字叫做EntityList。这个向量将会保存我们所有的实体,使之很容易的通过CEntity::EntityList,因为他被声明为静态的(static)。在这里我要特别的强调一下:我们声明CEntityList在CEntity类里面,因为那样能防止以后产生循环依赖关系。比如说让地图类和实体类通信,实体类和地图类通信。比如说,CMap声明了一个CEntity的成员,并且CEntity声明的一个CMap的成员。这就会在编译阶段产生问题。
所以这个vector含有所有游戏里的实体。注意到,每个vector的成员是一个指针。这是因为以后,我们将要从CEntity继承出具体的实体类。所以,例如如果我们要去做一个Megaman的游戏,我们将会有一个从CEntity类继承过来的CMegaMan类。并且,通过多态,我们可以将CMegaman类的保存在EntityList里。这是就是为什么我们声明上面的函数为虚函数,并且某些成员设为保护。
接下来,我们有了基本关于实体的信息,对于所有的实体,坐标,维度,和一个图像的surface是通用的。 接下来,我们有一个加载函数基本的接受文件名,加载这个图像。默认的,我们设置一个透明颜色。我想靠边站了一会儿这里,让大家知道,某些事情我不是一成不变的。你可以,并且被鼓励去使用这些代码并且去修改它适合你自己的喜好。你也许想要更多的参数 在你的OnLoad函数上,或者你想要更少的参数。你也许不想要要有一个默认的透明色。我鼓励你去测试不同的东西。不要担心,我的代码仍然在这里如果你把它搞得一团糟。
接下来,我们有一个基本呢OnLoop函数用来处理基本的计算。现在我们只计算动画。请也注意我们只设置最大的动画帧率(MaxFrames),并且让默认值放在那里。接下来,我们有了OnRender函数。 不是让他只能只能渲染到显示屏上,我允许一个参数指定这个实体渲染到哪里。所以你可以,如果你想要,渲染一个实体到另一个实体上。
最后,我们有一个OnCleanup函数用来归还内存到系统和其他诸如此类的东西。
像我开始提到的,这是一个基本的实体类的结构,它基本上还做不了多少事情,但不要着急,它会在接下来的课程中做很多事情的。所以我们让它工作吧。打开CApp.h并且加上头们到顶部,声明两个实体:
//…
private:
CEntity Entity1;
CEntity Entity2;
现在,让我们来加载这两个实体。打开CApp_OnInit.cpp加入下面的代码
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);
现在,根据你使用的图像,你需要在OnLoad函数里设置合适的值。而我重复使用了上一节课的yoshi图像,并且你仍然需要它:
现在, 记得我一开始是怎样把这些游戏的基本的函数到封装到实体类里的吗?我们现在不得不在CApp类的成员函数里分别调用这些函数。所以,打开CApp_OnLoop.cpp加入下面的代码。
if(!CEntity::EntityList[i]) continue;
CEntity::EntityList[i]->OnLoop();
}
我们基本上是遍历每个在我们的vector里的实体,并且在OnLoop函数里被调用。够简单吧!(并且我们要做一个错误检测,因此我们不调用一个空的指针)。现在,让我们在CApp_OnRender.cpp里做同样的事情:
if(!CEntity::EntityList[i]) continue;
CEntity::EntityList[i]->OnRender(Surf_Display);
}
同样的在CApp_OnCleanup.cpp:
if(!CEntity::EntityList[i]) continue;
CEntity::EntityList[i]->OnCleanup();
}
CEntity::EntityList.clear();
注意,我加入clear函数调用,清除向量(vector)使里面什么都没有。基本上是一个重置(reset)。
好极了,我们现在试着去编译这些代码。你应该可以看到两个yoshis在屏幕上一起跑动。下一课,我们将会去看一下做地图吧,并且创建一个基本的为我们的地图准备的文件格式。