首页 存档 技术 查看内容

给游戏引擎开发者的64个建议(1):客户端

2018-3-30 13:00 |来自: 互联网 356 0

摘要: (点击上方公众号,可快速关注) 英文:ithare 译文:伯乐在线 - 风-晴-雪 链接:http://ithare.com/64-network-dos-and-donts-for-game-engine-developers-part-i-client-side/ 在过去的十年间,越来越多的游戏改 ...

(点击上方公众号,可快速关注)


英文:ithare

译文:伯乐在线 - 风-晴-雪

链接:http://ithare.com/64-network-dos-and-donts-for-game-engine-developers-part-i-client-side/

在过去的十年间,越来越多的游戏改版为网络游戏,这是个强烈的趋势。当为一款游戏添加网络支持面临着全世界的挑战时,我最近的经验(同时作为一名玩家和咨询者)表明,太多游戏开发者违反了做一款足够好的网络应用程序的基本准则。我们经常看到用户界面“一动不动”、网络中断未被激活(同时Internet的其余部分是可访问的)、游戏随机崩溃,还有高峰期的服务器超载。坏消息是,这些问题直接影响到游戏玩家的满意度(比开发者通常以为的管理和图像因素来得直接得多)。

好消息是,处理这些问题并非是一门高精尖科技,只要网络引擎开发者确实知道他们在做什么以及他们确实读了这篇文章。

在这篇文章里,我们主要聚焦于网络开发的某些方面,这对很多游戏引擎开发者而言并不那么明显。如果我写的一些东西对你来说显而易见,那么很抱歉,但请别因为我写了而太厉害地抨击我。我向你保证,有大量拥有百万级玩家的流行游戏都违反了这份清单上的其中一项(可能有一两项除外)。因此,我写这篇文章是为了给出一份建议清单,它会帮你避免开发者在为有大量交互的应用程序,如游戏或股票交易程序,实现网络层时会犯的最烦人同时也是最容易犯的错误。

在这篇文章的第(1)部分,我们将抛开网络协议,讨论客户端网络开发的常见事项。接下来的章节有:

  • (2a). 协议和API(上)

  • (2b). 协议和API(下)

  • (3a). 服务器端(存储处理和发展的体系)

  • (3b). 服务器端(配置、优化和测试)

  • (4). TCP-vs-UDP的大论战

  • (5). UDP

  • (6). TCP

  • (7). 安全

0. 范围

整体来看,游戏引擎获得了大量的网络支持。这就是我们为什么**了我们给出忠告的范围。更具体地说:

  • 我们将专注于有客户端的游戏app,不考虑浏览器端或基于AJAX的游戏。而App游戏和基于浏览器的游戏有很多类似之处,但它们的确各自也有相当不少的差异之处。

不过,这篇文章试图要覆盖大部分和游戏的网络层开发相关的其他方面:

  • 我们不会将自己**在一个指定的游戏类型(比如MMORPG的模拟世界类)。当MMORPG在这篇文章的一定范围内,社交游戏、多人策略游戏(既是实时游戏又是基于回合制的那种游戏)、赌场游戏以及股票交易游戏也是如此。令人惊奇地是,大多数的风格型游戏需要的网络支持相当类似(尽管很多时候依赖于时间因素,正如我们将在(4). TCP vs UDP的大论战里讨论的那样)。

  • 我们也不必将自己局限于一个平台:实际上,我们强烈支持开发跨平台的引擎,包括网络引擎。作为练习,我自己开发了一套网络引擎,它能在5个以上不同的平台上运行(这些平台的清单在第6条建议下列出)。

  • 当这篇文章是出自一名游戏引擎开发者的观点时,我们应当注意到相当多的时候开发者需要为他们自己开发游戏引擎。在这些情况下,这篇文章的大部分忠告仍然适用。

  • 当“哪个现有的引擎或者网络引擎是最好的?”这样的问题不在这篇文章的探讨范围内时,我们仍然期望本文在回答这个问题时能派得上用场。然而,这个问题的答案取决于你的游戏细节,因此你需要读读这篇文章,决定哪些忠告适用于你的游戏,哪些不适用。换句话说:如果你的游戏引擎或框架提供了网络访问功能你可以用这篇文章作为允许一窥游戏框架的工具,再看看如果他们的网络实现是否适合你的游戏。

现在,抛开前提,让我们开始吧:

