基于Unity的弹幕游戏多人联机尝试

基于Unity的弹幕游戏多人联机尝试

文/Waka

AngerForce:Reloaded 是我们制作的一个弹幕射击游戏。自从去年发布以来,我就一直在考虑给他添加多人联机功能。


    给一个已经定型的游戏添加哪怕是一个小特性,都是棘手的事情,很容易引入新的bug,或者破坏已有的功能。复杂的联网更是如此,它涉及到的改动几乎遍及系统的方方面面。玩家的一举一动都需要在其他人的屏幕上展现出来,所以我们需要选择一个低成本的,容易实现的方式,来开发多人在线的功能。

技术选型

    Unity引擎内置了多人联机的解决方案,涵盖了从最底层的网络数据传输,到不同玩家之间的消息发送,再到游戏大厅这样的高级功能。考虑到Unity官方提供的云服务(Internet Services)在国内的延迟较高,而且需要付费,我们决定采用Steam与Unity相结合的方式。底层用Steam发送网络数据包,中间由Unity负责把各个包整合成游戏逻辑所需要的格式,高层的大厅也使用Steam提供的服务。

    说到这里要赞美一下Unity Networking的模块设计,它把具体的网络数据传输细节和抽象的消息发送功能分离开来。使得开发者既可以使用传统的“IP地址+端口”的方式实现玩家之间的连接,也可以相对方便地接入Steam或WeGame,利用这些平台SDK包含的更高级的功能去收发网络数据。而且Unity的网络模块是开源的,不仅方便查阅,还可以根据自身需求进行修改,然后替换掉引擎自带的模块。

服务器

    联网游戏需要一个服务器,用于协调多个玩家之间的游戏进程。否则大家的电脑有快有慢,很容易出现游戏节奏不一致的情况。比如,玩家A的电脑配置高,运行流畅;玩家B的电脑有点卡,会掉帧。那么,当游戏需要3个小飞碟从上方飞入屏幕攻击玩家的时候,可能玩家A那边的飞碟已经全部就位,开始发射子弹了;但玩家B那边刚刚创建出第二个飞碟。这样就导致不同玩家屏幕上显示的内容完全不同,很难进行正常的游戏。引入服务器就是要避免出现各种各样的不一致行为,让速度快的机器等等速度慢的,大家尽可能保持相同的步调去执行游戏逻辑。

    这个服务器可以是独立的后台,就像一个网站那样托管在某个云计算厂商那里;也可以让某个玩家来充当服务器,在运行自己游戏逻辑的同时也负责调度其他玩家的游戏。不过,开发并维护一个独立服务器的成本相对而言还是挺高的,所以我们选择了第二种方案,就让创建房间的玩家来兼职做服务器。Unity恰好有一个Host模式,支持一个玩家同时扮演服务器和客户端。

游戏房间

    完成了前期的技术准备后,我们就开始着手制作游戏房间。目前我们仅支持双人协作,这部分的流程也比较直观:

玩家在新建的房间内独自选择角色,并等待

其他玩家加入房间后选择自己的角色

  1. 创建房间:玩家A向Steam发起申请,并设置最大人数为2。如果成功,A就成为房间的所有者,进入角色选择界面。同时,A还需要启动服务器(NetworkServer),等待其他玩家的进入。

  2. 查询房间:玩家B设置筛选条件去查询当前列表,Steam会返回还有空余位置的房间。如果A创建的房间符合条件,该房间就会包含在返回结果之中。

  3. 进入房间:B申请进入A创建的房间。如果成功,A和B之间就可以通过Steam互相发送消息。但这时房间内的玩家只能进行基本的通信,还不能利用Unity提供的状态同步等机制。

  4. 建立C/S关系:B向A发送连接请求(NetworkClient),A收到后建立连接。这样,后续的游戏同步逻辑就可以按照Unity的方式进行。

  5. 开始游戏:B进入房间,选择自己的角色。完毕后,A通知双方加载游戏场景。

