重构了之前的游戏代码,寻思把游戏做成可以网络联机的游戏,于是百度各种资源,想学习一下。
学习资料倒是很多,最终选择使用跟 皇室战争 一样的同步方式,进行帧同步。
具体资料自行百度。
主要记录一下遇到的问题。
一、修改源码
由于使用的是cocos2dx引擎,看源码,在cocos2dx中帧的时间长短是动态的,这样可以出现动作补偿,让画面更加流畅。
但是这个样子没法进行帧同步,所以必须规定cocos2dx的帧的时间长度,这样在游戏当中的action时间就是一致的了。
查看源码在 CCDirector中
voidDirector::drawScene()
..
if (!_paused)
{
_eventDispatcher->dispatchEvent(_eventBeforeUpdate);
_scheduler->update(_deltaTime);
_eventDispatcher->dispatchEvent(_eventAfterUpdate);
}
..
可以看到这个_deltaTime
跟时间长度有关,修改源码函数
voidDirector::calculateDeltaTime()
..
if (_nextDeltaTimeZero)
{
_deltaTime =0;
_nextDeltaTimeZero =false;
}
else
{
//fix time
_deltaTime =0.02f;
}
..
把时间改为固定值,自己计算,这个也就是
director->setAnimationInterval(1.0 /50);
这个数值。
这样,在游戏中相同的帧数,发生的事情就是一样的了。
二、自己的代码问题
1、在进行帧同步的时候,一定要统一随机数种子
正在游戏中为了每次产生的效果不同都要用
setSand(time(NULL));
来让随机数种子随机。但是这里不行,一定要统一种子,比如
setSand(999);
1、经过测试上面的随机数种子方法的确可以达到同步效果,但是!!
在不同平台不同机型上运行的时候,是不能同步的。
由于机型差异和内存差异,随机还是不同的,所以要自己写伪随机数!!
#ifndef __MyRandom__
#define __MyRandom__
//random
float myRandom0_1();
void setMySeed(unsigned int seed);
unsigned int getRandomTime();
#endif // !__MyRandom__
#include "MyRandom.h"
#define RANDOMMAX 0xff
#define RANDOMA 25
#define RANDOMB 13
unsigned int randomTime = 0;
unsigned int randomSeed = 0;
float myRandom0_1() {
randomTime++;
randomSeed = (randomSeed*RANDOMA + RANDOMB) % RANDOMMAX;
return float(randomSeed) / RANDOMMAX;
}
void setMySeed(unsigned int seed) {
randomTime = 0;
randomSeed = seed;
}
unsigned int getRandomTime()
{
return randomTime;
}
一个简易的伪随机数算法,用来应对不是很平均的随机数产生
2、一定要在相同位置使用CCRANDOM0_1();
3、代码里面的变量一定要初始化!!
4、粒子系统,一定要在相同位置使用,粒子系统会使用随机数。
三、进行实际时间同步
帧同步了,但是实际的时间上,肯定会有一个快一个慢,或者其他原因导致的时间差异。
这就需要在游戏中每隔一段时间检查一下目标与本地的时间差多少,如果对方比本地跑的快,本地就要加速,反之亦然。
这里,
Director::getInstance()->getScheduler()->setTimeScale(1.5f);
这个函数肯定不行,这个是更改帧的时间间隔来加速的。用了帧就没法同步了。
所以只能改
director->setAnimationInterval(1.0 / 50);
自己测试了一下,具体什么原因不清楚,更改这个东西的确能加速或者降速,但是会莫名其妙的被使用 随机数,估计是源码里面某个地方。
所以...继续查看源码
参考http://blog.csdn.net/zhanglongit/article/details/8553440
查看框架的循环
发现Application::setAnimationInterval(float interval);
这个函数负责设置不同平台的循环时间。
修改源码新增一个函数来调用这个函数。
于是 实现了同步。
四、不同平台的坑
在win32下,只要更改这个函数就可以修改没帧的时间。但是在其他平台就不适合了。
通过查看源码,发现在iOS平台,帧率是固定的,每秒60.
那个定时器之负责 每几帧来执行 框架的帧。
所以只能是每帧,每两帧,每三帧。
也就是1/60,1/30,1/20,这些帧率才能正常调整。所以在iOS平台,只能是走的快的降速。而不是走的慢的减速。
这里如果用cocos2dx引擎的帧减速 还是正常的,但是如果用加速 不同平台也会出现错误。
所以要用加速的话还是要自己修改引擎。
五、指令的同步
指令只能是一方发起指令,预计在当前帧后执行。比如当前是20帧,指令就是预计在30帧执行。把这个指令发送给对方。
对方收到这个指令,判断指令时间是不是在自己的未来,如果是,就也加入到指令列表,等待执行。如果指令时间已经过去,就计算一个新的时间,发指令发回去,告诉对方更新指令。但是如果对方已经执行此命令。。。。。
这里使用stepbystep的同步方式进行帧同步,具体原理参考百度,记录一下自己的代码。最开始的时候并没有想用这个方式,只是想只要同步指令,那么游戏就肯定同步了,但是要同步指令就只有这个方法才能同步,要不就会陷入无限发送指令,或者同步失败。
#ifndef __GameDataControler__
#define __GameDataControler__
#define CHACKTIME 7
#include "../Socket/SocketManager.h"
#include "../GameMap/ConmandControler.h"
class GameDataControler
{
public:
static GameDataControler* getInstance();
//exit
static void destroy();
GameDataControler();
~GameDataControler();
//
void reset();
//wait data
void waitData();
//net data
void synData(unsigned int gameTime);
//get fast tiem
unsigned int getFastTime();
private:
static GameDataControler* _g;
//Send Chack
void sendChack(unsigned int gameTime);
//chack conmand
void chackConmand(unsigned int gameTime);
//get conmand
void getConmand(unsigned int gameTime);
void onRecv(const char* data, int count, unsigned int gameTime);
//conmand list
vector m_conmandNewList;
vector m_conmandWaitForChackList;
vector m_conmandChackList;
void onDisconnect();
void onNewConnection();
unsigned int m_gameFastTime;
unsigned int m_chackTime;
unsigned int m_getChack;
unsigned int m_errorTime;
};
#endif // !__GameDataControler
#include "GameDataControler.h"
#include "../Utils/LogList.h"
#include "MyRandom.h"
#define ERRORTIME 3
GameDataControler* GameDataControler::_g = nullptr;
GameDataControler::GameDataControler()
{
reset();
}
GameDataControler::~GameDataControler()
{
SocketManager::getInstance()->exitSocket();
}
void GameDataControler::reset()
{
setMySeed(99);
m_errorTime = 0;
m_getChack = CHACKTIME;
m_chackTime = 0;
m_gameFastTime = 0;
m_conmandNewList.clear();
m_conmandWaitForChackList.clear();
m_conmandChackList.clear();
}
void GameDataControler::waitData()
{
getConmand(m_errorTime);
}
void GameDataControler::synData(unsigned int gameTime)
{
GameDataControler::getInstance()->getConmand(gameTime);
if (m_chackTime == CHACKTIME)
{
m_chackTime = 0;
GameDataControler::getInstance()->chackConmand(gameTime);
GameDataControler::getInstance()->sendChack(gameTime);
m_getChack = 0;
}
++m_chackTime;
}
unsigned int GameDataControler::getFastTime()
{
return m_gameFastTime;
}
void GameDataControler::sendChack(unsigned int gameTime)
{
int nextchackTime = gameTime + CHACKTIME;
ConmandData *data = new ConmandData[m_conmandNewList.size() + 1];
data[0].chack = CHACK;
data[0].time = gameTime;
data[0].camp = m_conmandNewList.size();
data[0].dataSize = sizeof(ConmandData);
MLOG(String::createWithFormat("\n Random:%f,RandomTime:%d", myRandom0_1(), getRandomTime())->getCString());
for (unsigned int i = 0; i < m_conmandNewList.size(); i++)
{
data[i + 1] = m_conmandNewList.at(i);
//save chack time
data[i + 1].camp = nextchackTime;
data[i + 1].chack = DATAERROR;
}
SocketManager::getInstance()->sendMessage((const char*)data, sizeof(ConmandData)*(m_conmandNewList.size() + 1));
delete[] data;
//push conmand in chack list
for (unsigned int i = 0; i < m_conmandNewList.size(); i++)
{
m_conmandWaitForChackList.push_back(m_conmandNewList.at(i));
}
m_conmandNewList.clear();
}
void GameDataControler::chackConmand(unsigned int gameTime)
{
if (m_conmandChackList.size() != 0)
{
unsigned int idx = m_conmandChackList.size();
if (m_conmandChackList.size()>m_conmandWaitForChackList.size())
{
idx = m_conmandWaitForChackList.size();
}
auto ite = m_conmandWaitForChackList.begin();
auto ite2 = m_conmandChackList.begin();
for (unsigned int i = 0; i < idx; i++)
{
if (ite2->camp <= gameTime)
{
if (ite->time == ite2->time)
{
if (ite->time < gameTime - m_chackTime + CHACKTIME)
{
ite->time = gameTime - m_chackTime + CHACKTIME + 1;
}
ConmandControler::getInstance()->gameConmand_addConmand((*ite));
m_conmandWaitForChackList.erase(ite);
m_conmandChackList.erase(ite2);
}
else
{
break;
}
}
}
}
//error
if (m_getChack {
m_errorTime++;
if (m_errorTime>ERRORTIME)
{
//pause wait
m_errorTime = gameTime;
CCGI()->gameConmand_wait();
}
}
}
void GameDataControler::getConmand(unsigned int gameTime)
{
auto msg = SocketManager::getInstance()->getMessage();
while (msg != nullptr)
{
switch (msg->getMsgType())
{
case NEW_CONNECTION:
onNewConnection();
break;
case DISCONNECT:
onDisconnect();
break;
case RECEIVE:
onRecv((const char*)msg->getMsgData()->getBytes(), msg->getMsgData()->getSize(), gameTime);
break;
default:
break;
}
CC_SAFE_DELETE(msg);
msg = SocketManager::getInstance()->getMessage();
}
}
void GameDataControler::onRecv(const char * data, int count, unsigned int gameTime)
{
ConmandData* charaData = (ConmandData*)data;
if (charaData->dataSize == sizeof(ConmandData))
{
string s;
switch (charaData->chack)
{
case DATABACK:
//error
m_conmandNewList.push_back(*charaData);
break;
case DATAERROR:
break;
case DATANEW:
//new conmand
if (charaData->time>gameTime + DELAYFRAME)
{
charaData->chack = DATABACK;
//send ok
SocketManager::getInstance()->sendMessage(data, count);
//ok
m_conmandNewList.push_back(*charaData);
}
else {
charaData->chack = DATABACK;
//send error
charaData->time = ConmandControler::getInstance()->getNextTime();
SocketManager::getInstance()->sendMessage(data, count);
m_conmandNewList.push_back(*charaData);
}
break;
case CHACK:
//speed
/*
#if (CC_TARGET_PLATFORM != CC_PLATFORM_WIN32)
#else//elif(CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
if (charaData->time>gameTime)
{
m_gameFastTime = charaData->time;
Director::getInstance()->resetAnimationInterval(1.0f / (50 + charaData->time - gameTime));
}
#endif
*/
if (charaData->time {
//slow
m_gameFastTime = gameTime + gameTime - charaData->time;
Director::getInstance()->resetAnimationInterval(0.04f);
}
//chack conmand
m_getChack = charaData->time + CHACKTIME;
if (charaData->camp != 0)
{
for (size_t i = 0; i < charaData->camp; i++)
{
m_conmandChackList.push_back(charaData[i + 1]);
}
}
if (m_errorTime>ERRORTIME)
{
//back
CCGI()->gameConmand_resume();
}
m_errorTime = 0;
break;
default:
break;
}
}
}
void GameDataControler::onDisconnect()
{
//pause wait
CCGI()->gameConmand_wait();
}
void GameDataControler::onNewConnection()
{
}
GameDataControler* GameDataControler::getInstance()
{
if (_g == nullptr)
{
_g = new GameDataControler;
}
return _g;
}
void GameDataControler::destroy()
{
if (_g != nullptr)
{
_g->reset();
delete _g;
_g = nullptr;
}
}
指令同步类,放到update()里面执行,就可以就行指令同步了
void SceneGameNet::update(float dt){
if (GameDataControler::getInstance()->getFastTime() == m_gameTime)
{
Director::getInstance()->resetAnimationInterval(0.02f);
}
GameDataControler::getInstance()->synData(m_gameTime);
ConmandControler::getInstance()->gameConmand_enConmand(m_gameTime);
++m_gameTime;
}