unity架构师进阶课程: ECS MMORPG 战斗系统 服务端运行游戏逻辑+客户端同步
状态同步的一个核心是把整个游戏的所有数据与逻辑都跑到服务器上。
一听感觉很难,其实也就是像写客户端一样,跑一个update的帧频率来进行迭代(一般1秒15~20次迭代,或者动态调整)。
和客户端一样,每个游戏的玩家都有一个数据对象,唯一不同的是客户端有渲染,服务器只有数据没有渲染。一般我们会在逻辑服上创建一个类似的数据结构:
public class PlayerEntity {
@Protobuf(order = 1, required = true)
public StatusComponent statusInfo; // 玩家的状态;
@Protobuf(order = 2, required = true)
public AccountComponent accountInfo;
@Protobuf(order = 3, required = true)
public GhostPlayerInfoComponent ghostInfo;
@Protobuf(order = 4, required = true)
public EquipmentComponent equipmentInfo;
@Protobuf(order = 5, required = true)
public TransformComponent transformInfo;
// 玩家私密的数据是不必要的;
@Protobuf(order = 6, required = false)
public PrivatePlayerInfoComponent playerInfo;
public AOIComponent aoiComponent;
public SkillBuffComponent skillBuffComponent;
public AttackComponent attackComponent;
public ShapeComponent shapeInfo;
public AStarComponent astarComponent;
public JoystickComponent joystickComponent;
public PlayerEntity() {
this.accountInfo = null;
this.playerInfo = null;
this.ghostInfo = null;
this.equipmentInfo = null;
this.shapeInfo = null;
this.transformInfo = null;
this.astarComponent = null;
this.joystickComponent = null;
this.attackComponent = null;
this.skillBuffComponent = null;
this.statusInfo = null;
}
public static PlayerEntity create(Player p) {
PlayerEntity entity = new PlayerEntity();
entity.statusInfo = new StatusComponent();
entity.statusInfo.state = RoleState.Idle;
entity.accountInfo = new AccountComponent();
entity.playerInfo = new PrivatePlayerInfoComponent();
entity.ghostInfo = new GhostPlayerInfoComponent();
entity.equipmentInfo = new EquipmentComponent();
entity.shapeInfo = new ShapeComponent();
entity.transformInfo = new TransformComponent();
entity.aoiComponent = new AOIComponent();
SkillBuffSystem.InitSkilBuffComponent(entity);
entity.astarComponent = new AStarComponent();
entity.joystickComponent = new JoystickComponent();
entity.attackComponent = new AttackComponent();
entity.statusInfo.entityId = p.getId();
LoginMgr.getInstance().loadAccountComponentFromAccountId(entity.accountInfo, p.getAccountId());
PlayerMgr.getInstance().loadPlayerEntityFromPlayer(entity, p);
PlayerMgr.getInstance().loadEquipMentComponentDataFromPlayer(entity.equipmentInfo, p);
entity.shapeInfo.height = 2.0f;
entity.shapeInfo.R = 0.5f;
return entity;
}
}
比如上面的TransformComponent, 描述的就是这个游戏对象在世界中的位置,旋转。
public class TransformComponent {
@Protobuf(order = 1, required = true)
public Vector3Component position;
@Protobuf(order = 2, required = true)
public float eulerAngleY = 0;
public TransformComponent clone() {
TransformComponent t = new TransformComponent();
t.position = this.position.clone();
t.eulerAngleY = this.eulerAngleY;
return t;
}
public void LookAt(Vector3Component point) {
Vector3Component src = this.position;
Vector3Component dir = Vector3Component.sub(point, src);
float degree = (float)(Math.atan2(dir.z, dir.x) * 180 / Math.PI);
degree = 360 - degree;
this.eulerAngleY = degree + 90;
}
}
当创建一个玩家登录到逻辑服的时候,服务器中的3D世界就会创建一个这样的数据对象。接下来就要尝试让这个对象在游戏世界中跑动交互起来,服务端的地图如何做呢?
其实地图数据可以导出为地形高度图(x, y, z)+道路连通数据(哪些是可以行走,哪些不可以行走)。这个对团队的技术积累是有一点要求的。根据游戏不同的类型来做地图编辑器,来采用最合适的技术。
同时客户端+服务端都要使用这套,客户端有地图编辑器工具编辑地图的地形+烘焙地图连通数据,能将这些数据按照对应的格式导出給服务端用,服务端使用这些数据利用上面的Update来进行迭代计算(和客户端开发的Update迭代是一样的)。
地图技术+寻路导航解决以后,其它的推动游戏计算的也移植到到服务端,比如物理引擎,我们可以在服务器上部署一个物理引擎,然后从服务端的update来做物理引擎模拟迭代,再把物理刚体位置旋转等同步給服务端上的玩家数据对象,这样让服务器上也可以跑物理引擎。

