Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

4.1 同步模型

同步模型是实时游戏的核心技术,决定了玩家看到的世界是否一致、响应是否流畅。选择错误的同步模型会导致游戏无法运行或体验极差。

核心问题:如何让所有玩家看到相同的世界?

问题场景

// 场景:两个玩家同时开枪
// 玩家A:看到自己先开枪,击杀玩家B
// 玩家B:看到自己先开枪,击杀玩家A
// 服务器:谁先击杀谁?

type ShootEvent struct {
    PlayerID    uint64
    TargetID    uint64
    Timestamp   int64   // 时间戳
    BulletPos   Vector3
    BulletDir   Vector3
}

// 问题:如何判定谁先开枪?

三大同步模型

1. 状态同步(State Synchronization)

核心思想:服务器权威计算,客户端只显示

// StateSync 状态同步
type StateSync struct {
    // 服务器端
    serverState     *GameState
    updateRate      int           // 更新频率(Hz)
    clientStates    map[uint64]*GameState  // 客户端状态

    // 客户端端
    lastServerState *GameState
    interpolator    *Interpolator  // 插值器
}

// GameState 游戏状态
type GameState struct {
    Players map[uint64]*Player
    Bullets []*Bullet
    Timestamp int64
}

type Player struct {
    ID       uint64
    Position Vector3
    Rotation Vector3
    Health   int
    Weapon   *Weapon
}

// 服务器主循环
func (ss *StateSync) ServerLoop() {
    ticker := time.NewTicker(time.Second / time.Duration(ss.updateRate))
    defer ticker.Stop()

    for range ticker.C {
        // 1. 收集客户端输入
        inputs := ss.collectClientInputs()

        // 2. 服务器权威计算
        ss.serverState = ss.updateGameState(inputs)

        // 3. 广播状态给所有客户端
        ss.broadcastState(ss.serverState)
    }
}

// 客户端插值显示
func (ss *StateSync) ClientRender() {
    // 不直接显示服务器状态,而是插值
    if ss.lastServerState == nil {
        return
    }

    // 获取插值时间点(延迟100ms显示)
    renderTime := time.Now().Add(-100 * time.Millisecond).Unix()

    // 在两个状态之间插值
    t := ss.calculateInterpolationT(renderTime)
    interpolated := ss.interpolator.Interpolate(
        ss.lastServerState,
        ss.serverState,
        t,
    )

    // 渲染插值后的状态
    ss.render(interpolated)
}

// 插值计算
func (ss *StateSync) calculateInterpolationT(renderTime int64) float64 {
    // 计算插值系数(0.0 - 1.0)
    total := ss.serverState.Timestamp - ss.lastServerState.Timestamp
    elapsed := renderTime - ss.lastServerState.Timestamp
    return float64(elapsed) / float64(total)
}

状态同步的优缺点

// 优点
type StateSyncPros struct {
    AntiCheat      bool   // 反作弊能力强(服务器权威)
    Implementation bool   // 实现简单
    Bandwidth      bool   // 带宽可控(只同步变化)
    Scalability    bool   // 可扩展性好(可支持大量玩家)
}

// 缺点
type StateSyncCons struct {
    Latency        bool   // 有延迟感(需要插值平滑)
    ServerLoad     bool   // 服务器压力大(所有计算在服务器)
    Consistency    bool   // 客户端显示可能不一致
}

// 适用场景
var stateSyncGames = []string{
    "MMORPG(魔兽世界)",
    "MOBA(英雄联盟、王者荣耀)",
    "大逃杀(PUBG、Apex)",
}

2. 帧同步(Frame Synchronization / Lockstep)

核心思想:所有客户端执行相同的逻辑,保证结果一致

// FrameSync 帧同步
type FrameSync struct {
    // 帧率
    frameRate      int           // 60Hz
    frameInterval  time.Duration

    // 输入缓冲
    inputBuffer    *InputBuffer

    // 确定性计算
    deterministic  bool

    // 同步机制
    lockstep       bool          // Lockstep模式
    frameCounter   uint32        // 帧计数器
}

