unity架構(gòu)師進(jìn)階課程: ECS MMORPG 戰(zhàn)斗系統(tǒng) 服務(wù)端運(yùn)行游戲邏輯+客戶端同步
狀態(tài)同步的一個(gè)核心是把整個(gè)游戲的所有數(shù)據(jù)與邏輯都跑到服務(wù)器上。
一聽感覺很難,其實(shí)也就是像寫客戶端一樣,跑一個(gè)update的幀頻率來進(jìn)行迭代(一般1秒15~20次迭代,或者動(dòng)態(tài)調(diào)整)。
和客戶端一樣,每個(gè)游戲的玩家都有一個(gè)數(shù)據(jù)對象,唯一不同的是客戶端有渲染,服務(wù)器只有數(shù)據(jù)沒有渲染。一般我們會(huì)在邏輯服上創(chuàng)建一個(gè)類似的數(shù)據(jù)結(jié)構(gòu):
public class PlayerEntity {
@Protobuf(order = 1, required = true)
public StatusComponent statusInfo; // 玩家的狀態(tài);
@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;
// 玩家私密的數(shù)據(jù)是不必要的;
@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, 描述的就是這個(gè)游戲?qū)ο笤谑澜缰械奈恢?,旋轉(zhuǎn)。
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;
}
}
當(dāng)創(chuàng)建一個(gè)玩家登錄到邏輯服的時(shí)候,服務(wù)器中的3D世界就會(huì)創(chuàng)建一個(gè)這樣的數(shù)據(jù)對象。接下來就要嘗試讓這個(gè)對象在游戲世界中跑動(dòng)交互起來,服務(wù)端的地圖如何做呢?
其實(shí)地圖數(shù)據(jù)可以導(dǎo)出為地形高度圖(x, y, z)+道路連通數(shù)據(jù)(哪些是可以行走,哪些不可以行走)。這個(gè)對團(tuán)隊(duì)的技術(shù)積累是有一點(diǎn)要求的。根據(jù)游戲不同的類型來做地圖編輯器,來采用最合適的技術(shù)。
同時(shí)客戶端+服務(wù)端都要使用這套,客戶端有地圖編輯器工具編輯地圖的地形+烘焙地圖連通數(shù)據(jù),能將這些數(shù)據(jù)按照對應(yīng)的格式導(dǎo)出給服務(wù)端用,服務(wù)端使用這些數(shù)據(jù)利用上面的Update來進(jìn)行迭代計(jì)算(和客戶端開發(fā)的Update迭代是一樣的)。
地圖技術(shù)+尋路導(dǎo)航解決以后,其它的推動(dòng)游戲計(jì)算的也移植到到服務(wù)端,比如物理引擎,我們可以在服務(wù)器上部署一個(gè)物理引擎,然后從服務(wù)端的update來做物理引擎模擬迭代,再把物理剛體位置旋轉(zhuǎn)等同步給服務(wù)端上的玩家數(shù)據(jù)對象,這樣讓服務(wù)器上也可以跑物理引擎。