階段目標
- 利用KBE的Event事件系統解耦客戶端相應邏輯
- 匹配和房間結構調整
- 頂號和斷線重連回戰場
效果演示
進入戰場流程調整
之前版本中,大廳和戰場都是SpaceRoom,這會導致一個問題,就是我們需要給space加一個type字段,然後根據type來區分具體的戰場類型,而且大廳和戰場的進入邏輯並不一致,會導致在相應的接口裏面做一堆的if…else判斷,這明顯會增加代碼的複雜性和可讀性。
針對以上這些問題,server端將大廳space和戰場space進行拆分, 分別是GameHall和GameBattleSpace,同時匹配的功能也拆分出一個單獨的Entity來完成,叫做MatchStub。
同時調整匹配和進入戰場的流程,調整後的流程圖如下:
其中Cell_xxx代表的是cellapp部分的名爲xxx的entity實體,其餘都是baseapp上的實體。
具體代碼,可以搜索流程圖中的函數名進行查看。
斷線重連進戰場
斷線重連主要就兩個問題,一個是網絡連接的問題,另一個是恢復戰場數據的問題。
- 對於已經在線的賬號,我們再次登錄該賬號時,會觸發腳本層的onLogOnAttempt方法,一些具體的業務邏輯可以在這裏處理。
- proxy對象是用於和client通信的對象,主要處理網絡連接。可以通過giveClientTo將當前客戶端連接的proxy轉交給一個entity實體。
- 玩家斷線後PlayerAvatar是否需要銷燬的問題。
- 如果銷燬,那麼每次重連就需要重新創建一個PlayerAvatar,可以複用首次登陸游戲的邏輯,但這麼做會多一次連接GameHall再跳轉到GameBattleSpace的流程,同時需要保證其他客戶端本地的對象索引不能是Entity.id,因爲重新創建的PlayerAvatar對象的唯一id是不同的。
- 如果不銷燬,可以直接查找到之前的PlayerAvatar,然後將proxy進行更新即可。不過無法直接複用首次登陸流程。
- 最終遊戲採用的是不銷燬PlayerAvatar的方式。
針對上面兩點,其實就差不多可以完成斷線重連的邏輯處理了。但是,在過程中,缺遇到了一個坑,坑了我好久,有坑的代碼示例如下:
def onClientEnabled(self):
"""
KBEngine method.
該entity被正式激活爲可使用, 此時entity已經建立了client對應實體, 可以在此創建它的
cell部分。
"""
INFO_MSG("333333333333333333333")
self.become_player_avatar()
def onLogOnAttempt(self, ip, port, password):
INFO_MSG("[Account], %i onLogOnAttempt: ip=%s, port=%i, selfclient=%s, %s" % (self.id, ip, port, self.client, self.active_avatar))
if self.active_avatar is not None:
INFO_MSG("111111111111111111111")
if self.active_avatar.client is not None:
self.active_avatar.giveClientTo(self)
// 此處省略其他代碼
INFO_MSG("222222222222222222222")
return KBEngine.LOG_ON_ACCEPT
上訴代碼,模擬一次頂號重連,猜猜日誌會輸出啥?
我預期輸出是:
111111111111111111111
222222222222222222222
333333333333333333333
而實際輸出卻是:
111111111111111111111
333333333333333333333
222222222222222222222
這個流程可真的是出乎我的意料,也就導致我最開始寫的重連流程都是有問題的,折騰了許久,最後結合源碼調試才發現了問題所在(KBEngine相關源碼分析準備在後續文章中寫寫)。原因在於giveClientTo這個函數,會觸發一次目標對象的onClientEnabled回調。
最後的頂號重連代碼如下:
def onClientEnabled(self):
"""
KBEngine method.
該entity被正式激活爲可使用, 此時entity已經建立了client對應實體, 可以在此創建它的
cell部分。
"""
INFO_MSG("account[%i] entities enable. entityCall:%s" % (self.id, self.client))
# 原賬號在線
if self.active_avatar:
if self.active_avatar.reconnect_flag:
INFO_MSG("[Account], %i re login avatar battle space id:%s" % (self.id, self.active_avatar.battle_space_id))
self.giveClientTo(self.active_avatar)
self.active_avatar.re_connect_to_battle_space()
return
self.become_player_avatar()
def onLogOnAttempt(self, ip, port, password):
"""
KBEngine method.
客戶端登陸失敗時會回調到這裏reloginBaseapp
"""
INFO_MSG("[Account], %i onLogOnAttempt: ip=%s, port=%i, selfclient=%s, %s" % (self.id, ip, port, self.client, self.active_avatar))
# 置空掉之前avatar的client
if self.active_avatar:
self.active_avatar.giveClientTo(self)
self.active_avatar.reconnect_flag = True
return KBEngine.LOG_ON_ACCEPT
PlayerAvatar的重連戰場
def re_connect_to_battle_space(self):
if self.battle_space_id > 0:
_battle_space = KBEngine.entities.get(self.battle_space_id, None)
_battle_space_cell = _battle_space.cell
if _battle_space and _battle_space_cell:
INFO_MSG("[PlayerAvatar], %s, %i, re_connect_to_battle_space" % (self, self.id))
self.cell.teleport_space(_battle_space_cell, self.born_position, (0, 0, 0))
後續內容
到這裏,關於遊戲腳本層想寫的其實我都寫完了,對於所謂的戰鬥同步啥的,都是相應的遊戲邏輯實現了,思想都類似,就不準備寫了,以上三篇文章,完成了一個賬號的創建、登錄、重連回戰場,以及戰場內的移動同步。對於我來說,通過自己去實現一遍這個流程,對於KBEngine的基本使用目標已經達成。
後續,就開始慢慢剖析KBEngine的源碼了,那纔是我的主要學習和記錄對象哈。以具體的遊戲需求來引導自己入門上手一款引擎,然後開始剖析其背後的原理和實現。關於源碼的學習記錄,準備另外開個系列的標題文章來寫,慢慢看,慢慢學,慢慢記錄。
源碼地址
github地址:地址在這裏