妖尾歷經幾年開發,終於在今年6月底順利上線,至今運營兩個多月,筆者從2017年初參與開發,主要負責妖尾戰鬥系統開發,一路解決了一些技術問題,踩了一些坑,感受有很多點是值得記錄和分享的,但願能借幾篇文字,系統性總結MMORPG戰鬥系統的開發經驗。
本文主要介紹戰鬥錄像系統,戰鬥錄像基本是全部MMORPG遊戲的標配系統,它同時也能成爲開發調試利器,在整個開發階段扮演重要角色。html
一些項目組開發戰鬥系統時,可能會優先開發涉及表現的相關功能,迭代的新增戰鬥表現,修復Bug,直到整個戰鬥表現看起來至關完整了,到了後期再應策劃要求,補充戰鬥錄像系統。筆者項目是在開發中期加入戰鬥錄像功能的,在經歷完整個戰鬥開發階段後,得出的經驗是儘量在基礎框架搭建、先後臺開始聯調階段就同步開發戰鬥錄像系統,利用戰鬥錄像來輔助系統開發、調試。到了項目中後期,戰鬥錄像會發揮更大的用處,此時戰鬥系統已經提交到SVN版本控制,項目組全部人均可以體驗到戰鬥系統,全部人都或多或少地扮演測試人員的角色,項目羣會頻繁地反饋戰鬥系統的表現問題,諸如報錯、卡死,單位詐屍等等,什麼反饋都會有,總之發揮你的想象力。當時開發會頻繁地奔波於各個項目組成員的電腦面前,溝通、查看日誌,嘗試弄清問題,有了戰鬥錄像後,咱們會讓對方給錄像文件,在本地環境重放戰鬥錄像,重現現場慢慢定位問題。
戰鬥錄像能多大程度的輔助開發調試,取決於相關工具鏈有多完善,下面介紹妖尾項目對於工具鏈的打造。首先簡單看下戰鬥錄像框架:web
上圖是戰鬥錄像播放器通過幾回迭代後的截圖,除了實現最基本的播放錄像、查看數據功能,還有查看設備數據,上傳/下載錄像、生成戰鬥播報、差量構建指定回合戰場包等功能。筆者以爲在開發初期,先實現播放錄像、查看數據的功能就能知足大部分調試需求了,開發時間成本只有2-3天,但它會在以後1-2個月甚至更久的前期開發階段幫你縮短調試定位時間,節省更多時間早(bu)點(cun)下(zai)班(de),或幫策劃作更多需求,重要的是解放心態,再也不疲於溝通Bug,構造現場,由於現場就在錄像裏。數據庫
簡單描述這套調試工具的使用姿式:緩存
這裏另外分享1個Bug調試修復的經驗。我的認爲Bug修復總時間 = 問題溝通時間 + 問題定位時間 + 代碼修改時間 + 編譯驗證時間,像戰鬥這類大型系統,可能會經歷多輪問題定位、代碼修改、編譯驗證才能修好1個Bug。Lua代碼作好Hot reload開關,最好作到修改某處代碼,重進戰鬥就能驗證最新代碼。每次重啓遊戲至少花費30+秒,1個Bug平均幾回重啓驗證就是幾分鐘時間,作好Hot reload節省下的時間至關可觀。服務器
初期在項目組內推行用錄像反饋戰鬥Bug時,咱們讓你們把保存下來的錄像文件單發給戰鬥開發來調試,很快發現用戶體驗並不友好,不是全部人都是開發,你們不清楚錄像保存到哪一個目錄了,找到目錄,他們也弄不清楚要發哪一個錄像給開發。在忍受了一段時間的靈魂三連問後,筆者又加上了錄像上傳/下載功能。網絡
上面兩張圖是錄像上傳/下載流程及錄像下載頁面。咱們將Bug反饋操做簡化成遊戲內一鍵反饋,點擊按鈕就能自動保存錄像文件,並將二進制文件數據Base64編碼成字符串,利用魔方質管組幫忙搭建的Web服務,經過Http請求將數據上傳到Web服務器保存數據庫,開發經過Web頁面就能夠搜索/下載base64字符串格式的錄像文件,最後錄像播放接口作適配,支持二進制/base64字符串兩種格式數據的錄像播放,整個環節就打通了。數據結構
開發階段咱們自行開發了戰鬥錄像來輔助調試,確實也是到了戰鬥系統基本穩定後,策劃們才先後提了戰鬥錄像的正式需求,先作了一版基於服務器保存的活動錄像,又作了一版基於客戶端保存的戰鬥錄像大廳。app
先後作這兩版錄像需求,雖然都是觀看錄像,但其實現大不相同,所以須要謹慎設計整個錄像模塊,讓兩套邏輯獨立並行,能共用底層功能,並儘可能保持外部接口一致性。上圖是整個戰鬥錄像的模塊劃分,可劃分爲實現戰鬥錄像基礎功能的核心模塊,及涉及界面UI的兩版業務功能模塊。BattleReplayManager是核心類,它對外接收錄像相關的控制請求,對內調度其餘核心模塊類,獲取/保存/構造數據,控制錄像播放流程,並經過給戰鬥網絡層發送協議數據影響戰鬥表現。框架
基於服務器保存的活動錄像,全部數據都由服務器提供。前臺首先發送觀看錄像請求,接收錄像概要數據包,獲取戰鬥波次/回合等信息用於顯示和跳回合。收到初始戰場包後進入戰鬥,在每回合表演完後請求下一回合表演數據。正常播放錄像時,收到的協議數據跟普通戰鬥是同樣的,但若是在戰鬥中途跳回合,除了新回合的表演包,還會收到新回合的戰場包,用於恢復新回合初的戰場單位狀態。這個過程跟戰鬥斷線重連恢復戰場是同一套邏輯,所以把戰鬥斷線重連的坑填完,實現服務器錄像基本沒有難點。ide
相對服務器錄像,實現基於客戶端保存的錄像功能要考慮比較多問題:
模塊開發初期就考慮這些問題,就能夠避免基礎設計出錯,後期積重難返的尷尬狀況。
首先是肯定錄像文件格式,因爲妖尾協議基於pb通訊,錄像文件一開始就沒有打算自定義二進制格式,而是直接基於pb定義數據結構,這樣有幾點好處:
基於幾點考慮,錄像文件由BattleReplayFile錄像頭、BattleReplayFileBlock錄像數據塊兩部分組成。BattleReplayFile的blocks字段用於存放BattleReplayFileBlock列表,BattleReplayFile其餘字段是概要信息。這樣查看錄像列表時,後臺只須要返回不帶blocks數據的BattleReplayFile列表便可。上傳/下載錄像時也能夠先傳錄像頭、再批量分次傳錄像數據塊。
message BattleReplayFile
{
optional string name = 1; // 錄像文件名
repeated BattleReplayFileBlock blocks = 2; // 協議文件塊
optional uint32 block_num = 3; // 協議文件總塊數
repeated string ext_info_keys = 4; // 錄像額外信息參數Key
repeated string ext_info_values = 5; // 錄像額外信息參數Value
... // id、時間、雙方成員、回合、波次等錄像概要信息
... // 簡介、點贊、收藏等錄像大廳業務信息
}
message BattleReplayFileBlock
{
optional uint32 index = 1; // 協議塊序號
optional string name = 2; // 協議類名
optional bytes data = 3; // 協議數據
... //時間、回合等其餘信息
}
網絡抖動、切出遊戲再切回來等狀況致使斷線重連,可能致使戰鬥錄像數據損壞,所以保存本地前先作錄像文件校驗,判斷有沒有丟關鍵協議包,包括初始戰場包、入包表演包、各回合表演包及退出戰場包,保證協議包序,經過校驗才保存錄像文件,不經過就提示玩家錄像數據損壞沒法保存。
一場戰鬥錄像單靠收到的協議包,能夠正常順序播放整個戰鬥,卻不能跳轉回合播放,由於中間跳過了幾次合的表演演算,戰鬥邏輯層沒法將戰場數據修正成跳轉回合的狀態。服務器錄像能夠依靠後臺發跳轉回合戰場包作恢復,客戶端錄像就要靠前臺本身處理,用錄像表演包演算出跳轉回合的戰場狀態。
第一直覺是在戰鬥邏輯層處理跳出的表演包,只是跳過表演,直接作數據演算,但稍加思考會發現有不少問題:戰鬥邏輯層裏,數據與表現基本耦合在一塊兒,畢竟這樣的編碼實現方式最直觀。想抽離表現只演算數據,只能在原有代碼里加ifelse分支,重寫數據演算邏輯。幾十個表演類,新增這麼多分支,編碼再加調試,必然失去對代碼的把控,也破壞了原有系統穩定性。即便哼哧哼哧硬寫下來,也會發現只實現了向後跳轉回合,沒實現向前跳轉回合,由於戰鬥邏輯層實現的是按回合往下演算的邏輯。
跳出這個誤區,咱們認爲戰鬥錄像數據應該要有每一個回合的戰場包,跳轉時供戰鬥邏輯層重置回合戰場,所以後臺修改了戰鬥邏輯,每回合都會發當回合戰場包,這些戰場包作了特殊標記,只用於錄像存儲,不會影響戰鬥邏輯,實現起來很快,但也清楚有明顯效率問題。
基本上,戰場包都會比表演包大,甚至大不少,若是某個回合技能不太複雜,那表演包數據其實很是小,爲了實現跳回合,由後臺給每一個回合加發戰場包,會很是影響戰鬥的協議數據量,保存錄像文件變大,也會增長上傳/下載錄像時的負擔。這麼實現不合理的點在於,每回合戰場包實際上是冗餘數據,每回合狀態是能夠經過初始戰場包加表演包推算出來的。爲了優化這個問題,前臺實現了一個戰場包構建器,以初始戰場包、回合1~n-1表演包爲輸入,輸出目標回合n的戰場包。這樣在保存錄像時不須要保存回合戰場包,錄像跳轉回合時由構造器動態生成戰場包便可。編寫調試戰場包構建器時,要注意檢查先後臺的戰場包差別,咱們會打印戰場包數據,經過Beyond Compare查看差別,不斷調整代碼,直到構建的關鍵數據一致爲止。戰場包構建器調試好後,只要後續不新增表演類型,就能夠保證構建器可信可用,即便新增表演,代碼工做量也不多。
優化完作下簡單測試,打了一場40回合的5v5 pvp戰鬥保存錄像,比較兩種方案的保存錄像文件大小:優化後文件大小是優化前的65%,減小了252KB,因爲5v5pvp表演複雜,所以回合表演包數據自己也很是多,換作是通常的戰鬥,數據優化比率會更高。
妖尾一次協議收發有64KB大小限制,看前面的數據可知,回合數比較多的戰鬥錄像文件大小確定會超過64KB,咱們既不但願上傳/下載錄像單次傳輸的數據量超過64KB,又不但願單次傳輸數據量太少,致使協議發送次數過多,浪費太多時間在RRT上,所以採用的錄像傳輸策略是,首次傳輸單獨發送錄像頭,後續傳輸錄像數據塊切塊傳輸,保證每次傳輸的全部BattleReplayFileBlock的data總大小不超過50KB。採用這樣的策略,5回合之內的小型戰鬥基本都能分2次傳輸完畢,像上面的5v5 pvp大型戰鬥則須要進行11次傳輸。這就引出了下個問題思考,大型戰鬥的錄像觀看會不會有體驗問題。
戰鬥錄像大廳的設計初衷,是讓玩家能夠自主分享/觀看他們以爲滿意的戰鬥錄像,因此咱們猜想玩家會比較多的上傳/下載/觀看大型pvp戰鬥錄像,對於上傳而言並不會有什麼問題,由於就是一次性操做,但對下載/觀看場景就要儘可能進行優化,咱們不但願玩家每次看錄像,都要有感知地等待一會,等上10次網絡回包,下載完錄像文件才能觀看錄像,也不但願玩家每次看錄像都得重複下載文件,對玩家的手機流量也很不友好。
針對這兩點問題,戰鬥錄像參考網絡視頻的作法,加上了流式傳輸及錄像緩存的特性。
如上圖所示,流式傳輸的目的在於優化玩家觀看新錄像的體驗,無論一個完整的錄像有多大,須要多少次傳輸才能完成,只須要先得到部分頭部數據,就能觀看錄像。前臺只須要頭2次回包,獲取錄像概況、初始回合戰場包和表演包,就足以表演第1回合的戰鬥,進入錄像戰鬥後,靜默下載其他的錄像數據,通常後續的錄像數據下載速度遠遠快於戰鬥表演速度,這樣徹底不影響整場戰鬥的錄像觀看。假設網絡環境極端惡劣,表演完當前回合戰鬥後,後續錄像數據還沒返回,BattleReplayManager會每幀輪詢等待下個回合表演數據,即便網絡斷掉了拿不到數據,玩家仍然能夠點擊按鈕退出戰鬥錄像。
錄像緩存的目的則在於優化玩家重複觀看錄像的體驗,減小流量消耗。當看過一次錄像,下載了完整的錄像數據後,前臺就會把錄像保存到本地緩存起來了,儘管錄像頭裏存儲了部分戰鬥錄像大廳的字段,好比點贊、收藏數等,這些字段數據會失效,但戰鬥數據是不會變的。查看大廳的錄像列表時,後臺會返回只有錄像頭BattleReplayFile,沒有數據塊BattleReplayFileBlock的列表,玩家請求觀看時,判斷本地緩存有沒有該錄像緩存,有就再也不走原來的下載流程,直接讀取緩存文件播放便可。
洋洋灑灑寫了一些關於戰鬥錄像的總結,也確實是由於錄像系統對戰鬥開發調試有所幫助,做爲一個功能系統,也須要在早期考慮一些問題,作設計和優化,但願本文能對MMORPG或其餘類型遊戲戰鬥的設計開發,提供一些借鑑經驗。
附上咱們的遊戲官網[妖精的尾巴:魔導少年],快來玩吧~