C++实现的手机游戏局域网联机对战库
前言
我一直想做一个可以局域网联机对战的游戏,但是无论是使用还是蓝牙的方式进行开发都要学习相关平台的知识。其实我去年了解过这方面的知识,据不可靠的资料显示我会遇到这些问题:
- 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 ServiceDelegate
10 {
11 public:
12 virtual void OnPlayerJoin(SOCKET session, const Address &address) override
13 {
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) override
21 {
22 std::cout << "OnPlayerLeave" << std::endl;
23 }
24
25 virtual void OnMessageReceive(SOCKET session, void *data, size_t size) override
26 {
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 ClientDelegate 10 { 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。
源码下载
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。