1. 一定要在客户端使用事件驱动编程

大多数客户端的框架都有个所谓的“主线程”(或者是能隐含在这个“主线程”里运行的“主循环”),而这个“主线程”基本上只访问特定的事件(起初是UI事件)。这个模式适用于整个客户端框架的领域,从Windows GUI、Direct X和Cocoa,到Unity 3D、Android和iOS。这个现象也有个能解释它的原因:因为不这样做的话,开发就会成为一场噩梦。事实上,据我所知只有一个没有这样运作的框架:它是Java开发的原始AWT框架,而在AWT框架里开发一个app是一件公认相当痛苦的事情(中肯地说,AWT框架从来就没流行过;Google特别需要为Android开发一整套全新的GUI框架)。

当我们把可执行程序或者app联网时,它的事件驱动模式应该怎样变化呢?答案是,“不应该变化”。从逻辑上而言,所有的游戏网络通信包括发送和接收的信息,每个接收的网络信息应当被看作是游戏事件驱动逻辑的又一个触发事件(伴随着传统UI事件的触发,比如点击鼠标或按下键盘等)。通过把消息注入到主线程的“消息队列”中(例如,在Win32环境下通过PostMessage()或者PostThreadMessage()来注入)可以相当轻易地实现事件触发。假如你使用的图形框架(比如Unity3D)不支持这个概念,你可能需要创建队列并使用它来模拟该框架(瞧,例如,[Unity3D2012])。和在一个单线程里强制访问所有事件(包括UI事件和网络消息)比起来,是否将事件作为信息传递(像在Win32下),抑或将事件作为回调(像在[Unity3D2012]里)都不那么重要了。

注意:如果用的是Unity,这个小技巧几乎用不上,因为Unity的内建网络机制(该机制已使用Unity的事件访问线程)对“实时世界模拟器”而言足够好用。然而,使用Unity的网络即意味着用UDP方式传播信息,这种方式,如同我们将在(4)里看到的一样,可能是也可能不是最好的方式,这取决于游戏本身特别是如果游戏偏离了“实时世界模拟器”的话。

在某些情况下,事件处理线程可能和框架的“主线程”不一样,但重要的是,要把所有至少有点相关的事件都放在一个单独线程中处理。然而,仅仅是通信相关的事(那些和游戏逻辑一点关系都没有的事),比如信息集结、加/解密和(压缩)解压缩,可能(以及如果可能的话,应该)在“主线程”外处理。我们将在接下来的第3条建议里讨论一些线程分离的细节

2.一定不要在事件处理线程外调用应用程序回调

当我年轻并且是个相对经验不足的开发新手时,我为一个股票交易开发了一个网络框架(不要问一个缺乏经验的开发者是怎么拿到这个照理说来这样大的一个任务的我自己也不知道为什么)。我不得不承认,尽管第一次尝试开发网络库,我已经做得相当好了,但是我还是犯了一个重大错误。我创建了一个属于网络框架的线程,用它来调用应用层的一个回调(如果我没弄错的话,它是一个回应属于我的sendMessageOverTheNetworkAndCallbackOnReply()类型函数的回调)。这个微不足道的回调给后来几任使用该框架的开发者带来了相当大的不便。首先,对后来的开发者而言,游戏中的交互(和潜在的资源竞争(!))变得相当难理解(对我而言一切显而易见,但它还是我的问题,还有一个原因是:这是个可以避免的问题,而我害得他们不得不处理它)。其次,它引起了好几个很难追踪的缺陷和资源争用。最后,这个框架不是太糟糕,整体而言程序运行得相当不错,但是如果没有做这个单次回调的话,在该框架上的开发原本可以比现在更平滑。

有好几年我曾被分配任务,为一款相当大的多玩家游戏开发一个网络框架(我乐意炫耀的是这款游戏同时在线用户50万,每天收发5亿个网络数据信息)。这次我学了乖,避免了这种线程回调。整个框架运行得非常流畅(同时也更容易移植到多平台上)。

底线:如果你需要从网络层向应用层回调,首先把事件传送给事件处理线程(通常是‘主’线程),然后在事件处理线程生成的网络层库的调用里处理事件,必要时调用应用级回调。

换句话说,下面这种方式不错:

network thread

声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系 [邮箱地址] 删除

路过

雷人

握手

鲜花

鸡蛋

相关分类

返回顶部