Urho3D网络 您所在的位置:网站首页 脚本引擎响应数据为空 Urho3D网络

Urho3D网络

#Urho3D网络| 来源: 网络整理| 查看: 265

网络子系统使用SLikeNet提供可靠和不可靠的UDP消息传递。可以创建一个侦听传入连接的服务器,并且可以与服务器建立客户端连接。连接后,服务器上运行的代码可以将客户端分配到场景中以启用场景复制,前提是在连接时,客户端为接收更新指定了一个空白场景。

场景复制是单向的:服务器始终具有权限,并以固定的更新速率(默认为30 FPS)向客户端发送场景更新。客户端通过以固定速率发送控件更新(按钮、偏航和俯仰+可能的额外数据)进行响应。

服务器和客户端之间的双向通信可以使用原始网络消息(二进制序列化数据)或远程事件(与普通事件类似,但仅在接收端处理)进行。服务器上的代码可以向一个客户端、分配到特定场景的所有客户端或所有连接的客户端发送消息或远程事件。相反,客户端只能向服务器发送消息或远程事件,而不能直接向其他客户端发送。

请注意,如果特定的网络应用程序不需要场景复制,也可以在不将客户端分配给场景的情况下传输网络消息和远程事件。聊天示例就是这样做的:它不会在服务器或客户端上创建场景。

连接到服务器

启动服务器和连接服务器都是通过网络子系统进行的。请参阅StartServer()和Connect()。必须选择UDP端口;示例使用端口2345。

注意连接时作为参数提供的场景(用于复制)和标识VariantMap。身份数据可以包含例如用户名或凭据,它完全由应用程序指定。身份数据在连接后立即发送,并在收到时在服务器上发送E_CLIENTIDENTITY事件。通过订阅此事件,服务器代码可以检查传入连接并接受或拒绝它们。默认值是接受所有连接。

连接成功后,客户端代码可以获取表示服务器连接的Connection对象,请参阅GetServerConnection()。同样,在服务器上,将为每个连接的客户机创建一个Connection对象,这些对象可以迭代。此对象用于向远程对等端发送网络消息或远程事件,将客户端分配到场景(仅在服务器上),或断开连接。

NAT穿透

网络子系统可以使用NAT穿孔功能。这需要NAT穿透主服务器在公共主机上运行。要设置它,首先必须将IP地址或域名告诉网络子系统,然后通过主服务器进行NAT穿孔,为此,请调用SetNATServerInfo()。

服务器:应首先调用StartServer()启动服务器,然后调用StartNATClient()。如果连接到NAT punchrough主服务器成功,将生成唯一的GUID。客户端应使用此生成的GUID连接到此服务器。

客户端:当服务器成功启动并连接到NAT punchrough主服务器时,客户端应通过调用AttemptNATPunchtrough()连接到服务器,并传递服务器生成的GUID。

有关演示,请查看示例52_NATPunchout。

局域网发现

网络子系统支持LAN发现模式来搜索正在运行的服务器。创建服务器时,您可以设置当某人启动LAN发现模式时应向网络发送哪些数据,请参阅SetDiscoveryBeacon()。此数据可以包含有关服务器的任何信息。要启动LAN发现模式,应调用DiscoveryHosts()。找到服务器后,将发送“NetworkHostDiscovered”事件。

有关演示,请查看示例53_LANDiscovery。

场景复制

   场景内容的网络复制已使用属性以简单的方式实现。尚未在本地模式下创建的节点和组件(请参见CreateChild()或CreateComponent()的CreateMode参数)将自动复制。请注意,创建到本地节点的复制组件不会被复制,因为首先检查节点的位置。

  CreateMode转换为两个不同的节点和组件ID范围-复制ID的范围从0x1到0xffffff,而本地ID的范围是0x1000000到0xffffff。这意味着场景中最多有16777215个复制节点或组件。

如果场景最初是从服务器上的文件加载的,客户端也将首先从同一文件加载场景。在这种情况下,所有预定义的静态对象(如世界几何体)都应定义为本地节点,以便在初始更新期间不会不必要地通过网络重新传输,并且不会耗尽更有限的复制ID范围。

可以使服务器向客户端发送所需的资源包。这需要通过调用AddRequiredPackageFile()将包文件附加到场景。在客户端上,必须在接收包之前选择包的缓存目录。这是可能的:请参阅SetPackageCacheDir()。

