Defold 多人联机:网络同步、权威服务器与状态同步实战

系统覆盖 Defold 多人联机:网络架构选型(P2P vs 客户端-服务器)、权威服务器与状态同步、位置插值与预测、房间/匹配、Defold 网络库(WebSocket/HTTP)、断线重连与延迟处理。

引言

多人游戏最难的不是「连上」,而是「同步」——每个人的屏幕都要看到同一个世界。本文系统讲 Defold 多人联机:先做架构选型(P2P 与客户端-服务器谁适合你),再讲核心的「权威服务器」模型(为什么服务器说了算),接着覆盖状态同步方案(完整状态 vs 增量)、位置插值与预测、房间与匹配、Defold 里的网络接入(WebSocket/HTTP),最后处理延迟补偿、断线重连与防作弊。

前置:/defold-game-engine-complex-logic-state-management/(消息路由与同步)、/defold-cross-platform-publish/(平台网络)。网络协议见 [[network]]。


目录


1. 多人游戏的核心难点:同步

玩家 A 移动,玩家 B 怎么知道?——网络同步的本质:

A 端:本地模拟 → 发送「状态/输入」→ 网络延迟
B 端:收到 → 渲染对方状态
问题:延迟让 B 看到的 A 是「过去的 A」

同步的三个核心问题:

问题说明
一致性各端世界状态是否相同
延迟传输往返时间(RTT)
带宽每秒要传多少数据

游戏类型决定同步需求:

回合制/卡牌:低频同步(每回合一次)
实时竞技:高频同步(每帧输入)
MMO:分区 + 只同步附近玩家

心智:同步 = 在「一致性、延迟、带宽」三者的权衡——回合制传状态、实时传输入、MMO 分区。


2. 架构选型:P2P vs 客户端-服务器

两种网络拓扑:

P2P:玩家直接互联(无服务器)
  优点:零服务器成本、低延迟
  缺点:无权威、防作弊难、NAT 穿透难

客户端-服务器:一个权威服务器,所有客户端连它
  优点:权威、好防作弊、实现简单
  缺点:要服务器、增加一跳延迟

选型决策:

场景架构
小范围联机(局域网/朋友局)P2P
上架游戏/对抗竞技客户端-服务器
Web 游戏服务器(浏览器对等难)

Defold 生态现实:

Defold 没有内置网络库 → 常用方案:
  1. 自建 WebSocket 服务器(Go/Node)
  2. 第三方联机服务(Nakama/Photon 等)
  3. Web 端用浏览器 WebSocket

记忆:没有服务器就没有「裁判」——上架游戏几乎都走客户端-服务器,权威才是防作弊的根。


3. 权威服务器:为什么服务器说了算

核心原则:服务器拥有最终世界状态:

客户端发「输入」而不是「结果」
服务器收到 → 用权威逻辑计算 → 广播「结果」给所有人
客户端只渲染服务器结果

为什么:

1. 防作弊:客户端不能改自己血量/位置(它只是提案)
2. 一致性:所有人以服务器状态为准
3. 简化:逻辑集中一处,各端不用各自算

消息流:

客户端 A:发送 { input: move_right, ts: 1234 }
服务器:  校验 → 计算新位置 → 广播 { pos, hp, ts }
客户端 A/B:收到 → 渲染

记忆:权威服务器 = 客户端「提议」、服务器「裁决」——客户端想什么不重要,服务器说是什么就是什么。


4. 状态同步:完整状态 vs 增量更新

两种同步数据的粒度:

完整状态(snapshot):每帧/每 N 帧发全部位置血量
  优点:简单、掉包自愈
  缺点:带宽大

增量更新(delta):只发变化的部分
  优点:带宽小
  缺点:掉包要请求补帧

消息压缩:

-- 状态消息示例(Lua 侧序列化)
local function pack_state(self)
    -- 只传变化的实体
    local changes = {}
    for _, e in ipairs(self.entities) do
        if e.dirty then   -- 标记了「变了」
            table.insert(changes, {
                id = e.id,
                x = e.pos.x, y = e.pos.y,
                hp = e.hp,
            })
            e.dirty = false
        end
    end
    return changes
end

节奏选择:

类型同步频率带宽
回合制事件触发极小
实时(位置)10-20Hz 状态或 60Hz 输入中
MMO分区内 5-10Hz受控

记忆:完整状态简单抗丢包、增量省带宽——小游戏用完整状态,实体多了再上增量。


5. 位置插值:让移动丝滑

对方移动为什么「跳」?——收到的位置是离散的。插值让它连续:

收到对方位置 P1(时间 t1)
下一帧收到 P2(t2)
中间帧 → 在 P1 和 P2 之间插值渲染

插值实现:

-- 用 vmath.lerp 在两次状态间插值
function update(self, dt)
    -- 每收到新位置都推入缓冲
    self.lerp_t = math.min(self.lerp_t + dt * self.interp_speed, 1)
    local a, b = self.state_prev.pos, self.state_next.pos
    local render = vmath.lerp(self.lerp_t, a, b)
    go.set_position("." , render)
end

-- 收到新状态:把 prev 推进到 next,载入新 next
function on_net_state(self, msg)
    self.state_prev = self.state_next
    self.state_next = { pos = msg.pos, t = msg.ts }
    self.lerp_t = 0
end
方案手感
无插值瞬移跳变
线性插值平滑但迟滞
带延迟缓冲平滑 + 稳

记忆:插值 = 在「上一个状态」和「最新状态」间补帧——对方移动从瞬移变成滑行。


