代码风格请尽量统一,写下来的代码不是自己的,是整个团队的。说不定某天也要看别人的代码,或者某个新来的要看我们的代码。说的不好听,项目时间一长,就会出现人员流动,如果每个人都写一个风格,就会很难看了。
很多问题还是要提早修正,否则后面东西太多,特殊处理太多,牵一发而动全身,要花更多的时间。
一些功能性、业务逻辑上的东西我就不修改了,这样相当于重写了。只是单纯从技术上进行优化,以后有空再重构。
1.在写配置文件的时候,尽量用map代替vector,因为查找某条配置的时候,可以减少运算。有的地方,set可以代替vector会更好
例如:
优化前:
bool GameConfigData::CMountConfigManager::getMountConfig(int code, Message::Db::Tables::TMount& tMount)
{
for (Message::Db::Tables::SeqTMount::iterator iter = _tMounts.begin();
iter != _mountMap.end();
iter ++)
{
if (iter->code == code)
{
tMount == *iter;
return true;
}
}
return false;
}
优化后:
bool GameConfigData::CMountConfigManager::getMountConfig(int code, Message::Db::Tables::TMount& tMount)
{
MapTMount::iterator iter = _mountMap.find(code);
if (iter == _mountMap.end())
{
return false;
}
tMount = iter->second;
return true;
}
可以用set替代vector的地方,set不会重复,而且可以用count函数来看是否存在,就不用下面这个循环了。
bool GateApp::CPlayer::isFunctionOpen(std::string functionName)
{
for (std::vector<std::string>::iterator it = _openFunction.begin(); it != _openFunction.end(); ++it)
{
if (functionName == *it)
{
return true;
}
}
return false;
}
优化后:
bool GateApp::CPlayer::isFunctionOpen(std::string functionName)
{
return _openFunction.find(functionName) != _openFunction.end();
}
例如:itemCode,skillCode,buffCode等等,不要只写code,难以阅读,而且代码增多之后很容易造成混乱。
3.建表的时候,主键在配置文件里尽量用code,在主库中保存的信息用id。含义:code表示编码,相当于一种定义;id表示具体的标识,对应着一个实例。
例如在配置里面技能的主键是skill_code。在主库里面,我们很多表的主键是没有实际意义的,直接写id就行了。有意义的例如t_player的player_id,t_guild的guild_id。
4.字符串拼接,一般使用“,”,如果要多一层,可以使用“;”。当然,有些配置的地方,使用了“[]”来包含一条数据,这样可以看的比较清晰。
有些符号是不适合作为分隔符的,例如:“:”
保存和读取,碰到时间CDateTime就会出错了。
//限时皮肤
{
std::string timingSkin = "";
for (std::map<int, cdf::CDateTime>::iterator it = _timingSkin.begin(); it != _timingSkin.end(); it++)
{
timingSkin += ToStr(it->first);
timingSkin += ":";
timingSkin += it->second.asString();
timingSkin += ",";
}
_playerMountJson[TIMING_MOUNT_KEY] = Json::Value(timingSkin);
}
Message::Public::SeqString mountVec;
cdf::CStrFun::split(mountVec, (*iter).c_str(), ':');
if (mountVec.size() == 2)
{
if (cdf::CStrFun::is_num(mountVec[0].c_str()))
{
int mountCode = cdf::CStrFun::str_to_int32(mountVec[0].c_str());
cdf::CDateTime limitDate;
limitDate.parse(mountVec[1].c_str(), "YYYY-MM-DD hh:mm:ss");
_timingSkin[mountCode] = limitDate;
}
}
datetime是的字符串里面是包含冒号的,这样分割,就会出错。
5.把时间保存到json的时候,使用整形保存秒数,或者字符串都可以。经过计算,使用整形来表示时间,因为从1970年开始算起,大概道2030年之后几年才会溢出。所以如果想方便,可以使用整形来保存秒数到json中。用int能够节省json的长度!
6.关于指针的问题,我们使用智能指针,但是智能指针并不是万能的,在星型结构(就是互相指引或者间接互相指引),如果不认为释放,可能会有内存泄露。所以,请尽量保持树形结构。
例如:在GateApp中,
GateEntity
Player Role Bag Mount 。。。。。
这些都是挂在GateEntity中,缺了什么,就从GateEntity中取出来。
像这样:
/*
* 获取背包中坐骑卡数量
* return 坐骑卡数量返回
* @param cardCode 坐骑卡Code
*/
int GateApp::CMountManager::getMountCardCount(int cardCode) const
{
assert(_bag);
return _bag->getItemCount(cardCode);
}
在Mount下面再挂一下Bag,实在是有很大隐患的。
7.不要觉得很简单的代码,不按照规范没所谓,如果是细枝末节的就算了,如果是在比较顶层的功能中,一定要严格按照规范。我们的需求是经常变动的。一个功能可能改十几二十次,现在比较少见,如果上线之后,效果不好,就会经常碰到了。
8.一些结构的typedef在CdlPublic中,另外,Common/Public/ComPublic.h写了一些cdl里面没有的,方便大家使用。
例如:
//set
typedef std::set<int> SetInt;
typedef std::set<long64_t> SetLong64;
typedef std::set<std::string> SetString;
//map
typedef std::map<int, SeqInt> MapSeqInt;
typedef std::map<long64_t, int> MapLongInt;
typedef std::map<int, long64_t> MapIntLong;
typedef std::map<int, MapIntLong> MapMapIntLong;
9.关于json,有好处也有坏处,json可以节省大量字段,但是调试的时候,如果要看内存的值,非常困难,而且使用json的计算量肯定会比较大的。
例如:
/*
* 检测玩家是否已拥有该坐骑
* return true为已拥有,false为未拥有
* @param mountCode 坐骑Code
*/
bool GateApp::CMountManager::isMountExist(int mountCode) const
{
for (Json::Value::const_iterator citer = _playerMountJson[ALL_MOUNT_KEY].begin();
citer != _playerMountJson[ALL_MOUNT_KEY].end(); ++citer)
{
if ((*citer).isInt())
{
if ((*citer).asInt() == mountCode)
{
return true;
}
}
}
return false;
}
这里面的内容基本上是无法查看的。
所以一般情况下,建议json只是保存某些数据,用来节省字段。另外用一个结构来保存在内存,保存数据库的时候再转换一下。
10.一些功能的检测、登陆特殊处理,必须等整体数据初始化之后才能使用。
void CPlayer::setTPlayerExtend2( const Message::Db::Tables::TPlayerExtend2& tPlayerExtend2 )
{
_tPlayerExtend2 = tPlayerExtend2;
_playerExtend2Json.parse(_tPlayerExtend2.jsStr);
if (!isFunctionOpen("OfflineHour"))//这里还没初始化!!!
{
_tPlayerExtend2.offlineHour = 0;
}
else
{
11.关于保存数据库的问题,很多功能,如果没有特殊要求,都是延时保存的。服务端最大的压力的地方。
如果要立刻保存,可以这样写,
getSaveInfo(ETPlayerMount).immediatelyUpdate= true;
但是这是不推荐的。所有GateEntity的component里面,都是默认300秒的延时保存的。
所以一般都这样写:
mountManager->getSaveInfo( ETPlayerMount ).changeFlag= true;
mountManager->save(false);//记得加上这个,否则没保存的
可以看一下这里的代码:
logout是下线的时候调用的,time是当前时间,intervalSeconds是延时保存的时间间隔
bool SSaveInfo::needToSave( bool logOut, const cdf::CDateTime& time, int intervalSeconds )
{
if ( immediatelyUpdate )//一些特殊的,非常非常重要的东西才立刻保存,用这个属性
{
return true;
}
if ( ! changeFlag )//一般情况下用这个属性。
{
return false;
}
if ( logOut )
{
return true;
}
#ifdef _DEBUG
{
intervalSeconds = 10;
}
#endif
if ( ( time - lastUpdateTime ).getTotalSeconds() > intervalSeconds )
{
return true;
}
return false;
}
记得调用save函数,否则不会触发保存的。
12.虽然代码顺序一般是不能更改,但是也应该保持独立性,尽量做到封装。
例如:
Message::Game::SCodeAttribute GateApp::CMountManager::getMountAttribute(int code)
{
Message::Game::SCodeAttribute sCodeAttribute;
Message::Db::Tables::TMount tMount;
if (CMountConfigManager::instance()->getMountConfig(code, tMount))
{
Message::Db::Tables::TAttribute attr;
CAttributeConfigManager::instance()->getAttribute(tMount.attributeId, attr);
//攻击、生命、物防、法防、穿透、格挡、命中、闪避、暴击、韧性
sCodeAttribute.attribute.push_back(attr.attack);
sCodeAttribute.attribute.push_back(attr.life);
sCodeAttribute.attribute.push_back(attr.physicalDefense);
sCodeAttribute.attribute.push_back(attr.magicalDefense);
sCodeAttribute.attribute.push_back(attr.wreck);
sCodeAttribute.attribute.push_back(attr.block);
sCodeAttribute.attribute.push_back(attr.miss);
sCodeAttribute.attribute.push_back(attr.demiss);
sCodeAttribute.attribute.push_back(attr.crit);
sCodeAttribute.attribute.push_back(attr.decrit);
}
sCodeAttribute.code = code;
return sCodeAttribute;
}
这种按顺序把这么多个属性放在一个数组里面,如果中间不小心插入或者删除一个,就会没救了。
vector是用来存放同样的东西,这个同样的东西,不是只数据类型上面,同样的整形,而是指业务逻辑上,同样的内容。这里面是八个不同的属性。调试起来也无法看了。
13.在涉及到客户端的cdl定义的时候,尽量考虑扩展性,因为我们自己改自己代码容易,让客户端改一下还是很麻烦的。
14.像某个功能的战斗力,这种服务端完全没有意义,不用保存,只是显示作用的,如果客户端能够获得数据,应该让客户端去做,而不是服务端计算好才传过去。用于显示的东西,应该是客户端做的,这是表现层的功能,而不应该服务端做了。而且服务端应该尽量减少计算,以降低负荷,这是有实际意义的。另外做的时候还是得看沟通,有时候如果确实不影响性能,是可以适当分担一下客户端的开发工作的。不过本质上,应该客户端去分担服务端的计算量是不变的。
15.任何输入都是不可信的。对于一个函数来说,输入的参数是不可信的,需要检测,当然,都是服务端的代码,大部分的时候不是那么严格。但是对于cdl定义的接口,客户端传过来的内容,是绝对不可信的,需要严格检测其合法性。这并不是说我们的客户端程序员的水平不行,而是因为市面上存在大量免费的外挂,不免费的可能也几块钱,用来刷我们的漏洞。被刷的漏洞,轻的影响是刷的很厉害,被其他玩家或者运营发现,我们修改回去。中度的影响是发现了问题,我们怎么也找不到是怎么刷的。更严重的是我们根本不知道被刷了。
所以要减低出错的可能,cdl定义的接口,可以尽可能减少传输的内容。
16.有时候为了方便,将一些数据配在t_const表里面,每次用到的时候都取出来。但是如果放进去的是一窜数字,最好还是在启动服务端的时候取出来,整理成合适的格式。
例如:
在坐骑培养的时候使用这个,其实代码没有什么大的问题,就是每次都用字符串解释成数字,多麻烦啊。
/*
* 坐骑培养暴击经验
* @return 暴击经验返回
*/
int GateApp::CMountManager::getFosterCritExp()
{
int mountCritExp = 0;
int haveRate = 0;
int oldRate = 0;
int randRate = ::Common::CUtil::myRand(1, 10000);
std::string critStr = CConstConfigManager::instance()->getConstValueStr("MountPropCritFoster");
int wakanFosterLv = CConstConfigManager::instance()->getConstValue("MountWakanFosterLevel");
if (wakanFosterLv > _tPlayerMount.fosterLevel)
{
critStr = CConstConfigManager::instance()->getConstValueStr("MountWakanCritFoster");
}
std::vector<std::string> critTemp;
cdf::CStrFun::split_ex(critTemp, critStr.c_str(), "[]");//每次都要转化,多麻烦啊
for (std::vector<std::string>::iterator iter = critTemp.begin(); iter != critTemp.end(); ++iter)
{
std::vector<std::string> critTemp1;
cdf::CStrFun::split(critTemp1, (*iter).c_str(), ',');
if (critTemp1.size() == 2)
{
oldRate = haveRate;
haveRate += atoi(critTemp1[1].c_str());
if (oldRate < randRate && randRate <= haveRate)
{
mountCritExp = atoi(critTemp1[0].c_str());//这里已经获得了,还不跳出循环,后面的运算都是多余的。
}
}
}
return mountCritExp;
}
修改一下:
//在load坐骑的时候,顺便转一下这些字符串
const std::string& pStr = CConstConfigManager::instance()->getConstValueStr("MountPropCritFoster");
Common::CUtil::changBracketsStrToMap(_propFosterExpMap, pStr);//使用道具的配置,使用这个函数可以把[]的格式的字符串转成map
const std::string& wStr = CConstConfigManager::instance()->getConstValueStr("MountWakanCritFoster");
Common::CUtil::changBracketsStrToMap(_wakanFosterExpMap, wStr);//使用灵力
_wakanFosterLevel = CConstConfigManager::instance()->getConstValue("MountWakanFosterLevel");
然后每次点击培养的时候,运行一下获得经验值,这里单纯配置的运算,可以放到配置的管理类里面。
int GameConfigData::CMountConfigManager::computeFosterExp(int fosterLevel)
{
int randRate = ::Common::CUtil::myRand(1, 10000);
Message::Public::DictIntInt expMap;
if (_wakanFosterLevel >= fosterLevel)
{
expMap = _wakanFosterExpMap;//使用灵力培养的配置
}
else
{
expMap = _propFosterExpMap;//使用道具培养的配置
}
for (DictIntInt::iterator iter = expMap.begin(); iter != expMap.end(); ++iter)
{
if (randRate <= iter->second)
{
return iter->first;//已经获得就直接返回
}
else
{
randRate -= iter->second;
}
}
return 1;
}
17.推送数据要尽量缩减,一个全局的数据,在登陆的时候推一次就行了。之后有更新的时候,一般而言,只推送更新的数据。而不是每次都推全部的数据。
Message::Game::SPlayerMount_Ptr clientMountMsg = new Message::Game::SPlayerMount();
mountManager->makeClientMountInfo(gateEntity, *clientMountMsg);
gateEntity->messageToClient(ECmdGateMountInfo, clientMountMsg);
这里的坐骑信息多,没有必要每次都推送,这样增大了推送的数据量,增大了出现网络丢包的概率。客户端收到数据之后,又要全局初始化,双方都要算更多。
18.对于一些升级的功能,每一级都有属性增加,例如坐骑,如果每级的属性都是直接配,以后每次计算的时候,都要加起来。看起来没什么。但是坐骑900个等级,每次改一下属性就要重新计算一下。。。。。。应该每一个当前等级都是一个总和,升级之后,直接用新的attribute_id就行了。
void GameConfigData::CMountConfigManager::getMountFosterConfigAttribute(int transTimes, int curFosterLevel, int& totalExp,
Message::Db::Tables::TAttribute& configAttribute)
{
MapSubType2MountConfig fosterConfig = _mapMountFosterConfig[transTimes];
//1.[0, curFosterLevel)经验,attribute属性
int toFosterLv = curFosterLevel;
if (curFosterLevel == _maxFosterLevel)
{
toFosterLv = _maxFosterLevel - 1;
}
for (int fosLv = 0; fosLv <= toFosterLv; fosLv++)//在这里,如果到10级就要循环10次了。
{
if (!fosterConfig[fosLv])
{
assert(false);
break;
}
//total experience
totalExp += fosterConfig[fosLv]->_tMountFoster.maxExp;
//[0, toFosterLv]的所有attributeId属性
Message::Db::Tables::TAttribute tempAttr;
CAttributeConfigManager::instance()->getAttribute(fosterConfig[fosLv]->_tMountFoster.attributeId, tempAttr);
configAttribute.attack += tempAttr.attack;
configAttribute.physicalDefense += tempAttr.physicalDefense;
configAttribute.magicalDefense += tempAttr.magicalDefense;
configAttribute.life += tempAttr.life;
configAttribute.crit += tempAttr.crit;
configAttribute.block += tempAttr.block;
configAttribute.demiss += tempAttr.demiss;
configAttribute.miss += tempAttr.miss;
configAttribute.decrit += tempAttr.decrit;
configAttribute.wreck += tempAttr.wreck;
configAttribute.moveSpeed += tempAttr.moveSpeed;
}
19.价格,是在指定了货币单位之后才有意义的。从商店里面取出来的物品,就要按配置的价格,不能因为自己知道现在是某种价格,就写死了某种价格。
例如:这个函数,只返回了需要的货币数量,没有返回货币价格,调用的时候是写死了。
/*
* 道具不足,获取培养所需的费用
* return 道具费用返回
* @param propCode 道具Code
* @param propNum 道具数量
*/
int GateApp::CMountManager::getFosterPropPrice(const CGateEntityPtr& gateEntity,
const CPlayerPtr& player,
int propCode,
int propNum)
{
int price = 0;
CShopPtr shop = CShopConfigManager::instance()->getShop(SHOP_CODE_AUTO_BUY);
if (shop)
{
CShopSellPtr shopSell = shop->getShopSell(propCode);
if (shopSell)
{
price = shopSell->_tShopSell.price;
}
}
if (price == 0)
{
CErrorCodeManager::throwException("ErrorGate_MountItemNotOnSale");
}
return price * propNum;
}
costMoney = mountManager->getFosterPropPrice(gateEntity, player, propCode, config->_tMountFoster.propNum2 - ownPropNum);
player->enoughMoneyException(EPriceUnitPoint, costMoney, updateCode);
这样就限制死了使用EPriceUnitPoint了,万一策划改了是不会通知你的。急着,策划就像女人一样,是善变的。
20.对于一些==0的检测,如果不会有负数,最好改成≤0。
if (moneyAmount == 0)
{
CErrorCodeManager::throwException("ErrorGate_MountItemNotOnSale");
}
例如这个购买商品,如果某个地方出错或者溢出了变成负数,这里就检测不出来了。
另外,很多最大值的检测也是改成≥比较好。
21.声明变量的时候,顺手初始化一下,要养成习惯。
int flushNum;
int leftSeconds;
mountManager->getPotentialLeftSeconds(flushNum, leftSeconds);
22.对于某些经常用到的“[]”结构的字符串,例如一些配置,最好在GameConfigData中,启动的时候就进行分解。不要每次用到的时候才分解。其他的情况也应该这样处理。写代码的时候,要把自己看成一台计算机,怎样才能让自己计算更少而达到相同的目的。东西不多,估计优化的效果也不大,但是整个游戏,所有功能,所有代码都这样,肯定会有问题的。
例如:坐骑计算皮肤属性的时候,每次都要分解这个字符串。
void GateApp::CMountManager::calSkinAttribute(
int mountCode,
Message::Public::DictIntInt& specSkinAdd,
Message::Public::DictIntInt& specWeaponAdd,
Message::Db::Tables::TPlayerRoleFightInfo& playerFightInfo)
{
Message::Db::Tables::TMount tMountConfig;
if (! CMountConfigManager::instance()->getMountConfig(mountCode, tMountConfig))
{
return;
}
SeqString vecTemp;
cdf::CStrFun::split_ex(vecTemp, tMountConfig.specialAddition.c_str(), "[]");
for (std::vector<std::string>::iterator iter = vecTemp.begin(); iter != vecTemp.end(); ++iter)
{
std::vector<std::string> vecAddition;
cdf::CStrFun::split(vecAddition, (*iter).c_str(), ',');
if (vecAddition.size() == 3)
{
//1跟2区分坐骑装备加成还是皮肤某一属性加成
if (atoi(vecAddition[0].c_str()) == 1)
{
specWeaponAdd[atoi(vecAddition[1].c_str())] = atoi(vecAddition[2].c_str());
}
else
{
specSkinAdd[atoi(vecAddition[1].c_str())] = atoi(vecAddition[2].c_str());
}
}
}
修改一下,放到MountConfigManager::loadCofngi()中去。
//TMount
Message::Db::Tables::SeqTMount mounts;
Message::Db::Tables::Loader::loadFile(mountConfigFile, mounts);
for (Message::Db::Tables::SeqTMount::iterator iter = mounts.begin();
iter != mounts.end();
iter ++)
{
CMountConfigPtr m = new CMountConfig();
m->_tMount = *iter;
SeqString vecTemp;
cdf::CStrFun::split_ex(vecTemp, iter->specialAddition.c_str(), "[]");
for (SeqString::iterator iter2 = vecTemp.begin(); iter2 != vecTemp.end(); ++iter2)
{
SeqString vecAddition;
cdf::CStrFun::split(vecAddition, iter2->c_str(), ',');
if (vecAddition.size() == 3)
{
CMountSpecialAdd sa;
sa.type = atoi(vecAddition[0].c_str());
sa.attrType = atoi(vecAddition[1].c_str());
sa.addPercent = atoi(vecAddition[2].c_str());
m->_specialAdds.push_back(sa);
}
}
_mountMap[iter->code] = m;
}
23.一些字段的命名的问题。这是个很蛋疼的问题,一方面我们要命名得尽量跟策划写的文档尽量相同,但是很多中文无法翻译到对应的英文的。另外,策划是善变的,更何况这些名称,一到上线的时候,就会变得更快。所以要自己把握,不要直接按照策划定义的名称来做。
例如:坐骑的配置表,按照策划写的来做,其实怎么看都看不懂。因为在我们的代码里面,type、subType好多都是类型、小类来定义了。贸然按照策划的命名,尤其是策划定义的英文,很有可能是跟我们一些常用的翻译,甚至是C++里面的关键字相悖。对代码阅读造成极大困难。
1) 星配置:坐骑共有0-14转(0-9),每转有10阶(1阶0星-10阶0星),每阶有5星(10阶没有5星,而是要转生),因此每转需要配置45星,t_mount_foster的type是转,sub_type是星级,config_type=1是培养进阶,config_type=2是转生
Type=0,sub_type=0,是0转1阶0星
Type=0,sub_type=1,是0转1阶1星
Type=0,sub_type=2,是0转1阶2星
Type=0,sub_type=3,是0转1阶3星
Type=0,sub_type=4,是0转1阶4星
Type=0,sub_type=5,是0转2阶0星(1阶5星满则升1阶,变为2阶0星,所以1阶5星=2阶0星)
以此类推……
24.关于Map的一些命名,直接上例子好了:
typedef std::map<int, CMountFosterConfigPtr> MapSubType2MountConfig;
这个别名,加了个subType,但是其他地方一样可以用到,可能key值不是subtype。
所以我的建议是
typedef std::map<int, CMountFosterConfigPtr> MapCMountFosterConfigPtr;
typedef std::map<int, MapCMountFosterConfigPtr> MapMapCMountFosterConfigPtr;
对于typedef的命名,脱离具体功能。在使用的时候,注释上具体的key和value
MapCMountFosterConfigPtr _mapMountTransConfig; //坐骑转生配置[转生次数,config]
MapMapCMountFosterConfigPtr _mapMountFosterConfig; //坐骑培养配置[阶[星,config]]
25.坐骑配置分散的问题,会导致后面高级的时候有一点变化就会造成循环几百次。对于这种情况,要从两个角度来考虑。
(1)在极短时间内,例如那1秒钟内,会不会造成计算量暴增,通常一些限制时间的功能,例如答题、帮派战等,将整个服的玩家堆积在一起,大家都对服务器发送相似的请求。计算量肯定会暴增的,这个时候可能会卡。这个现象会非常明显,玩家肯定会投诉,但是有明确修改方向,往往针对性优化一下,就会能够解决问题。
(2)在一个比较长的时间段里面,例如一整天,累积下来的总的时间量会不会很大。坐骑配置的问题就是这个,可能不会导致服务器卡,甚至开始的时候完全不会存在问题。但是越往后面,服务器会越来越卡,让你找问题也找不到,玩起来就是觉得卡,想优化,发现优化一个地方也不会有明显效果。这是慢性毒药,通常发现问题的时候就是病入膏肓,做什么都回天无力的感觉。
所以卡的问题,要有意识地避免。
void GameConfigData::CMountConfigManager::initFosterExpAndAttribute()
{
//计算每个培养等级的总属性以及总经验。
int lastExp = 0;//之前的所有等级的经验。
Message::Db::Tables::TAttribute lastAttr;//之前所有等级获得的属性
lastAttr.__init();
Message::Db::Tables::TAttribute thisAttr;//当前这条属性
for (MapCMountFosterConfigPtr::iterator iter = _transMap.begin();
iter != _transMap.end();
iter++)
{
MapCMountFosterConfigPtr& curFosterMap = _fosterMap[iter->first];//当前转的培养配置。
for (MapCMountFosterConfigPtr::iterator iter2 = curFosterMap.begin();
iter2 != curFosterMap.end();
iter2++)
{
//经验值
lastExp += iter2->second->_tMountFoster.maxExp;//
iter2->second->_currentMaxExp = lastExp;
//属性
if (!CAttributeConfigManager::instance()->getAttribute(iter2->second->_tMountFoster.attributeId, thisAttr))
{
continue;//当前配置没有属性
}
Common::CFightAlgorithm::addAttribute(lastAttr, thisAttr);//当前的加上之前的属性
iter2->second->_currentAttribute = lastAttr;//当前的总属性
}
//这是培养满了,没有转生的情况
//经验值
lastExp += iter->second->_tMountFoster.maxExp;
iter->second->_currentMaxExp = lastExp;
//转生前的总属性
if (!CAttributeConfigManager::instance()->getAttribute(iter->second->_tMountFoster.attributeId, thisAttr))
{
continue;//当前配置没有属性
}
Common::CFightAlgorithm::addAttribute(lastAttr, thisAttr);//当前的加上之前的属性
iter->second->_currentAttribute = lastAttr;//当前的总属性
}
}
这是坐骑属性的解决办法。本来真正的解决办法,是改配置的方式,让策划把东西算好。但是现在功能已经做完,客户端也是用到这个配置。这种涉及到客户端的问题,可能就比较难沟通。自己当时没想好的东西,要人家背锅,而且,你不知道客户端要改到什么程度。所以一般在功能完成了,再去优化,我们能做的事有限,往往要选择折衷的办法,最好还是在开干之前就考虑这方面的问题。这次的解决办法是在启动的时候,把所有等级的培养属性都算一遍。