有一些事情需要注意:

    将客户端指定给场景后,客户端将首先从场景中删除所有现有的复制场景节点,以准备从服务器接收对象。这意味着,例如,应将客户端的相机创建到本地节点中,否则在连接时会将其删除。

    连接到服务器后,客户端不应自行创建、更新或删除非本地节点或组件。然而,为了创建客户端特殊效果等,客户端可以自由操作本地节点。

    节点的用户变量VariantMap将在每个变量的基础上自动复制。这在传输由多个组件共享的数据时非常有用,例如球员的得分或健康状况。

     为了实现插值,在客户端上启用了节点渲染变换的指数平滑。它可以由场景的两个财产控制,平滑常数和捕捉阈值。捕捉阈值是网络更新之间的距离,如果超过该距离,将导致节点立即捕捉到终点位置,而不是平滑移动。请参见SetSmoothingConstant()和SetSnapThreshold()。

     位置和旋转是节点属性,而线性和角速度是刚体属性。为了减少所需的网络带宽,可以将物理组件创建为服务器上的本地组件:在这种情况下,客户端根本看不到它们,并且只会根据节点的变换变化来插值运动。复制实际物理组件允许客户端使用自己的物理模拟进行推断,并执行冲突检测,尽管总是非权威性的。

     默认情况下,当渲染帧速率高于物理FPS时,物理模拟还执行插值以启用平滑运动。这应该在服务器场景中禁用,以确保客户端不会接收插值,因此可能不会接收非物理位置和旋转。请参见设置插值()。

    AnimatedModel本身不会复制动画。相反,AnimationController将复制其命令状态(例如“淡入此动画,以1.5倍的速度播放该动画。”)若要禁用动画复制,请将AnimationController创建为本地。为了确保正确接收第一次动画更新,请始终先创建AnimatedModel组件,然后创建AnimationController。

    网络属性可以是增量更新或最新数据模式。增量更新是小的增量更改,必须按顺序应用,如果网络消息传递出现停滞(例如由于数据包丢失),这可能会导致延迟增加。位置、旋转和速度等高容量数据作为最新数据传输,不需要排序,相反,此模式只需丢弃任何无序接收的旧数据。注意,节点和组件的创建(当需要发送初始属性时)和删除也可以被视为增量更新,因此按顺序应用。

   为了避免在发送网络更新时遍历整个场景,节点和组件在必要时明确标记自己以进行更新。编写自己的复制C++组件时,在修改任何网络属性的成员函数中调用MarkNetworkUpdate()。

   服务器更新逻辑对复制消息进行排序,以便父节点在其子节点之前创建和更新。远程事件排队,仅在复制更新后发送,以确保如果它们来自新创建的节点,则接收端已存在该节点。然而,也可以为远程事件指定无序传输,在这种情况下,保证不成立。

   节点具有所有者连接的概念(例如,控制特定游戏对象的玩家),可以在服务器代码中设置。此属性不会复制到客户端。消息或远程事件可以用来告诉玩家他们控制的对象。

   如果要为本地连接的客户端和远程客户端运行相同的服务器逻辑,可以同时使用网络子系统中的服务器和客户端功能。但是,在这种情况下,您需要场景的两个副本:服务器和客户端。仅应在本地客户端上渲染客户端场景,而服务器场景仅用于模拟。

利益管理

场景复制包括用于减少带宽使用的简单、基于距离的兴趣管理机制。要使用,请将NetworkPriority组件创建到您希望对其应用兴趣管理的节点。该组件可以创建为本地组件,因为它对客户端不重要。

该组件有三个参数用于控制更新频率:基本优先级、距离因子和最小优先级。

当前优先级值在每次服务器更新时计算为“基本优先级-距离因子*距离”。此外,它不能低于最低优先级。然后将该值添加到更新累加器中。每当更新累加器达到100.0时,将发送节点及其组件的属性更改,并重置累加器。

默认值为基本优先级100.0、距离因子0.0和最小优先级0.0。这意味着默认情况下总是发送更新(如果节点没有NetworkPriority组件,也是如此)。此外,还有一条规则,即节点的所有者连接总是以全频率接收更新。此规则可以通过调用SetAlwaysUpdateOwner()来控制。  

计算距离需要客户端告知其当前观察者的位置(通常是摄像机或玩家角色的世界位置)。这是通过客户端代码在服务器连接上调用SetPosition()来实现的。客户端还可以通过调用SetRotation()来告知其当前的观察者旋转,但这只对自定义逻辑有用,因为NetworkPriority组件不使用它。

