恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
HelloNetcode Relay Server 样例:基于 Unity Relay 服务的 Netcode 联机接入实现
首页
资讯中心
/
HelloNetcode Relay Server 样例:基于 Unity Relay 服务的 Netcode 联机接入实现
HelloNetcode Relay Server 样例:基于 Unity Relay 服务的 Netcode 联机接入实现
发布时间:2026/9/16 11:42:41
HelloNetcode Relay Server 样例基于 Unity Relay 服务的 Netcode 联机接入实现【免费下载链接】EntityComponentSystemSamples项目地址: https://gitcode.com/GitHub_Trending/en/EntityComponentSystemSamples本文以 HelloNetcode 示例集中的 Relay Server 样例为对象完整解析 Netcode 项目如何集成 Unity Relay 中继服务实现 NAT 穿透联机从 Frontend 场景的 UI 交互、Host 与 Client 两条异步状态机流程到RelayServerData的构造与驱动层Driver的注册机制最终帮助读者掌握在 Unity Netcode基于 Entities/NetStream项目中接入 Relay 的完整链路。样例定位与前置要求Relay Server 样例位于 RelayServer.md 所描述的目录中是 HelloNetcode 基础样例集里专门用于演示中继服务器集成的场景。Relay 是 Unity 提供的托管服务客户端通过中继节点与主机通信从而绕过 NAT 直接连接无法达成的网络环境。运行该样例的前提对应原文档Requirements已为项目完成 Relay 服务的接入配置即按照 Unity 官方 Relay 文档的 Get started with Relay 流程创建 Unity 服务、开通 Relay 产品并获得可用的服务配置涉及 Unity Services 的初始化与匿名登录能力。项目中包含 Unity NetcodeUnity.NetCode、Unity TransportUnity.Networking.Transport及其 Relay 扩展、Unity Relay 服务包Unity.Services.Relay、Authentication 服务包与 Services 核心包。样例源码的using引用可以直接印证这些依赖HostServer.cs 顶部引用了Unity.Services.Relay、Unity.Services.Authentication、Unity.Services.Core与Unity.Networking.Transport.Relay命名空间。样例的目录结构如下文件职责NetcodeSetup/RelayFrontend.csFrontend 场景 HUD负责按钮行为、Host/Client 设置流程编排与状态显示NetcodeSetup/RelayDriverConstructor.cs自定义驱动构造器按 Relay 配置注册 UDP/WebSocket/IPC 驱动NetcodeSetup/RelayHUD.cs对局内 HUD定义JoinCode组件并显示 join codeHostServer.csECS 系统负责联系 Relay 服务、分配会话并生成 join codeConnectingPlayer.csECS 系统负责用 join code 加入 Relay 会话EnableRelayServer.cs启用组件控制样例系统是否运行RelayUtilities.cs按连接类型dtls/wss筛选 Relay 端点的工具方法RelayFrontend.unity、RelayHUD.unity入口场景与对局场景UI 交互流程Relay 开关与 Join Code 输入框该样例通过 Frontend 场景RelayFrontend.unity中的Relay Server 复选框与按钮联动。按照 RelayServer.md 的描述勾选Relay Support后点击Start Client Server会启动一个启用 Relay 的主机随后客户端通过 Relay 服务连入该主机。Join Existing Game按钮用于把客户端连入一个已存在的 Relay 会话即加入现有游戏。Relay Server 复选框旁会显示状态文本启动与初始化 Relay 服务过程中若出错界面显示失败信息具体细节输出到控制台日志。切换 Relay 开关时地址/端口输入区会被替换为单个输入框用于填写 join code——这是经 Relay 服务连入主机会话所需的全部信息。这些行为在 RelayFrontend.cs 的OnRelayEnable方法中实现开启时隐藏端口输入Port.gameObject.SetActive(false)、启用本地连接是否也走 Relay的切换、清空输入框并把占位文本改为 Enter Join Code...关闭时恢复地址输入默认回填127.0.0.1。此外Update()中有一段交互约束逻辑Relay 开启且 join code 输入框非空时会禁用 Client Server 按钮因为此时用户意图是加入会话而非自建会话RelayFrontend.cs。RelayFrontend用ConnectionState状态机SetupHost → SetupClient → JoinGame/JoinLocalGame → Unknown在每帧Update()中推进流程当 Host 侧系统返回有效的RelayServerData.Endpoint、或 Client 侧RelayClientData.Endpoint有效时才进入下一步启动世界World与驱动连接RelayFrontend.cs。这正是原文档所说状态消息显示在复选框旁、错误细节进控制台的实现方式各WaitForXxx方法在 Task 失败时Debug.LogError/LogException并经由UIBehaviour.HostConnectionStatus/ClientConnectionStatus属性把短状态写回 UI 文本。Host 侧流程HostServer 的异步状态机HostServer.cs 的系统注释完整列出了主机接入 Relay 的五个步骤初始化服务UnityServices.InitializeAsync()匿名登录AuthenticationService.Instance.SignInAnonymouslyAsync()分配会话RelayService.Instance.CreateAllocationAsync(RelayMaxConnections)样例中RelayMaxConnections 5即最多 5 个外部 peer 连接、连同主机共 6 名玩家获取 join codeRelayService.Instance.GetJoinCodeAsync(allocation.AllocationId)获取 Relay 服务器信息IP、端口等构造RelayServerData系统以HostStatus标志枚举驱动状态机OnUpdate()每帧轮询各Task的完成状态把上述 5 步串起来HostServer.cs// HostServer.cs 中的状态推进节选 case HostStatus.InitializeServices: m_InitializeTask UnityServices.InitializeAsync(); m_HostStatus HostStatus.Initializing; break; case HostStatus.SigningIn: m_HostStatus WaitForSignIn(m_SignInTask, out m_AllocationTask); break; case HostStatus.Allocating: m_HostStatus WaitForAllocations(m_AllocationTask, out m_JoinCodeTask); break; case HostStatus.GetRelayData: m_HostStatus BindToHost(m_AllocationTask, out RelayServerData); break;其中几个关键实现细节值得注意系统激活条件HostServer标注[DisableAutoCreation]并在OnCreate()中RequireForUpdateEnableRelayServer()HostServer.cs。EnableRelayServer是一个空的IComponentData结构体EnableRelayServer.cs——HelloNetcode 的惯例是只有场景或运行时里存在某个样例的启用组件实体该样例的系统才会运行避免所有样例系统同时被驱动。RelayFrontend在HostServer()与JoinAsClient()中都会手动CreateEntity加上该组件RelayFrontend.cs。连接类型选择非 WebGL 平台使用dtlsWebGL 使用wssHostServer.cs。源码注释明确说明connectionType也支持udp但不推荐。RelayServerData的 nonce 语义主机在HostRelayData中把自己的ConnectionData传两次自身既是主机又是连接方客户端则传自己的 ConnectionData 主机的 HostConnectionData见下文。端点选择由 RelayUtilities.GetEndpointForConnectionType 从allocation.ServerEndpoints列表中按ConnectionType过滤得到。// HostServer.HostRelayData构造主机侧 RelayServerData节选 var endpoint RelayUtilities.GetEndpointForConnectionType(allocation.ServerEndpoints, connectionType); var isWebSocket connectionType wss || connectionType ws; var relayServerData new RelayServerData(endpoint.Host, (ushort)endpoint.Port, allocation.AllocationIdBytes, allocation.ConnectionData, allocation.ConnectionData, allocation.Key, endpoint.Secure, isWebSocket);HostServer.csClient 侧流程ConnectingPlayer 用 Join Code 加入会话ConnectingPlayer.cs 是客户端侧的对应系统同样以RequireForUpdateEnableRelayServer()门控通过ClientStatus状态机推进WaitForInit → WaitForSignIn → WaitForJoin → Ready或FailedToConnect。核心调用是// ConnectingPlayer.JoinUsingJoinCode把 join code 发给 Relay 服务 joinTask RelayService.Instance.JoinAllocationAsync(hostServerJoinCode);ConnectingPlayer.csjoin code 有两个来源对应样例 UI 的两种玩法本机同进程自测RelayFrontend在SetupClient()之后调用m_HostClientSystem.GetJoinCodeFromHost()系统直接读取本机HostServer系统已生成的JoinCodeConnectingPlayer.cs。模拟远端玩家用户在 Frontend 输入框里填写 host 展示出来的 join codeJoinAsClient()将输入值传入JoinUsingCode先做UnityServices.InitializeAsync()与匿名登录再发起JoinAllocationAsyncRelayFrontend.cs。加入成功后PlayerRelayData把JoinAllocation转换为客户端视角的RelayServerData注意此处ConnectionData与HostConnectionData的顺序与主机侧不同——客户端携带自己的连接凭据和主机的连接凭据var relayServerData new RelayServerData(endpoint.Host, (ushort)endpoint.Port, allocation.AllocationIdBytes, allocation.ConnectionData, allocation.HostConnectionData, allocation.Key, endpoint.Secure, connectionType wss);ConnectingPlayer.cs驱动层RelayDriverConstructor 如何按配置选择传输拿到两侧RelayServerData后真正的网络驱动由自定义的INetworkStreamDriverConstructor实现 RelayDriverConstructor.cs 创建。其 XML 注释给出了清晰的决策表Mode | Relay Settings Client/Server | Valid - 用 relay 连接本地服务器 | Invalid - 用 IPC 连接本地服务器 Client | 总是走 relay期望数据有效CreateClientDriver若请求的是 ClientAndServer 模式且客户端 Relay 数据无效Endpoint.IsValid为假回退到RegisterClientIpcDriver本机进程内 IPC否则对RelayServerData执行settings.WithRelayParameters(...)后注册 UDP 驱动WebGL 平台则注册 WebSocket 驱动RelayDriverConstructor.cs。CreateServerDriver主机同时注册两个驱动——第一个是 IPC供本机内部客户端使用第二个在相同端口上以 Relay 参数监听承接外部经中继进来的连接RelayDriverConstructor.cs。RelayFrontend.SetupRelayHostedServerAndConnect()展示了这套构造器如何被临时挂载先保存旧的NetworkStreamReceiveSystem.DriverConstructor替换为new RelayDriverConstructor(relayServerData, relayClientData)调用ClientServerBootstrap.CreateServerWorld(ServerWorld)/CreateClientWorld(ClientWorld)手动创建服务器与客户端 World再恢复原构造器RelayFrontend.cs。随后服务端驱动执行Listen(NetworkEndpoint.AnyIpv4)客户端驱动若持有有效relayClientData则Connect(client.EntityManager, relayClientData.Value.Endpoint)走 Relay否则回退连接 IPC 端点ipcLocalEndPointRelayFrontend.cs。若 Host 与 Client 位于不同构建/机器纯客户端场景则走ConnectToRelayServer()创建客户端 World 后写入NetworkStreamRequestConnect实体Endpoint指向 Relay 端点。注释特别说明连接不会立即绑定Request 结构体会让传输层持续轮询直到连接建立RelayFrontend.cs。Join Code 的展示JoinCode 组件与 RelayHUD原文档提到join code 会显示在主机对局的左上角。这一功能由 RelayHUD.cs 实现public struct JoinCode : IComponentData { public FixedString64Bytes Value; } public class RelayHUD : MonoBehaviour { public void Awake() { var world World.All[0]; var joinQuery world.EntityManager.CreateEntityQuery(ComponentType.ReadOnlyJoinCode()); if (joinQuery.HasSingletonJoinCode()) { var joinCode joinQuery.GetSingletonJoinCode().Value; JoinCodeLabel.text $Join code: {joinCode}; } } }数据链路上RelayFrontend.SetupRelayHostedServerAndConnect()在服务端 World 中创建携带JoinCodeFixedString64Bytes的单例实体RelayFrontend.cs并SceneManager.LoadScene(RelayHUD, LoadSceneMode.Additive)附加加载 HUD 场景RelayHUD.Awake()查询该单例组件并写入 UI 文本完成主机生成 join code → 写入实体 → HUD 读取展示的闭环。设计决策两种 Relay 连接测试模式对应原文档Design decisions一节样例刻意提供两种可切换的验证方式Frontend 菜单中的UseRelayForLocalConnectionToggle 用于选择见 RelayFrontend.cs自托管 Relay 只用于入站主机经 Relay 对外提供连接但同进程内的客户端走 IPC 直连本地服务器。这是最常见的我既是 host 又当玩家场景能验证主机双驱动注册IPC Relay的正确性。模拟远端客户端同进程客户端也强制走 Relay 链路连入本地主机端到端验证客户端拿 join code → JoinAllocation → 经中继连接主机的完整路径。RelayFrontend的ConnectionState.SetupClient分支根据m_UseRelayForLocalClient决定是否调用SetupClient()并等待客户端 Relay 数据就绪再进入JoinLocalGame分支启动双 WorldRelayFrontend.cs模式 2 的纯客户端路径则由JoinGame分支中的ConnectToRelayServer()完成。Web 构建约束原文档Web build constraints指出Web 构建下未勾选 Relay 前Start Client Server 按钮保持禁用——因为 WebGL 无法以 UDP 直接对外监听只能经 RelayWebSocket联机。源码中两处实现相互印证RelayFrontend.csStart()中#if UNITY_WEBGL时ClientServerButton.interactable falseOnRelayEnable中勾选 Relay 时ClientServerButton.interactable true取消勾选时 WebGL 下恢复禁用RelayFrontend.cs。传输层的对应关系是RelayDriverConstructor与HostServer/ConnectingPlayer都通过#if !UNITY_WEBGL区分dtls/UDP 与wss/WebSocket 两条路径RelayDriverConstructor的注释还提到在 Editor 中 WebGL 的客户端始终优先使用 WebSocket以尽量贴近打包后的玩家行为。另外RelayFrontend基类在UNITY_SERVER专用服务器构建下退化为普通MonoBehaviourUI 相关逻辑被#if !UNITY_SERVER条件编译隔离说明该 Frontend 面向带客户端的构建专用服务器只复用其核心流程。Play Mode 下的延迟现象原文档附有一条值得保留的使用提示在 Play Mode 中启用 Relay 后对局场景的加载会比不启用时更慢。该延迟来源于到 Relay 服务的往返时间RTT——HostServer与ConnectingPlayer两个系统分别需要完成服务初始化 → 匿名登录 → 分配/加入会话 → 获取 join code → 拉取 Relay 端点的多轮异步往返且RelayFrontend的Update()状态机必须等到RelayServerData.Endpoint/RelayClientData.Endpoint有效后才会创建 World 并启动驱动连接因此这部分网络耗时直接体现在场景启动前的等待上。小结HelloNetcode 的 Relay Server 样例用一套紧凑而完整的代码展示了 Netcode 接入 Unity Relay 的标准链路HostServer/ConnectingPlayer两个门控 ECS 系统分别封装主机分配与客户端加入的异步状态机RelayServerData含双份ConnectionData的 nonce 约定、dtls/wss 端点选择在RelayDriverConstructor中驱动 UDP/WebSocket/IPC 三类传输的注册EnableRelayServer组件与JoinCode单例实体则承担了样例开关与 join code 传递的职责。理解这条服务层Unity Relay SDK→ 数据层RelayServerData→ 传输层DriverConstructor→ 表现层Frontend/HUD的四层结构后即可将其作为自研多人联机项目的 Relay 接入参考实现。【免费下载链接】EntityComponentSystemSamples项目地址: https://gitcode.com/GitHub_Trending/en/EntityComponentSystemSamples创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考