综述
游戏核心部分目前完成了Json数据载入、Lua调用、大地图的显示、玩家移动、墙壁阻断这些内容。结构为GBase(基础设施:数据类、实例类、宏定义)、Logic(主要逻辑实现)、UI(用户接口)。
GBase-helper: GGameLoader , GManager , GObject
GBase-data: GItem , GMap , GPackage
GBase-instance: GInstance , GItemInstance , GMapInstance , GRoleInstance , GWallInstance , GMapTile
Logic: Runtime , MapEventCallbacks , Logic
UI: LMap , LMapPlane , KeyMapping
GBase
GBase主要实现了数据、实例、管理器、游戏加载器四个部分。一个数据对应多个实例,管理器管理数据集,加载器从Json加载游戏。
在游戏加载时GPackage
、GMap
、GItem
是同一层级的,不具备树状关系,它们Json中的Name
字段也是全局唯一的,即便在不同包之间也必须可以通过Name
来进行精确的定位。游戏中有一些不同的GManager
,例如GToolManager
用来管理游戏中的道具,只要Tool被载入后(即便来自不同包),他们也会通过同一个GToolManager
来管理,因此Name
的唯一性十分重要。开发过程中Name
具有命名规范,大约为包名-种类名-Item名
等,不再详述。这样做的好处是逻辑上的松耦合,Item和Map以及Package没有关联,减少逻辑复杂性。
GGameLoader
在需要的时候从Json加载数据,生成游戏数据,把它们交给GManager
管理,GGameLoader
不会生成数据的实例。
GInstance
是个基类,被实现为GItemInstance
、GMapInstance
等,实际上目前大部分是typedef
。关于数据和实例的关系在第一篇文章中介绍过了。
UI部分负责数据的显示和相应操作,数据的实例化是在UI中实现的,例如当玩家创建地图开始游戏时,UI层会创建一个GMapInstance
,GMapInstance
会创建它内部的GItemInstance
。GMapInstance
首先通过RJCBLib::Matrix
管理GMapTile
,直接地把地图块当做矩阵管理,每一个块有地形、道具、陷阱、墙等实例。LMap是一个继承自cocos2d::Layer
的Map UI,LMap
下面有一些LMapPlane
同样继承自cocos2d::Layer
,在GMapInstance
中实例是通过GMapTile
作为矩阵被管理的,到了UI层这些实例又被打散为一层一层的被LMapPlane
管理。这样就可以按层调整显示顺序,如地形在最后面,然后是陷阱、道具、角色、墙等。但是这样不利于逻辑的控制,所以UI部分被实现得尽量透明,开发者只要通过GMapInstance
中的Matrix来管理GMapTile
就可以,对象的移动会自动的反应到UI上。
GItem
和GInstance
继承自GObject
,GObject
继承自RJCBLib::LuaObject
和RJCBLib::KVObject
,GInstance
还继承了cocos2d::Sprite
这样实力可以直接被显示在UI上。
实际上之前GInstance
并没有直接继承自cocos2d::Sprite
而是在UI层增加了一个叫NMapNode
的类,这个类做为实例和UI的接口,这样有利于保持逻辑上的隔离。但是经过实践发现NMapNode
本身没有新功能的实现,它仅仅是逻辑上的存在对实际使用没有帮助,而且增加了逻辑复杂性和操作的复杂性。在使用时要使用类似node->instance->getAttribute<int>("key")
的代码,体验很不好于是就将其删除了。
GObject
GObject
为游戏对象提供KV属性和Lua支持。
class GObjectNoManaged : public RJCBLib::LuaObject, public RJCBLib::KVObject
{
public:
//直接从KV定义中开启Lua支持,用于对Json文件的支持
void openLuaSupportFromKVObject(std::string packageName);
//通过文件路径开启Lua支持
void openLuaSupport(std::string luaScriptFilePath);
protected:
//存储这个对象的Lua文件路径
std::string luaScriptFilePath;
GObjectNoManaged();
virtual std::string getLuaScriptFilePath() override;
virtual std::string getUpdateFunctionName() override;
virtual std::string LuaObjectGetString(std::string key) override;
virtual int LuaObjectGetInt(std::string key) override;
virtual bool LuaObjectGetBool(std::string key) override;
virtual double LuaObjectGetDouble(std::string key) override;
virtual void LuaObjectSetString(std::string key, std::string value) override;
virtual void LuaObjectSetInt(std::string key, int value) override;
virtual void LuaObjectSetBool(std::string key, bool value) override;
virtual void LuaObjectSetDouble(std::string key, double value) override;
};
class GObject : public GObjectNoManaged, public cocos2d::Ref
{
};
结合上一篇文章对RJCBLib的介绍,GObject
继承了RJCBLib::LuaObject
和RJCBLib::KVObject
两个类,KVObject
可以自动读取rapidjson的数据,将它们作为KV存储起来,结合getLuaScriptFilePath()
和getUpdateFunctionName()
可以实现自动从Json获取Lua相关数据。GObjectNoManaged
是没有继承cocos2d::Ref
的版本,这样做是为了消除继承的二义性,后面会提到。
GItem
GItem仅仅继承自GObject,没有其他功能。
class GItem : public GObject
{
private:
GItem();
public:
static GItem* create();
};
Instance
GInstance继承自GObject,其保留一个对数据(GItem)的指针。
class GInstance : public GBase::GObjectNoManaged , public cocos2d::Sprite
{
private:
cocos2d::Vec2 toGamePosition;//MovingByUpdating中记录目标点
bool isMovingByUpdate;//标记是否处于MovingByUpdate中
bool shouldCallInstanceLuaUpdate;//这个实例要求调用Lua Update回调
bool shouldCallDataLuaUpdate;//这个实例对应的数据要求调用Lua Update回调
cocos2d::Vec2 gamePosition;//这个对象所在地图的格子坐标,应总是为整数
cocos2d::Vec2 gamePositionOffset;//这个对象实际坐标对gamePosition的位移,XY位对格子尺寸WH的系数
public:
GObject* data;//对应的数据
GMapInstance* iMap;//所在Map实例
bool isMoving;//移动中
private:
//将gamePosition的变化更新到Lua中
void updateLuaPosition();
//通过update实现move的函数
void updateMoveStep(float deltaTime);
protected:
GInstance();
public:
~GInstance();
bool init(GObject* data,GMapInstance* iMap = nullptr);
//获取属性,优先从实例获取,若不存在则从数据获取
template<typename T>
RJCBLib::FUNC_RETURN_TYPE getAttribute(std::string key, RJCBLib::GenericValue & out)
{
RJCBLib::FUNC_RETURN_TYPE_W ret;
ret = this->getValue<T>(key, out);
if (ret == RJCBLib::FUNC_RETURN_VALUE_SUCCESS)
return RJCBLib::FUNC_RETURN_VALUE_SUCCESS;
ret = this->data->getValue<T>(key, out);
return ret;
}
//设置属性,可选(toData)为实例或数据设置
template<typename T>
RJCBLib::FUNC_RETURN_TYPE setAttribute(std::string key, const RJCBLib::GenericValue& in,bool toData = false)
{
if (toData == true)
return this->data->setValue<T>(key, in);
return this->setValue<T>(key, in);
}
//GamePosition存取器
inline void setGamePositionAtt(const cocos2d::Vec2& pos)
{
gamePosition = pos;
updateLuaPosition();
}
//GamePosition存取器
inline cocos2d::Vec2 getGamePositionAtt()
{
return gamePosition;
}
//cocos2d update
virtual void update(float deltaTime) override;
//获取移动速度
double getSpeed();
//执行Lua回调,state - 回调名
void doLua(GBase::LUA_INTERFACE_TYPE state);
//设置这个对象的位置(游戏坐标)
void setGamePosition(const cocos2d::Vec2 & position, const cocos2d::Vec2 & positionOffset = cocos2d::Vec2::ZERO);
//使用cocos2d action移动
void moveTo(const cocos2d::Vec2 & to);
//使用update实现的移动,会触发Lua的Update回调(若注册了)
void moveToByUpdate(const cocos2d::Vec2 & to);
};
GInstance
还实现了移动、Lua事件的具体功能,getAttribut<>()
和setAttribute<>()
封装了RJCBLib::KVObject
的相关方法,因为玩家肯能对实例操作也可能对他背后的数据操作。例如一个道具小刀,它可能有自己的数据材质为木头,但是实例化后某一个小刀可能为钢铁材质,这样只需要将实例的材质改为钢铁,这样对实例getAttribute<>()
会优先返回实例属性,若不存在则返回数据属性,设置的时候可以选择是对实例还是数据操作。
移动没有使用cocos2d的MoveTo
动作而是自己通过Update
实现的移动,这样做是为了对代码提供更好的控制,例如在Update
每一帧可能会调用Lua事件,这时修改了对象的速度,新的速度就可以直接的产生作用。
GItem
继承自GObject
,GInstance
继承自GObjectNoManaged
,这样做是因为GObject
和cocos2d::Sprite
都继承自cocos2d::Ref
,如果GInstance
继承自GObject
,那么通过cocos2d::Ref
对其管理将造成二义性。
GManager
GManager
不能再简单了。
class GPackageManager : public RJCBLib::ObjectManager<GPackageManager, std::string, GPackage*>
{
};
class GMapManager : public RJCBLib::ObjectManager<GMapManager, std::string, GMap*>
{
};
class GItemManager :public RJCBLib::ObjectManager<GItemManager, std::string, GItem*>
{
};
GMapInstance
class GMapInstance : public GInstance
{
private:
GMapInstance();
public :
GMap* getDataMap();
RJCBLib::Matrix<GMapTile>* dataTiles;
virtual ~GMapInstance();
static GMapInstance* create();
bool init(std::string mapName, std::string packageName);
bool isPositionValid(int x, int y);
static void calcDirectionPosition(std::string direction, int inX, int inY, int &outX, int &outY);
};
GMapTile
class GMapTile : public RJCBLib::KVObject,public cocos2d::Ref
{
private:
GMapTile();
public:
static GMapTile* create();
GItemInstance *trap;
GItemInstance *tool;
GItemInstance *terrainBack;
GItemInstance *terrainFront;
GWallInstance *wallLeft;
GWallInstance *wallRight;
GWallInstance *wallTop;
GWallInstance *wallBottom;
GRoleInstance *role;
GMapInstance *iMap;
int x;
int y;
bool init(int y,int x,std::string terrainBackName,std::string terrainFrontName, std::string toolName, std::string trapName,GMapInstance* iMap);
//设置墙,会根据wallName创建新的墙,返回创建的墙
GWallInstance* setWall(GBase::TYPE_DIRECTION direction, std::string wallName);
//设置墙,使用传入的已有墙实例,返回这个实例
GWallInstance* setWall(GBase::TYPE_DIRECTION direction, GWallInstance* wallInstance);
//获取墙
GWallInstance* getWall(GBase::TYPE_DIRECTION direction);
void setRole(GRoleInstance *rolei);
GRoleInstance *getRole();
virtual ~GMapTile();
};
LMap
class LMap : public cocos2d::Layer
{
private:
LMap();
cocos2d::EventListenerKeyboard* eventListenerKeyboard;
public:
GBase::GMapInstance* iMap;
LMapTerrainPlane* terrainBackPlane;
LMapTerrainPlane* terrainFrontPlane;
LMapTrapPlane* trapPlane;
LMapToolPlane* toolPlane;
LMapWallPlane* wallPlane;
LMapRolePlane* rolePlane;
static LMap* create();
bool init(std::string mapName, std::string packageName);
void onKeyPressed(cocos2d::EventKeyboard::KeyCode keycode, cocos2d::Event *event);//implemented in Logic/MapEventCallbacks.cpp
virtual ~LMap();
};
LMapPlane
class LMapPlane :public cocos2d::Layer
{
protected:
LMapPlane();
public:
RJCBLib::Matrix<GBase::GInstance>* data;
cocos2d::Vector<GBase::GInstance*>* dataList;
GBase::GMapInstance* iMap;
LMap* lMap;
static LMapPlane* create();
bool init(LMap* lMap);
virtual void afterDataSeted();
virtual ~LMapPlane();
};
LMapPlane
有两个数据集:data
和dataList
,对于地形等使用矩阵data管理,对于稀疏数据如角色、墙使用dataList
管理。
afterDataSeted()
在数据设置完毕时被执行,对数据进行定位等操作,将数据显示到UI上(UI透明性)。
小结
工程进展目前(2016-3-7)如此,日后在适当的时候我会继续这个系列的写作。