// Input 输入指令
type Input struct {
    PlayerID    uint64
    FrameNum    uint32        // 帧号
    Actions     []Action      // 操作列表
}

type Action struct {
    Type    string  // "move", "attack", "skill"
    Params  map[string]interface{}
}

// InputBuffer 输入缓冲
type InputBuffer struct {
    inputs     map[uint32][]Input  // 帧号 -> 输入列表
    frameNum   uint32
    mutex      sync.RWMutex
}

// 添加输入
func (ib *InputBuffer) AddInput(frameNum uint32, input Input) {
    ib.mutex.Lock()
    defer ib.mutex.Unlock()

    if ib.inputs == nil {
        ib.inputs = make(map[uint32][]Input)
    }

    ib.inputs[frameNum] = append(ib.inputs[frameNum], input)
}

// 获取帧输入(等待所有玩家输入)
func (ib *InputBuffer) GetFrameInputs(frameNum uint32, playerCount int) []Input {
    ib.mutex.RLock()
    defer ib.mutex.RUnlock()

    inputs, ok := ib.inputs[frameNum]
    if !ok || len(inputs) < playerCount {
        return nil  // 还没收集齐所有玩家输入
    }

    return inputs
}

// 帧同步主循环
func (fs *FrameSync) GameLoop(playerCount int) {
    ticker := time.NewTicker(fs.frameInterval)
    defer ticker.Stop()

    for range ticker.C {
        // 1. 收集所有玩家输入
        inputs := fs.inputBuffer.GetFrameInputs(fs.frameCounter, playerCount)
        if inputs == nil {
            // 还没收集齐,跳过这一帧
            continue
        }

        // 2. 确定性计算(所有客户端结果相同)
        fs.updateGameState(inputs)

        // 3. 渲染
        fs.render()

        // 4. 帧计数递增
        fs.frameCounter++
    }
}

// 确定性更新
func (fs *FrameSync) updateGameState(inputs []Input) {
    // 按玩家ID排序(保证执行顺序一致)
    sort.Slice(inputs, func(i, j int) bool {
        return inputs[i].PlayerID < inputs[j].PlayerID
    })

    // 执行所有输入
    for _, input := range inputs {
        for _, action := range input.Actions {
            fs.executeAction(action)
        }
    }

    // 更新游戏状态
    fs.gameState.Update()
}

// 执行操作(必须确定性)
func (fs *FrameSync) executeAction(action Action) {
    switch action.Type {
    case "move":
        // 确定性的移动计算
        fs.executeMove(action)
    case "attack":
        // 确定性的攻击计算
        fs.executeAttack(action)
    }
}

帧同步的关键要求

// 确定性要求
type DeterministicRequirement struct {
    FloatPrecision    bool  // 浮点数精度
    RandomNumber      bool  // 随机数生成
    ExecutionOrder    bool  // 执行顺序
    ExternalFactors   bool  // 外部因素(时间、输入等)
}

// ❌ 非确定性代码
func nonDeterministicUpdate() {
    // 使用浮点数(不同CPU结果可能不同)
    x := 0.1 + 0.2  // 可能是0.30000000000000004

    // 使用系统时间
    now := time.Now()  // 每个客户端不同

    // 使用随机数
    r := rand.Float64()  // 每个客户端不同
}

// ✅ 确定性代码
func deterministicUpdate() {
    // 使用定点数
    x := FixedFromFloat(0.1) + FixedFromFloat(0.2)  // 精确结果

    // 使用游戏帧时间
    now := fs.frameCounter  // 所有客户端相同

    // 使用确定性随机数
    r := fs.deterministicRandom.Next()  // 相同种子,相同结果
}

帧同步的优缺点

// 优点
type FrameSyncPros struct {
    Consistency    bool   // 所有客户端完全一致
    Latency        bool   // 延迟极低(本地计算)
    ServerLoad     bool   // 服务器压力小(只转发输入)
}

