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, // %(部分计算在服务器)
}
小结
同步模型的核心要点:
- 状态同步:服务器权威,适合大多数游戏
- 帧同步:确定性计算,适合低延迟游戏
- 混合同步:结合两者优势,适合复杂游戏
选择依据:
- 玩家数量:>20用状态同步
- 延迟要求:<50ms用帧同步
- 反作弊需求:强反作弊用状态同步
真实案例:
- 《英雄联盟》:状态同步 + AOI优化
- 《守望先锋》:混合同步 + 延迟补偿
- 《星际争霸2》:Lockstep帧同步
踩坑经验:
- ❌ 帧同步不要用浮点数
- ❌ 状态同步不要省略插值
- ❌ 帧同步要保证执行顺序
下一节(4.2)我们将学习:Tick与时间管理,深入设计游戏Tick系统。