跳到主要内容

游戏服务器托管

最后更新:2026 年 9 月 15 日

游戏服务器是你编写的程序,用来持有一个或多个活动房间——即玩家真正进行游戏的权威实例。服务器 SDK 提供 Room 基类承载你的逻辑,以及一个 RoomHost,它运行你的房间、把它们注册到 PlayServ、用心跳维持存活,并在结束时下线。把玩家送到房间是客户端的事(匹配与房间、连接到游戏);本页讲的是拥有房间的那个服务器。

服务器 SDK,而非游戏客户端

这是服务器端 SDK(PlayServ.Sdk.Rooms),由你部署为游戏服务器的程序使用,而不是游戏客户端。它以服务器身份认证,因此不受面向客户端的表权限限制。游戏服务器既可以是平台替你启动的**托管(managed)实例,也可以是你自己运行的外部(external)**进程;两者用同一个 SDK。


游戏服务器的构成​

两个类型完成工作:Room 的子类承载你的游戏,RoomHost 运行该类型的房间。

RoomHost运行你的房间Room你的逻辑注册表座位 · 心跳客户端浏览 · 进房

一个最小房间​

你用自己的玩家类型和配置类型继承 Room<TPlayer, TConfig>。基类管理花名册(AddPlayer、RemovePlayer、Players),并暴露供你重写的生命周期钩子。

using PlayServ.Sdk.Rooms;

public sealed class MyPlayer : RoomPlayer { }
public sealed record MyConfig;

public sealed class MyRoom : Room<MyPlayer, MyConfig>
{
protected override void OnPlayerJoined(MyPlayer p) { /* 安排座位 */ }
protected override void OnPlayerLeft(MyPlayer p) { /* 释放座位 */ }
protected override void OnTick(float dt) { /* 推进房间 */ }
}

RoomHost 通过一个工厂运行该类型的房间:

var host = new RoomHost<MyRoom, MyPlayer, MyConfig>(name => new MyRoom());
var room = host.GetOrCreate("lobby-1");

这已经是一个可用的权威服务器:host 会接好心跳、注册房间、应用从平台拉取的配置,并为你管理房间生命周期。


自注册与心跳​

你不需要用单独的调用把房间告知平台——host 会自动注册每个房间,并以周期性心跳保持其最新,上报房间的占用与容量。这正是客户端房间浏览器所读取的信号,因此房间一运行就可被发现,而在服务器停止为其发送心跳后不久便会从浏览中消失。


房间配置由平台所有​

房间设置——容量、预订有效期、房间生命周期、空闲超时,以及每服务器的 max_rooms 配额——都由平台所有、在 Backoffice 中编辑,而不是随代码或清单发布。host 会把实时配置拉下来并随其变化应用,因此运营者无需重新部署即可重新调优某个房间类型。

// 容量与上限来自平台,而非构造函数——
// 从房间读取它们,而不要假设你在代码里设的值
备注

由于配置是拉取的,在 Backoffice 改容量或某个超时,会在一个心跳内对运行中的服务器生效。不要把这些值写死;把平台下发的当作事实来源。


上报房间在哪​

客户端需要知道如何抵达房间。你的服务器用 DeclareConnect 声明它的连接信息——主机、端口、传输,以及可选的连接串与区域——平台会在客户端的预订中把它们返回给客户端。

DeclareConnect(new RoomConnect { Host = publicHost, Port = port, Transport = "udp" }, region: "eu");

当平台替你启动了服务器(托管实例)时,连接信息可以自动从编排器的环境填入;当你运行自己的进程时,由你来声明。


房间的生命周期​

房间会经历一个小而明确的生命周期。大部分由 host 驱动;你通过钩子和少量声明来影响它。

阶段发生了什么
开启房间接受新玩家;浏览器可以展示它。
占用玩家进出;OnPlayerJoined / OnPlayerLeft 触发;心跳上报实时人数。
空闲所有人都离开了。OnIdle 触发;你可以暂停模拟,或让清理逻辑将房间下线。
排空临近生命周期末尾,房间停止接纳新玩家,同时让现有玩家打完。
关闭房间被下线。关闭会将房间标记为已关闭,而非删除,并作废指向它的预订票据。

有两项清理无需你做任何事:空闲清理下线无人房间,生命周期清理强制房间的最大存续时长。两者都遵循上面平台所有的配置。

重连宽限

掉线的玩家会在短暂的重连宽限内保留座位,之后房间才会将其视为离开,这样一次网络抖动不会让玩家丢掉位置。用 DeclareReconnect(...) 声明它。


接纳玩家​

玩家携带着从客户端 SDK 拿到的预订抵达你的服务器。host 在玩家算作在场之前校验该预订:它必须有效、未被使用,且是发给这个房间的。只有这样 OnPlayerJoined 才会触发。

  • 有效预订 → 玩家入座;OnPlayerJoined(player) 运行。
  • 缺失、过期、房间错误或已被使用的预订 → 进入被拒绝。

这就是为什么客户端与服务器是一个系统的两半:客户端预订并连接,而你编写的 host 兑换预订。你无需手动校验票据——host 会做——但你要决定 OnPlayerJoined / OnPlayerLeft 时发生什么:分配出生点、把玩家加入你广播的花名册、启动他们的计时器。


服务器在哪里运行​

游戏服务器可以以几种方式运行,平台会为每个房间显示是哪种:

  • 托管(Managed)——平台通过托管编排器替你启动服务器实例(PlayServ 集成了 Edgegap)。编排器令牌是你自己工作室的,按项目和环境保存。
  • 外部(External)——你自己运行服务器(你自己的机群或机器)。你用 playserv functions declare 把它作为游戏服务器声明给 PlayServ,它像其他服务器一样注册自己的房间;它永远不需要作为云函数部署。
  • 池化(Pooled)——从预置的池中提供的房间。

无论哪种,房间模型都相同:注册、心跳、接受预订、上报在场。


容量与启动配额​

有两个不同的限制。房间有容量——其中的座位,来自平台配置。游戏服务器有房间配额(max_rooms)——同时可运行多少房间。达到配额时,平台会在任何付费的编排器调用之前拒绝再启动房间,因此失控不会变成账单。


环境​

托管服务器通过 PLAYSERV_* 环境变量获得它所需的东西——最重要的是 PLAYSERV_DEPLOYMENT_TOKEN,SDK 用它换取一个会话来与平台通信。托管启动时你无需手动设置这些;对于外部服务器,你提供对应项,好让 SDK 能认证并注册。


下一步​

  • 权威模拟——tick 循环、sim system,以及向客户端流式发送状态
  • 匹配与房间——浏览并预订本服务器所托管房间的客户端
  • 连接到游戏——客户端如何抵达并进入被托管的房间