角色同步

双人同屏战斗

    游戏角色作为玩家的控制对象,接收绝大部分用户输入,是同步的重点。

  • 移动:Unity提供了同步物体位置、旋转的组件(NetworkTransform),可以设置频率调整每秒同步的次数。但每次同步之间没有插值,导致物体的移动显得特别僵硬。所以我们选择自行发送位置数据,远程角色在收到数据后进行平滑处理。也就是说不是立即将角色设置到新的位置,而是以一定速率,每帧逐渐靠近新的位置。这种方式虽然会导致角色的位置跟实际位置有所偏差,但会有一个能够接受的视觉效果。

  • 状态:角色的血量、炸弹数量还有当前动画等通过Unity提供的状态同步方式来处理。引擎自行监控这些特别标注的变量(SyncVar),在初始和变化的时候,把新数值同步到客户端上去。

  • 技能:释放技能和炸弹由玩家自己控制的角色发起,在服务器端执行,然后同步到其他玩家的客户端。这种类型的操作适合使用Command+ClientRpc方式去实现。

  • 子弹:自机的子弹也是通过监测当前是否正处于发射的状态去同步。角色或敌机中弹则作为事件发送到服务器去处理。

场景同步

    游戏场景占据了最多的工作量,主要是因为之前很多工作都是利用PlayMaker插件来做的。这个可视化的状态机工具能方便非技术人员去调整物体在游戏内的动态行为。截止到目前的1.8.9版本,它还不支持Unity的多人模块,同步起来比较麻烦。

复杂的状态机,其中蓝色高亮显示的是需要强制同步的状态

    以下是场景中需要注意的事项:

  • 时间:许多场景的移动、关键动作的触发都跟时间相关,所以当前游戏进行的时间是场景保持同步的关键。

  • 杂兵行为:我们游戏中有一百多个杂兵,基于这些杂兵有八百多个不同的行为。这些行为都是用PlayMaker编辑的状态机。要让如此众多的状态机去支持联网,手动去挨个修改是不可能的,只能利用脚本批量修改。

  • 状态机:我们设计关卡时,会根据游戏进行的时间或者地图移动的位置去指定某个杂兵的行为。这些行为一般遵循先创建杂兵单位,再移动射击,最后被击落或离开屏幕的规律。这里边包含两部分,一是用于交互和同步的杂兵,二是控制杂兵行为的状态机。在单机情况下,状态机创建出单位紧接着执行后续操作;在联网模式下为了状态同步,场景中物体的创建和销毁需要在服务器端进行。所以,原有的状态机在服务器和客户端上的执行不再一致。服务器创建的杂兵单位,会通过Unity的机制自动在客户端上克隆出来,这样客户端不再需要自己创建,而是等待单位被服务器创建出来后作为参数传入状态机里去执行后续动作。

  • Boss行为:一般的杂兵行为比较简单,在屏幕中存在的时间也较短,在服务器和客户端上各自运行也不会产生太大差别。但Boss的行为比较复杂,运行一段时间后就会出现明显偏差。我们在状态机内部的关键节点上加入等待机制,让各玩家在运行到节点处同步进入下一状态。

总结

    至此游戏已经可以支持基本的多人体验了。对于联网功能的开发,我们总结了以下心得:

  • 给基于Unity引擎的游戏添加联网功能的难度并不大,但最好在游戏设计初期就确定是否单机还是在线游戏,能少走弯路,节省工作量。

  • 定期清理工程文件,做好素材分类,尽可能多地复用资源,避免冗余。这样能减少项目后期的维护成本。

  • 在写这篇文章时,碰巧看到关于帧同步的介绍。感觉这项技术对于弹幕这种精确度要求较高的游戏可能更加合适。将来有机会在新的项目中尝试。

知乎原文:https://zhuanlan.zhihu.com/p/34794958

打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