RPG游戏制作-01-搭建游戏框架,初进游戏世界

本游戏为我个人的毕业设计,开贴旨在用来用来记录。个人写的角色扮演游戏(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都为单例类,且为了保证其功能的单一性,这两个类都是只与数据相关,都不包含与渲染相关的功能。

本节运行界面如下:

RPG游戏制作-01-搭建游戏框架,初进游戏世界_第1张图片

本节代码:链接:https://pan.baidu.com/s/1MiB-b5AZUlDUlpHypxklxg 密码:z529

 

你可能感兴趣的:(SDL_Engine)