標籤:
前言
我一直想做一個可以區域網路聯機對戰的遊戲,但是無論是使用還是藍芽的方式進行開發都要學習相關平台的知識。其實我去年瞭解過這方面的知識,據不可靠的資料顯示我會遇到這些問題:
- IOS的藍芽不可串連其它廠商的藍牙裝置;
- WIFI好像也是和第一條一樣;
- 如何知道某個藍芽或者wifi裝置所在的手機上啟動了我的遊戲?
由於涉及到不同平台,問題還有很多,我也懶得去解決這些問題。於是我就選擇了一個懶辦法,使用TCP協議來進行區域網路聯機對戰,這樣有一個前提就是多台裝置必須要在一個區域網路內。但是可以做到跨平台,而且也不用那麼麻煩。於是 Buddy 庫誕生了,這個名字用於紀念童年一起玩小霸王遊戲機的小夥伴們。
實現原理
實現原理很簡單,首先主機玩家開一個遊戲房間,向區域網路內廣播主機玩家的地址連接埠等資訊,並啟動遊戲伺服器。其它玩家收到主機玩家廣播的訊息後(廣播資訊裡麵包含了遊戲伺服器的地址和連接埠資訊),就串連到遊戲伺服器,從而達到聯機對戰的目的。這些步驟 Buddy 庫已經封裝好啦,我們可以直接使用。
使用說明
使用上也是比較簡單的,這裡把服務端和用戶端的使用分開來講解,其實它們是非常相似的。
服務端
範例程式碼:
1 #include <iostream> 2 #include "Classes/Buddy.h" 3 4 enum eMessageDelegate 5 { 6 BuddyServiceID = (int)ReservedDelegateID::UserCustom + 1, 7 }; 8 9 class SessionManage : public ServiceDelegate10 {11 public:12 virtual void OnPlayerJoin(SOCKET session, const Address &address) override13 {14 std::cout << "OnPlayerJoin" << std::endl;15 16 char buffer[] = "hello";17 SendData(session, buffer, sizeof(buffer));18 }19 20 virtual void OnPlayerLeave(SOCKET session, const Address &address) override21 {22 std::cout << "OnPlayerLeave" << std::endl;23 }24 25 virtual void OnMessageReceive(SOCKET session, void *data, size_t size) override26 {27 std::cout << "OnMessageReceive" << std::endl;28 }29 };30 31 int main(int argc, char **argv)32 {33 InitBuddy();34 35 SessionManage manage;36 BuddyService server(Address(kHostPort), 10, BuddyServiceID, &manage);37 server.Accept();38 39 while (true)40 {41 std::this_thread::sleep_for(std::chrono::milliseconds(kFrameDeltaTime));42 MessageManager::GetInstance()->Update(0);43 server.Broadcast();44 }45 46 return 0;47 }
首先,第4行聲明一個枚舉類型,用來表示訊息代理的ID。Buddy 庫內部實現了一個訊息佇列,需要遊戲主迴圈來更新它。我們可以為一個對象(對象的類類型必須繼承自MessageDelegate)指定一個ID,這樣向這個ID發送訊息後,這個對象的OnMessageReceive函數就會被調用。
在第9行實現一個類SessionManage,繼承自ServiceDelegate。實際上這個類是對所有會話的管理類,當玩家進入或離開或收到玩家發送的資料時,ServiceDelegate類的虛函數將被調用。另外ServiceDelegate類還有提供了其它幾個函數來管理玩家。
第33行是對庫進行初始化,這個必須在主線程中調用。
第36行建立了一個遊戲伺服器,建構函式的參數分別是伺服器的地址、訊息代理的ID和服務代理的指標。服務代理其實就是前面說的會話管理類的執行個體。
第37行調用Accept後,將會接受區域網路內的串連請求,並不會阻塞線程。
第39行其實是在類比遊戲的主迴圈,一般是每秒60幀,一幀耗時就是16毫秒。
第42行是在遊戲主迴圈中更新訊息佇列。
第43行是在區域網路內廣播訊息,這樣區域網路裡的玩家才能夠探索服務器。
用戶端
範例程式碼:
1 1 #include <iostream> 2 2 #include "Classes/Buddy.h" 3 4 enum eMessageDelegate 5 { 6 BuddyClientID = (int)ReservedDelegateID::UserCustom + 1, 7 }; 8 9 class ConnectManage : public ClientDelegate10 {11 public:12 virtual void OnFoundHost(const Address &address, int number)13 {14 std::cout << "FoundHost: " << address.ToString() << std::endl;15 std::cout << "線上人數: " << number << std::endl;16 client_->Connect(address);17 }18 19 virtual void OnNotFoundHost()20 {21 std::cout << "OnNotFoundHost" << std::endl;22 }23 24 virtual void OnConnectSuccess()25 {26 std::cout << "OnConnectSuccess" << std::endl;27 }28 29 virtual void OnConnectFail()30 {31 std::cout << "OnConnectFail" << std::endl;32 }33 34 virtual void OnDisconnect()35 {36 std::cout << "OnDisconnect" << std::endl;37 }38 39 virtual void OnMessageReceive(void *data, size_t size)40 {41 std::cout << "OnMessageReceive" << std::endl;42 std::cout << (char *)data << std::endl;43 }44 45 public:46 void SetBuddyClient(BuddyClient *client)47 {48 client_ = client;49 }50 51 public:52 BuddyClient* client_ = { nullptr };53 };54 55 int main(int argc, char **argv)56 {57 init_sockets();58 MessageManager::GetInstance();59 60 ConnectManage manage;61 BuddyClient client(BuddyClientID, &manage);62 manage.SetBuddyClient(&client);63 client.SearchHost(60);64 65 while (true)66 {67 std::this_thread::sleep_for(std::chrono::milliseconds(16));68 MessageManager::GetInstance()->Update(0);69 }70 71 return 0;72 }
你會發現跟服務端的用法很相似對吧?下面來說說不同的地方。
在第9行實現一個類ConnectManage,這是一個串連管理類(永遠只有一個串連)。
第61行跟服務端的用法類似,建立了一個用戶端,建構函式傳入訊息代理的ID和管理類的指標。
第63行調用SearchHost函數將會在區域網路內搜尋有無遊戲服務端,參數是逾時時間,單位為秒。這個操作不會阻塞主線程。
第65行也是在類比遊戲主線程,在主線程中更新訊息佇列。
值得注意的是,遊戲的邏輯通常在會話管理類或者串連管理類裡面實現。遊戲主線程中每幀只需更新一次訊息佇列。訊息的代理ID必須每個對象都是唯一的,意思就是說兩個不同的對象不能擁有同一個訊息代理ID。
源碼下載
https://github.com/zhangpanyi/Buddy
C++實現的手機遊戲區域網路聯機對戰庫