1. 程式人生 > >使用cocos2dx製作 幀同步 遊戲

使用cocos2dx製作 幀同步 遊戲

重構了之前的遊戲程式碼,尋思把遊戲做成可以網路聯機的遊戲,於是百度各種資源,想學習一下。

學習資料倒是很多,最終選擇使用跟 皇室戰爭 一樣的同步方式,進行幀同步。

具體資料自行百度。

主要記錄一下遇到的問題。

一、修改原始碼

由於使用的是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<ConmandData> m_conmandNewList;
 vector<ConmandData> m_conmandWaitForChackList;
 vector<ConmandData> 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<gameTime)
 {
  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<gameTime)
   {
    //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;
}