686
社区成员




多人在线游戏需要同步玩家数据,许多公司都拥有至少一款此类游戏。对于状态同步,首先需要掌握两层的设计架构思想以及计算机网络的原理,其难点在于:1. 网络延迟。2. 数据量.3. 安全性。
状态同步和帧同步的不同这里不做额外说明,可在参考资料中了解。
这里不涉及客户端的本地服务器和服务端的实现,只涉及对状态同步的学习小结,包括角色的添加/删除/更新,并省略Debug与校验等工作。
为了实现和使用状态同步,首先要应用ECS架构对实体进行设计,将数据与操作分离,并维护一个实体的管理器,方便对从服务端接收的实体数据进行更新,所学的客户端的一种实现如下:
Character character = new Character(cha);
this.Characters[cha.Id] = character;
调用EntityManager添加新的游戏实体, EntityManager.Instance.AddEntity(character);
同时,需要在地图中生成对应的角色,通过GameObjectManager统一管理所有的游戏对象并根据预制体生成。由于层次结构的设计,Manager层只关心同层的其他Manager,而不直接与GameObjectManager(处于实体层)交互,通过事件进行解耦。 if(OnCharacterEnter!=null)
{
// 定义的公共委托,GameObjectManager注册其生成方法
OnCharacterEnter(character);
}
这里要注意的是,网络中传输整个表数据会浪费带宽,因此传输的时表内容的Id属性,客户端拥有角色定义,根据Id使用不同的角色信息,但我认为会遭到数据篡改,因此可能需要进行额外的安全性校验。private void OnMapEntitySync(object sender,MapEntitySyncResponse response)
{
foreach(var entity in response.entitySyncs)
{
Managers.EntityManager.Instance.OnEntitySync(entity);
}
}
EntityManager.OnEntitySync: if (notifiers.ContainsKey((int)data.Id))
{
// 基于接口的观察者模式,调用字典中注册的处理者来更新数据
notifiers[entity.entityId].OnEntityChanged(entity);
notifiers[entity.entityId].OnEntityEvent(data.Event);
}
服务端的同步工作,是保存客户端发来的状态信息并转发,所以实际操作和客户端类似,只不过不需要渲染出真正的游戏对象,所以只需要维护一份角色数据,在角色刚进入游戏时即可在CharacterManager中创建,只需要更新状态信息,并需要向其他客户端发送所更新的信息。
当收到客户端发送的添加角色请求,分为进入游戏与进入地图两个阶段,进入游戏时和客户端一样,交给CharacterManager-》EntityManager生成新的角色数据,并返回给客户端所生成的角色数据,保证一定的安全性,这里是独立的。
而进入游戏后,进入地图时,又会让MapService接收到信息,将character信息交给MapManager处理:
MapService:
MapManager.Instance[request.mapId].CharacterEnter(sender, character);
Map:
// 向Response添加需要增加的角色
message.Response.mapCharacterEnter.Characters.Add(character.Info);
// 添加地图中的其他角色并向其他玩家告知角色增加
foreach (var kv in this.MapCharacters)
{
// 添加地图中的其他角色
message.Response.mapCharacterEnter.Characters.Add(kv.Value.character.Info);
// 向这个角色告知角色添加至地图
this.SendCharacterEnterMap(kv.Value.connection, character.Info);
}
当收到客户端发送的状态更新请求:
void OnMapEntitySync(NetConnection<NetSession> sender, MapEntitySyncRequest request)
{
Character character=sender.Session.Character;
// 更新对应地图Map中的信息
MapManager.Instance[character.Info.mapId].UpdateEntity(request.entitySync);
}
Map: internal void UpdateEntity(NEntitySync entitySync)
{
// 遍历当前地图的角色
foreach (var character in this.MapCharacters)
{
// 如果是需要更新的角色则更新
if (character.Value.character.entityId == entitySync.Id)
{
// 更新位置、方向等
……
}
else
{
// 如果是其他客户端的角色,则直接发送更新消息
MapService.Instance.SendEntityUpdate(character.Value.connection, entitySync);
}
}
}
实现的同步功能比较基础,未进行难点的实现,故没有遇到较大困难。
由于客户端所生成的角色定义是服务端传递的,故在角色刚进入游戏时,会依次发送两次请求
状态同步需要同步客户端的状态数据,如位置、状态、伤害等,其主要逻辑在服务端,但也有些状态同步基于客户端,比如小型的联机游戏。
状态同步虽然只是网络游戏中的一种同步方式,想要完全掌握还是很复杂,特别是要考虑网络的各种情况,需要一定的经验。而其他的同步方式比如帧同步、状态帧同步,似乎水更是深。作为一名客户端程序,也要明白其大概模型才能更好的和后端人员沟通。
状态同步与帧同步
谷歌Protobuf通信详解
如有问题,欢迎指正!