凡人修真3D(3)神翼
1.对于一些集成起来的通用配置表,如下面这样的:
还是上图吧
这种通用表,字段直接是value1,value2,value3,value4,value5,value6。
这是GameConfigData里面的类:
class CGodWingJinJieBase :public cdf::CRefShared { public: Message::Db::Tables::TGodWingConfig _godWingJinJieConfig; }; typedef cdf::CHandle<CGodWingJinJieBase> GodWingJinJieBasePtr;这是使用的代码:
if(!CGodWingConfigManager::instance()->getJinJieInfoConfig(playerGodWing.jinJieLevel, godWingJinJieConfig)) { CErrorCodeManager::throwException("Public_GameConfigDataError"); } //判断是否已拥有该皮肤 if(!godWingManager->isSkinExist(godWingJinJieConfig.value6, true)) { //添加皮肤 godWingManager->addGodWingSkin(godWingJinJieConfig.value6, "", gateEntity, player); //设置皮肤 godWingManager->setGodWingSkin(godWingJinJieConfig.value6, player, gateEntity); }
这样直接使用value6,以后每个人看到这里的代码,都要去数据库查一下,还得对照一下其他代码。非常麻烦。
在GameConfigData读取的时候应该进行封装,转换成可读取的字段,而不是直接使用。
修改后:
class CGodWingJinJieConfig :public cdf::CRefShared { public: int jie;//阶 int jiePropNum;//消耗进阶道具数量 int blessValue;//祝福值 int attributeId;//属性 int skillCode;//技能 int skinCode;//皮肤 }; typedef cdf::CHandle<CGodWingJinJieConfig> CGodWingJinJieConfigPtr; typedef std::map<int, CGodWingJinJieConfigPtr> MapCGodWingJinJieConfig;
Message::Db::Tables::SeqTGodWingConfig::const_iterator iter1 = tGodWingConfigs.begin(); for(; iter1 != tGodWingConfigs.end(); iter1++) { switch(iter1->type) { case 1://神翼升星的经验 { _starExpMap[iter1->value1][iter1->value2] = iter1->value3; } break; case 2://阶的数据 { CGodWingJinJieConfigPtr jinJenConfig = new CGodWingJinJieConfig(); jinJenConfig->jie = iter1->value1; jinJenConfig->jiePropNum = iter1->value2; jinJenConfig->blessValue = iter1->value3; jinJenConfig->attributeId = iter1->value4; jinJenConfig->skillCode = iter1->value5; jinJenConfig->skinCode = iter1->value6; _jinJieMap[iter1->value1] = jinJenConfig; } break;这样取出来的配置,别人可以直接使用。
做游戏的代码,一定要时刻准备随时让别人看,因为随时要改。
2.typedef相当于重新声明了一个结构,所以开头应该大写。
typedef std::map<int, GodWingJinJieConfigPtr> mapCGodWingJinJieInfoList; typedef std::map<int, GodWingExpSkinPtr> mapCGodWingSkinInfoList; typedef std::map<int, GodWingSkillPtr> mapCGodWingSkillInfoList; typedef std::map<int, Message::Game::SPlayerGodWingCritPrompt> mapJinJieExpInfoList;开头的m都应该改成M.
另外,对于这种别名,最好起名字的时候不要带有具体功能意思的名字,毕竟其他地方可以重用的。
3.对于拼音的命名,一个词的开头才大写。是词,不是字。
GodWingJinJieConfigPtr应该是
GodWingJinjieConfigPtr如果要用拼音的话。
4.除数不能为0
bool CGodWingConfigManager::getBlessingValue(int jinJieLevel, int blessingValue, bool& isLevelUp, Message::Game::SPlayerGodWingCritPrompt& cirtInfo) { MapCGodWingJinjieConfig::const_iterator iter1 = _jinJieMap.find(jinJieLevel); if(_jinJieMap.end() == iter1) { return false; } if (iter1->second->blessValue <= 0) { return false;//这里是我加的。 } //获取当前经验百分比 int blessPercent = blessingValue * 10000 / iter1->second->blessValue;//load配置的地方没检测,函数内也没有检测 //判断是否可以直接进阶 int rand = Common::CUtil::myRand(1, 10000); for(DictIntInt::const_reverse_iterator iter2 = _jinjieLevelUpMap.rbegin(); _jinjieLevelUpMap.rend() != iter2; ++iter2) {
5.函数不要用大写开头,类名,结构名都要用大写开头。属性不要用大写。
interface IGodWing { /* * 神翼培养接口 * @param type 1激活神翼系统,2神翼加经验 * @param amount 使用数量 * @exception --失败返回异常 使用异常代码 */ ["ami","amd"] void GodWingLevelUp(int type, int amount);//大写了 /* * 神翼进阶接口 * @param autoBuy 是否自动买 0不自动,1自动 * @param result 返回进阶结果 1进阶成功,2进阶失败 * @param jinJieValue 返回进阶值 * @exception --失败返回异常 使用异常代码 */ ["ami","amd"] void GodWingJinJie(int autoBuy, out int result, out int jinJieValue);//大写了 /* * 打开神翼系统接口 */ ["ami","amd"] void OpenGodWing();//<span style="font-family: Arial, Helvetica, sans-serif;">大写了</span> /* * 领取返还进阶石 */ ["ami","amd"] void getJinJieReward();居然还写到cdl里面了。。。。。。
6.保存数据库的时候如非必要,都是延时保存。immediatelyUpdate是立即更新的意思,一般使用changeFlag。
//保存数据库 getSaveInfo(ETPlayerGodWing).immediatelyUpdate = true; save(false);
7.使用json,有好处也有坏处。好处是比较方便,节省字段。
但是坏处也很明显,
1.多了很多额外的运算
2.极大增加调试的时候查看内存
3.因为不需要声明,所以也就不知道具体有多少字段在里面。
所以一般情况下,
1.json只是用来节省字段,保存数据库的时候才转化一下。
2.对于一些大的系统json的key值放在Common/Config中
3.对于一些小的地方,只是简单一个字符串就行。
4.不保存复杂的结构,太复杂的要考虑新建一个表。
5.如果实在要保存复杂的内容,先转成string,然后保存到json中,登陆的时候,转成各自的结构,要保存的时候才转成字符串。
6.key中要写明是哪个数据类型,value一般只是基础数据类型。
总结得不好,其实我觉得最大问题是,根本没人知道你里面保存的是什么。
bool GateApp::CGodWingManager::isSkinExist(int skinCode, int flag) { Json::Value json; json.parse(_playerGodWing.skinJsStr); if(flag) { for(Json::Value::iterator iter = json[ALL_SKIN_KEY].begin(); json[ALL_SKIN_KEY].end() != iter; ++iter) { if((*iter).isInt()) { if(skinCode == (*iter).asInt()) { return true; } } } } else { for(Json::Value::iterator iter = json[LIMIT_SKIN_KEY].begin(); json[LIMIT_SKIN_KEY].end() != iter; ++iter) { if(!iter.key().isNull()) { std::string temp = iter.key().asString(); if(skinCode == atoi(temp.c_str())) { return true; } } } } return false; }像这种以后不要再用了。
8.playerExtend和playerExtend2保存
CPlayerHelper::updatePlayerExtend(gateEntity, player);//用这个 player->getSaveInfo(ETPlayerExtend).changeFlag = true;//不要这样写
9.自己写的功能,要每一句代码都调试过才行。
void GateApp::CItemExtend::useProp( CGateEntityPtr gateEntity, CPlayerPtr player, ::GateApp::Bag::CBagPtr bag, const CPlayerItemPtr& playerItem, Common::CBaseItemPtr item, int useAmount, const ::Message::Public::SeqString& values ) { switch( item->getTItem().type ) { case EPropMountDan: { CItemExtend::useMountAttributeDan(gateEntity, bag, playerItem, item, useAmount); } break; case EPropGodWingDan: { CItemExtend::useMountAttributeDan(gateEntity, bag, playerItem, item, useAmount);//这里明显是坐骑的丹药。。。 } break;我们做游戏的流程,测试人员只是负责做黑盒测试,因为非技术人员的员工,关注点一般都在客户端。而服务端的bug除非很明显,当然这个是很明显的。否则是测不到的,很多情况下要靠自己的编码习惯来规避一些低级错误。
10.商品价格必须从t_shop_shell中获得,
//判断元宝是否足够 player->enoughMoneyException(EPriceUnitEMoney, cost * oweAmount, updateCode);//商品价格和单位必须从t_shop_shell中获得, //购买道具 CShopManager::instance()->buyItem(gateEntity, SHOP_CODE_AUTO_BUY, itemCode, oweAmount, updateCode);//这里根本不需要购买道具,而是在下面直接扣钱不能自己找个地方配置。
使用商品的时候必须使用t_shop_shell的unit和amount。
商店打折活动肯定会有的。这个必须要注意。
11.对于bool的内容,做判断的时候不要再==true或者==false了。
tlogGodWing.oldLevel = (true == flag) ? playerGodWing.jinJieLevel - 1 : playerGodWing.jinJieLevel; tlogGodWing.newLevel = playerGodWing.jinJieLevel; tlogGodWing.newExp = playerGodWing.blessingValue + playerGodWing.limitBlessingValue; tlogGodWing.oldExp = oldBless; tlogGodWing.addExp = (true == isLevelUp) ? totalExp - oldBless : exp;
12.在一般的消耗某种道具,然后获得某种属性的流程中,要先判断所有可能抛错误码的情况,然后再去执行扣除道具,最后是获得东西。
按具体的例子来说吧:
在神翼进阶的流程中,先判断了是否有当前配置,然后根据道具为空判断是否为最大阶数。其实是没必要的。下一个阶的配置是无论如何都要获取的。
//获取进阶信息 CGodWingJinjieConfigPtr jinjieConfig = CGodWingConfigManager::instance()->getJinJieConfig(playerGodWing.jinJieLevel); if (!jinjieConfig) { CErrorCodeManager::throwException("Public_GameConfigDataError"); } //判断是否最高阶数 if (0 == jinjieConfig->jiePropNum)//不消耗道具,是最大阶 { CErrorCodeManager::throwException("Public_jinJieMaxLevel"); }
这是后面,消耗道具之后,再去获取下一阶的配置。如果这里出现问题,之前计算的内容,都白费了,而且还白扣钱了。
CGodWingJinjieConfigPtr nextJinjieConfig = CGodWingConfigManager::instance()->getJinJieConfig(playerGodWing.jinJieLevel + 1); if (!nextJinjieConfig) { CErrorCodeManager::throwException("Public_jinJieMaxLevel"); }这里可以考虑一个问题。在程序运行过程中,极小概率出现意想不到的情况,是先扣除玩家道具呢?还是先给玩家属性?
按理说,玩家处于弱势,扣了钱,不给属性,非常亏,一怒之下可能都不想玩了。好像我们应该优先为玩家考虑的样子。
但是实际情况是,市面上存在各种各样的外挂以及各种各样想占便宜的玩家。如果玩家发现了bug,而这个bug对自己有利,可以不劳而获,即使是极小概率,他们也会去研究,将这种特殊情况出现的概率大大提高,甚至每次都出现。如果是玩家自己发现的还好,可能他自己偷偷刷bug,我们发现了就处理了,没发现也只是影响一个服的问题。但是如果是被外挂利用了,可能在极短时间内,被无数人疯狂刷,这个时候很可能我们就得回档,即使没这么惨,也得各种补偿,对游戏的生命周期大大影响。而且非常打击正常玩家的信心。
说多了,总之是先扣钱,再给属性。
在写代码的时候要有意识,先把可能抛错的放前面,到了扣物品扣钱的那一步开始,通常就不能再去抛错了。
13.还是那一句,起名是非常重要的,关乎后来者能不能正确看你的代码。
这是神翼进阶的代码,同一个函数内的。
int totalExp = jinjieConfig->blessValue; int totalexp = playerGodWing.blessingValue + playerGodWing.limitBlessingValue;
14.不要在一个语句里面做太多的计算。
其实我是想说三目运算符?:
tlogGodWing.addExp = ( isLevelUp) ? totalExp - oldBless : exp;出现这种情况,通常都是上面已经分成很多分支了,定义个addExp,在其中复制就好了。可以代码更加清晰。
15.客户端调用接口的时候,一些不是很紧要的代码,可以放在Response之后。
GodWingJinJieCB->cdeResponse(isLevelUp, exp); //for log Message::Db::Tables::TLogGodWing tlogGodWing; tlogGodWing.__init(); tlogGodWing.userName = player->getTPlayer().username;就像这样,日志的保存,放在response之后。
16.bool不要和int混用
//判断皮肤是否已拥有,flag为true检测永久卡中是否已拥有该卡,false检测限时卡中是否已拥有该卡 bool isSkinExist(int skinCode, int flag);一般情况下是不会有问题的,但是如果出问题的时候,只能呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵了,碰到过,非常难找。尽量不要用默认类型转换。这相当于削弱了C++强类型的属性,变成弱类型,就会像客户端的Lua一样,非常多bug,而且很难修的。
17.检测限时的皮肤的时候,要判断一下时间是否过期了。虽然已经有每分钟检测的定时器,但是出于严谨,还是要检测一下。
bool GateApp::CGodWingManager::isSkinExist(int skinCode, bool isForever) { if (isForever) { for (SeqInt::iterator iter = _foreverSkins.begin(); iter != _foreverSkins.end(); iter ++) { if (skinCode == *iter) { return true; } } } else { DictIntInt::iterator iter = _limitSkinMap.find(skinCode); if (iter != _limitSkinMap.end()) { cdf::CDateTime now; if (iter->second > now.getTotalSecond())//-----这个检测是我加的,本来没有的---------------- { return true;//还没过期 } } } return false; }
18.对于穿戴皮肤,传0过来就是脱掉了。没必要弄多一个接口。语义上也是一样的,分出来很多余。
void ::Message::Game::IGodWingImpl::setGodWingSkin_async(const ::Message::Game::AMD_IGodWing_setGodWingSkinPtr& setGodWingSkinCB, int skinCode, const ::cde::CContext& context)下面这个是多余的。
void ::Message::Game::IGodWingImpl::takeOffGodWingSkin_async(const ::Message::Game::AMD_IGodWing_takeOffGodWingSkinPtr& takeOffGodWingSkinCB, const ::cde::CContext& context)
19.有些函数里面会抛异常,在外面就不用再去抛了。
GateApp::Bag::CBagPtr roleBag = CBagHelper::getBag(gateEntity, EPlayerItemPosTypeRole); if(!roleBag) { CErrorCodeManager::throwException("ErrorGate_BagNotExist"); }实际上函数里面已经抛异常了。。。
GateApp::Bag::CBagPtr GateApp::CBagHelper::getBag( const CGateEntityPtr& gateEntity, const ::Message::Public::EPlayerItemPosType& type, bool isException /*= true*/ ) { ::GateApp::Bag::CBagSystemPtr bagSystem = getBagSystem(gateEntity, isException); if (bagSystem == NULL) { return NULL; } GateApp::Bag::CBagPtr bag = bagSystem->getBag( type ); if ( ! bag && isException ) { Common::CErrorCodeManager::throwException( "ErrorGate_BagNotExist" ); } return bag; }
20.对于某些属性,过了某个时间失效的情况。设个定时器,到了这个时间点,就把所有的都改掉。这是策划的思想,而不是程序员的做法。
int CPlayerGodWingTimer::handleTimeout(const cdf::CDateTime ¤tTime, const void *act) { CGateEntityPtr gateEntity = CGateEntityManager::instance()->findGateEntity( _entityId ); if (!gateEntity) { return 0; } CPlayerPtr player = CPlayerPtr::dynamicCast(gateEntity->getComponent(ECOMPONENT_TYPE_PLAYER)); if(!player) { return 0; } CGodWingManagerPtr godWingManager = CGodWingManagerPtr::dynamicCast(gateEntity->getComponent(ECOMPONENT_TYPE_PLAYER_GODWING)); if(!godWingManager) { return 0; } //等级判断 if (player->isFunctionOpen("GodWing")) { //清空限时祝福值 godWingManager->checkLimitBless(player); } gateEntity->removeTimer(ETIMER_TYPE_PLAYER_GOD_WING_TIMER); CPlayerGodWingTimerPtr timer = new CPlayerGodWingTimer(); timer->_entityId = _entityId; cdf::CReactor::instance()->schedule(timer, NULL, cdf::CInterval(1, 5, 0, 0, 0), 0); gateEntity->addTimer(ETIMER_TYPE_PLAYER_GOD_WING_TIMER, timer); return 0; }
定时器是个很方便的东西,但是用多了可能造成负担。而且这种限定时间,对所有玩家都有效的情况。可能会造成集中时间爆发大量计算。那个时刻会很卡。
而且五点钟,应该是很少人的时候,系统一般都进行一些比较耗时的操作,更新升级维护备份什么的。说多了,就是尽量不要用定时器。
正确的做法是在用到的时候,检测一下,发现时间过时了,就更新一下。
void ::Message::Game::IGodWingImpl::godWingJinJie_async(const ::Message::Game::AMD_IGodWing_godWingJinJiePtr& GodWingJinJieCB, int autoBuy, const ::cde::CContext& context) { CGateEntityPtr gateEntity; CPlayerPtr player; CGateHelper::getPlayerAndEntity(context, gateEntity, player); //参数检查 CCheckInput::validValue(autoBuy, 0, 1); CGodWingManagerPtr godWingManager = CGodWingManagerPtr::dynamicCast(gateEntity->getComponent(ECOMPONENT_TYPE_PLAYER_GODWING)); if(!godWingManager) { CErrorCodeManager::throwException("Public_NullPointer"); } godWingManager->checkLimitBless(player);
makeClientGodWingInfo函数里面也检测一下就够了。
21.虽然我们做的不是网站,但是也要有MVC的概念。发送给客户端的信息应该相对独立。不能从发送给客户端的消息中触发属性的计算。假如没发到呢,难道属性就不用算了?
void GateApp::CGodWingManager::makeClientGodWingInfo(Message::Game::SPlayerGodWingInfo& playerGodWing, CPlayerPtr& player, CGateEntityPtr& gateEntity) { 。。。此处省略。。。 if(_isNeedcalc) { getGodWingAllAttribute(gateEntity, player, playerGodWing.attributeInfo); getGodWingBaseAttribute(playerGodWing.baseAttribute); _allAttribute = playerGodWing.attributeInfo; _baseAttribute = playerGodWing.baseAttribute; _isNeedcalc = false; } else { playerGodWing.attributeInfo = _allAttribute; playerGodWing.baseAttribute = _baseAttribute; } }
22.过度依赖json,写出来的代码,调试的时候根本没法看到内存。
void GateApp::CGodWingManager::getActivedSkinEquipSkills(int skinCode, Message::Public::DictIntInt& skillList) { std::string temp1 = _activeSkinSkillJson[ToStr(skinCode)].asString(); if(temp1.empty()) { return; } Json::Value js; js.parse(temp1); std::string temp2 = js[ACTIVE_EQUIT_SKILL_LIST].asString(); if(temp2.empty()) { return; } std::vector<std::string> VecSkill; cdf::CStrFun::split(VecSkill, temp2.c_str(), ';'); for(std::vector<std::string>::const_iterator it = VecSkill.begin(); VecSkill.end() != it; ++it) { std::vector<std::string> temp; cdf::CStrFun::split(temp, it->c_str(), ','); if(2 != temp.size()) { continue; } skillList[atoi(temp[0].c_str())] = atoi(temp[1].c_str()); } }一层套一层的,无法看。如此复杂的结构,应该新建一个表来保存内容。
后台php统计的时候用到json也是很麻烦的,这样最终还是拖慢整个计算的速度。