一个单机棋盘式半即时解谜RPG的开发与反思、3

综述

游戏核心部分目前完成了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加载游戏。
在游戏加载时GPackageGMapGItem是同一层级的,不具备树状关系,它们Json中的Name字段也是全局唯一的,即便在不同包之间也必须可以通过Name来进行精确的定位。游戏中有一些不同的GManager,例如GToolManager用来管理游戏中的道具,只要Tool被载入后(即便来自不同包),他们也会通过同一个GToolManager来管理,因此Name的唯一性十分重要。开发过程中Name具有命名规范,大约为包名-种类名-Item名等,不再详述。这样做的好处是逻辑上的松耦合,Item和Map以及Package没有关联,减少逻辑复杂性。
GGameLoader在需要的时候从Json加载数据,生成游戏数据,把它们交给GManager管理,GGameLoader不会生成数据的实例。
GInstance是个基类,被实现为GItemInstanceGMapInstance等,实际上目前大部分是typedef。关于数据和实例的关系在第一篇文章中介绍过了。
UI部分负责数据的显示和相应操作,数据的实例化是在UI中实现的,例如当玩家创建地图开始游戏时,UI层会创建一个GMapInstanceGMapInstance会创建它内部的GItemInstanceGMapInstance首先通过RJCBLib::Matrix管理GMapTile,直接地把地图块当做矩阵管理,每一个块有地形、道具、陷阱、墙等实例。LMap是一个继承自cocos2d::Layer的Map UI,LMap下面有一些LMapPlane同样继承自cocos2d::Layer,在GMapInstance中实例是通过GMapTile作为矩阵被管理的,到了UI层这些实例又被打散为一层一层的被LMapPlane管理。这样就可以按层调整显示顺序,如地形在最后面,然后是陷阱、道具、角色、墙等。但是这样不利于逻辑的控制,所以UI部分被实现得尽量透明,开发者只要通过GMapInstance中的Matrix来管理GMapTile就可以,对象的移动会自动的反应到UI上。
GItemGInstance继承自GObjectGObject继承自RJCBLib::LuaObjectRJCBLib::KVObjectGInstance还继承了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::LuaObjectRJCBLib::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继承自GObjectGInstance继承自GObjectNoManaged,这样做是因为GObjectcocos2d::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有两个数据集:datadataList,对于地形等使用矩阵data管理,对于稀疏数据如角色、墙使用dataList管理。
afterDataSeted()在数据设置完毕时被执行,对数据进行定位等操作,将数据显示到UI上(UI透明性)。

小结

工程进展目前(2016-3-7)如此,日后在适当的时候我会继续这个系列的写作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值