去年下半年,我正在考虑如何将多人游戏添加到我的游戏Pathos中。 有超过20万行代码,因此重写以引入典型的网络抽象将非常耗时。 我需要一个横向解决方案,以保留我的代码投资。 您如何将单人游戏变成多人游戏而无需从头开始?
我决定刚开始并建立一个分屏模式。 这使我得以解决与拥有多个播放器有关的许多代码问题。 这是一个相对容易的重构,并且如果您不介意在平板电脑上使用BFF可以很好地工作。
下一步是探索屏幕共享软件,例如TeamViewer。 这意味着现在可以在互联网上玩分屏游戏。 看到其他玩家在行动很有趣,但确实花费了一半的屏幕空间。 但是主要的局限性是缺乏对两只小鼠的支持。 最后,我没有找到在这个领域能很好工作的技术或产品。
我必须设法解释一下,我的游戏是基于一个名为发明的用户界面抽象库构建的。 这是为Android,iOS和Windows构建本机应用程序的早期决定,但仍具有适当的代码共享。 感谢C#.NET,Mono和Xamarin,该项目进展顺利。
使用TeamViewer取得的适度成功让我开始思考如何编写自己的远程投影代码。 在突破实现之前,它激怒了我几周:
我可以使用相同的技术,并构建与本地平台同级的远程平台!
我将此远程平台命名为“服务器”,我将尽力解释其工作原理。 这既令人惊讶,又难以解释。
应用程序代码完全在服务器端运行。 服务器侦听客户端通过TCP / IP套接字进行连接。 服务器上托管的每个客户端都有一个单独的应用程序实例。 客户端很瘦,并且将服务器消息映射到本机控件,并且没有特定的应用程序逻辑。 我们正在谈论的是图形Telnet 。
该应用程序正在服务器中运行,并且用户界面从套接字向下投射到客户端应用程序。 对于最终用户,远程应用程序的行为类似于任何其他本机应用程序。
服务器平台也是可移植的代码,这意味着您可以在Android,iOS或Windows中托管。 它不是对等的,但是您也不需要中央服务器。 您可以在iPhone上托管游戏,如果您不介意防火墙端口转发,则您的朋友可以通过本地WiFi甚至互联网与他们的Android平板电脑一起加入。
服务器状态很容易在客户端应用程序之间共享,因为它只是内存中的对象。 应用程序代码不处理任何进程间通信或消息传递协议。 您甚至可以坚持使用单线程,以避免出现关键部分的麻烦。
通信协议使用类似于.NET BinaryWriter / Reader API的紧凑二进制格式。 数据包的实际发送和接收是通过异步队列进行的。 图像和声音效果之类的资产会根据需要发送到客户端,然后进行缓存。
首次连接时,服务器执行版本握手,以检测协议中的重大更改。 不兼容的客户端将立即断开连接,必须升级。 可以想象,您可以部署通用客户端应用程序(即图形Telnet客户端)以连接到作为服务器运行的任何发明应用程序。
对用户界面的Delta更改将整理成数据包并以每秒60次的速度发送到客户端。 如果服务器上的用户界面没有更改,则不会发送任何消息。
此远程平台有一个重要限制。 如果您的应用具有60 FPS的动画,那么我们需要能够每16ms发送一个数据包。 这意味着客户端和服务器之间的可容忍延迟小于32ms。 如果延迟超过此最大值,则将丢失帧和滞后峰值。 当然,强度较低的应用程序可以应付更高的延迟。 无论如何都比大多数Web应用程序好或更好!
让我们考虑一个按钮示例,该按钮在用户点击时从灰色变为蓝色。
var Surface = Application.Window.NewSurface();
var Button = Surface.NewButton();
Surface.Content =按钮;
Button.Alignment.Center();
Button.Margin.Set(5);
Button.Padding.Set(5);
Button.Background.Colour = Inv.Colour。 暗灰色 ;
var Label = Surface.NewLabel();
Button.Content =标签;
Label.JustifyCenter();
Label.Font.Size = 30;
Label.Font.Colour = Inv.Colour。 白色 ;
Label.Text = “连接” ;
Button.SingleTapEvent + =()=>
{
Button.Background.Colour = Inv.Colour。 道奇蓝 ;
Label.Text = “连接” ;
};
Application.Window.Transition(Surface).Fade();
基本用户界面在以下两种状态下看起来像这样:

将通信协议视为用户界面更新的连续流可能会有所帮助。 类似于telnet协议中的字符和换行符。 以下是消息的文本表示形式,以包边界(==)分隔。
==发送到客户端/从服务器接收==
var S1 = Application.Window.NewSurface();
var P1 = S1.NewButton();
P1.Alignment.Center();
P1.Margin.Set(5);
P1.Padding.Set(5);
P1.Background.Colour = Inv.Colour。 深灰色 ;
var P2 = S1.NewLabel();
P2.JustifyCenter();
P2.Font.Size = 30;
P2。字体颜色= Inv.Colour。 白色 ;
P2.Text = “连接” ;
P13。内容= P14;
Application.Window.Transition(S1).Fade();
==从客户端/发送到服务器接收==
P1.SingleTap();
==发送到客户端/从服务器接收==
P1.Background.Colour = Inv.Colour.DodgerBlue;
P2.Text = “连接” ;
客户端从服务器读取消息并更新用户界面以使其匹配。 当最终用户点击一个按钮时,客户端会将这些消息发送到服务器。 服务器通过执行应用程序代码来响应这些消息,从而导致更多的用户界面更新以及更多消息发送到客户端。