目前,节点的创建和删除总是立即发送,而无需咨询利益管理。这是基于节点的运动更新消耗最多带宽的假设。

客户端控件更新

Controls结构用于将控制信息从客户端发送到服务器,默认情况下也为30 FPS。这包括按下按钮,这是一个应用程序定义的32位位字段、浮点偏航和俯仰,以及存储在VariantMap中的可能的额外数据(例如当前选定的武器)。

通过在服务器连接上调用SetControls(),客户端代码可以确保它们保持最新。事件E_NETWORKUPDATE将被发送以提醒即将更新,事件E_NETORKUPDATESENT将在更新后发送。然后可以通过调用GetControls()在服务器端检查控件。

控件更新消息还包括一个运行的8位时间戳,请参阅GetTimeStamp(),以及用于兴趣管理的客户端观察者位置/轮换。为了节省带宽,如果从未分配过位置和旋转值,则会忽略它们。

客户端预测

Urho3D没有为玩家可控制的对象实现内置的客户端预测,因为很难倒带和重新模拟通用物理模拟。然而,当不使用物理进行玩家移动时,可以拦截来自服务器的权威网络更新,并在应用程序级别上构建预测系统。

通过调用SetInterceptNetworkUpdate(),将重定向单个网络属性的更新以发送事件(E_INTERCEPTNETWORKUPDATE),而不是直接应用属性值。对于要预测的节点或组件,应在客户端上调用该命令。例如,要重定向节点的位置更新:

    node->SetInterceptNetworkUpdate("Network Position", true);

该事件包括属性名称、索引、作为变量的新值以及服务器从客户端看到的最新8位控件时间戳。通常,事件处理程序将存储从服务器到达的值,并设置一个内部“更新到达”标志,应用程序逻辑更新代码可以稍后在同一帧上使用该标志,方法是获取服务器发送的值并在其上重播任何用户输入,以及需要重放多少输入。

原始网络消息

所有网络消息都有一个整数ID。您可以用于自定义消息的第一个ID是153(较低的ID保留给SLikeNet或网络子系统的内部使用。)消息可以不可靠或可靠地按顺序或无序发送。数据有效载荷是简单的原始二进制数据,可以通过使用VectorBuffer来制作。

要向Connection发送消息,请使用其SendMessage()函数。在服务器上,还可以通过调用BroadcastMessage()函数向所有客户端连接广播消息。

当接收到一条消息,且该消息不是内部协议消息时,它将作为E_NETWORKMESSAGE事件转发。有关发送和接收的详细信息,请参阅聊天示例。

为了获得高性能,请考虑使用无序消息,因为对于有序消息,连接中只有一个通道,所有先前的有序消息必须先到达,然后才能处理新的消息。

远程事件

远程事件由其事件类型(名称散列)、告诉是按顺序发送还是无序发送的标志以及事件数据VariantMap组成。可以选择将其设置为源自接收器场景中的特定节点(“远程节点事件”)

要将远程事件发送到Connection,请使用其SendRemoteEvent()函数。要同时向多个连接广播远程事件(仅限服务器),请使用Network的BroadcastRemoteEvent()函数。

为了安全起见,必须注册允许的远程事件类型。请参阅RegisterRemoteEvent()。注册仅影响接收事件;总是允许发送任何事件。Source/Urho3D/Network/Network.cpp中定义了一个固定的事件类型黑名单,这些事件类型会带来安全风险,并且永远不允许注册接收;例如E_CONSOLECOMMAND。

与普通事件一样,为了方便起见,脚本中的远程事件类型是字符串而不是名称散列。

远程事件始终将原始连接作为事件数据中的参数。以下是如何在C++和脚本中获得它(在C++中,包括NetworkEvents.h):

C++:

   using namespace RemoteEventData;

   Connection* remoteSender = static_cast(eventData[P_CONNECTION].GetPtr());

脚本:

   Connection@ remoteSender = eventData["Connection"].GetPtr();

HTTP请求

除了UDP消息传递,网络子系统还允许发出HTTP请求。为此,请使用MakeHttpRequest()函数。您可以指定URL、要使用的动词(如果为空,则默认为GET)、可选的标头和可选的post数据。返回的HttpRequest对象类似于反序列化器,您可以以适当大小的块读取响应数据。读取整个响应后,连接关闭。通过允许请求对象过期,也可以提前关闭连接。

网络条件模拟

网络子系统可以选择性地增加发送分组的延迟,以及模拟分组丢失。请参阅SetSimulatedLatency()和SetSimulatedPacketLoss()。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有