2012-08-06 115 views
9

背景:我幫助開發多人遊戲,在C++中,使用標準的客戶端 - 服務器架構編寫的居多。服務器可以自行編譯,並且客戶端與服務器一起編譯,因此您可以託管遊戲。飼養代碼組織

問題

遊戲結合客戶端和服務器代碼到相同的類,並且這被開始是非常麻煩的。

例如,以下是一些小樣品,你可以在一個共同的類看:

// Server + client 
Point Ship::calcPosition() 
{ 
    // Do position calculations; actual (server) and predictive (client) 
} 

// Server only 
void Ship::explode() 
{ 
    // Communicate to the client that this ship has died 
} 

// Client only 
#ifndef SERVER_ONLY 
void Ship::renderExplosion() 
{ 
    // Renders explosion graphics and sound effects 
} 
#endif 

和標題:

class Ship 
{ 
    // Server + client 
    Point calcPosition(); 

    // Server only 
    void explode(); 

    // Client only 
    #ifndef SERVER_ONLY 
    void renderExplosion(); 
    #endif 
} 

正如你所看到的,在編譯的時候只有預處理器定義用於排除圖形和聲音代碼(這看起來很醜陋)。

問:

是一些保持代碼組織和清潔客戶端 - 服務器架構的最佳實踐是什麼?

謝謝!

編輯:使用良好的組織開源項目的例子,也歡迎:)

回答

2

定義了客戶端API的客戶端stub類。

定義實現了服務器的服務器類。

定義傳入消息映射到服務器調用服務器存根。

的stub類通過你使用什麼都協議除了代理的命令沒有實現到服務器。

現在,您可以更改協議,而無需更改設計。

使用圖書館像MACE-RPC自動生成從服務器API客戶端和服務器存根。

+1

你碰巧知道你的設計的一個例子嗎? – faffy 2012-08-23 20:45:10

3

我會考慮使用Strategy design pattern,因此您將擁有一個Ship類,它具有客戶端和服務器通用的功能,然後創建另一個類層次結構,稱爲ShipSpecifics,它將是Ship的一個屬性。該ShipSpecifics將裝有服務器或客戶端具體的派生類來創建和注入船舶。

它可能是這個樣子:

class ShipSpecifics 
{ 
    // create appropriate methods here, possibly virtual or pure virtual 
    // they must be common to both client and server 
}; 

class Ship 
{ 
public: 
    Ship() : specifics_(NULL) {} 

    Point calcPosition(); 
    // put more common methods/attributes here 

    ShipSpecifics *getSpecifics() { return specifics_; } 
    void setSpecifics(ShipSpecifics *s) { specifics_ = s; } 

private: 
    ShipSpecifics *specifics_; 
}; 

class ShipSpecificsClient : public ShipSpecifics 
{ 
    void renderExplosion(); 
    // more client stuff here 
}; 

class ShipSpecificsServer : public ShipSpecifics 
{ 
    void explode(); 
    // more server stuff here 
}; 

的類船舶和ShipSpecifics是常見的兩個客戶端和服務器的代碼庫,而ShipSpecificsServer和ShipSpecificsClient類顯然是在服務器和客戶端代碼庫分別。

的使用可能是類似以下內容:

// client usage 
int main(int argc, argv) 
{ 
    Ship *theShip = new Ship(); 
    ShipSpecificsClient *clientSpecifics = new ShipSpecificsClient(); 

    theShip->setSpecifics(clientSpecifics); 

    // everything else... 
} 

// server usage 
int main(int argc, argv) 
{ 
    Ship *theShip = new Ship(); 
    ShipSpecificsServer *serverSpecifics = new ShipSpecificsServer(); 

    theShip->setSpecifics(serverSpecifics); 

    // everything else... 
} 
1

爲什麼不採取簡單的方法?提供一個描述Ship類將做什麼的單頭,帶有註釋但沒有ifdefs。然後在ifdef中提供客戶端實現,就像您在問題中所做的一樣,但提供了一個替代的(空)實現集,當客戶端未被編譯時將使用該實現。

如果您清楚在您的意見和代碼結構,這種做法會更容易閱讀和理解比提出了更爲「複雜」的解決方案。

此方法還有一個優點,即如果共享代碼(此處爲calcPosition())需要在客戶端與服務器上採用稍微不同的執行路徑,並且客戶端代碼需要調用其他客戶端功能(請參閱下面的示例),您不會遇到構建複雜問題。

頁眉:

class Ship 
{ 
    // Server + client 
    Point calcPosition(); 

    // Server only 
    void explode(); 
    Point calcServerActualPosition(); 

    // Client only 
    void renderExplosion(); 
    Point calcClientPredicitedPosition(); 
} 

身體:

// Server + client 
Point Ship::calcPosition() 
{ 
    // Do position calculations; actual (server) and predictive  (client) 
    return isClient ? calcClientPredicitedPosition() : 
        calcServerActualPosition(); 
} 

// Server only 
void Ship::explode() 
{ 
    // Communicate to the client that this ship has died 
} 

Point Ship::calcServerActualPosition() 
{ 
    // Returns ship's official position 
} 


// Client only 
#ifndef SERVER_ONLY 

void Ship::renderExplosion() 
{ 
    // Renders explosion graphics and sound effects 
} 

Point Ship::calcClientPredicitedPosition() 
{ 
    // Returns client's predicted position 
} 

#else 

// Empty stubs for functions not used on server 
void Ship::renderExplosion()    { } 
Point Ship::calcClientPredicitedPosition() { return Point(); } 

#endif 

此代碼似乎相當的可讀性(除了由客戶介紹/只的#ifndef SERVER_ONLY位認知失調,可以解決不同的名稱),特別是如果整個應用程序重複該模式。

我看到的唯一缺點是,你將需要重複你的唯一客戶端函數簽名的兩倍,但如果你陷入困境,那將是顯而易見的,微不足道的修復,一旦你看到的編譯錯誤。

+1

那麼,這是最簡單的方法,到目前爲止,但它僅僅比已經存在的代碼不太麻煩。 – faffy 2012-08-09 15:14:39