跳到主要内容

把 Project 连接到 Unity

最后更新:2026 年 8 月 5 日

在 PlayServ Backoffice 中创建好 Project 之后,下一步就是把它连接到 Unity。导入 SDK 包、填入 Project 凭据、把 Schema 同步成生成的 Model,再加一个很小的引导脚本 —— 做完这些,Unity 工程就可以连接 PlayServ 了。


开始之前​

在 Unity 中接入 SDK 之前,请确认你已经有:

  • Unity 2021.3 或更高版本(下面的截图用的是 Unity 6)
  • 一个已在 PlayServ Backoffice 中创建的 Project —— 见 Creating a New Project
  • 一个 Client key,用作 Game Access Token —— 见 API Key Management
  • Project 的 Game ID
备注

配置放在 Unity 内的 PlayServ 编辑器控制窗口中,使用的是在 Backoffice 生成的凭据。窗口通过 Project 的 config asset(Assets/Resources/PlayServConfig.asset)解析它们。


导入 SDK​

把 PlayServ SDK 导入你的工程。导入后 Unity 会重新编译脚本 —— 请等 Compiling Scripts 进度条走完再继续。

打开 Assets ▸ Import Package ▸ Custom Package…,选择 playserv-unity-sdk-<version>.unitypackage,在 Import Unity Package 对话框中保持全部勾选,然后点击 Import。

导入 PlayServ SDK 的 Unity 包导入对话框
备注

以这种方式导入时,SDK 位于 Assets/playserv-unity-sdk,Unity 不会读取包自带的 manifest。如果 com.unity.nuget.newtonsoft-json 没有被自动安装,请把 "com.unity.nuget.newtonsoft-json": "3.2.2" 加到 Packages/manifest.json 中。


配置 PlayServ 编辑器控制窗口​

从 Tools ▸ PlayServ ▸ Settings 打开 PlayServ editor controls 窗口。运行时身份管理、Model 同步和服务端代码部署都集中在这一处,还有一个直达 Backoffice Dashboard 的快捷入口。窗口顶部显示当前的 Game ID 和已安装的 SDK Version。

窗口分为几个区块:

  • Control Room → PlayServ Config —— 运行时身份、凭据、固定 endpoint,以及 Project 一侧的 config asset
  • Server Code → Deployment —— 预览 RPC 代码闭包、同步已部署的版本,并把 ZIP 包发到部署 endpoint
  • Schema → Model Sync —— 检查最新的 Schema、比对时间戳,并重新生成编辑器一侧的 Model
  • Realtime → Events —— 生成强类型的 Events API,让 Event payload 的契约贴近运行时
  • Automation → Code Generation —— 按需生成 DTO(也可以设成自动),需要重来时清空生成层

底部的开关 Show this window on Unity startup 控制窗口是否自动打开,Open Docs 则链回本文档。

Unity 中的 PlayServ 编辑器控制窗口

本指南用到的是 PlayServ Config(见下)和 Model Sync(第 3 步)。其余区块讲的是服务端部署和代码生成。

在 Control Room → PlayServ Config 中填写 Project 凭据:

1

确认 config asset

Config Asset 字段应指向 Assets/Resources/PlayServConfig.asset。如果它还不存在,窗口会提示创建。用 Ping 可以在 Project 窗口中定位到它。

2

填入 Game Access Token

粘贴你在 Backoffice 创建的 Client key。Client key 可以安全地随游戏 build 分发 —— 见 API Key Management。

危险

绝不要拿 Server key 当 Game Access Token。从游戏二进制中提取出的 Server key,会让攻击者获得你 Project 后端的完整访问权限。

3

填入 Game ID 和版本

把 Game Id 设为你 Project 的 Game ID,把 Game Version 设为你的 build 版本(例如 1.0.0)。如果同一台机器会开多个客户端 —— 比如编辑器内运行的同时又跑一个 build —— 请勾选 Allow Multiple Connections。

注意

请确认 token 和 Game ID 属于同一个 PlayServ Project 和同一个 Environment。token 与 Project ID 对不上,SDK 就无法正常连接。

提示