// 缺点
type FrameSyncCons struct {
    Complexity     bool   // 实现复杂(保证确定性)
    AntiCheat      bool   // 反作弊困难(客户端计算)
    Scalability    bool   // 可扩展性差(玩家越多,同步越难)
    Debug          bool   // 调试困难(一点不一致,全部错误)
}

// 适用场景
var frameSyncGames = []string{
    "格斗游戏(街霸、铁拳)",
    "RTS(星际争霸、War3)",
    "FPS(CS早期版本)",
}

3. 混合同步(Hybrid Synchronization)

核心思想:结合状态同步和帧同步的优势

// HybridSync 混合同步
type HybridSync struct {
    // 状态同步部分
    stateSync      *StateSync

    // 帧同步部分
    frameSync      *FrameSync

    // 分类策略
    objectTypes   map[string]SyncType  // 对象类型 -> 同步方式
}

type SyncType int

const (
    SyncTypeState SyncType = iota  // 状态同步
    SyncTypeFrame                  // 帧同步
    SyncTypeHybrid                 // 混合同步
)

// GameObject 游戏对象
type GameObject struct {
    ID        uint64
    Type      string
    SyncType  SyncType
    Position  Vector3
    Rotation  Vector3
    Velocity  Vector3
    Health    int
}

// 混合同步更新
func (hs *HybridSync) Update() {
    // 关键对象:状态同步
    for _, obj := range hs.getCriticalObjects() {
        hs.syncState(obj)
    }

    // 特效对象:帧同步
    for _, obj := range hs.getEffectObjects() {
        hs.syncFrame(obj)
    }
}

// 获取关键对象(需要状态同步)
func (hs *HybridSync) getCriticalObjects() []*GameObject {
    return hs.filterObjects(func(obj *GameObject) bool {
        return obj.Type == "player" ||
               obj.Type == "bullet" ||
               obj.Type == "npc"
    })
}

// 获取特效对象(可以帧同步)
func (hs *HybridSync) getEffectObjects() []*GameObject {
    return hs.filterObjects(func(obj *GameObject) bool {
        return obj.Type == "effect" ||
               obj.Type == "particle" ||
               obj.Type == "sound"
    })
}

// 状态同步
func (hs *HybridSync) syncState(obj *GameObject) {
    // 服务器权威计算
    newState := hs.stateSync.serverState.UpdateObject(obj)

    // 广播给客户端
    hs.stateSync.broadcastObject(newState)
}

// 帧同步
func (hs *HybridSync) syncFrame(obj *GameObject) {
    // 本地确定性计算
    obj.Update()
}

混合同步的策略

// 同步策略
var syncStrategy = map[string]SyncType{
    // 关键对象:状态同步
    "player":    SyncTypeState,
    "bullet":    SyncTypeState,
    "npc":       SyncTypeState,

    // 特效对象:帧同步
    "effect":    SyncTypeFrame,
    "particle":  SyncTypeFrame,
    "sound":     SyncTypeFrame,

    // 混合对象
    "vehicle":   SyncTypeHybrid,  // 位置状态同步,特效帧同步
}

// 混合同步示例
func hybridSyncExample() {
    // 玩家移动:状态同步(服务器权威)
    playerMove := &Input{
        Type: "player_move",
        Data: map[string]interface{}{
            "player_id": 12345,
            "position": Vector3{X: 100, Y: 0, Z: 200},
        },
    }
    sendToServer(playerMove)

    // 技能特效:帧同步(本地计算)
    skillEffect := &Input{
        Type: "skill_effect",
        Data: map[string]interface{}{
            "skill_id": 1,
            "position": Vector3{X: 100, Y: 0, Z: 200},
        },
    }
    localCompute(skillEffect)
}

真实案例分析

案例1:《英雄联盟》的状态同步

背景

  • 玩家:10人(5v5)
  • 延迟要求:<100ms
  • 特点:大量技能、复杂判定

技术方案

// LoL 状态同步方案
type LoLSync struct {
    // 服务器更新频率:30Hz
    serverTickRate int

    // 客户端插值延迟:100ms
    interpolationDelay int

    // 关键对象
    players       map[uint64]*Champion
    minions       []*Minion
    towers        []*Tower
}