6. 预测与回滚:低延迟手感

问题:权威服务器 + 网络延迟 = 输入到看到要一个 RTT,操作有「黏滞感」。

解法:客户端预测:

1. 客户端本地立即执行输入(不等服务器)→ 即时响应
2. 同时把输入发给服务器
3. 服务器返回权威状态 → 与本地预测比对
4. 不一致 → 回滚(reconciliation)重放正确状态

预测示例(本地即时移动):

-- 本地即时模拟移动(预测)
function on_input(self, action_id, action)
    if action.pressed then
        -- 立即移动 + 记录输入(带序号)
        move_local(self, dir)
        table.insert(self.pending_inputs, { dir = dir, seq = self.seq })
        self.seq = self.seq + 1
    end
end

记忆:预测治「迟滞」、回滚治「偏差」——本地先动、服务器裁决、对不上就重放,手感就回来了。


7. Defold 网络接入:WebSocket 与 HTTP

Defold 没有内置网络库——用 websocket 扩展或 HTTP:

WebSocket(实时双向):

-- 使用 websocket 扩展
local socket = websocket.create(url, {
    on_open = function(ws) print("连接成功") end,
    on_message = function(ws, msg) handle_net_msg(self, msg) end,
    on_error = function(ws, err) print("错误:", err) end,
    on_close = function(ws) print("断开") end,
})

-- 发送消息
socket.send(json.encode({ type = "move", x = 10, y = 20 }))

HTTP(请求-响应,低频):

-- 用 http 扩展做登录/排行榜
http.request("https://api.example.com/login", "POST",
    json.encode({ user = "abc", token = "xyz" }),
    function(status, body) 
        if status == 200 then self.logged_in = true end
    end)
协议适合特点
WebSocket实时联机全双工低延迟
HTTP登录/存档/匹配简单可靠

记忆:实时联机用 WebSocket、低频逻辑用 HTTP——Defold 靠扩展,服务端配个 WebSocket 网关就齐了。


8. 房间与匹配系统

房间 = 一局游戏的独立会话:

创建房间 → 玩家加入 → 开始 → 结束 → 解散
服务器侧维护房间列表与成员

匹配流程:

1. 玩家请求匹配(可选参数:等级/地区)
2. 匹配服务找合适的房间/玩家
3. 双方确认 → 建立连接 → 开局

Defold 侧房间客户端逻辑:

-- 创建/加入房间(通过服务器 API)
local function join_room(self, room_id)
    send_net("join", { room = room_id })
    self.state = "joining"
end

function on_net_message(self, msg)
    if msg.type == "joined" then
        self.room = msg.room
        self.state = "in_game"
        start_game(self)
    elseif msg.type == "player_left" then
        handle_disconnect(self, msg.player_id)
    end
end

记忆:房间 = 服务器维护的「会话名单」——创建/加入/退出都走服务器 API,客户端只管收发。


9. 断线重连、延迟处理与防作弊

断线处理:

-- 心跳 + 超时判定
function update(self, dt)
    self.no_resp_t = (self.no_resp_t or 0) + dt
    if self.no_resp_t > 5 then      -- 5 秒无响应
        self.state = "reconnecting"
        reconnect(self)
    end
end

-- 收到任何消息 → 重置计时
function on_net_any(self)
    self.no_resp_t = 0
end

延迟显示与补偿:

延迟:显示 ping(服务器回显时间戳)
补偿:预测 + 插值缓冲(见第 5/6 节)
发包节流:移动输入本地合并,避免每帧发

防作弊手段:

1. 权威服务器(位置/血量服务器算)
2. 输入校验(移动速度上限、穿墙检测)
3. 服务器状态快照(可疑时回滚)
4. 加密与签名(防篡改消息)

> 铁律:**永远别信客户端**——权威服务器 + 输入校验 + 状态快照,三层防作弊缺一不可。

---

## 10. 速查表

| 需求 | 做法 |
|------|------|
| 架构 | 上架游戏走客户端-服务器 |
| 权威 | 客户端发输入、服务器裁决 |
| 同步粒度 | 小游戏完整状态、大项目增量 |
| 平滑 | 位置插值(lerp 缓冲) |
| 手感 | 客户端预测 + 服务器回滚 |
| 网络库 | WebSocket(实时)/ HTTP(低频) |
| 房间 | 服务器维护会话名单 |
| 断线 | 心跳超时 + 重连 |
| 延迟 | 显示 ping + 预测补偿 |
| 防作弊 | 权威 + 校验 + 快照 + 加密 |

**一句话记忆**:**多人联机 = 架构(客户端-服务器)+ 权威(服务器裁决)+ 同步(完整/增量)+ 平滑(插值)+ 手感(预测回滚);Defold 用 WebSocket 扩展接服务器、房间走 API、断线心跳重连;防作弊三层——权威服务器、输入校验、状态快照——别信客户端。**

---

## 延伸阅读

- /defold-game-engine-complex-logic-state-management/ — 消息路由与状态管理
- /defold-cross-platform-publish/ — 平台网络与打包
- [[network]] — TCP/WebSocket 协议
- [[nodejs]] — 用 Node 写联机服务器
- [[game]] — 多人游戏设计

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「defold」更多文章

  1. Defold 音频系统:Sound 组件、背景音乐、3D 音效与声音管理
  2. Defold 精灵与动画:Sprite、Flipbook、缓动与程序动画
  3. Defold 着色器与后处理:GLSL 材质、特效与全屏后期