如果你的流程把 config asset 当作唯一可信来源,那就把凭据留在这个窗口里,不要在场景组件中再次覆盖它们 —— 除非你是有意为之。


同步 Schema 并生成 Model​

编辑器一侧的 C# Model 是从 Project 的 Schema 生成的,Schema 才是唯一可信来源。写 gameplay 代码之前,先同步它们。

1

检查 Schema 是否有变化

在 Schema → Model Sync 中,窗口会向服务器查询 Schema 的变更。当它提示 A newer schema is available 时,请对照 Current schema 和 Latest available schema —— 比较两者的 hash 和时间戳。

2

应用最新的 Schema

点击 Apply New Schema,接受来自服务器的最新 Schema。

3

重新生成 Model

点击 Re-generate Models。生成的 Model 会写入 Assets/Shared/Generated/Models。

提示

任何时候都可以用 Check Updates 看看本地 Model 是否落后于服务器上的 Schema。每当 Backoffice 中的 Schema 有变动,就重新跑一次 Re-generate Models。


给场景添加引导脚本​

配置就绪之后,创建一个引导脚本,挂到场景中的某个 GameObject 上。这个脚本读取 PlayServ 配置、应用 SDK 设置、在场景启动时建立连接,并让连接跨场景加载保持可用。

1

创建一个常驻的引导对象

在工程的第一个场景中创建一个空 GameObject,命名为 PlayServBootstrap。一个常驻的引导对象很适合承载连接逻辑。

2

挂上引导脚本

把下面的脚本挂到该对象上。

using System;
using System.Threading.Tasks;
using Playserv.Proxy.Common;
using Playserv.Wrapper;
using UnityEngine;
using UnityEngine.Serialization;

namespace Playserv.Examples
{
/// <summary>
/// Persistent bootstrap component for configuring and connecting PlayServ in samples.
/// </summary>
public sealed class PlayServBootstrapSample : MonoBehaviour
{
private static PlayServBootstrapSample _instance;

[Header("Credentials")]
[SerializeField] private string gameAccessToken = "your-token";
[SerializeField] private string gameId = "game-001";
[SerializeField] private string userId = "player-001";
[SerializeField] private string gameVersion = "1.0.0";
[SerializeField] private bool overrideCredentialsFromInspector;

[Header("Resolved Endpoints (Read Only)")]
[FormerlySerializedAs("remoteEndpoint")]
[SerializeField] private string backendServerAddress = PlayServSettings.DefaultBackendServerAddress;
[SerializeField] private string deployApiServerAddress = PlayServSettings.DefaultDeployApiServerAddress;
[SerializeField] private string schemaApiServerAddress = PlayServSettings.DefaultSchemaApiServerAddress;

[Header("Behavior")]
[SerializeField] private bool autoConnect;
[SerializeField] private bool disconnectOnDestroy = true;

[Header("KeepAlive")]
[SerializeField] private int keepAlivePingIntervalMs = 5000;
[SerializeField] private int keepAlivePongTimeoutMs = 5000;

private bool _isOwner;

private void Awake()
{
if (_instance != null && _instance != this)
{
Destroy(gameObject);
return;
}

_instance = this;
_isOwner = true;
DontDestroyOnLoad(gameObject);
RefreshResolvedEndpointsPreview();
}

private void Start()
{
if (!_isOwner)
return;

Configure();

if (autoConnect &&
PlayServ.State != PlayServState.Online &&
PlayServ.State != PlayServState.Connecting &&
PlayServ.State != PlayServState.Handshaking)
{
_ = ConnectAsync();
}
}

private void OnEnable()
{
if (!_isOwner)
return;

PlayServ.OnTransportError += OnTransportError;
}

private void OnDisable()
{
if (!_isOwner)
return;

PlayServ.OnTransportError -= OnTransportError;
}

private void OnDestroy()
{
if (_instance == this)
_instance = null;

if (_isOwner && disconnectOnDestroy)
PlayServ.Disconnect();
}

private void OnValidate()
{
RefreshResolvedEndpointsPreview();
}

[ContextMenu("Configure SDK")]
public void Configure()
{
var settings = BuildSettingsFromConfig();

if (overrideCredentialsFromInspector)
{
settings.GameAccessToken = gameAccessToken;
settings.GameId = gameId;
settings.UserId = userId;
settings.GameVersion = gameVersion;
}

settings.KeepAlivePingIntervalMs = keepAlivePingIntervalMs;
settings.KeepAlivePongTimeoutMs = keepAlivePongTimeoutMs;

PlayServ.Config(settings);
ApplyResolvedEndpointsPreview(settings);
Debug.Log(
$"[PlayServ][Sample] Configured. gameId={settings.GameId}, credentialsSource={(overrideCredentialsFromInspector ? "inspector" : "config")}, backend={settings.BackendServerAddress}, pingInterval={settings.KeepAlivePingIntervalMs}ms, pongTimeout={settings.KeepAlivePongTimeoutMs}ms");
}

[ContextMenu("Connect SDK")]
public void Connect()
{
_ = ConnectAsync();
}

[ContextMenu("Disconnect SDK")]
public void Disconnect()
{
PlayServ.Disconnect();
Debug.Log("[PlayServ][Sample] Disconnected.");
}

public async Task ConnectAsync()
{
try
{
bool connected = await PlayServ.Connect();
Debug.Log(connected
? "[PlayServ][Sample] Connected."
: "[PlayServ][Sample] Connection failed.");
}
catch (Exception ex)
{
Debug.LogError($"[PlayServ][Sample] Connect error: {ex.Message}");
}
}

private void OnTransportError(TransportError error)
{
Debug.LogError($"[PlayServ][Sample] Transport error: {error}");
}

private void RefreshResolvedEndpointsPreview()
{
var settings = BuildSettingsFromConfig();
ApplyResolvedEndpointsPreview(settings);
}

private void ApplyResolvedEndpointsPreview(PlayServSettings settings)
{
if (settings == null)
return;

backendServerAddress = settings.BackendServerAddress;
deployApiServerAddress = settings.DeployApiServerAddress;
schemaApiServerAddress = settings.SchemaApiServerAddress;
}

private static PlayServSettings BuildSettingsFromConfig()
{
var config = Resources.Load<PlayServConfig>("PlayServConfig");
if (config == null)
return PlayServPackageDefaultsProvider.LoadSettingsOrDefault();

#if UNITY_EDITOR
return PlayServSettingsResolver.ResolveEditorSettings(config);
#else
return config.ToSettings();
#endif
}
}
}
备注

