文章目錄
-
- 階段目标
- 效果示範
- 單機版的結構
- 聯機版的結構
- 戰場的跳轉
- 移動同步
- 存在的問題
- 後續内容
- 源碼位址
階段目标
- 從大廳進入戰場
- 用戶端修改為雙人版本
- 戰場内移動同步的實作
效果示範
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiZpdmL5kTN2AjM0EjMyITMxkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.gif)
單機版的結構
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位址:位址在這裡
個人有點懶,再加上前端時間工作上比較忙,代碼和文章會不定時更新哦。得逼自己一把,争取後續内容一個月内更完吧。