本节将模仿ogre的ScreenManager编写一个SDL的ScreenManager。效果图如下
这是利用场景管理器创建的一个扫雷游戏界面,为后面的扫雷游戏做准备。
这里的场景管理器主要有四个类,SDLEntity(实体)、SDLSceneNode(节点)、SDLLayer(层)、SDLSceneManager(场景管理器)。
他们的关系是:一个SDLSceneManager管理多个SDLLayer,一个SDLLayer有多个SDLSceneNode,一个SDLSceneNode上面关联一个SDLEntity。
SDLSceneManager管理多个SDLLayer是为了将不同的层分离开来,并且通过索引号来决定哪个层显示在下面,哪个层显示在上面。这样做有利用分开管理,比如将游戏界面中不变的背景和游戏中经常变化的前景分开,设置背景的索引号小于前景索引号,这样就可以保证前景在上面显示,背景在后面显示,同时也可以通过整体平移背景层,来达到整个游戏场景的向前移动的效果。
#ifndef SDLSCENEMANAGER_H_
#define SDLSCENEMANAGER_H_
#include <map>
#include "SDL/SDLSurface.h"
class SDLLayer;
class SDLSceneManager
{
friend class SDL;
private:
SDLSceneManager();
public:
virtual ~SDLSceneManager();
public:
//获取默认层,默认层为最底层
SDLLayer* getDefaultLayer();
//获取索引号对应的层,索引号必须大于0,索引号大的显示在前面
SDLLayer* getLayer(int index);
//移除索引号对应的层
void removeLayer(int index);
//绘制整个场景
void draw(SDLSurfacePtr screen);
private:
std::map<int, SDLLayer*> layers;
};
#endif /* SDLSCENEMANAGER_H_ */
SDLLayer负责管理层中的SDLSceneNode,每个层都有一个根节点,根节点位于整个层的左上角,通过根节点可以创建子节点,然后子节点可以创建自己的子节点,以此类推,就可以得到一棵节点树。整棵节点树就构成了当前层要显示内容的布局。
#ifndef SDLLAYER_H_
#define SDLLAYER_H_
#include "SDLSceneNode.h"
class SDLSceneManager;
class SDLLayer
{
friend class SDLSceneManager;
private:
SDLLayer(SDLSceneManager * sceneManager, int id);
public:
virtual ~SDLLayer();
public:
//获得当前层的根节点
SDLSceneNode* getRootSceneNode();
//获取当前层的索引号
int getID();
private:
//绘制整个层
void draw(SDLSurfacePtr screen);
private:
SDLSceneNode * rootNode;
int id;
SDLSceneManager * sceneManager;
};
#endif /* SDLLAYER_H_ */
SDLSceneNode代表SDLLayer中的一个具体位置,他记录有相对与父节点的相对坐标,通过父节点的绝对坐标,可以获取当前节点的绝对坐标,根节点的绝对坐标为(0,0)。一个节点可以有多个子节点,不同的子节点通过名称来区分;一个节点可以关联一个实体(以后可能会扩充到关联多个),当关联了实体后,场景管理器就会在节点的当前位置绘制实体。
#ifndef SDLSCENENODE_H_
#define SDLSCENENODE_H_
#include "SDL/SDLCore.h"
#include "SDLEntity.h"
class SDLLayer;
class SDLSceneNode
{
friend class SDLLayer;
private:
SDLSceneNode();
public:
SDLSceneNode(SDLSceneNode *parent, std::string name, int x = 0, int y = 0);
public:
virtual ~SDLSceneNode();
public:
//获取节点名称
std::string getName();
//回去节点相对位置
SDL_Point getPosition();
//获取节点绝对位置
SDL_Point getAbsolutePosition();
//关联实体
void attachEntity(SDLEntity *entity);
//取消与实体的关联
void detachEntity();
//创建子节点
SDLSceneNode* createChildSceneNode(std::string name, int x = 0, int y = 0);
//创建一个与自己关联的实体
SDLEntity* createAttachedEntity(const std::string name, SDLSurfacePtr surface);
//获取已经关联的实体
SDLEntity* getEntity();
private:
//绘制本节点和所有的子节点
void draw(SDLSurfacePtr screen);
private:
SDLEntity *entity;
std::map<std::string, SDLSceneNode*> children;
std::string name;
SDLLayer *layer;
SDLSceneNode *parent;
SDL_Point position;
};
#endif /* SDLSCENENODE_H_ */
SDLEntity负责管理要显示在屏幕上的图像,一个SDLEntity只能包含一个surface,只能关联到一个节点。这里实体名称的作用主要是为了标示一个实体,便于跟踪调试,以后可能会有其他用途(在ogre里面能在场景管理器中通过实体的名称来获取实体,我们这里不行,因为暂时没有看出这样做有什么大的用途)。
#ifndef SDLENTITY_H_
#define SDLENTITY_H_
#include "SDL/SDLSurface.h"
#include <string>
class SDLSceneNode;
class SDLEntity
{
private:
SDLEntity();
public:
SDLEntity(std::string name, SDLSurfacePtr surface, SDLSceneNode *node = NULL);
virtual ~SDLEntity();
public:
//获取关联的节点
SDLSceneNode * getSceneNode();
//获取名称
std::string getName();
//获取包含的Surface
SDLSurfacePtr getSurface();
//设置包含的Surface
void setSurface(SDLSurfacePtr surface);
//取消关联节点
void detachSceneNode();
//关联节点
void attachSceneNode(SDLSceneNode * node);
private:
std::string name;
SDLSurfacePtr surface;
SDLSceneNode * node;
};
#endif /* SDLENTITY_H_ */
这里的逻辑比较简单,以后随着应用的深入,我们再来强化他的功能。
有了场景管理器后,我们不需要在onRender函数中写任何代码了。
在SDLFrame::open的消息循环中,去掉调用onRender的代码,加入场景绘制的代码,同时在场景绘制前和绘制后各加一个函数,便于在显示之前和之后做一些事情。
beforeRender();
SDL::sceneManager()->draw(screen);
afterRender();
下面是应用的代码
void Lesson04::onInit()
{
SDLLayer * background = SDL::sceneManager()->getDefaultLayer();
SDLSceneNode *root = SDL::sceneManager()->getLayer(1)->getRootSceneNode();
//加载图片
SDLSurfacePtr mine_unknown = SDL::imageManager()->loadImage(MINE_UNKNOWN);
SDLSurfacePtr frame_outer_h = SDL::imageManager()->loadImage(FRAME_OUTER);
SDLSurfacePtr frame_outer_v = SDL::transform()->Rotate90Degrees(frame_outer_h, 1);
SDLSurfacePtr frame_inner_h = SDL::imageManager()->loadImage(FRAME_INNER);
SDLSurfacePtr frame_inner_v = SDL::transform()->Rotate90Degrees(frame_inner_h, 1);
SDLSurfacePtr frame_background_src = SDL::imageManager()->loadImage(FRAME_BACKGROUND);
SDL_Rect mine_unknown_rect = mine_unknown->value()->clip_rect;
SDL_Rect frame_outer_rect = frame_outer_h->value()->clip_rect;
SDL_Rect frame_inner_rect = frame_inner_h->value()->clip_rect;
SDL_Rect frame_background_rect = frame_background_src->value()->clip_rect;
//计算扫雷区域的大小
SDL_Rect mine_frame;
mine_frame.w = MINE_COLS * mine_unknown_rect.w
+ (MINE_COLS-1) * frame_inner_rect.h
+ frame_outer_rect.h * 2;
mine_frame.h = MINE_ROWS * mine_unknown_rect.h
+ (MINE_ROWS-1) * frame_inner_rect.h
+ frame_outer_rect.h * 2;
SDLSurfacePtr frame_background = SDL::transform()->zoom(frame_background_src, mine_frame.w, mine_frame.h);
frame_background_rect = frame_background->value()->clip_rect;
//----------------------------------------------------------------------------------------
//创建背景图
SDLSurfacePtr imageSrc = SDL::imageManager()->loadImage(BACKGROUND_ROOT);
SDLSurfacePtr image = SDL::transform()->zoom(imageSrc, screen->value()->clip_rect.w, screen->value()->clip_rect.h) ;
background->getRootSceneNode()->createChildSceneNode("image", 0, 0)->createAttachedEntity("image",image);
//----------------------------------------------------------------------------------------
//创建雷区背景
int x = (screen->value()->clip_rect.w - frame_background_rect.w)/2;
int y = (screen->value()->clip_rect.h - frame_background_rect.h) - 20;
SDLSceneNode *frame_background_node = root->createChildSceneNode(FRAME_BACKGROUND, x, y);
frame_background_node->createAttachedEntity(FRAME_BACKGROUND, frame_background);
//----------------------------------------------------------------------------------------
//创建边框
SDLSurfacePtr frame_outer_h_all = SDL::transform()->flat(frame_outer_h
, frame_background_rect.w
, frame_outer_rect.h);
SDLSurfacePtr frame_outer_v_all = SDL::transform()->flat(frame_outer_v
, frame_outer_rect.h
, frame_background_rect.h);
//上
std::string FREAM_OUTER_TOP = FRAME_OUTER + "TOP";
SDLSceneNode * frame_outer_top_node = frame_background_node->createChildSceneNode(FREAM_OUTER_TOP
, 0
, 0);
frame_outer_top_node->createAttachedEntity(FREAM_OUTER_TOP, frame_outer_h_all);
//下
std::string FREAM_OUTER_BOTTOM = FRAME_OUTER + "BOTTOM";
SDLSceneNode * frame_outer_bottom_node = frame_background_node->createChildSceneNode(FREAM_OUTER_BOTTOM
, 0
, frame_background_rect.h - frame_outer_rect.h);
frame_outer_bottom_node->createAttachedEntity(FREAM_OUTER_BOTTOM, frame_outer_h_all);
//左
std::string FREAM_OUTER_LEFT = FRAME_OUTER + "LEFT";
SDLSceneNode * frame_outer_left_node = frame_background_node->createChildSceneNode(FREAM_OUTER_LEFT
, 0
, 0);
frame_outer_left_node->createAttachedEntity(FREAM_OUTER_LEFT, frame_outer_v_all);
//右
std::string FREAM_OUTER_RIGHT = FRAME_OUTER + "RIGHT";
SDLSceneNode *frame_outer_right_node = frame_background_node->createChildSceneNode(FREAM_OUTER_RIGHT
, frame_background_rect.w - frame_outer_rect.h
, 0);
frame_outer_right_node->createAttachedEntity(FREAM_OUTER_RIGHT, frame_outer_v_all);
//-----------------------------------------------------------------------------------------
//创建初始雷格
for(int row = 0; row < MINE_ROWS; row++)
{
for(int col = 0; col < MINE_COLS; col++)
{
std::string name = MINE_UNKNOWN + boost::lexical_cast<std::string>(row*MINE_COLS + col);
SDLSceneNode *node = frame_background_node->createChildSceneNode(name
, frame_outer_rect.h + col * (mine_unknown_rect.w + frame_inner_rect.h)
, frame_outer_rect.h + row * (mine_unknown_rect.h + frame_inner_rect.h));
node->createAttachedEntity(name, mine_unknown);
this->mines[row][col] = node;
}
}
//-----------------------------------------------------------------------------------------
//创建内部网格
SDLSurfacePtr frame_iner_h_all = SDL::transform()->flat(frame_inner_h
, frame_background_rect.w - frame_outer_rect.h*2
, frame_inner_rect.h);
SDLSurfacePtr frame_iner_v_all = SDL::transform()->flat(frame_inner_v
, frame_inner_rect.h
, frame_background_rect.h - frame_outer_rect.h*2);
for(int col = 1; col < MINE_COLS; col++)
{
std::string name = FRAME_INNER + "v" + boost::lexical_cast<std::string>(col);
SDLSceneNode *node = frame_background_node->createChildSceneNode(name
, frame_outer_rect.h + col * mine_unknown_rect.w + (col-1)*frame_inner_rect.h
, frame_outer_rect.h);
node->createAttachedEntity(name, frame_iner_v_all);
}
for(int row = 1; row < MINE_ROWS; row++)
{
std::string name = FRAME_INNER + "r" + boost::lexical_cast<std::string>(row);
SDLSceneNode *node = frame_background_node->createChildSceneNode(name
, frame_outer_rect.h
, frame_outer_rect.h + row * mine_unknown_rect.h + (row-1)*frame_inner_rect.h);
node->createAttachedEntity(name, frame_iner_h_all);
}
//-----------------------------------------------------------------------------------------
//创建上面的显示剩余雷数和所花时间数
//剩余雷数
int xStart = 0, yStart = 0;
SDLSurfacePtr mineAmountTitleSf = SDL::imageManager()->loadImageWithoutColor(MINE_AMOUNT, 255, 255, 255);
xStart = frame_background_node->getPosition().x;
yStart = (frame_background_node->getPosition().y - mineAmountTitleSf->value()->clip_rect.h)/2;
SDLSceneNode *mineAmountTitelNode = root->createChildSceneNode(MINE_AMOUNT, xStart, yStart);
mineAmountTitelNode->createAttachedEntity(MINE_AMOUNT, mineAmountTitleSf);
SDLFontPtr font = SDL::fontManager()->OpenFont(FONT, 24);
SDLSurfacePtr mineAmountSf = font->RenderUNICODEBlended("99", SDL::assistant()->makeColor(255, 0, 0));
xStart = mineAmountTitleSf->value()->clip_rect.w + frame_background_node->getPosition().x + 5;
yStart = (frame_background_node->getPosition().y - mineAmountSf->value()->clip_rect.h)/2;
this->mineAmountNode = root->createChildSceneNode(MINE_AMOUNT+boost::lexical_cast<std::string>(1), xStart, yStart);
mineAmountNode->createAttachedEntity(MINE_AMOUNT+boost::lexical_cast<std::string>(1), mineAmountSf);
//重启图标
SDLSurfacePtr restartSf = SDL::imageManager()->loadImageWithoutColor(RESTART, 255, 255, 255);
xStart = (screen->value()->clip_rect.w - restartSf->value()->clip_rect.w)/2;
yStart = (frame_background_node->getPosition().y - restartSf->value()->clip_rect.h)/2;
SDLSceneNode *restartNode = root->createChildSceneNode(RESTART, xStart, yStart);
restartNode->createAttachedEntity(RESTART, restartSf);
//计时器
SDLSurfacePtr timeCounterSf = font->RenderUNICODEBlended("000", SDL::assistant()->makeColor(255, 0, 0));
xStart = frame_background_node->getPosition().x + frame_background_rect.w - timeCounterSf->value()->clip_rect.w;
yStart = (frame_background_node->getPosition().y - timeCounterSf->value()->clip_rect.h)/2;
SDLSceneNode *timeCounterNode = root->createChildSceneNode(CLOCK+boost::lexical_cast<std::string>(1), xStart, yStart);
timeCounterNode->createAttachedEntity(CLOCK+boost::lexical_cast<std::string>(1), timeCounterSf);
SDLSurfacePtr clockSf = SDL::imageManager()->loadImageWithoutColor(CLOCK, 255, 255, 255);
xStart = frame_background_node->getPosition().x + frame_background_rect.w
- timeCounterSf->value()->clip_rect.w - clockSf->value()->clip_rect.w - 5;
yStart = (frame_background_node->getPosition().y - clockSf->value()->clip_rect.h)/2;
this->clockNode = root->createChildSceneNode(CLOCK, xStart, yStart);
clockNode->createAttachedEntity(CLOCK, clockSf);
//插旗
SDLSurfacePtr mine_flag = SDL::transform()->zoom(SDL::imageManager()->loadImage(MINE_FLAG), mine_unknown_rect.w, mine_unknown_rect.w);
this->mines[4][5]->getEntity()->setSurface(mine_flag);
this->mines[8][10]->getEntity()->setSurface(mine_flag);
this->mines[10][3]->getEntity()->setSurface(mine_flag);
//插问号
SDLSurfacePtr mine_doubt_src = SDL::imageManager()->loadImageWithoutColor(MINE_DOUBT, 255, 255, 255);
SDLSurfacePtr mine_doubt = SDL::video()->copySurface(mine_unknown);
SDL::video()->BlitSurface(mine_doubt_src, NULL, mine_doubt, NULL);
this->mines[8][5]->getEntity()->setSurface(mine_doubt);
this->mines[4][10]->getEntity()->setSurface(mine_doubt);
this->mines[5][3]->getEntity()->setSurface(mine_doubt);
//插0,1,2,3,4,5,6,7,8
SDLEntity *entity = this->mines[8][3]->getEntity();
entity->detachSceneNode();
delete entity;
SDLSurfacePtr mine_number_1 = SDL::imageManager()->loadImageWithoutColor(MINE_NUMBER_1, 255, 255, 255);
this->mines[8][6]->getEntity()->setSurface(mine_number_1);
SDLSurfacePtr mine_number_2 = SDL::imageManager()->loadImageWithoutColor(MINE_NUMBER_2, 255, 255, 255);
this->mines[9][10]->getEntity()->setSurface(mine_number_2);
SDLSurfacePtr mine_number_3 = SDL::imageManager()->loadImageWithoutColor(MINE_NUMBER_3, 255, 255, 255);
this->mines[2][3]->getEntity()->setSurface(mine_number_3);
SDLSurfacePtr mine_number_4 = SDL::imageManager()->loadImageWithoutColor(MINE_NUMBER_4, 255, 255, 255);
this->mines[5][6]->getEntity()->setSurface(mine_number_4);
SDLSurfacePtr mine_number_5 = SDL::imageManager()->loadImageWithoutColor(MINE_NUMBER_5, 255, 255, 255);
this->mines[12][10]->getEntity()->setSurface(mine_number_5);
SDLSurfacePtr mine_number_6 = SDL::imageManager()->loadImageWithoutColor(MINE_NUMBER_6, 255, 255, 255);
this->mines[15][3]->getEntity()->setSurface(mine_number_6);
SDLSurfacePtr mine_number_7 = SDL::imageManager()->loadImageWithoutColor(MINE_NUMBER_7, 255, 255, 255);
this->mines[13][12]->getEntity()->setSurface(mine_number_7);
SDLSurfacePtr mine_number_8 = SDL::imageManager()->loadImageWithoutColor(MINE_NUMBER_8, 255, 255, 255);
this->mines[11][15]->getEntity()->setSurface(mine_number_8);
}
附件中是工程的完整代码和图片,大家可以通过跟踪代码来了解这节的内容。