最近一直在看网游(即MMO游戏)架构的资料, 总结了一些要点
1. MMO游戏仿真框架
MMO游戏的客户端和服务端架构比较相似,
上层是网络层, 负责数据包的收发. 底层是游戏仿真层, 负责游戏对象的仿真, 如运动,战斗等
游戏中的对象, 比如人物, 怪物, 物品, 障碍, 地图都是仿真对象, 游戏业务逻辑就是这些仿真对象的交互(物理, AI等)
client-server的数据流:
client的玩家操纵人物, 引起client仿真层的变化,
client仿真层的变化数据发送给server
server用这些数据来更新自己的仿真层
server把自己仿真层的变化广播给所有相关客户端
client用这些数据来更新自己的仿真层
2. 仿真层和观察者模式
仿真层就是MVC模式中的Model, 仿真层作为数据源, 要向各种接收者发送数据, 来实现游戏的各种功能.
观察者模式可以成功的解耦仿真层和其他模块的交互.
在服务端, 仿真层作为Observerable, 网络层作为Observer, 数据库也作为Observer
当仿真层产生变化时, 主动把更新发送给网络层去广播, 也发送给数据库做持久化.
在客户端, 仿真层作为Observerable, 网络层作为Observer, 3D/2D图形引擎作为Observer
当仿真层变化时, 主动把更新发送给网络层转发到服务端, 也发送给图形引擎显示到屏幕上
这样就把仿真层独立出来, 可以应用于不同的通信框架, 图形引擎, 数据库.
3. 同步问题
目前我理解, 网游最麻烦的地方就是N个客户端和1个服务端的同步问题
客户端应该以什么频率发送什么消息给服务端
服务端应该以什么频率发送什么消息给哪些客户端
服务端在客户端延迟时, 应该怎么更新仿真对象
客户端在服务端延迟时, 应该怎么更新仿真对象
客户端和服务端是否要用相同的时钟
解决好这些问题, 就可以让客户端更流畅, 准确
但是流畅和准确却几乎是互斥的, 如果要准确, 就不可能流畅
比如客户端要求一个人物30ms移动一个位置, 但是服务端每隔300ms才给客户端发送一个位置
如果要求完全准确的话, 这个怪物就会瞬间移动, 如果要求流畅(客户端自己估计怪物的移动), 则又与服务端发来的位置不符.
4. 服务端消息管理
2003年的端游统计数据, 每个玩家需要的带宽是1KB/s, 根据大陆的移动网络情况, 这个带宽也适用于今天的手游
因此, 发送消息的频率和大小都要做好限制
服务端有很多消息(战斗信息, 位置信息, 角色状态, NPC信息, 广告, 聊天)要发给一个客户端, 这些消息要根据优先级排列
所以在服务端需要维护一个循环队列
该循环队列由时间槽组成, 时间槽里放的是信息源(比如某个角色的位置, 聊天信息), 队列的头是马上要发送的信息源.
当队列的头发送时, 从信息源里取出要发送的信息, 然后拼接成网络包发送出去. 队列的头移到队尾.
但是有些信息要经常发送, 比如敌人的位置, 所以要马上重新发送该信息.
即使是角色的位置, 也有优先级, 一般, 距离目标客户端主角较近的角色(曼哈顿距离)发送的频率高一点, 较远的角色, 频率低一点
还要注意的一个问题是, 给一个主角发送哪些角色的信息, 邻近角色的范围如何界定.
5. 客户端延迟处理
服务端向客户端更新的频率是每秒4-20次, 因此客户端的主要问题是: 没收到服务端的更新时该如何处理.
处理延迟有3种常用方法
1) 命令时间同步
比如客户端A让自己的角色A移动到某点, 其移动的速度是10m/s
当其他客户端收到该消息时, 可能A在客户端A上已经移动了1S,因此, 其他客户端上,A的移动速度应该大于10m/s
这样, 所有客户端上, A都是在相同的时间到达目标点
2) 推导和差值
如果客户端A上显示B角色(由客户端B控制)正在移动, 但是某一时刻, 服务器发生延迟, 300ms内没收到B的后续位置
则客户端A应该让B以其最后的速度和方向继续移动, 或者根据其之前的运动轨迹来估算其后续运动轨迹
当最终服务端把B的位置发来时(这个是B的准确位置), 则应该马上移动到准确位置, 或者把准确位置加入运动轨迹, 继续估算
3) 反向仿真
客户端A中, A向B瞄准并设计, 对A来说, 一定击中B, 但是射击的消息发到服务端后, 发现B其实是在另一个位置的
这是因为客户端中的位置可能是推导出来的. 这时服务端要根据客户端A最后收到服务端消息的时间, 来反向模拟出来对于客户端A来说, B到底在什么位置, 如果确实应该被击中, 那么就要击中.
4) 时间同步
客户端的世界时间和帧频时间要分开管理. 客户端的世界时间和服务端的世界时间可以保持一致, 也可以让客户端的世界时间比服务端的世界时间稍微慢一点. 如果是后者, 则推导时比较准确, 不会发生推导到新的位置后, 服务端发送来的位置反而在推导位置之前的情况.
6. 计划
服务端需要异步编程, 所以最好用现成的异步编程框架, 比如Pomelo或Firefly, 这一部分还要仔细研究.
同步问题具体怎么处理, 要结合游戏来实验. 以前为单机游戏的设计和实现几乎要全部重写.
MMO游戏的客户端和服务端架构比较相似,
上层是网络层, 负责数据包的收发. 底层是游戏仿真层, 负责游戏对象的仿真, 如运动,战斗等
游戏中的对象, 比如人物, 怪物, 物品, 障碍, 地图都是仿真对象, 游戏业务逻辑就是这些仿真对象的交互(物理, AI等)
client-server的数据流:
client的玩家操纵人物, 引起client仿真层的变化,
client仿真层的变化数据发送给server
server用这些数据来更新自己的仿真层
server把自己仿真层的变化广播给所有相关客户端
client用这些数据来更新自己的仿真层
2. 仿真层和观察者模式
仿真层就是MVC模式中的Model, 仿真层作为数据源, 要向各种接收者发送数据, 来实现游戏的各种功能.
观察者模式可以成功的解耦仿真层和其他模块的交互.
在服务端, 仿真层作为Observerable, 网络层作为Observer, 数据库也作为Observer
当仿真层产生变化时, 主动把更新发送给网络层去广播, 也发送给数据库做持久化.
在客户端, 仿真层作为Observerable, 网络层作为Observer, 3D/2D图形引擎作为Observer
当仿真层变化时, 主动把更新发送给网络层转发到服务端, 也发送给图形引擎显示到屏幕上
这样就把仿真层独立出来, 可以应用于不同的通信框架, 图形引擎, 数据库.
3. 同步问题
目前我理解, 网游最麻烦的地方就是N个客户端和1个服务端的同步问题
客户端应该以什么频率发送什么消息给服务端
服务端应该以什么频率发送什么消息给哪些客户端
服务端在客户端延迟时, 应该怎么更新仿真对象
客户端在服务端延迟时, 应该怎么更新仿真对象
客户端和服务端是否要用相同的时钟
解决好这些问题, 就可以让客户端更流畅, 准确
但是流畅和准确却几乎是互斥的, 如果要准确, 就不可能流畅
比如客户端要求一个人物30ms移动一个位置, 但是服务端每隔300ms才给客户端发送一个位置
如果要求完全准确的话, 这个怪物就会瞬间移动, 如果要求流畅(客户端自己估计怪物的移动), 则又与服务端发来的位置不符.
4. 服务端消息管理
2003年的端游统计数据, 每个玩家需要的带宽是1KB/s, 根据大陆的移动网络情况, 这个带宽也适用于今天的手游
因此, 发送消息的频率和大小都要做好限制
服务端有很多消息(战斗信息, 位置信息, 角色状态, NPC信息, 广告, 聊天)要发给一个客户端, 这些消息要根据优先级排列
所以在服务端需要维护一个循环队列
该循环队列由时间槽组成, 时间槽里放的是信息源(比如某个角色的位置, 聊天信息), 队列的头是马上要发送的信息源.
当队列的头发送时, 从信息源里取出要发送的信息, 然后拼接成网络包发送出去. 队列的头移到队尾.
但是有些信息要经常发送, 比如敌人的位置, 所以要马上重新发送该信息.
即使是角色的位置, 也有优先级, 一般, 距离目标客户端主角较近的角色(曼哈顿距离)发送的频率高一点, 较远的角色, 频率低一点
还要注意的一个问题是, 给一个主角发送哪些角色的信息, 邻近角色的范围如何界定.
5. 客户端延迟处理
服务端向客户端更新的频率是每秒4-20次, 因此客户端的主要问题是: 没收到服务端的更新时该如何处理.
处理延迟有3种常用方法
1) 命令时间同步
比如客户端A让自己的角色A移动到某点, 其移动的速度是10m/s
当其他客户端收到该消息时, 可能A在客户端A上已经移动了1S,因此, 其他客户端上,A的移动速度应该大于10m/s
这样, 所有客户端上, A都是在相同的时间到达目标点
2) 推导和差值
如果客户端A上显示B角色(由客户端B控制)正在移动, 但是某一时刻, 服务器发生延迟, 300ms内没收到B的后续位置
则客户端A应该让B以其最后的速度和方向继续移动, 或者根据其之前的运动轨迹来估算其后续运动轨迹
当最终服务端把B的位置发来时(这个是B的准确位置), 则应该马上移动到准确位置, 或者把准确位置加入运动轨迹, 继续估算
3) 反向仿真
客户端A中, A向B瞄准并设计, 对A来说, 一定击中B, 但是射击的消息发到服务端后, 发现B其实是在另一个位置的
这是因为客户端中的位置可能是推导出来的. 这时服务端要根据客户端A最后收到服务端消息的时间, 来反向模拟出来对于客户端A来说, B到底在什么位置, 如果确实应该被击中, 那么就要击中.
4) 时间同步
客户端的世界时间和帧频时间要分开管理. 客户端的世界时间和服务端的世界时间可以保持一致, 也可以让客户端的世界时间比服务端的世界时间稍微慢一点. 如果是后者, 则推导时比较准确, 不会发生推导到新的位置后, 服务端发送来的位置反而在推导位置之前的情况.
6. 计划
服务端需要异步编程, 所以最好用现成的异步编程框架, 比如Pomelo或Firefly, 这一部分还要仔细研究.
同步问题具体怎么处理, 要结合游戏来实验. 以前为单机游戏的设计和实现几乎要全部重写.
7. 简单的移动和攻击同步
![](http://images.cnitblog.com/blog/601647/201401/262058190171215.jpg)