凡人修真3D(1)坐骑

        代码风格请尽量统一,写下来的代码不是自己的,是整个团队的。说不定某天也要看别人的代码,或者某个新来的要看我们的代码。说的不好听,项目时间一长,就会出现人员流动,如果每个人都写一个风格,就会很难看了。

        很多问题还是要提早修正,否则后面东西太多,特殊处理太多,牵一发而动全身,要花更多的时间。

        一些功能性、业务逻辑上的东西我就不修改了,这样相当于重写了。只是单纯从技术上进行优化,以后有空再重构。


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();
}




2.代码里面的code很多,物品,技能,buff,updateCode。。。。。几乎每个配置都有一个,最好在用到的时候加上各自的单词。

例如: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阶(10-100星),每阶有5星(10阶没有5星,而是要转生),因此每转需要配置45星,t_mount_fostertype是转sub_type是星级config_type=1是培养进阶,config_type=2是转生

Type=0sub_type=0010

Type=0sub_type=1011

Type=0sub_type=2012

Type=0sub_type=3013

Type=0sub_type=4014

Type=0sub_type=5020星(15星满则升1阶,变为20星,所以15=20星)

以此类推……


对了,顺便说一句,配置文件还是我们自己设计吧,策划给的只是建议和参考。


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;//当前的总属性 
	}
}
这是坐骑属性的解决办法。本来真正的解决办法,是改配置的方式,让策划把东西算好。但是现在功能已经做完,客户端也是用到这个配置。这种涉及到客户端的问题,可能就比较难沟通。自己当时没想好的东西,要人家背锅,而且,你不知道客户端要改到什么程度。所以一般在功能完成了,再去优化,我们能做的事有限,往往要选择折衷的办法,最好还是在开干之前就考虑这方面的问题。这次的解决办法是在启动的时候,把所有等级的培养属性都算一遍。

















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值