// Champion 英雄
type Champion struct {
    ID          uint64
    Position    Vector3
    Health      int
    Mana        int
    Abilities   []*Ability
    Stats       ChampionStats
}

// 服务器主循环
func (lol *LoLSync) ServerUpdate() {
    ticker := time.NewTicker(time.Second / 30)
    defer ticker.Stop()

    for range ticker.C {
        // 1. 收集客户端输入
        inputs := lol.collectInputs()

        // 2. 服务器权威计算
        for _, input := range inputs {
            lol.processInput(input)
        }

        // 3. 更新所有对象
        lol.updateWorld()

        // 4. 广播状态(只广播变化)
        lol.broadcastDelta()
    }
}

// Delta更新(只发送变化的部分)
func (lol *LoLSync) broadcastDelta() {
    for _, player := range lol.players {
        // 计算变化
        delta := lol.calculateDelta(player.LastState, player.CurrentState)

        // 只发送有变化的对象
        if len(delta.Changes) > 0 {
            lol.sendToClient(player.ID, delta)
        }
    }
}

// Delta结构
type DeltaUpdate struct {
    FrameNum    uint32
    Changes     []ObjectChange
}

type ObjectChange struct {
    ObjectID    uint64
    Properties  map[string]interface{}
}

优化技巧

// 优化1:AOI过滤(只发送可见对象)
func (lol *LoLSync) aoiFilter(player *Champion) []uint64 {
    visibleObjects := []uint64{}

    for _, obj := range lol.allObjects {
        if lol.isInAOI(player.Position, obj.Position, 3000) {
            visibleObjects = append(visibleObjects, obj.ID)
        }
    }

    return visibleObjects
}

// 优化2:优先级队列(重要对象优先)
type PriorityObject struct {
    ObjectID    uint64
    Priority    uint8
    Data        []byte
}

func (lol *LoLSync) sendWithPriority() {
    queue := &PriorityQueue{}

    // 英雄:高优先级
    for _, player := range lol.players {
        queue.Push(&PriorityObject{
            ObjectID: player.ID,
            Priority: 1,
            Data:     player.Serialize(),
        })
    }

    // 小兵:低优先级
    for _, minion := range lol.minions {
        queue.Push(&PriorityObject{
            ObjectID: minion.ID,
            Priority: 3,
            Data:     minion.Serialize(),
        })
    }

    // 按优先级发送
    for !queue.IsEmpty() {
        obj := queue.Pop()
        lol.send(obj.Data)
    }
}

// 优化3:带宽控制(限制每帧发送量)
func (lol *LoLSync) bandwidthControl() {
    maxBytesPerFrame := 50 * 1024  // 50KB/帧

    sentBytes := 0
    for _, obj := range lol.objects {
        data := obj.Serialize()
        if sentBytes + len(data) > maxBytesPerFrame {
            // 超出带宽,跳过低优先级对象
            if obj.Priority > 2 {
                continue
            }
        }

        lol.send(data)
        sentBytes += len(data)
    }
}

效果

  • 延迟:P50 < 50ms
  • 带宽:< 20KB/s/玩家
  • 一致性:99.9%

案例2:《守望先锋》的混合同步

背景

  • 玩家:12人(6v6)
  • 延迟要求:<50ms
  • 特点:高速移动、大量射击

技术方案

// Overwatch 混合同步
type OverwatchSync struct {
    // 状态同步:玩家、子弹
    stateSyncObjects []*StateSyncObject

    // 帧同步:技能特效
    frameSyncObjects []*FrameSyncObject
}

// 状态同步对象
type StateSyncObject struct {
    Type      string  // "player", "bullet", "shield"
    Position  Vector3
    Velocity  Vector3
    Health    int
}

// 帧同步对象
type FrameSyncObject struct {
    Type      string  // "effect", "particle"
    Position  Vector3
    Duration  int
    Params    map[string]interface{}
}

