本游戏为我个人的毕业设计,开贴旨在用来用来记录。个人写的角色扮演游戏(RPG)已经有了一个成品,但是个人感觉部分实现上不便于以后的扩展,故进行第二次重构。
本游戏基于SDL_Engine进行开发(个人模仿cocos2d-x开发,可以认为是简化版的cocos2d-x)。
目前先构建框架。本游戏采用MVC设计模式,GameScene为MVC中的Control控制器,主要起到负责全局的管理,逻辑处理,事件接收与分发调用函数等。并且为了便于以后lua脚本的接入,故GameScene为单例类。先看看代码吧。
GameScene.h
#ifndef __GameScene_H__
#define __GameScene_H__
#include
#include "SDL_Engine/SDL_Engine.h"
using namespace std;
USING_NS_SDL;
class MapLayer;
class GameScene : public Scene
{
private:
static GameScene* s_pInstance;
public:
static GameScene* getInstance();
static void purge();
private:
MapLayer* m_pMapLayer;
public:
static const int CHARACTER_LOCAL_Z_ORDER = 9999;//需要比tmx地图总图块大
private:
GameScene();
~GameScene();
bool init();
bool initializeMapAndPlayers();
public:
//改变场景
void changeMap(const string& mapName, const Point& tileCoodinate);
//设置视图中心点
void setViewPointCenter(const Point& position, float duration = 0.f);
};
#endif
目前的GameScene类中的内容还是比较少的,仅仅包含了一个MapLayer指针和CHARACTER_LOCAL_Z_ORDER,这个静态成员主要是为了处理角色和tmx地图的图块之间的遮挡关系的,它会在MapLayer类中和GameScene中的设置角色的localZOrder中等用到。
GameScene.cpp
#include "GameScene.h"
#include "MapLayer.h"
#include "StaticData.h"
#include "DynamicData.h"
//static
GameScene* GameScene::s_pInstance = nullptr;
GameScene* GameScene::getInstance()
{
//仅在场景在运行时才会分配新的对象
if (s_pInstance == nullptr && Director::getInstance()->isRunning())
{
s_pInstance = new GameScene();
s_pInstance->init();
}
return s_pInstance;
}
void GameScene::purge()
{
SDL_SAFE_RELEASE_NULL(s_pInstance);
}
GameScene::GameScene()
:m_pMapLayer(nullptr)
{
}
GameScene::~GameScene()
{
StaticData::purge();
DynamicData::purge();
}
bool GameScene::init()
{
m_pMapLayer = MapLayer::create();
this->addChild(m_pMapLayer);
//初始化地图和角色
this->initlizeMapAndPlayers();
return true;
}
bool GameScene::initializeMapAndPlayers()
{
//获取地图
auto dynamicData = DynamicData::getInstance();
//TODO:暂时使用存档1
dynamicData->initializeSaveData(1);
auto mapFilePath = dynamicData->getMapFilePath();
auto tileCooridinate = dynamicData->getTileCoordinateOfPlayer();
//改变地图
this->changeMap(mapFilePath, tileCooridinate);
return true;
}
void GameScene::changeMap(const string& mapName, const Point &tileCoodinate)
{
//改变当前地图
m_pMapLayer->clear();
m_pMapLayer->init(mapName);
//改变当前中心点
this->setViewPointCenter(tileCoodinate);
}
void GameScene::setViewPointCenter(const Point& position, float duration)
{
Size visibleSize = Director::getInstance()->getVisibleSize();
const int tag = 10;
//地图跟随点移动
float x = (float)MAX(position.x, visibleSize.width / 2);
float y = (float)MAX(position.y, visibleSize.height / 2);
//获取地图层的地图
auto tiledMap = m_pMapLayer->getTiledMap();
auto tileSize = tiledMap->getTileSize();
auto mapSize = tiledMap->getMapSize();
auto mapSizePixel = Size(tileSize.width * mapSize.width, tileSize.height * mapSize.height);
//不让显示区域超过地图的边界
x = (float)MIN(x, (mapSizePixel.width - visibleSize.width / 2.f));
y = (float)MIN(y, (mapSizePixel.height - visibleSize.height / 2.f));
//实际移动的位置
Point actualPosition = Point(x, y);
//屏幕中心位置坐标
Point centerOfView = Point(visibleSize.width / 2, visibleSize.height / 2);
Point delta = centerOfView - actualPosition;
Action* action = nullptr;
//地图运动
if (duration < FLT_EPSILON)
{
action = Place::create(delta);
}
else
{
action = MoveTo::create(duration, delta);
}
action->setTag(tag);
if (tiledMap->getActionByTag(tag) != nullptr)
{
tiledMap->stopActionByTag(tag);
}
tiledMap->runAction(action);
}
changeMap函数用的很是广泛,其主要功能就是起到切换地图的功能。需要注意的是,切换地图只是更换了MapLayer对象中的m_pTiledMap指针的指向,GameScene对象中的MapLayer对象是不变的。而setViewPointCenter函数的主要功能则是是让界面跟随角色进行移动,以保证角色能一直显示在屏幕中间,这个函数不依赖于任何一个角色,故其用途广泛。
MapLayer.h
#ifndef __MapLayer_H__
#define __MapLayer_H__
#include
#include "SDL_Engine/SDL_Engine.h"
using namespace std;
USING_NS_SDL;
class MapLayer : public Layer
{
private:
TMXTiledMap* m_pTiledMap;
//地图文件路径
string m_filepath;
public:
MapLayer();
~MapLayer();
CREATE_FUNC(MapLayer);
static MapLayer* create(const string& filepath);
bool init();
bool init(const string& filepath);
//清除
void clear();
//获取该地图的文件路径
string& getFilepath();
//获取地图名字
string getMapName() const;
//获取碰撞层
TMXLayer* getCollisionLayer() const;
//获取脚本对象
TMXObjectGroup* getScriptObjectGroup() const;
TMXTiledMap* getTiledMap() const;
private:
void resetLocalZOrderOfTile();
};
#endif
MapLayer.cpp
#include "MapLayer.h"
#include "StaticData.h"
#include "GameScene.h"
MapLayer::MapLayer()
:m_pTiledMap(nullptr)
{
}
MapLayer::~MapLayer()
{
}
MapLayer* MapLayer::create(const string& filepath)
{
auto layer = new MapLayer();
if (layer && layer->init(filepath))
layer->autorelease();
else
SDL_SAFE_DELETE(layer);
return layer;
}
bool MapLayer::init()
{
return true;
}
bool MapLayer::init(const string& filepath)
{
m_filepath = filepath;
m_pTiledMap = TMXTiledMap::create(filepath);
this->addChild(m_pTiledMap);
this->resetLocalZOrderOfTile();
return true;
}
void MapLayer::clear()
{
if (m_pTiledMap != nullptr)
{
m_pTiledMap->removeFromParent();
m_pTiledMap = nullptr;
}
}
string& MapLayer::getFilepath()
{
return m_filepath;
}
string MapLayer::getMapName() const
{
auto text = m_pTiledMap->getPropertyForName("name").asString();
return text;
}
TMXLayer* MapLayer::getCollisionLayer() const
{
auto layerName = STATIC_DATA_STRING("collision_layer_name");
return static_cast(m_pTiledMap->getLayer(layerName));
}
TMXObjectGroup* MapLayer::getScriptObjectGroup() const
{
auto layerName = STATIC_DATA_STRING("script_layer_name");
return m_pTiledMap->getObjectGroup(layerName);
}
TMXTiledMap* MapLayer::getTiledMap() const
{
return m_pTiledMap;
}
void MapLayer::resetLocalZOrderOfTile()
{
//保存所有优先级为2的图块
vector tileIds;
auto tilesets = m_pTiledMap->getTilesets();
for (auto tileset : tilesets)
{
auto& properties = tileset->getProperties();
for(auto itMap = properties.cbegin(); itMap != properties.cend(); itMap++)
{
auto id = itMap->first;
auto& valueMap= itMap->second;
auto it = valueMap.find("priority");
if (it != valueMap.cend() && it->second.asInt() == 2)
tileIds.push_back(id + tileset->firstGrid);
}
}
//设置优先级为2的图块localZOrder
auto& children = this->getCollisionLayer()->getChildren();
for (auto child : children)
{
//获取名字
auto name = child->getName();
if (name.empty())
continue;
int id = SDL_atoi(name.c_str());
//设置localZOrder
if (find(tileIds.begin(),tileIds.end(),id) != tileIds.end())
{
child->setLocalZOrder(child->getLocalZOrder() + GameScene::CHARACTER_LOCAL_Z_ORDER);
}
}
}
值得一提的是resetLocalZOrderOfTile函数,其主要功能就是获取m_tiledMap中引用的图块中的存在property属性,且为2的图块,并进行重新设置其localZOrder,以保证和角色的正确遮挡,比如如下4个图块
角色在是可以通过上面两个图块的,且角色应该被该树遮挡。这里规定,priority为0,则不可通过;为1则人物遮挡该图块;为2则人物被该图块遮挡。人物的localZOrder一般为CHARACTER_LOCAL_Z_ORDER,故一般情况下,人物的绘制会晚于优先级为0的图块,会早于优先级为2的图块(主要涉及到Node中的成员_localZOrder)。
接下来谈一谈MVC中的Model模型。先谈谈静态数据
StaticData.h
#ifndef __StaticData_H__
#define __StaticData_H__
#include
#include "SDL_Engine/SDL_Engine.h"
using namespace std;
USING_NS_SDL;
//定义一些常用的宏
#define STATIC_DATA_PATH "data/static_data.plist"
/*简化使用*/
#define STATIC_DATA_STRING(key) (StaticData::getInstance()->getValueForKey(key)->asString())
#define STATIC_DATA_INT(key) (StaticData::getInstance()->getValueForKey(key)->asInt())
#define STATIC_DATA_FLOAT(key) (StaticData::getInstance()->getValueForKey(key)->asFloat())
#define STATIC_DATA_BOOLEAN(key) (StaticData::getInstance()->getValueForKey(key)->asBool())
#define STATIC_DATA_POINT(key) (StaticData::getInstance()->getPointForKey(key))
#define STATIC_DATA_ARRAY(key) (StaticData::getInstance()->getValueForKey(key)->asValueVector())
#define STATIC_DATA_TOSTRING(key) (StaticData::getInstance()->toString(key))
class StaticData : public Object
{
private:
static StaticData* s_pInstance;
public:
static StaticData* getInstance();
static void purge();
private:
//键值对
ValueMap m_valueMap;
private:
StaticData();
~StaticData();
bool init();
public:
/**
@brief 根据键获取值
@key 要查询的键
@return 返回的值,如果不存在对应的值,则返回空Value
*/
Value* getValueForKey(const string& key);
};
#endif
StaticData.cpp
#include "StaticData.h"
StaticData* StaticData::s_pInstance = nullptr;
StaticData* StaticData::getInstance()
{
if (s_pInstance == nullptr)
{
s_pInstance = new StaticData();
s_pInstance->init();
}
return s_pInstance;
}
void StaticData::purge()
{
SDL_SAFE_RELEASE_NULL(s_pInstance);
}
StaticData::StaticData()
{
}
StaticData::~StaticData()
{
}
bool StaticData::init()
{
//读取文件并保存键值对
m_valueMap = FileUtils::getInstance()->getValueMapFromFile(STATIC_DATA_PATH);
return true;
}
Value* StaticData::getValueForKey(const string& key)
{
auto iter = m_valueMap.find(key);
if(iter != m_valueMap.end())
return &iter->second;
return nullptr;
}
静态数据即在程序运行期间不会发生改变的数据,其主要功能是提供一些外部文件的读取,比如static_data.plist文件的读取以及以后的角色升级属性文件等也由StaticData类提供。
DynamicData.h
#ifndef __DynamicData_H__
#define __DynamicData_H__
#include
#include "SDL_Engine/SDL_Engine.h"
using namespace std;
USING_NS_SDL;
class DynamicData : public Object
{
private:
static DynamicData* s_pInstance;
public:
static DynamicData* getInstance();
static void purge();
private:
DynamicData();
~DynamicData();
private:
//存档
ValueMap m_valueMap;
//存档名称
string m_fullpath;
//是否第一次进入游戏
bool m_bFirstGame;
//金币数目
int m_goldNumber;
//出售比例
float m_sellRatio;
public:
//读取存档
bool initializeSaveData(int idx);
//获取角色map
ValueMap& getValueMapOfCharacter();
//获取地图文件路径
string getMapFilePath() const;
//获取角色对应位置
Point getTileCoordinateOfPlayer() const;
};
#endif
DynamicData.cpp
#include "DynamicData.h"
DynamicData* DynamicData::s_pInstance = nullptr;
DynamicData* DynamicData::getInstance()
{
if (s_pInstance == nullptr)
{
s_pInstance = new DynamicData();
s_pInstance->init();
}
return s_pInstance;
}
void DynamicData::purge()
{
SDL_SAFE_RELEASE_NULL(s_pInstance);
}
DynamicData::DynamicData()
:m_bFirstGame(true)
,m_goldNumber(0)
,m_sellRatio(0.f)
{
}
DynamicData::~DynamicData()
{
}
bool DynamicData::initializeSaveData(int idx)
{
auto fileUtil = FileUtils::getInstance();
//获取存档路径
string path = fileUtil->getWritablePath();
//对应的存档完整路径
string filepath = m_fullpath = StringUtils::format("%ssave%d.plist", path.c_str(), idx);
//不存在对应存档,则使用默认存档
if ( !fileUtil->isFileExist(m_fullpath))
{
filepath = "data/default_data.plist";
m_bFirstGame = true;
}
else
m_bFirstGame = false;
//获得对应存档的键值对
m_valueMap = fileUtil->getValueMapFromFile(filepath);
//获取有用的数据
m_goldNumber = m_valueMap.at("gold").asInt();
m_sellRatio = m_valueMap.at("sell_ratio").asFloat();
//TODO:解析背包中的物品并生成Good
return true;
}
ValueMap& DynamicData::getValueMapOfCharacter()
{
return m_valueMap.at("character").asValueMap();
}
string DynamicData::getMapFilePath() const
{
return m_valueMap.at("map").asString();
}
Point DynamicData::getTileCoordinateOfPlayer() const
{
auto strPoint = m_valueMap.at("tile_position").asString();
auto position = PointFromString(strPoint);
return position;
}
DynamicData类主要功能就是负责动态数据的提供和更新,比如主角的金钱数目,主角所处的地图名和位置,背包物品以及主角的属性,穿戴的装备,技能(以后添加)等等。目前的DynamicData类主要就是负责读取存档。如果是第一次进行游戏的话,则使用默认存档,并解析出有用的属性,此为initializeSaveData函数所做的功能,且为了可扩展性,添加了一个idx形参,允许RPG游戏有不同的存档。
StaticData和DynamicData都为单例类,且为了保证其功能的单一性,这两个类都是只与数据相关,都不包含与渲染相关的功能。
本节运行界面如下:
本节代码:链接:https://pan.baidu.com/s/1MiB-b5AZUlDUlpHypxklxg 密码:z529