階段目標
- 從大廳進入戰場
- 客戶端修改爲雙人版本
- 戰場內移動同步的實現
效果演示
單機版的結構
GameManager作爲一個全局的遊戲管理器存在,每個具體的坦克是由TankManager類來進行管理,其中,坦克具體的移動和射擊邏輯是分別交由TankMovment和TankShooting去完成。
聯機版的結構
聯機版和單機版在類的設計實現上是一樣的,主要區別也就下面幾點:
- TankManager交由PlayerAvatar來持有,因爲聯機時,我們每個角色的數據創建時由server來驅動的,與server對應的玩家實體是PlayerAvatar,這個PlayerAvatar相當於是一個真實的服務器一致的邏輯數據實體。
- 區分出主玩家控制的坦克角色和其他非主玩家坦克
戰場的跳轉
這類開房間式的遊戲,都會涉及到一個從大廳跳轉戰場的流程。坦克大戰也是一樣,我們的戰場和大廳在這個版本里面都是SpaceRoom這一個類來完成,只是戰場和大廳的uType值不同。
class SpaceType(object):
SPACE_TYPE_NONE = 0
SPACE_TYPE_HALL = 1
SPACE_TYPE_BATTLE = 2
在完成匹配後,我們會申請創建一個戰場space,並且在space創建完畢後,讓base上的playAvatar一個個enter進space,在enter到space上時,會先離開大廳這個space,然後調用playAvatar在cell部分的onTeleportSpaceCB方法,該方法主要做一件事情,就是讓玩家teleport到戰場所在的space。
跳轉戰場的時序圖如下:
說明下,base_Hall就是大廳,base_SpaceRoom是戰場,他們都是SpaceRoom這個類,只是圖裏面爲了區分,用了不同的名稱。
對應的基本代碼(git版本號 459eb4a3980f183d5e8b9b191d91a1845bd7bf1c):
base/SpaceRoom.py
def enter(self, avatar_entity_call):
if self.uType == SpaceType.SPACE_TYPE_HALL:
avatar_entity_call.createCellEntity(self.cell)
else:
# 從大廳進入戰場
# 先從大廳離開
avatar_entity_call.curHallSpace.leave(avatar_entity_call.id)
avatar_entity_call.cell.onTeleportSpaceCB(self, self.cell, avatar_entity_call.born_position, (0, 0, 0))
self._avatar_dict[avatar_entity_call.id] = avatar_entity_call
avatar_entity_call.onEnterSpace(self.id, self.uType)
cell/PlayerAvatar.py
def onTeleportSpaceCB(self, spaceBaseEntityCall, spaceCellEntityCall, position, direction):
"""
defined.
baseapp返回teleportSpace的回調
"""
DEBUG_MSG("PlayerAvatar::onTeleportSpaceCB. spaceBase:%s...spaceCell:%s" % (spaceBaseEntityCall, spaceCellEntityCall))
self.curSpaceBaseEntityCall = spaceBaseEntityCall
self.teleport(spaceCellEntityCall, position, direction)
移動同步
對於View範圍內的Entity,其基礎的屬性,Kbengine引擎底層會自動幫我們做好了屬性的同步,包括position和direction等。
但是,對於服務器自動同步下來的position,肯定都是一個個離散的點,同步數據到達客戶端的時間也受到當前網絡的影響,其次,服務端的tick頻率也往往會比客戶端低。如果客戶端每次收到position的位置,就直接更新模型到對應的點上,看上去的移動一定會是一卡一卡的。
爲了解決非主玩家的移動平滑同步,這裏採用了簡單的影子跟隨算法。影子跟隨簡單理解就是,模型(Gameobject)一直去追隨與之關聯的邏輯對象(PlayAvatar)位置。這個PlayAvatar是個Entity,也就是和服務端保持一直的一個實體對象,這是個邏輯的數據,其上的position會經由服務器自動同步下發。每個PlayAvatar都有一個與之綁定的Gameobject對象,Gameobject是個渲染模型對象。
追隨的核心算法在Scripts/Tank/TankMovement.cs裏面。
// 影子追隨
private float CalcNewValueByShadow(float curValue, float shadowValue, float deltaTime)
{
if (curValue == shadowValue)
return curValue;
float deltaValue = Mathf.Abs(curValue - shadowValue);
int ratio = 1;
if (curValue < shadowValue)
{
// 大於一定閾值,加速
if (deltaValue > m_Speed * 40 * deltaTime)
ratio = 2;
curValue += Mathf.Min(deltaValue, m_Speed * deltaTime * ratio);
}
else
{
if (deltaValue > m_Speed * 5 * deltaTime)
ratio = 2;
curValue -= Mathf.Min(deltaValue, m_Speed * deltaTime * ratio);
}
return curValue;
}
說明: 代碼中有個40數值,這個僅僅是個保持相位差的最大閾值,可以根據需要自行調整,所謂相位差,就是每一幀下,實體和影子相對保持的距離。因爲我們設定的移速是m_Speed,一幀的時間是deltaTime,40相當於是我們允許模型和影子的最大位移差是40倍的m_Speed*deltaTime。
存在的問題
這個版本其實是相對粗糙的,無論是代碼結構還是遊戲功能,都還屬於功能驗證和引擎學習階段,對於戰場也是隻能每次只進入1輪便必須結束。這些問題會在下一篇文章裏統一解決掉。這裏只作爲KBEngine和Unity的入門練習準備。
後續內容
- 戰鬥同步
- 代碼結構調整,模塊解耦(比如space拆分、ui和邏輯拆分)
- 斷線重連和頂號
- 多房間創建(目前僅支持開一個戰場,不然會出問題)
- KBEngine開發中遇到的坑
- KBEngine中部分功能的源碼分析
源碼地址
github地址:地址在這裏
個人有點懶,再加上前端時間工作上比較忙,代碼和文章會不定時更新哦。得逼自己一把,爭取後續內容一個月內更完吧。