在网络或互联网上使多人游戏可玩

17

您好,我用Java编写了一个多人游戏,想知道为了让多台电脑之间通过网络或互联网玩这个游戏,我需要学习什么和/或使用什么。我对如何入手很茫然,所以任何建议都将有助于我,谢谢。


游戏在当前状态下如何支持多人功能?它是否使用套接字与同一台机器上的多个实例进行通信? - Mark Wilkins
不,它只是基于轮流进行,但我想在将其变成可在网络上玩之前,我需要这样做吧? - Bob Anderson
多人游戏意味着多个用户在玩同一款游戏,并且玩家之间存在一些互动。你说你的游戏是“基于轮流进行”的。它是否是多人游戏? - Peter Knego
我猜它的多人游戏方式类似于国际象棋。 - Bob Anderson
2
@Bob:如果不了解游戏架构,很难给出建议。但基本上,你可能需要学习如何使用套接字。第一个谷歌搜索结果“java socket tutorial”找到了这个链接,这可能是一个不错的起点。 - Mark Wilkins
基本上,架构类似于国际象棋或四人跳棋游戏,但有4名玩家。有一些简单的棋子类可以执行特定的移动,还有一个用于跟踪所有棋子和不同玩家的棋盘。这样解释清楚了吗? - Bob Anderson
3个回答

51
那些其他的答案都比较高级,这没问题,但你不想要高级的,你想要低级的,比如“我怎样才能真正发送数据,这意味着什么,我应该发送什么等等。” 这是你需要做的:
首先,TCP还是UDP?如果你不知道这两个东西是什么,就去了解一下吧,因为我没有足够的空间在这里详细介绍它们,但对于你的选择,需要知道以下内容:
  1. TCP对于回合制游戏或者延迟相对较高的游戏很好,因为TCP保证数据包的传递,所以有可能需要一些时间来重新传递丢失的数据包。这对于象棋之类的回合制游戏或其他需要轮流操作的游戏非常有用。
  2. UDP对于那些并不关心消息可靠性,而更希望数据持续发送的游戏来说是很好的选择,如果你错过了某个消息也无所谓。这适用于实时动作类游戏,比如《光环:Reach》或《使命召唤》。在这些游戏中,如果你发送了一个物体的位置信息,但该物体从未到达目的地,那么发送一个新的位置信息要比重新发送一个旧的位置信息(现在更旧了)更好,因此并不总是需要保证可靠性。然而,你必须确保某些特定的内容百分之百可靠,比如对象的创建和销毁。这意味着你需要在UDP之上自己实现一种半可靠、基于优先级的协议。这很困难。

因此,请考虑什么是重要的,学习TCP和UDP的工作原理,然后做出明智的选择。