// 混合同步更新
func (ow *OverwatchSync) Update() {
    // 1. 状态同步部分(30Hz)
    if ow.shouldUpdateStateSync() {
        ow.updateStateSync()
    }

    // 2. 帧同步部分(60Hz)
    if ow.shouldUpdateFrameSync() {
        ow.updateFrameSync()
    }
}

func (ow *OverwatchSync) updateStateSync() {
    // 收集输入
    inputs := ow.collectInputs()

    // 服务器权威计算
    for _, obj := range ow.stateSyncObjects {
        obj.Update(inputs)
    }

    // 广播状态
    ow.broadcastState()
}

func (ow *OverwatchSync) updateFrameSync() {
    // 确定性计算
    for _, obj := range ow.frameSyncObjects {
        obj.Update()
    }
}

延迟补偿技术

// LagCompensation 延迟补偿
type LagCompensation struct {
    serverTime      int64
    stateHistory    *StateHistory  // 状态历史
}

// StateHistory 状态历史
type StateHistory struct {
    states    []GameState
    maxSize   int
}

// 保存状态(每帧保存)
func (sh *StateHistory) SaveState(state GameState) {
    sh.states = append(sh.states, state)

    // 只保留最近1秒的状态(60帧)
    if len(sh.states) > sh.maxSize {
        sh.states = sh.states[1:]
    }
}

// 获取历史状态
func (sh *StateHistory) GetState(timestamp int64) GameState {
    // 找到最接近的时间点
    for i := len(sh.states) - 1; i >= 0; i-- {
        if sh.states[i].Timestamp <= timestamp {
            return sh.states[i]
        }
    }

    return sh.states[0]
}

// 服务器回溯判定
func (lc *LagCompensation) ServerRewind(clientTime int64, shot ShootInput) bool {
    // 1. 回溯到客户端看到的时间
    historicalState := lc.stateHistory.GetState(clientTime)

    // 2. 使用历史状态进行判定
    hit := lc.checkHit(shot, historicalState)

    return hit
}

// 命中判定
func (lc *LagCompensation) checkHit(shot ShootInput, state GameState) bool {
    // 射线检测
    for _, player := range state.Players {
        if lc.raycast(shot.Position, shot.Direction, player.Position) {
            return true
        }
    }

    return false
}

效果

  • 延迟:P50 < 35ms
  • 命中准确率:98%
  • 玩家满意度:95%

案例3:《星际争霸2》的Lockstep帧同步

背景

  • 玩家:2-8人
  • 延迟要求:<200ms
  • 特点:数百个单位、复杂AI

技术方案

// SC2 Lockstep帧同步
type SC2Lockstep struct {
    // 帧率:22Hz(固定)
    frameRate       int

    // 输入缓冲
    inputBuffer     *LockstepBuffer

    // 确定性计算
    deterministic   *DeterministicEngine
}

// LockstepBuffer Lockstep输入缓冲
type LockstepBuffer struct {
    inputs      map[uint32]map[uint64][]Input  // 帧号 -> 玩家ID -> 输入
    frameNum    uint32
    playerCount int
}

// Lockstep等待所有玩家输入
func (lb *LockstepBuffer) WaitForInputs(frameNum uint32) []Input {
    // 等待所有玩家输入
    for {
        inputs, ok := lb.inputs[frameNum]
        if !ok || len(inputs) < lb.playerCount {
            time.Sleep(1 * time.Millisecond)
            continue
        }

        // 收集齐了,返回所有输入
        var allInputs []Input
        for _, playerInputs := range inputs {
            allInputs = append(allInputs, playerInputs...)
        }

        return allInputs
    }
}

// 确定性引擎
type DeterministicEngine struct {
    // 定点数计算
    fixedPointMath bool

    // 确定性随机数
    random         *DeterministicRandom

    // 状态机
    stateMachine   *DeterministicFSM
}

// 确定性随机数
type DeterministicRandom struct {
    seed    uint64
    current uint64
}

// 生成确定性随机数
func (dr *DeterministicRandom) Next() int32 {
    // 使用线性同余生成器
    dr.current = (dr.current * 1103515245 + 12345) & 0x7fffffff
    return int32(dr.current)
}

