我们将多人游戏固定在以C ++编写的iOS和Android手机游戏“用词做词”中

早些时候,我写了我在Android和iOS上开发手机文字游戏的经验,该游戏非常流行,我决定将多人游戏模式固定到该模式,当两名玩家相互竞争并依次写词时,作为Sergey Suponev的电视转播的最后一轮小时。”



我花了一个半月的时间来研究和实现多人游戏,在这篇文章中,我将尝试在不包含源代码示例的情况下描述该概念,从而使工作量有所减少。

一点历史


该应用程序是使用Marmalade SDK用C ++编写的。 从那时起,供应商就停止了对该平台的支持,将其出售给日本人,并且这种开发环境的未来变得非常模糊。



出现了一个问题,该如何移植当前项目以获得进一步的支持。

为什么不cocos2d-x




Cocos2d-x是C ++中最常见的跨平台手机游戏开发引擎之一。 显然,由于其免费和开放源代码。 引擎文件记录不充分。 该说明涵盖了发动机的微薄部分,大部分材料已过时。

根据一段时间的结果,我仍然设法创建了应用程序的原型。 但是印象非常糟糕:感觉就像cocos2d-x组装在膝盖上一样。 在我看来,抽象场景,子画面,应用程序委托的级别似乎非常不舒服,并且在椰子论坛上寻找问题答案的需求越来越多地使您意识到您做错了什么。 可能我的手伸出错误的地方。

我的选择落在了SDL上




与Marmalade SDL一样,SDL不是引擎,而是平台。 它提供了一个低级API,然后我可以从中构建对我来说很方便的抽象级别。 所有这些都是用C编写的,源代码是开放的。

简而言之,SDL是一个免费的跨平台库,用于处理来自操作系统的图形,声音和处理消息。 在Windows上进行win32构建和调试游戏逻辑非常方便,而移动模拟器和物理设备仅调试特定于OS的功能,这非常方便。

幸运的是,SDL并没有提供用于完成诸如iOS和Android的多人游戏之类的狭窄任务的工具,因此我必须自己与相应的服务集成。

多线程应用程序架构


应用程序的逻辑以及所有与图形有关的逻辑都是在主线程中实现的,这是一个消息处理周期,从主函数开始。 将此流称为SDL线程。 反过来,其他线程将事件(SDL_PushEvent)抛出以进行处理,然后他使用SDL_WaitEvent和SDL_PollEvent从队列中读取事件。 这些要么是系统抛出的系统事件,并且已经在SDL中实现了对它的支持,要么是我们已经实现了SDL功能之外的Callbacks和Listener-s的调用。



所有游戏逻辑都是用C ++编写的。 项目目录包含一组* .cpp文件,这些文件可以分为三组:

  • 跨平台-所有平台的程序集中包含的那些文件(游戏逻辑);
  • 单平台,即 包含在任何一个平台的应用程序中以实现其功能。

因此,每个平台的设计都有三个单独的目录:

  • proj.win32-项目VS2017社区版;
  • proj.android-使用Gradle的Android项目;
  • proj.ios-适用于iOS的Xcode项目。

与多人游戏服务集成


现在我们需要粘贴一个单独的层,它将负责以下功能:

  • 寻找对手,与游戏建立联系;
  • 竞争对手之间的消息传递;
  • 从游戏室退出;
  • 在排行榜中固定玩家点数。

iOS和Android平台均支持实时多人播放(RTMP)。 对于Android,我们与Google Play服务(GPS)集成,对于iOS,我们与Game Center集成。 以前,Google支持与iOS集成,但今年决定放弃它。

在本文中,我不会描述您需要在Google Play控制台和AppStoreConnect中执行的用于配置多人游戏的操作,我不会描述类和集成方法的规范-所有这些都在供应商站点上进行了描述。

接下来,我将简要描述每个平台在项目中需要进行哪些更改。

安卓系统


怎么了 我还没说过吗? Android NDK用于编译C ++代码。 虽然,如果您是Android开发人员,那么您已经知道了。

网站上为Android开发人员介绍了将Google Play服务集成到Android项目中的一般说明 。 在我的项目中,我使用以下依赖项:

implementation 'com.google.android.gms:play-services-games:16.0.0' implementation 'com.google.android.gms:play-services-nearby:16.0.0' implementation 'com.google.android.gms:play-services-auth:16.0.1' 

最初的想法是使用C ++ api ,它以没有源代码的已编译静态库的形式出现。 由于库列表中没有针对x86_64平台的程序集,我决定Google的人员并没有真正监视此SDK的相关性,因此决定发明自己的自行车以Java编写此层,并用JNI包装器将其包装。 然后,为什么我需要libs形式的额外依赖项,而没有Java内仍然可以拉Java的源代码? 除了Java类的相关性之外,您还需要监视这些库的相关性。

作为指导,我使用了Google Samples中的一个很好的例子。 感谢Google。 苹果,以Google为例!

的iOS


要与Game Center集成,必须连接GameKit框架。 我们在一个* .m文件中描述了Game Center的整个集成层,并通过一个单独的* .h文件提供了接口。 由于C ++是Objective-C语言的子集,因此在一个项目中* .cpp和* .m文件的组装不会有问题。

官方文档外,他还受以下项目指导: GameCenterManager 。 确实,该示例中的某些内容已经过时了,Xcode 10会告诉您这一点,您将用新功能替换过时的功能。

多人游戏层的工作原理


单入口