在默认流程下,这个脚本从 PlayServ 的 config asset 读取凭据。只有当你确实想覆盖 PlayServ 编辑器控制窗口中设定的值时,才启用 overrideCredentialsFromInspector。


运行场景并验证连接​

进入 Play Mode。如果配置无误,引导组件会配置好 SDK,在 autoConnect 启用时自动建立连接,并在该对象存在期间保持连接。

在 Unity Console 中验证连接,找这两行:

  • [PlayServ][Sample] Configured.
  • [PlayServ][Sample] Connected.

出问题时,脚本同样会报告连接失败、传输错误和配置问题。


这套配置是怎么运作的​

这个流程把配置和运行时行为分开了:

  • PlayServ 编辑器控制窗口保存 SDK 配置,并让编辑器一侧的 Model 与 Schema 保持同步
  • 引导脚本在运行时应用这份配置并建立连接

也就是说:Project 凭据集中在一处管理,数据 Model 由 Schema 生成而不是手写,场景里只需要一个小而可复用的引导组件。

提示

整个工程用一个常驻的引导对象就够了,不要在多个场景里各放一份连接脚本。


常见排查点​

如果 Project 没有按预期连上,请检查:

  • SDK 包是否正确导入
  • Game Access Token 是不是 Client key,以及是否复制完整
  • Game ID 是否对应 Backoffice 中的同一个 Project
  • PlayServ 的 config asset 是否存在并已填写
  • 最近一次 Schema 变更之后是否重新生成过 Model
  • 引导脚本是否挂在一个处于激活状态的 GameObject 上
  • 如果你期望场景启动时就连上,autoConnect 是否已启用
注意

如果凭据已经在 PlayServ 编辑器控制窗口中设定好了,注意不要在引导组件的 inspector 中用不同的值把它们无意覆盖掉。


下一步​