// 使用示例
func sc2LocksyncExample() {
    lockstep := &SC2Lockstep{
        frameRate: 22,
        inputBuffer: &LockstepBuffer{
            inputs: make(map[uint32]map[uint64][]Input),
            frameNum: 0,
            playerCount: 2,
        },
    }

    // 游戏主循环
    for {
        // 1. 等待所有玩家输入
        inputs := lockstep.inputBuffer.WaitForInputs(lockstep.inputBuffer.frameNum)

        // 2. 确定性计算
        lockstep.deterministic.Update(inputs)

        // 3. 渲染
        lockstep.render()

        // 4. 帧计数递增
        lockstep.inputBuffer.frameNum++
    }
}

优化技巧

// 优化1:输入压缩(减少网络传输)
func compressInputs(inputs []Input) []byte {
    // 使用RLE(Run-Length Encoding)压缩
    compressed := []byte{}

    for i, input := range inputs {
        // 只记录变化的部分
        if i > 0 && inputs[i-1].PlayerID == input.PlayerID {
            // 相同玩家,只记录变化
            delta := calculateDelta(inputs[i-1], input)
            compressed = append(compressed, delta...)
        } else {
            // 新玩家,记录完整输入
            compressed = append(compressed, input.Serialize()...)
        }
    }

    return compressed
}

// 优化2:帧预测(减少等待时间)
func (lb *LockstepBuffer) PredictInputs(frameNum uint32) []Input {
    // 预测玩家输入(基于历史输入)
    predicted := []Input{}

    for playerID := uint64(1); playerID <= uint64(lb.playerCount); playerID++ {
        // 获取玩家最近的输入
        lastInput := lb.getLastInput(playerID)

        // 预测下一个输入(假设玩家行为不变)
        predictedInput := lastInput.PredictNext()
        predicted = append(predicted, predictedInput)
    }

    return predicted
}

// 优化3:断线重连(状态快照)
func (lb *LockstepBuffer) SaveSnapshot(frameNum uint32) {
    // 每100帧保存一次快照
    if frameNum % 100 == 0 {
        snapshot := lb.captureSnapshot()
        lb.saveSnapshot(snapshot)
    }
}

// 客户端重连后,从最近的快照恢复
func (lb *LockstepBuffer) RestoreSnapshot(frameNum uint32) {
    snapshot := lb.loadSnapshot(frameNum)
    lb.restoreFromSnapshot(snapshot)
}

效果

  • 延迟:P50 < 150ms
  • 带宽:< 5KB/s/玩家
  • 一致性:100%

同步模型选择决策树

// 同步模型选择
type SyncModelSelector struct {
    PlayerCount    int
    LatencyRequirement time.Duration
    ObjectCount    int
    AntiCheat      bool
    ServerCost     bool
}

// 选择同步模型
func (sms *SyncModelSelector) SelectModel() SyncType {
    // 1. 根据玩家数量
    if sms.PlayerCount > 20 {
        // 大量玩家:必须用状态同步
        return SyncTypeState
    }

    // 2. 根据延迟要求
    if sms.LatencyRequirement < 50*time.Millisecond {
        // 极低延迟:考虑帧同步
        if sms.ObjectCount < 100 {
            return SyncTypeFrame
        }
    }

    // 3. 根据反作弊需求
    if sms.AntiCheat {
        // 强反作弊:必须用状态同步
        return SyncTypeState
    }

    // 4. 根据服务器成本
    if sms.ServerCost {
        // 服务器成本敏感:考虑帧同步
        return SyncTypeFrame
    }

    // 默认:状态同步
    return SyncTypeState
}

// 决策树
func decisionTree() {
    // 开始
    // ├── 玩家数 > 20?
    // │   ├── 是:状态同步
    // │   └── 否:继续
    // ├── 延迟要求 < 50ms?
    // │   ├── 是:帧同步
    // │   └── 否:继续
    // ├── 需要强反作弊?
    // │   ├── 是:状态同步
    // │   └── 否:继续
    // ├── 服务器成本敏感?
    // │   ├── 是:帧同步
    // │   └── 否:状态同步
}

