Cocos 技術派:實時競技小遊戲技術實現分享
李清是來自華夏樂遊BigRoad工作室的客戶端主程,今日他將帶來其團隊製作的實時競技小遊戲《保衛豆豆-歡樂槍戰》的技術實現方案。
遊戲簡介
《保衛豆豆-歡樂槍戰》是一款北京華夏樂遊科技股份基於Cocos引擎研發的休閒射擊亂鬥小遊戲,融合了射擊、MOBA、吃雞等熱門玩法。
遊戲特點
- 萌寵射擊,實時競技
- 四人亂鬥,雙人組隊
- 多個英雄,身懷絕技
本文主要從三個方面來進行分享,分別是:
- ECS架構
- 網路同步機制
- 技術難點及解決方案
一、ECS架構
1、ECS架構目的:
降低不斷增長的程式碼庫的複雜度。
2、遊戲原型需求:
- 子彈:移動、碰撞
- 英雄:移動、碰撞、發射子彈
- 炮臺:發射子彈
3、傳統架構的弊端
要實現遊戲原型,按照我們之前的做法,是用一個類來實現一種遊戲實體的所有功能,這個類既有狀態,又有行為。程式碼複用使用繼承來解決。如果用這種做法,那麼類大概長這個樣子:
大家可以看到,父類會有很多共享的屬性和方法,子類繼承父類去做具體的事情。但是這種做法有很多弊端,比如說,隨著專案規模的增長,程式碼庫複雜度也不斷增長,父類會越來越複雜,子類的功能越來越不明確,與多個類相關的程式碼你不能太確切知道應該放在哪裡,擴充功能的時候極其不靈活,如果後期需要增加新功能的話,我們需要對整個繼承樹進行功能重構才能使其比較合理
在經歷過幾個專案之後,我們回頭反思,發現之前的做法,違反了很多物件導向設計原則。比如說:
- 單一責任原則(Single responsibility principle)每個類都應該只有單一的功能,並且該功能應該由這個類完全封裝起來。
- 組合重用原則(Composite Reuse Principle)預設情況下應當使用組合,只有在必須時才使用繼承。
在總結了從前的專案經驗,並參考了大量技術文章後,我們找到了一種架構,把大量的模組進行拆分解耦,然後再整合起來,這就是我們接下來要介紹的ECS架構。
4、ECS架構
ECS分別是:
- Entity(實體)
- Component(元件)
- System(系統)
看到實體和元件大家可能覺得比較熟悉,但是這裡要注意,這跟我們引擎中的實體元件框架可不是一回事,接下來我為大家簡單介紹一下ECS架構的元素。
(1)ECS架構元素:
Component:元件,儲存遊戲狀態
Entity:實體,元件的集合
System:系統,實現遊戲行為
World:系統和實體的集合,就是我們的遊戲世界,他們的關係大概是這個樣子的:
我們可以看到,遊戲世界中有很多System,每個System負責實現一種遊戲行為,同時有很多元件,每種元件中會有一些遊戲狀態,實體上可以掛載一個或多個元件,實體和System聚合成了我們的遊戲世界。
(2)ECS架構設計:
這個架構有個基礎原則:
- 元件只有狀態,沒有行為
- 系統只有行為,沒有狀態
剛看到這個原則的時候,大家可能會有一些疑問,什麼是遊戲行為呢?遊戲行為,其實就是根據一定的規則去修改遊戲狀態。比如說移動,就是根據實體的方向和移動速度去改變這個實體的位置。如果系統沒有遊戲狀態,它如何去實現遊戲行為呢?
這就是ECS架構最重要的職責了:為系統篩選出它關心的實體子集,只展示給它關心的遊戲狀態。具體我們是怎麼做的呢?
首先把可能單獨使用的遊戲狀態歸納為一個個元件:
比如最常見的位置、方向我們可以歸納為變換元件;移動速度這個元件可能會在移動系統中單獨使用,所以我們把它歸納到移動元件中;碰撞元件則有碰撞盒的大小;攻擊元件有攻擊方向,這樣我們就把各種屬性給拆開了。
接著,我們在系統實現的時候,要向框架宣告我關心哪些“元件元組”(Component Tuple)
什麼是“元件元組”?還是舉剛剛移動的例子。移動系統的移動行為,應該是關心實體的位置、方向以及移動速度,就是我們歸納的變換元件和移動元件,那麼只要一個實體同時掛載這2個元件,它就可以被移動系統遍歷到,系統就會進行操作從而實現移動行為。
最關鍵的一點,“元件元組”其實就是用來實現框架篩選實體的功能,實體只需要根據自身功能需求掛載相應的元件元組就可以了。比如說子彈它有移動和碰撞的功能,那麼就掛載上變換、移動和碰撞這3個元件。
最終實現的效果就是移動系統遍歷了英雄和子彈實體,在他們身上實現了移動的行為。攻擊系統遍歷了英雄和炮臺實體,然後他們就可以發射子彈。
(3)ECS架構例項:
接下來,我們看一下比較複雜的碰撞邏輯,這裡我們可以對碰撞進行拆解:
首先是碰撞的觸發系統。當碰撞發生時將產生一個碰撞事件,然後這個系統只幹這件事。剩下的碰撞處理呢,對於子彈來說,會有一個碰撞後銷燬系統,它會在碰撞之後把子彈銷燬。對於英雄來說,他有一個碰撞後的損血系統,通過這種方式,我們就可以把碰撞進行拆分,再通過剛剛的方式整合在一起。
(4)ECS架構作用:
這種架構可以讓每個開發人員負責不同模組的開發,有效地提高多人開發效率。最重要的就是模組的複用,可以便於功能擴充。如果你想改變一個實體的功能,只需要新增或者移除實體的元件就可以了。
比如說:一個英雄死亡之後,他應該失去移動功能,那麼在英雄死亡之後,我們只需要把移動元件給移除就可以了,等他復活的時候再給他加回來。可以看到,這種方式非常方便。既然這麼方便了,我們就可以做出一個編輯器,把這種能力開放給策劃人員。
實際上,暴雪就專門為Overwatch開發了一套Statescript的指令碼語言,它用起來就是一個視覺化的編輯器,策劃人員可以在這個編輯器中編輯每個英雄在各種遊戲狀態中擁有什麼遊戲能力,程式只要實現具體的功能模組,然後開放給策劃人員使用,非常地靈活。
以下是我們在實踐過程中參考的技術文章:
[參考文件]
《守望先鋒》架構設計與網路同步
http://gad.qq.com/article/detail/28682
《守望先鋒》回放技術-陣亡鏡頭、全場最佳和亮眼表現
http://gad.qq.com/article/detail/29595
《守望先鋒》中的網路指令碼化的武器和技能系統
http://gad.qq.com/article/detail/28219
淺談《守望先鋒》中的ECS構架
https://blog.codingnow.com/2017/06/overwatch_ecs.html
二、網路同步機制
1、常見同步機制:
常見的網路同步機制可以分為以下三種:
- 確定性幀同步(Deterministic lockstep)
- 快照插值(Snapshot interpolation)
- 狀態同步(State synchronization)
(1)確定性幀同步
服務端:收集並轉發玩家輸入資料,不運算遊戲邏輯
客戶端:在玩家輸入資料以後各自運算遊戲邏輯
優點:只有玩家輸入會被傳輸,資料流量非常小;程式碼都是寫在客戶端上的,所以程式碼複雜度較低。
缺點:對網路延遲要求非常高;每個機器浮點數運算不一致,需要將浮點數運算轉換成整數運算;斷線重連時間較長;因為遊戲邏輯寫在客戶端,所以不是很安全。
(2)快照插值
服務端:運算遊戲邏輯,將快照傳送給客戶端。
快照,就是我這一幀所有遊戲實體的遊戲狀態。
客戶端:不運算遊戲邏輯,收到快照以後進行差值平滑播放。
實際上,客戶端只是一個播放器。
優點:客戶端運算量小;斷線重連容易實現;遊戲邏輯全在客戶端,所以非常安全。
缺點:頻寬佔用非常大。
所以這種方式之前多用於像CS這種區域網對戰。
(3)狀態同步
服務端:運算遊戲邏輯,將玩家輸入和部分狀態傳送給客戶端
客戶端:在玩家輸入時,不等伺服器就立馬運算遊戲邏輯,就有點像單機遊戲了,但這種運算結果未經過伺服器,不一定是正確的,所以它實際上是一個遊戲邏輯的預測。在收到伺服器資料後,會對預測結果進行校驗,如果錯誤,就需要平滑地將其糾正到正確的狀態。
這裡說一下校驗的過程,其實就是先回滾再前滾。
服務端下發的資料是之前一個時間點的資料,我們本地賦值以後相當於回滾到之前的時間,然後我們會一幀幀的運算到當前的時間,這就叫前滾,最後將計算結果與預測結果進行比較,可以看到校驗的計算量是非常大的。
優點:客戶端可以進行遊戲邏輯預測;網路遊戲體驗好;以伺服器資料為準,比較安全。
缺點:程式碼複雜度高;客戶端運算量大;因為有客戶端預測,所以客戶端之間是不完全同步的。
2、小遊戲平臺特點
一開始我們的專案採用的是狀態同步的方式,但由於我們的專案是針對小遊戲平臺的,小遊戲平臺有以下幾個特點:
- 運算效能較差,客戶端計算量不能太大
- Javascript程式碼很容易被破解,玩家想要作弊的話很容易
- 網路連線只能使用TCP,所以頻寬佔用不能太高
3、歡樂槍戰的實現方案
(1)頻寬優化
基於小遊戲平臺的特點,我們專案從狀態同步開始做簡化,一直簡化到以下這種實現方案:
- 服務端:運算遊戲邏輯,將變化的狀態傳送給客戶端
- 客戶端:不運算遊戲邏輯,收到資料以後進行差值平滑播放
- 優化了頻寬佔用的快照插值
這個大家可能看著就有點眼熟了,其實就是優化了頻寬佔用的快照插值。這種方案最關鍵的一點是,你要把頻寬優化下來。而頻寬優化最關鍵的,是隻有在必要的情況比如遊戲開始和斷線重連時才傳送全量狀態,平時玩的過程中,只傳送變化的狀態。
另外一方面是資料壓縮,比如方向,剛開始我們用的是方向向量,但其實用弧度制乘以一千就可以了,這樣就把兩個Float優化成一個Short。
經過頻寬優化成果:
上行:2~15pkg/s,流量佔用:0.1 KB/s
下行:0~15pkg/s,流量佔用:2.5 KB/s
這個流量佔用對於目前的手機網路來說,是完全可以接受的。
(2)網路抖動優化
介紹完了頻寬優化,接下來我們來聊聊網路抖動。
網路抖動指的是,網路的傳輸是不穩定的,服務端每個邏輯幀會傳送一個包,它傳送的頻率是穩定的,但是對於客戶端,可能在一個邏輯幀內收不到包,也可能收到多個包。
這在遊戲中的體現就是,玩家在移動過程中,這一幀沒有收到包,就停下來了,下一幀收到2個包,就跳過去了,體現出走走停停的狀態。
對於這種網路抖動,最常見的優化方法是航位推測法。
航位推測法(Dead Reckoning):
- 客戶端和服務端約定至少每500ms同步一次
- 客戶端若沒有按時收到移動狀態,則用最後一次收到的移動狀態繼續預測一段時間
- 服務端若沒有按時收到玩家輸入,則用最後一次收到的玩家輸入繼續運算一段時間
用這種方案優化之後,走走停停的現象就基本沒有了。
抖動快取法
另一種優化方案是抖動快取,這是指收到包後不立馬處理,而是放入抖動快取中,延遲一段時間後再取出。
這種優化方案關鍵點在於快取的大小。如果快取太小,對於抖動還是比較敏感,抗抖動效果比較弱,快取太大,玩家的延遲又特別高,所以你需要根據演算法動態調整快取的大小以適應網路環境。
(3)全區全服
- 所有玩家都在同一個大區裡
- 前臺伺服器處理登入等戰鬥外邏輯
- 遊戲伺服器處理戰鬥邏輯
(4)分地域部署
我們的專案是實時競技遊戲,對於延遲比較敏感,因此我們的遊戲伺服器採用了分地域部署。伺服器入口使用的是阿里雲的“雲解析DNS”服務,按照地域自動分配遊戲伺服器(華北、華東、華南、西南),玩家在進行快速匹配戰鬥時,會根據地域分配伺服器,同一地域玩家進入該地域所屬伺服器。
以下是我們在網路優化方面參考的文章,都是乾貨,如果感興趣可以去了解一下。
[參考資料]
Networked Physics——Glenn Fiedler
https://gafferongames.com/categories/networked-physics/
《王者榮耀》技術總監覆盤回爐歷程
http://gad.qq.com/article/detail/30902
150ms流暢體驗NBA2KOnline如何網路同步優化
http://gad.qq.com/article/detail/10118
三、技術難點以及解決方案
1、Javascript語言使用
Cocos Creator上手很容易,不過Javascript語言非常靈活,需要統一程式碼規範。
所以在專案初期,我們就制定了程式和資源規範,包括程式碼格式、資源製作標準等,並且會定期去整理程式碼和資源。
2、客戶端效能優化
效能優化這一塊是我們比較頭疼的問題。騰訊方面對於技術標準要求很高,對載入時間、幀率、記憶體等都有卡死的嚴格限制,不通過技術評審則無法上線。
我們專案一開始用的是Cocos Creator 1.9版本,用盡畢生所學優化了好幾輪,還是隻能跑到40多幀。在專案快要上線之際,Cocos推出了2.0 Beta4版本,我們就在上線前2周去升級了大版本,現在想想還是挺刺激的。升級之後,體驗很流暢,2.0對於效能的提升是非常明顯的。
- iPhoneX從50幀提升到60幀(iOS機型)
- 一加5T從40幀提升到55幀(安卓機型)
- OPPO Y55與iPhone6穩定在25-30幀(老機型)
3、自定義裁剪功能
出於對效率的綜合考慮,Cocos Creator 2.0移除了自動裁剪功能(cc.macro.ENABLE_CULLING),所以螢幕外的節點仍然會進行渲染,戰鬥中drawcall較高。
於是我們就自己實現了一套裁剪功能。
- 當鏡頭或節點移動時判斷節點是否需要進行渲染。
- 修改了一部分Cocos原始碼,在渲染底層新增了一個自定義標誌位用來跳過不需要渲染的節點,從而快速實現我們想要的功能。
4、Spine換裝實現
整體換裝:
在《保衛豆豆-歡樂槍戰》中,英雄是可以進行整體換膚的,但是Spine官方並不支援一套動畫資料對應多套圖片。所以我們便開始研究原始碼,研究後發現,使用SkeletonTexture.setRealTexture()設定為另一張圖片資源就可以實現整體整體換裝功能了。
區域性換裝:
《保衛豆豆-歡樂槍戰》戰鬥中,英雄可以自由拾取武器,但是英雄與武器是兩套動畫檔案,具有交叉層疊關係,渲染時需要交叉渲染。這個功能Spine也是不支援的。在研究過原始碼之後,我們發現使用Slot.setAttachment()設定為另一個動畫檔案中的附件,就可以實現區域性換裝功能了。
結語
Cocos是開源的,所以大家在使用過程中,可以去多看看原始碼,都可以找到自己的解決方案。以上就是我今天帶來的分享,感謝各位!
作者:CocosEngine
來源:indienova
原地址:https://indienova.com/indie-game ... ini-game-explained/
相關文章
- IOS技術分享| 你畫我猜小遊戲快速實現iOS遊戲
- Cocos Creator 3D 案例《彈彈樂》技術實現分享3D
- web技術分享| 虛擬列表實現Web
- Oracle實時同步技術Oracle
- 大資料競賽技術分享大資料
- 虛擬現實技術
- 實時通訊技術大亂鬥
- Java技術分享:NIO實戰教程!Java
- 資料加密新技術-實時雲渲染技術應用加密
- 【區塊鏈技術實現】區塊鏈
- 【技術學院】iOS APNs實戰分享iOS
- web前端技術分享:使用react實現簡易路由Web前端React路由
- 技術分享| 小程式實現音視訊通話
- 技術教程網 -- 實用技術參考 (轉)
- Docker 核心技術與實現原理Docker
- Apache中URLRewrite技術的實現Apache
- 淘寶/天貓商品詳情實時資料API技術實現API
- 技術乾貨|如何利用 ChunJun 實現資料實時同步?
- 不懂技術的人不要對懂技術的人說這很容易實現
- SSLO如何實現會話保持?技術乾貨線上分享會話
- 技術分享| 如何使用Prometheus實現系統程式監控Prometheus
- web技術分享| 快速實現一個呼叫邀請 SDKWeb
- WEB 實時推送技術的總結Web
- 技術分享| HTTP 代理HTTP
- 技術分享主幹
- 技術選型的藝術---湖北技術價值分享會
- Android技術棧(三)依賴注入技術的探討與實現Android依賴注入
- 實現VR直播的關鍵技術VR
- 快速理解容器技術的實現原理
- websocket技術,實現單聊和群聊Web
- 個推技術實現原理介紹
- Delphi中停靠技術的實現 (轉)
- 技術分享 | MySQL:change buffer 何時生效MySql
- web前端技術分享:koa中介軟體是如何實現的?Web前端
- IOS技術分享| 快對講2.0會議場景實現iOS
- web技術分享| 【高德地圖】實現自定義的軌跡回放Web地圖
- Android技術分享| 【你畫我猜】Android 快速實現Android
- 爬蟲技術實戰爬蟲