研究了在两个平台上使用多人游戏的功能之后,我为我的应用程序创建了一个C ++摘要,并且在编译时,相应的实现取决于特定的平台“适合”它。 也就是说,我的应用程序不知道任何Google Play服务,Game Center及其功能。 它仅知道提供给它的C ++ api,例如,其中的方法如下:

 SignIn() //     SignOut() //     LeaveRoom() //    SendMessage(...) //    ShowLeaderboards() //    SubmitScore(...) //    ... 

寻找对手


玩家可以从他的联系人列表中邀请朋友,或者从一个随机的对手开始游戏。 收到邀请的玩家可以接受或拒绝。 对于所有这些情况,我都使用所使用服务的标准接口。 我想指出,Google枪口看上去比iOS iOS好得多。 也许有一天我的手会伸手去拿,然后我将与多米诺骨牌和小姐们写我的界面。

连接游戏室


当两个玩家连接到虚拟游戏室时,他们会收到相应的回调。 现在,您需要选择谁将是主持人。

主机选择


在玩家中,您需要选择一个主机,以便他确定游戏的初始状态。
考虑一般情况下在玩家之间路由消息的可能方法。 请注意,在第二实施例中,还为主机分配了路由器的角色。



由于我在游戏中始终只有两个玩家,因此我有一个点对点连接的特殊情况。 因此,只有初始状态的定义取决于主机角色,即,选择将要组成单词的单词。

因此,在玩家连接到游戏室之后,每个玩家都知道已经开始的游戏参与者的标识符列表。 我们将其称为参与者ID列表。 参与者ID是游戏参与者的某个唯一字符串标识符,由服务分配。 您需要选择其中一个将成为主机,将其带到主机本身,并告诉对方他的对手已被选为主机。 怎么做?

Android主机选择

我没有找到有关在Google码头中选择主机的任何建议。 他们沉默寡言,游击队。 但是stackoverflow.com上的好人为视频提供了链接,该视频详细解释了以下原理:

  • 每个参与者对参与者ID列表进行排序(升序或降序-没关系,主要的是每个人的操作顺序都相同);
  • 每个参与者将其参与者ID与列表中的第一个参与者ID进行比较;
  • 如果他们匹配,则当前玩家有权选择谁作为主持人。 他扔硬币,拉随机(),从而从现有参与者中选择一个主持人,并告诉每个人谁是主持人。

iOS上的主机选择

对于iOS,有一个choiceBestHostPlayerWithCompletionHandler方法,与我为Android描述的方法相比,该方法大大简化了主机选择方案。 但是从调用此方法期间明显的延迟来看,它估计了网络的响应参数,测量了ping,并根据这些统计信息确定了谁应该是主机。 对于上面的主机服务器充当路由器的上述客户端-服务器体系结构,这更有可能。 在我的私有对等连接版本中,这没有意义,并且为了节省时间,我使用了与Android相似的原理。

玩家之间的消息传递


什么是讯息? 消息是字节数组。

  • 在Java中,这是一种类型:
     byte[] 
  • 在目标C中,这是:
     NSData * 
  • 在C ++中,我将以上所有内容映射到
     std::vector<Uint8> 

有两种类型的发送消息:

  • 可靠-通过队列保证交付。 用于传递重要消息。
  • 不可靠-不保证交付。 可以忽略传递成功的二手消息。

通常,不可靠的交付要比可靠的交付更快。 您可以在供应商的网站上阅读更多信息:


我们将如何使用此数组? 很简单:

  • 在第一个字节中,我们将写入消息的类型。
  • 如果消息中包含任何参数,则将其放在以下字节中。 对于具有添加的每种消息。 参数,我们实现序列化和反序列化的功能。
  • 在邮件末尾检查完整性时,我们将放入一个校验和。

因此,我们用游戏中玩家之间相互交流的消息类型来定义枚举

  • 我被房东选中。 我传达了初始状态。 现在轮到我了(参数:消息传递协议的版本号,源单词);
  • 主机选择您。 我期待着您的回音。
  • 我打开(叫)这个词。 现在轮到您了(参数:named word);
  • 我放弃 你赢了;
  • 我在移动期间一言不发。 你赢了;
  • 我同意报仇。
  • 我退出了游戏;
  • 解析消息时出错。 断开连接;
  • 您的消息传递协议版本已过期。 检查应用程序更新。 断开连接;
  • 我的消息传递协议版本已过时。 需要检查更新。 断开连接;
  • Ping(系统消息);

当应用程序收到来自对手的传入消息时,将调用相应的回调,然后将其传递到主SDL线程进行处理。

连接监控


游戏服务(Google的服务,Apple的服务)具有侦听器功能,该侦听器功能以一种或另一种形式设计为通知我们与对手的断开连接。 但是,我注意到,如果其中一个播放器已断开与Internet的连接,则第二个播放器不会立即知道第一个播放器已断开连接并且没有人可以玩。 在这种情况下,将不会调用回调,或者会在较长时间后调用回调。 因此,在这种情况下,第二个玩家不必等待癌症在山上吹口哨,因此我必须对连接进行自己的监控,并遵循以下原则:

  • 每个玩家每秒都会向对方发送一次ping消息;
  • 每个玩家都检查:如果超过5秒钟没有来自对手的消息,则连接断开,我们退出游戏。

结果


由于工作的完成,我得到了一个与朋友和家人一起玩的游戏。 我同时在iOS和Android上玩。

的确,iOS上存在细微差别-出于某种原因,排行榜中的眼镜没有固定,我目前正在与Apple支持联系。

我希望本文对我的团队成员以及对开发移动应用程序有兴趣的人都将是有用的。 谢谢您的关注。

Source: https://habr.com/ru/post/zh-CN433172/


All Articles