踩坑经验

❌ 错误1:在帧同步中使用浮点数

// ❌ 错误:使用浮点数
type Player struct {
    X float64  // 不同CPU结果可能不同
    Y float64
}

func updatePosition(p *Player) {
    p.X += 0.1  // 可能产生精度误差
    p.Y += 0.2
}

// ✅ 正确:使用定点数
type PlayerFixed struct {
    X int64  // 定点数(精确)
    Y int64
}

const SCALE = 1000  // 精度到小数点后3位

func updatePositionFixed(p *PlayerFixed) {
    p.X += 100  // 0.1 * 1000 = 100
    p.Y += 200  // 0.2 * 1000 = 200
}

// 定点数转浮点数
func (p *PlayerFixed) ToFloat() (float64, float64) {
    return float64(p.X) / SCALE, float64(p.Y) / SCALE
}

❌ 错误2:状态同步不使用插值

// ❌ 错误:直接显示服务器位置
func (c *Client) OnServerPosition(pos Position) {
    c.player.Position = pos  // 画面卡顿
}

// ✅ 正确:使用插值
func (c *Client) OnServerPosition(pos Position) {
    // 保存到历史队列
    c.positionBuffer.Push(pos)

    // 插值显示(延迟100ms)
    renderTime := time.Now().Add(-100 * time.Millisecond)
    interpolated := c.positionBuffer.Interpolate(renderTime)
    c.player.Position = interpolated
}

❌ 错误3:帧同步不考虑执行顺序

// ❌ 错误:不保证执行顺序
func (fs *FrameSync) updateGameState(inputs []Input) {
    for _, input := range inputs {
        // 问题:不同客户端的输入顺序可能不同
        fs.executeAction(input)
    }
}

// ✅ 正确:保证执行顺序
func (fs *FrameSync) updateGameState(inputs []Input) {
    // 按玩家ID排序
    sort.Slice(inputs, func(i, j int) bool {
        return inputs[i].PlayerID < inputs[j].PlayerID
    })

    // 按顺序执行
    for _, input := range inputs {
        fs.executeAction(input)
    }
}

性能对比

带宽对比

// 带宽对比(10个玩家,60Hz)
var bandwidthComparison = map[string]float64{
    "状态同步": 20.0,  // KB/s/玩家
    "帧同步":   5.0,   // KB/s/玩家(只传输输入)
    "混合同步": 15.0,  // KB/s/玩家
}

延迟对比

// 延迟对比
var latencyComparison = map[string]time.Duration{
    "状态同步": 100 * time.Millisecond,  // 需要插值延迟
    "帧同步":   50 * time.Millisecond,   // 本地计算
    "混合同步": 75 * time.Millisecond,   // 介于两者之间
}

服务器负载对比

// 服务器负载对比(10个玩家)
var serverLoadComparison = map[string]float64{
    "状态同步": 100.0,  // %(所有计算在服务器)
    "帧同步":   20.0,   // %(只转发输入)
    "混合同步": 60.0,   // %(部分计算在服务器)
}

小结

同步模型的核心要点:

  1. 状态同步:服务器权威,适合大多数游戏
  2. 帧同步:确定性计算,适合低延迟游戏
  3. 混合同步:结合两者优势,适合复杂游戏

选择依据

  • 玩家数量:>20用状态同步
  • 延迟要求:<50ms用帧同步
  • 反作弊需求:强反作弊用状态同步

真实案例

  • 《英雄联盟》:状态同步 + AOI优化
  • 《守望先锋》:混合同步 + 延迟补偿
  • 《星际争霸2》:Lockstep帧同步

踩坑经验

  • ❌ 帧同步不要用浮点数
  • ❌ 状态同步不要省略插值
  • ❌ 帧同步要保证执行顺序

下一节(4.2)我们将学习:Tick与时间管理,深入设计游戏Tick系统。