• <nav id="yisao"><rt id="yisao"></rt></nav>
  • Cocos 技術派|實時競技小游戲技術實現分享

    在前陣子舉辦的 Cocos 開發者沙龍上,來自華夏樂游 BigRoad 工作室的客戶端主程李清,為現場開發者分享了其團隊制作的實時競技小游戲《保衛豆豆-歡樂槍戰》技術實現方案,深受開發者喜愛。在征得華夏樂游的同意后,技術派專欄將通過本文對李清的演講進行梳理總結,把他們寶貴的技術經驗分享給因為各種原因沒能去到沙龍現場的開發者伙伴們!

    1
    李清

    關于華夏樂游

    華夏樂游

    北京華夏樂游科技股份有限公司是一家專注于游戲研發與發行、泛娛樂IP打造和經營的創意文化企業。代表作品《奔跑吧-撕名牌大戰》3、4、5系列,《極速前進-狂野飛車》、《星河戰神》。目前聚焦于以 H5 為代表的無端和輕端游戲,先后研發和發行了《歡樂消消消》、《保衛豆豆-歡樂槍戰》、《全民小鎮》等微信小游戲,同時多款游戲上線微信小游戲精品平臺。

    游戲簡介

    《保衛豆豆-歡樂槍戰》是一款基于 Cocos 引擎研發的休閑射擊亂斗小游戲,融合了射擊、MOBA、吃雞等熱門玩法。

    3

    游戲特點:

    • 萌寵射擊,實時競技
    • 四人亂斗,雙人組隊
    • 多個英雄,身懷絕技

    本文主要從三個方面來進行分享,分別是:

    • ECS 架構
    • 網絡同步機制
    • 技術難點及解決方案

    一、ECS架構

    1、ECS 架構目的:

    降低不斷增長的代碼庫的復雜

    2、游戲原型需求:

    • 子彈:移動、碰撞
    • 英雄:移動、碰撞、發射子彈
    • 炮臺:發射子彈

    3、傳統架構

    要實現游戲原型,按照我們之前的做法,是用一個類來實現一種游戲實體的所有功能,這個類既有狀態,又有行為。代碼復用使用繼承來解決。如果用這種做法,那么類大概長這個樣子:

    4
    類圖

    大家可以看到,父類會有很多共享的屬性和方法,子類繼承父類去做具體的事情。但是這種做法有很多弊端,比如說,隨著項目規模的增長,代碼庫復雜度也不斷增長,父類會越來越復雜,子類的功能越來越不明確,與多個類相關的代碼你不能太確切知道應該放在哪里,拓展功能的時候極其不靈活,如果后期需要增加新功能的話,我們需要對整個繼承樹進行功能重構才能使其比較合理。

    在經歷過幾個項目之后,我們回頭反思,發現之前的做法,違反了很多面向對象設計原則。比如說:

    • 單一責任原則(Single responsibility principle)每個類都應該只有單一的功能,并且該功能應該由這個類完全封裝起來。
    • 組合重用原則(Composite Reuse Principle)默認情況下應當使用組合,只有在必須時才使用繼承。

    在總結了從前的項目經驗,并參考了大量技術文章后,我們找到了一種架構,把大量的模塊進行拆分解耦,然后再集成起來,這就是我們接下來要介紹的 ECS 架構。

    4、ECS架構

    ECS 分別是:

    • Entity(實體)
    • Component(組件)
    • System(系統)

    看到實體和組件大家可能覺得比較熟悉,但是這里要注意,這跟我們引擎中的實體組件框架可不是一回事,接下來我為大家簡單介紹一下 ECS 架構的元素。

    (1)ECS 架構元素:

    • Component:組件,存儲游戲狀態
    • Entity:實體,組件的集合
    • System:系統,實現游戲行為
    • World:系統和實體的集合,就是我們的游戲世界。

    他們的關系大概是這個樣子的:

    5

    我們可以看到,游戲世界中有很多 System,每個 System 負責實現一種游戲行為,同時有很多組件,每種組件中會有一些游戲狀態,實體上可以掛載一個或多個組件,實體和 System 聚合成了我們的游戲世界。

    (2)ECS 架構設計:

    這個架構有個基礎原則:

    • 組件只有狀態,沒有行為
    • 系統只有行為,沒有狀態

    剛看到這個原則的時候,大家可能會有一些疑問,什么是游戲行為呢?游戲行為,其實就是根據一定的規則去修改游戲狀態。比如說移動,就是根據實體的方向和移動速度去改變這個實體的位置。如果系統沒有游戲狀態,它如何去實現游戲行為呢?這就是 ECS 架構最重要的職責了:為系統篩選出它關心的實體子集,只展示給它關心的游戲狀態。

    具體我們是怎么做的呢?首先把可能單獨使用的游戲狀態歸納為一個個組件:

    6

    比如最常見的位置、方向我們可以歸納為變換組件;移動速度這個組件可能會在移動系統中單獨使用,所以我們把它歸納到移動組件中;碰撞組件則有碰撞盒的大??;攻擊組件有攻擊方向,這樣我們就把各種屬性給拆開了。接著,我們在系統實現的時候,要向框架聲明我關心哪些“組件元組”(Component Tuple)

    什么是“組件元組”?還是舉剛剛移動的例子。移動系統的移動行為,應該是關心實體的位置、方向以及移動速度,就是我們歸納的變換組件和移動組件,那么只要一個實體同時掛載這 2 個組件,它就可以被移動系統遍歷到,系統就會進行操作從而實現移動行為。

    7

    最關鍵的一點,“組件元組”其實就是用來實現框架篩選實體的功能,實體只需要根據自身功能需求掛載相應的組件元組就可以了。比如說子彈它有移動和碰撞的功能,那么就掛載上變換、移動和碰撞這 3 個組件。

    8

    最終實現的效果就是移動系統遍歷了英雄和子彈實體,在他們身上實現了移動的行為。攻擊系統遍歷了英雄和炮臺實體,然后他們就可以發射子彈。

    (3)ECS 架構實例:

    接下來,我們看一下比較復雜的碰撞邏輯,這里我們可以對碰撞進行拆解:

    首先是碰撞的觸發系統。當碰撞發生時將產生一個碰撞事件,然后這個系統只干這件事。剩下的碰撞處理呢,對于子彈來說,會有一個碰撞后銷毀系統,它會在碰撞之后把子彈銷毀。對于英雄來說,他有一個碰撞后的損血系統,通過這種方式,我們就可以把碰撞進行拆分,再通過剛剛的方式集成在一起。

    (4)ECS 架構作用:

    這種架構可以讓每個開發人員負責不同模塊的開發,有效地提高多人開發效率。最重要的就是模塊的復用,可以便于功能拓展。

    如果你想改變一個實體的功能,只需要添加或者移除實體的組件就可以了。比如說:一個英雄死亡之后,他應該失去移動功能,那么在英雄死亡之后,我們只需要把移動組件給移除就可以了,等他復活的時候再給他加回來??梢钥吹?,這種方式非常方便。既然這么方便了,我們就可以做出一個編輯器,把這種能力開放給策劃人員。實際上,暴雪就專門為 Overwatch 開發了一套 Statescript 的腳本語言,它用起來就是一個可視化的編輯器,策劃人員可以在這個編輯器中編輯每個英雄在各種游戲狀態中擁有什么游戲能力,程序只要實現具體的功能模塊,然后開放給策劃人員使用,非常地靈活。

    9

    以下是我們在實踐過程中參考的技術文章:

    [參考文檔]


    二、網絡同步機制

    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同步一次
    • 客戶端若沒有按時收到移動狀態,則用最后一次收到的移動狀態繼續預測一段時間
    • 服務端若沒有按時收到玩家輸入,則用最后一次收到的玩家輸入繼續運算一段時間

    用這種方案優化之后,走走停停的現象就基本沒有了。

    10
    客戶端流程圖

    抖動緩存法

    另一種優化方案是抖動緩存,這是指收到包后不立馬處理,而是放入抖動緩存中,延遲一段時間后再取出。

    11
    服務端流程圖

    這種優化方案關鍵點在于緩存的大小。如果緩存太小,對于抖動還是比較敏感,抗抖動效果比較弱,緩存太大,玩家的延遲又特別高,所以你需要根據算法動態調整緩存的大小以適應網絡環境。

    (3)全區全服

    • 所有玩家都在同一個大區里
    • 前臺服務器處理登錄等戰斗外邏輯
    • 游戲服務器處理戰斗邏輯

    (4)分地域部署

    我們的項目是實時競技游戲,對于延遲比較敏感,因此我們的游戲服務器采用了分地域部署。服務器入口使用的是阿里云的“云解析 DNS”服務,按照地域自動分配游戲服務器(華北、華東、華南、西南),玩家在進行快速匹配戰斗時,會根據地域分配服務器,同一地域玩家進入該地域所屬服務器。

    以下是我們在網絡優化方面參考的文章,都是干貨,如果感興趣可以去了解一下。

    [參考資料]


    三、技術難點以及解決方案

    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 是開源的,所以大家在使用過程中,可以去多看看源碼,都可以找到自己的解決方案。以上就是我今天帶來的分享,感謝各位!

    12
    歡迎各位掃碼體驗
    女人脱裤子让男生桶爽免费看,久青草无码视频在线播放,精品国产人成亚洲区,久久精品国产亚洲一区二区