说到这一点,现在你需要在网络上同步对象状态。这意味着你的对象需要序列化为可以表示为字节流并写入套接字的内容。写入套接字很容易;如果你能够写入文件,那么你也能写入套接字,这真的不难。重要的是确保你能够将对象表示为缓冲区,因此如果你的对象引用/指向其他对象,你不能只发送这些指针,因为在其他客户端上它们是不同的,所以你必须将它们转换为对所有主机都通用的内容。这意味着使用ID,尽管一个对象的ID必须在所有主机上是唯一的,所以你必须有一种协调各个主机之间的方式,以确保没有两个主机会创建具有相同ID的不同对象。有办法处理主机执行此操作,但我们不会在这里担心这个问题(提示:使用主机ID和网络ID之间的某种映射。更大的提示:如果不需要,请不要这样做)。
现在你可以发送数据了,太好了,接下来呢?每当游戏状态发生变化时,你必须以某种方式向其他机器发送更新。这就是客户端-服务器架构或者点对点架构的用武之地。客户端-服务器架构更容易实现。而且,一个扮演服务器角色的主机仍然是客户端-服务器模式,任何说不同的人都是错误的。
所以,服务器的责任是“拥有”所有的游戏状态。只有服务器能够明确地说明一个对象处于什么状态。如果你想移动一个对象,你告诉服务器你想要移动,然后服务器告诉你应该移动对象,你不能自行操作(尽管某种客户端预测通常很有用)。然后服务器将更新后的对象状态发送给所有其他主机。
所以,你提到了回合制游戏,对吧?非常简单:
你要解决的是当前轮到的客户端的一个完整回合。一旦该客户端完成他们想做的事情,将该回合的结果发送给服务器。然后,服务器验证客户端的移动(不仅仅信任客户端,因为作弊就是这样发生的),并将其应用于对象状态。
一旦服务器更新完毕,它会向每个其他客户端发送消息,告知世界的新状态,然后这些客户端会应用这些更新。这包括刚刚完成回合的客户端;当服务器告诉它更新世界状态时,该客户端才应该更新自己的世界状态,因为你希望确保与其他主机的一致性,并防止主机作弊。
然后,服务器发送一条消息,指示轮到谁了。你可以在前一步中同时发送这个消息和世界状态更新,那样也可以。只是要注意客户端尝试乱序进行回合。这就是为什么服务器对世界具有权威性;如果客户端尝试作弊,服务器可以制止他们。
这就是你在回合制游戏中需要做的全部。提示:使用TCP。 更大的提示:TCP实现了一种称为“Nagle算法”的东西,它将你的消息合并成一个单独的数据包。这意味着,如果你通过两次分别调用“Send”发送了两个独立的消息,可能对方主机在一次调用“Receive”时只会接收到一个数据包,但该数据包将包含被发送的两个数据包的内容。因此,如果你通过两次发送调用发送了两个100字节的数据包,可能在一次接收调用时会得到一个200字节的数据包。这是正常的,所以你需要想办法处理这个问题。一个技巧是让每个数据包的大小都相同,然后每次检查输入时从套接字中读取相同数量的字节。同时要记住,你也可能会收到部分消息。例如,如果你发送了两个100字节的消息,它们可以合并成一个200字节的消息。接下来,如果你在另一端的套接字上读取,但你使用了150字节的缓冲区大小进行读取,你将得到150字节的数据,其中包含第一个数据包和第二个数据包的一部分。你将不得不再次调用接收来获取第二个消息的剩余部分,所以要记录下你已经接收到多少数据,以免在某个地方丢失部分数据包。这就是为什么保持数据包大小相同很有用的原因。

还有一些其他有用的技巧可以减小消息的大小和频率,并跟踪实时非回合制游戏,但如果你的游戏是回合制的,那么正确的做法可能是使用TCP,不必担心其他问题。以下是一些有用的网站和文章链接,可以提供更多关于游戏网络编程的信息:

  • Glenn Fiedler's site,这里有很多很棒的信息。
  • 1500 archers,一篇关于如何实现一种称为确定性锁步的技术的优秀论文,对许多类型的游戏都很有用。

如果你想了解更多细节或有更具体的问题,请告诉我。


2
一种可能的架构方法是将游戏的一个实例作为主机(例如,第一个启动的实例)。它会协调游戏并向每个其他玩家发送游戏状态和回合信息。
当玩家进行移动时,它会将移动信息发送给主机,主机会更新游戏状态(并检查移动的有效性等)。然后,它会向每个玩家发送新的游戏状态,并通过另一种通信方式(可能作为单独的通知)将下一轮通知发送到适当的客户端,并等待其响应。
在某种意义上,主机在这种情况下充当游戏服务器,但由于不需要运行单独的进程来玩游戏,因此可能更简单易用/玩。

1

如果您想在网络上添加多人游戏功能,那么看一下Netty项目来构建通信基础设施可能会对您有所帮助。

但在此之前,您需要确保您的游戏具有正确的“架构”。您需要有两个大模块:客户端和服务器。

服务器负责所有游戏逻辑。从某种意义上说,它是游戏引擎。客户端负责查询服务器以获取游戏状态,将其显示给玩家,获取玩家输入并向服务器发送命令。

一种在不涉及学习网络编程的情况下解耦客户端和服务器代码的方法是拥有两个从CLI运行的不同程序。执行顺序可以如下:

  1. 运行服务器以初始化游戏状态,将游戏状态保存在文件中。
  2. 运行客户端,它读取游戏状态,以某种方式显示它并从玩家那里获取一些输入。它将该输入保存到文件中。
  3. 运行服务器,它从客户端读取命令,更改游戏状态并更新状态文件。

反复执行。这基本上就是PBEM服务器所做的事情。

为了解耦客户端和服务器,定义一种“语言”来表示游戏状态的变化以及服务器执行的命令是有意义的。如果客户端缓存状态并根据服务器发送的更改应用更改,则会有所帮助。
一旦您的客户端和服务器代码完全解耦,并且“语言”已经完全定义,您就可以将通信机制从基于文件的更改为基于套接字的通信。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接