关于云端存储、服务器通讯、跨实例通讯等关键参数文档
修改于04/2995 浏览开发心得 包含 AI 合成内容
# 云端存储与多人网络 — 技术参数速查手册
> 本文档汇总 clientCloud、serverCloud、Network 三大子系统的关键参数、频率限制和调用规范,由和塔拉拉对话反馈总结生成,不保证绝对正确。
> 供项目开发时快速查阅和理解。
---
## 目录
1. [clientCloud(客户端云变量)](#1-clientcloud客户端云变量)
2. [serverCloud(服务端云变量)](#2-servercloud服务端云变量)
3. [Network(引擎网络同步)](#3-network引擎网络同步)
4. [跨服务器(实例)通信](#4-跨服务器实例通信)
5. [三大系统对比总览](#5-三大系统对比总览)
6. [实践建议](#6-实践建议)
---
## 1. clientCloud(客户端云变量)
### 1.1 运行环境
| 项目 | 值 |
|------|-----|
| 可用环境 | 客户端(Standalone / Client 模式) |
| 服务端可用 | 否(服务端使用 serverCloud) |
### 1.2 频率限制
| 限制项 | 额度 | 超限错误码 |
|--------|------|-----------|
| **读请求** | 300 次 / 分钟 | `-429` |
| **写请求** | 300 次 / 分钟 | `-429` |
| **数据吞吐** | 48 MB / 分钟 | `-429` |
超限时 error 回调:`error(-429, "send failed")`
### 1.3 数据存储结构
| 存储表 | 写入方法 | 数据类型 | 可排行榜 |
|--------|---------|---------|---------|
| **values** | `Set(key, value)` | 任意(string / table / number...) | 否 |
| **iscores** | `SetInt(key, value)` / `Add(key, delta)` | 仅整数 | **是** |
- 每个 key 是**全量替换**,不支持部分更新
- Lua table 自动 JSON 编解码
- `sscores` 已废弃
### 1.4 API 清单
| 操作 | 方法 | 消耗 |
|------|------|------|
| 单次读 | `clientCloud:Get(key, events)` | 1 次读 |
| 单次写(任意类型) | `clientCloud:Set(key, value, events)` | 1 次写 |
| 单次写(整数) | `clientCloud:SetInt(key, value, events)` | 1 次写 |
| 整数增量 | `clientCloud:Add(key, delta, events)` | 1 次写 |
| 批量读 | `BatchGet():Key(...):Fetch(events)` | 1 次读(合并) |
| 批量写 | `BatchSet():Set/SetInt/Add/Delete:Save(desc, events)` | 1 次写(合并) |
| 排行榜列表 | `GetRankList(key, start, count, [orderAsc,] events, ...extraKeys)` | 1 次读 |
| 用户排名 | `GetUserRank(userId, key, events)` | 1 次读 |
| 排行榜总人数 | `GetRankTotal(key, events)` | 1 次读 |
### 1.5 排行榜 item 字段
| 字段 | 类型 | 说明 |
|------|------|------|
| `item.player` / `item.userId` | number | 用户 ID |
| `item.iscore` | table | 整数云变量(SetInt/Add 写入) |
| `item.score` | table | 任意类型云变量(Set 写入) |
> 排行榜**不含昵称**,需用 `GetUserNickname({ userIds, onSuccess, onError })` 单独查询。
### 1.6 属性
| 属性 | 类型 | 说明 |
|------|------|------|
| `clientCloud.userId` | number | 当前用户 ID |
| `clientCloud.mapName` | string | 当前地图名称 |
---
## 2. serverCloud(服务端云变量)
### 2.1 运行环境
| 项目 | 值 |
|------|-----|
| 可用环境 | 服务端(UrhoXServer 进程) |
| 操作对象 | **任意用户**的数据(所有操作需传 userId) |
| 全局存储 | **不支持**(uid=0 是未定义行为,不可靠) |
### 2.2 频率限制
| 限制项 | 额度 | 超限错误码 |
|--------|------|-----------|
| **读请求** | 300 次 / 分钟 | `-429` |
| **写请求** | 300 次 / 分钟 | `-429` |
| **数据吞吐** | 48 MB / 分钟 | `-429` |
| **单次 BatchCommit / BatchSet 操作数** | 1000 条 | `-429` |
> 限额粒度(per-server 还是 per-userId)尚未明确,已编写测试待验证。
### 2.3 数据存储结构
与 clientCloud 相同(values / iscores),但额外支持子对象域。
### 2.4 API 清单 — Score(顶层操作)
| 操作 | 方法 | 消耗 |
|------|------|------|
| 读取 | `serverCloud:Get(uid, key, events)` | 1 次读 |
| 写入任意类型 | `serverCloud:Set(uid, key, value, events)` | 1 次写 |
| 写入整数 | `serverCloud:SetInt(uid, key, value, events)` | 1 次写 |
| 整数增量 | `serverCloud:Add(uid, key, delta, events)` | 1 次写 |
| 删除 | `serverCloud:Delete(uid, key, events)` | 1 次写 |
| 单人批量读 | `BatchGet(uid):Key(...):Fetch(events)` | 1 次读 |
| 多人批量读 | `BatchGet():Player(uid1):Player(uid2):Key(...):Fetch(events)` | 1 次读 |
| 批量写 | `BatchSet(uid):Set/SetInt/Add/Delete:Save(desc, events)` | 1 次写 |
### 2.5 API 清单 — 子对象
| 子对象 | 方法 | 用途 |
|--------|------|------|
| `serverCloud.money` | `Get(uid, events)` / `Add(uid, key, amount)` / `Cost(uid, key, amount, events)` | 货币(扣除余额不足会 error) |
| `serverCloud.list` | `Get` / `GetById` / `Add` / `Modify` / `ModifyKey` / `Delete` | 列表存储(背包、日志等) |
| `serverCloud.item` | `Get` / `Add` / `Use` | 道具系统 |
| `serverCloud.message` | `Send` / `Get` / `MarkRead` / `Delete` | 消息系统(公告、邮件) |
| `serverCloud.quota` | `Get` / `Add` / `Reset` | 配额计数器(每日签到等限次操作) |
### 2.6 事务 — BatchCommit
可在一次提交中**混合多个子对象域**的操作,保证**原子性**。
| 可用方法 | 说明 |
|---------|------|
| `ScoreSet / ScoreSetInt / ScoreAddInt / ScoreDelete` | Score 域 |
| `MoneyAdd / MoneyCost` | 货币域 |
| `ListAdd / ListModify / ListModifyKey / ListDelete` | 列表域 |
| `QuotaAdd / QuotaReset` | 配额域 |
- 单次 BatchCommit 最多 **1000** 条操作
- 调用 `:Commit(events)` 提交
### 2.7 排行榜
与 clientCloud 相同,基于 iscores 整数排序:
- `GetRankList(key, start, count, [orderAsc,] events)`
- `GetUserRank(uid, key, events)`
- `GetRankTotal(key, events)`
### 2.8 与 clientCloud 的核心差异
| 差异项 | clientCloud | serverCloud |
|--------|------------|-------------|
| 操作对象 | 当前用户自己 | 任意用户(需传 uid) |
| 子对象 | 无 | money / list / item / message / quota |
| 事务 | 无 | BatchCommit(跨域原子操作) |
| 全局存储 | 无 | 无(uid=0 不可靠) |
---
## 3. Network(引擎网络同步)
### 3.1 架构
| 项目 | 值 |
|------|-----|
| 模型 | **权威服务器**(Server-Authoritative) |
| 服务器渲染 | 无(Headless 模式) |
| 传输协议 | WASM 平台使用 WebSocket (TCP);引擎底层 KCP/UDP |
### 3.2 服务器全局变量
| 变量 | 类型 | 说明 |
|------|------|------|
| `SERVER_MAX_PLAYERS` | int | 最大玩家数(来自 settings.json) |
| `SERVER_REGISTERED_PLAYERS` | int | 本局实际玩家数量(开局快照,不含中途加入) |
| `SERVER_TICK_RATE` | int | 服务器帧率 |
| `SERVER_MODE` | string | 多人模式 |
| `PERSISTENT_WORLD_KEY` | string | 常驻服房间标识(非空=常驻服) |
### 3.3 频率限制
Network **没有文档记录的硬性频率限制**(不同于 cloud 的 300 次/分钟),约束来自物理层面:
| 约束因素 | 说明 |
|---------|------|
| 服务器帧率 | `SERVER_TICK_RATE`,每帧处理一次同步 |
| 带宽 | 节点越多、变化越频繁,带宽消耗越大 |
| 节点数量 | REPLICATED 节点越多,每帧序列化开销越大 |
| VariantMap 大小 | RemoteEvent 携带的数据量影响单次传输 |
### 3.4 节点创建模式
| 模式 | 常量 | 说明 |
|------|------|------|
| `REPLICATED` | 默认值 | 同步到所有客户端 |
| `LOCAL` | 需显式指定 | 仅本地存在 |
> **关键**:`CreateChild()` 默认是 REPLICATED!客户端创建节点**必须显式指定 LOCAL**。
### 3.5 节点变量(Node Vars)同步
| 项目 | 说明 |
|------|------|
| 设置方式 | `node:SetVar(key, Variant(value))` |
| 同步方向 | 服务器 → 客户端(自动) |
| 支持类型 | int / float / bool / string / Vector2 / Vector3 / Quaternion / Color |
| 时序陷阱 | `NodeAdded` 事件触发时 Vars **尚未同步**,需用 `ScriptObject:DelayedStart()` |
### 3.6 远程事件(Remote Events)
| 方向 | API | 消耗 |
|------|-----|------|
| 客户端→服务器 | `connection:SendRemoteEvent(name, inOrder, [data])` | 无硬性限制 |
| 服务器→单个客户端 | `connection:SendRemoteEvent(name, inOrder, [data])` | 无硬性限制 |
| 服务器→全部客户端 | `network:BroadcastRemoteEvent(name, inOrder, [data])` | 无硬性限制 |
> **必须在接收方调用 `network:RegisterRemoteEvent(eventName)`**,否则收不到。
#### VariantMap 数据类型
RemoteEvent 携带 VariantMap,**只支持扁平键值对**(不支持嵌套 table):
```lua
local vm = VariantMap()
vm["Score"] = Variant(100) -- 整数
vm["Name"] = Variant("player1") -- 字符串
vm["Pos"] = Variant(Vector3(1,2,3)) -- Vector3
-- 复杂数据需 JSON 编码
vm["Data"] = Variant(cjson.encode({ items = {1,2,3} }))
```
### 3.7 玩家输入同步(Controls)
| 字段 | 类型 | 说明 |
|------|------|------|
| `controls.yaw` | float | 水平旋转角度 |
| `controls.pitch` | float | 垂直旋转角度 |
| `controls.buttons` | uint | 按钮状态位标志 |
- 通过 **unreliable** 通道发送(每帧覆盖式)
- 脉冲按键(如跳跃)使用 `SetPulseButtonMask(mask)` 切换到 **reliable** 通道,防止丢包
| 按键类型 | 传输通道 | 延迟 | 可靠性 |
|---------|---------|------|--------|
| 持续按键(如加速) | unreliable | 零 | 可能丢帧 |
| 脉冲按键(如跳跃) | reliable+ordered | 同 tick 多状态时 +1 tick | 保证送达 |
> WASM 平台 (WebSocket/TCP) 不存在丢包,引擎自动跳过 pulse 机制。
### 3.8 SmoothedTransform(位置插值)
- 引擎为所有 REPLICATED 节点**自动创建** SmoothedTransform
- 在网络数据包之间**平滑插值**,消除卡顿
- **相机跟随不要额外 lerp**,否则与 SmoothedTransform 冲突导致抖动
### 3.9 NetworkPriority(兴趣管理)
| 参数 | 类型 | 默认 | 说明 |
|------|------|------|------|
| `basePriority` | float | 100.0 | 基础优先级 |
| `distanceFactor` | float | 0.0 | 距离衰减因子(优先级 = base - distance × factor) |
| `minPriority` | float | 0.0 | 低于此值不发送更新 |
| `alwaysUpdateOwner` | bool | false | 始终向节点拥有者发送 |
客户端通过 `connection.position` 上报观察者位置,服务器据此计算距离。
### 3.10 节点删除
| 方法 | 效果 | 推荐 |
|------|------|------|
| `node:Remove()` | 依赖 Lua GC,客户端延迟数秒才收到通知 | ❌ |
| `node:Dispose()` | 立即断开引用、立即删除、立即通知客户端 | ✅ |
### 3.11 连接统计
| 属性 | 类型 | 说明 |
|------|------|------|
| `connection.roundTripTime` | float | 往返延迟 (ms) |
| `connection.bytesInPerSec` | float | 入站流量 (B/s) |
| `connection.bytesOutPerSec` | float | 出站流量 (B/s) |
### 3.12 连接时序(关键)
```
ClientConnected ← TCP 连接建立(不能获取 user_id)
↓
ClientIdentity ← 认证完成(user_id 可用)
↓
客户端: serverConnection.scene = scene_
客户端: SendRemoteEvent("ClientReady")
↓
服务器收到 ClientReady ← 此时才能 connection.scene = scene_
↓
场景全量同步开始
```
> 服务器**不要在 ClientConnected 中设置 connection.scene**,否则客户端报错:
> `Can not handle LoadScene message without an assigned scene`
### 3.13 昵称查询
| 项目 | 客户端 | 服务端 |
|------|--------|--------|
| 接口 | `GetUserNickname({ userIds, onSuccess, onError })` | 同左 |
| 数据来源 | Lobby 服务器(异步) | `SERVER_PLAYER_AUTH_INFOS`(同步,回调立即执行) |
| 适用范围 | 任意用户 | 仅本局已注册的玩家 |
### 3.14 游戏模式
| 模式 | 说明 | 中途加入 | 生命周期 |
|------|------|---------|---------|
| 普通匹配 | 凑齐 N 人开局 | 不支持 | 一局结束即销毁 |
| 后台匹配 | 脚本先加载,匹配在后台跑 | 不支持 | 同上 |
| 常驻服 (persistent_world) | 服务器持续运行 | **支持** | 空闲超时后自动关闭 |
---
## 4. 跨服务器(实例)通信
> 每个游戏服务器是一个**独立的 UrhoXServer 进程**,引擎**没有提供 server↔server 直连 API**。
> 以下是当前所有可用的跨实例数据交换方法。
### 4.1 架构前提
```
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 实例 A │ │ 实例 B │ │ 实例 C │
│ (房间 1) │ │ (房间 2) │ │ (常驻服) │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
│ ❌ 无直连通道 │ │
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────┐
│ 云端存储后端(共享) │
│ clientCloud / serverCloud / 排行榜 │
└─────────────────────────────────────────────────────────┘
```
- 实例之间**不能**直接发送消息或调用对方的方法
- 所有跨实例数据交换都需要经过**共享中间层**
### 4.2 方法一览
| # | 方法 | 中间层 | 方向 | 实时性 | 持久性 | 频率限制 |
|---|------|--------|------|--------|--------|---------|
| 1 | serverCloud Score 读写 | 云存储 | 任意实例 → 同一 uid 数据 | 非实时(需主动拉取) | 持久 | 300 读+写/分钟 |
| 2 | serverCloud.message | 云存储 | 实例 A 写 → 实例 B 读 | 非实时(需轮询) | 持久 | 同上 |
| 3 | 排行榜 GetRankList | 云存储 | 全局共享 | 非实时 | 持久 | 同上 |
| 4 | HttpClient 外部服务 | 自建 HTTP 服务 | 双向 | 准实时 | 取决于外部服务 | URL 白名单 |
| 5 | 客户端中继 | 玩家客户端 | 间接 | 取决于流程 | 非持久 | Network 无硬限制 |
---
### 4.3 方法 1:serverCloud Score 共享读写
**原理**:所有服务器实例访问的是**同一个云存储后端**,通过 userId 定位数据。实例 A 写入的数据,实例 B 可以通过同一个 uid 读取。
**适用场景**:玩家跨房间的持久数据(金币、等级、装备、战绩)
```lua
-- 实例 A:玩家离开时保存数据
serverCloud:BatchSet(uid)
:SetInt("gold", playerData.gold)
:SetInt("level", playerData.level)
:Set("equipment", playerData.equipment)
:Save("离开房间保存")
-- 实例 B:玩家加入时读取数据(同一个 uid)
serverCloud:BatchGet(uid)
:Key("gold"):Key("level"):Key("equipment")
:Fetch({
ok = function(scores, iscores)
-- 拿到实例 A 写入的数据
local gold = iscores.gold or 0
local level = iscores.level or 1
local equipment = scores.equipment or {}
end
})
```
| 优点 | 限制 |
|------|------|
| 无需额外基础设施 | 非实时,需主动拉取 |
| 天然按 userId 隔离 | 300 次/分钟频率限制 |
| 持久化,服务器重启不丢失 | 全 key 替换,无部分更新 |
---
### 4.4 方法 2:serverCloud.message 跨实例消息
**原理**:`serverCloud.message:Send()` 可以向**任意 userId** 发送消息。目标用户在任何实例上(甚至离线后重新上线)都可以通过 `serverCloud.message:Get()` 读取。
**适用场景**:跨房间邮件、公告推送、好友赠礼、异步通知
```lua
-- 实例 A(发送方):向目标用户发消息
serverCloud.message:Send(senderUid, "cross_server_mail", targetUid, {
type = "gift",
item = "rare_sword",
from_room = "room_123",
timestamp = os.time(),
}, {
ok = function(errorCode, errorDesc)
if errorCode == 0 or errorCode == nil then
print("跨服消息发送成功")
end
end
})
-- 实例 B(接收方):玩家加入时检查未读消息
serverCloud.message:Get(uid, "cross_server_mail", false, {
ok = function(messages)
for _, msg in ipairs(messages) do
print("收到跨服消息:", msg.value.type, msg.value.item)
-- 处理后标记已读
serverCloud.message:MarkRead(msg.message_id)
end
end
})
```
| 优点 | 限制 |
|------|------|
| 支持离线接收(持久化) | 非实时(需轮询或在加入时拉取) |
| 天然跨实例,无需知道对方在哪个房间 | 共享 300 次/分钟频率限制 |
| 支持标记已读、删除 | 不适合高频数据交换 |
---
### 4.5 方法 3:排行榜(天然跨实例)
**原理**:排行榜基于 iscores 整数全局排序,**所有实例的 SetInt/Add 写入汇聚到同一个排行榜**。
**适用场景**:全服排名、赛季积分榜、全局统计
```lua
-- 任意实例写入分数(自动汇聚到全局排行榜)
serverCloud:SetInt(uid, "season_score", 15000)
serverCloud:Add(uid, "total_kills", 3)
-- 任意实例读取全局排行榜(结果一致)
serverCloud:GetRankList("season_score", 1, 50, {
ok = function(rankList)
-- rankList 包含所有实例中所有玩家的排名
for i, item in ipairs(rankList) do
print(i, item.userId, item.iscore.season_score)
end
end
})
```
| 优点 | 限制 |
|------|------|
| 零额外开发成本 | 只能排整数(iscores) |
| 全局一致性由后端保证 | 排行榜更新有短暂延迟 |
| 天然去重(同一 uid 覆盖) | 不适合传输复杂结构数据 |
---
### 4.6 方法 4:HttpClient 对接外部服务
**原理**:服务端可通过 `http:Create()` 发送 HTTP/HTTPS 请求到外部服务器,实现实例间通过自建中间件通信。
**适用场景**:全服公告系统、跨服匹配调度、第三方数据接口、实时排行榜推送
```lua
-- 实例 A:向中间服务报告房间状态
http:Create()
:SetMethod(HTTP_POST)
:SetContentType("application/json")
:SetBody(cjson.encode({
room_id = "room_123",
player_count = 6,
status = "playing",
}))
:OnSuccess(function(client, response)
print("状态上报成功")
end)
:OnError(function(client, code, err)
print("上报失败:", err)
end)
:Send()
-- 实例 B:从中间服务拉取全服公告
http:Create()
:OnSuccess(function(client, response)
local data = cjson.decode(response.dataAsString)
for _, ann in ipairs(data.announcements) do
-- 广播给本实例内的所有客户端
local vm = VariantMap()
vm["Message"] = Variant(ann.content)
network:BroadcastRemoteEvent("ServerAnnouncement", true, vm)
end
end)
:Send()
```
**关键限制**:
| 限制项 | 说明 |
|--------|------|
| **客户端不可用** | `http` / `HttpClient` 在客户端模式被完全屏蔽 |
| **URL 白名单** | 服务端 HTTP 受 URL 白名单限制,采用**全字符串精确匹配** |
| **白名单申请** | 需联系 TapTap 制造团队添加 URL 到白名单 |
| **HTTP 方法** | 支持 `HTTP_GET` / `HTTP_POST` / `HTTP_PUT` / `HTTP_DELETE` / `HTTP_PATCH` |
| **超时设置** | `SetTimeout(msecs)` |
| 优点 | 限制 |
|------|------|
| 灵活度最高,可实现任意通信模式 | 需要自建/租用外部 HTTP 服务 |
| 支持 HTTPS,安全性好 | URL 白名单需审批 |
| 可实现准实时轮询 | 额外运维成本 |
---
### 4.7 方法 5:客户端中继
**原理**:玩家从实例 A 断开后连接到实例 B 时,可以在客户端携带上一局的信息。或者利用 clientCloud 作为客户端侧的中转。
**适用场景**:房间切换时的上下文传递
```lua
-- 客户端:离开实例 A 前,将数据存入 clientCloud
clientCloud:Set("transfer_data", {
from_room = "room_123",
pending_rewards = { gold = 50, xp = 200 },
})
-- 客户端:加入实例 B 后,读取并传给新服务器
clientCloud:Get("transfer_data", {
ok = function(values, iscores)
local data = values.transfer_data
if data then
local vm = VariantMap()
vm["TransferData"] = Variant(cjson.encode(data))
serverConnection_:SendRemoteEvent("PlayerTransfer", true, vm)
-- 清理
clientCloud:BatchSet():Delete("transfer_data"):Save("清理中转数据")
end
end
})
```
| 优点 | 限制 |
|------|------|
| 无需额外服务 | 依赖客户端流程(不安全,可被篡改) |
| 利用现有 clientCloud 基础设施 | 只在玩家切换房间时触发 |
| | 不适合实例间主动推送 |
> **安全提醒**:客户端中继的数据**不可信任**,服务端应通过 serverCloud 二次验证。
---
### 4.8 跨实例通信方法对比
| 维度 | Score 读写 | message | 排行榜 | HttpClient | 客户端中继 |
|------|-----------|---------|--------|-----------|-----------|
| **实时性** | 需拉取 | 需轮询 | 需拉取 | 准实时 | 被动(切房触发) |
| **持久性** | 持久 | 持久 | 持久 | 取决于外部 | clientCloud 持久 |
| **复杂数据** | 支持 | 支持 | 仅整数 | 支持 | 支持 |
| **离线可达** | 是 | 是 | 是 | 否 | 否 |
| **额外成本** | 无 | 无 | 无 | 需自建服务+白名单 | 无 |
| **安全性** | 可信 | 可信 | 可信 | 可信 | **不可信**(需验证) |
| **频率限制** | 300/分钟 | 300/分钟 | 300/分钟 | URL 白名单 | cloud 300/分钟 |
| **典型用途** | 持久数据同步 | 邮件/通知 | 全服排名 | 公告/调度/第三方 | 房间切换上下文 |
### 4.9 无法实现的场景
| 需求 | 原因 | 替代方案 |
|------|------|---------|
| 实例 A 实时推送消息到实例 B | 无 server↔server 通道 | HttpClient + 外部消息队列轮询 |
| 跨实例实时同步游戏状态 | 无共享内存/直连 | 不支持,需合并到同一实例 |
| 服务端广播到其他实例的客户端 | BroadcastRemoteEvent 仅限本实例 | serverCloud.message + 各实例轮询 |
---
## 5. 三大系统对比总览
| 维度 | clientCloud | serverCloud | Network |
|------|------------|-------------|---------|
| **运行环境** | 客户端 | 服务端 | 双端 |
| **通信目标** | 云端存储服务 | 云端存储服务 | 客户端 ↔ 服务端 |
| **数据持久性** | 持久化 | 持久化 | 非持久化(内存) |
| **读频率限制** | 300 次/分钟 | 300 次/分钟 | 无硬性限制(受帧率/带宽约束) |
| **写频率限制** | 300 次/分钟 | 300 次/分钟 | 同上 |
| **吞吐限制** | 48 MB/分钟 | 48 MB/分钟 | 无硬性限制 |
| **数据格式** | Lua table(自动 JSON) | 同左 | VariantMap(扁平键值对) |
| **嵌套数据** | 支持(table 自动序列化) | 支持 | 不支持(需 cjson 手动编码) |
| **部分更新** | 不支持(全 key 替换) | 不支持 | 支持(Node Vars 逐字段同步) |
| **事务** | 无 | BatchCommit(跨域原子) | 无 |
| **排行榜** | 支持(iscores 整数) | 支持(iscores 整数) | 不适用 |
| **典型用途** | 单机存档、个人排行榜 | 多人游戏数据持久化、商店、邮件 | 实时游戏状态同步、输入同步 |
---
## 5. 实践建议
### 5.1 cloud 请求合并
单次 `BatchSet` / `BatchGet` 只消耗 **1 次**读写额度,应尽量合并:
```lua
-- ❌ 差:3 次写额度
serverCloud:SetInt(uid, "gold", 100)
serverCloud:SetInt(uid, "kills", 5)
serverCloud:Set(uid, "config", { music = true })
-- ✅ 好:1 次写额度
serverCloud:BatchSet(uid)
:SetInt("gold", 100)
:SetInt("kills", 5)
:Set("config", { music = true })
:Save("批量保存")
```
### 5.2 cloud 存储结构 — 按领域分组
推荐将数据按**变更频率**和**排行需求**分组:
| key 名 | 存储方式 | 变更频率 | 排行需求 |
|--------|---------|---------|---------|
| `high_score` | `SetInt` → iscores | 低 | 是 |
| `total_kills` | `SetInt` → iscores | 中 | 是 |
| `gold` | `SetInt` → iscores | 高 | 可选 |
| `game_progress` | `Set` → values | 中 | 否 |
| `equipment` | `Set` → values | 低 | 否 |
| `settings` | `Set` → values | 极低 | 否 |
### 5.3 Network 带宽优化
| 策略 | 方法 |
|------|------|
| 减少 REPLICATED 节点 | 环境、光照、相机用 LOCAL |
| 使用 NetworkPriority | 远处实体降低更新频率 |
| 远程事件精简数据 | 只发必要字段,复杂数据用 cjson 编码 |
| 客户端上报 position | `connection.position = cameraPos`,配合 NetworkPriority 做兴趣管理 |
### 5.4 回调结构统一格式
clientCloud 和 serverCloud 的回调格式一致:
```lua
{
ok = function(...) -- 成功(参数因 API 而异)
end,
error = function(code, reason) -- 失败(code=-429 为限流)
end,
timeout = function() -- 超时(可选)
end,
}
```
### 5.5 全局数据的替代方案
serverCloud 不支持全局(无 userId)存储。替代方案:
| 需求 | 方案 | 持久性 |
|------|------|--------|
| 实时广播(公告) | `network:BroadcastRemoteEvent()` | 非持久 |
| 离线消息 / 邮件 | `serverCloud.message:Send(uid, ...)` 逐人发送 | 持久 |
| 服务端内存缓存 | Lua 全局变量 / module 变量 | 进程生命周期 |
---
*最后更新: 2026-04-29*


