Cocos 技术派:实时竞技小游戏技术实现分享
前言
李清是來(lái)自華夏樂(lè)游BigRoad工作室的客戶端主程,今日他將帶來(lái)其團(tuán)隊(duì)制作的實(shí)時(shí)競(jìng)技小游戲《保衛(wèi)豆豆-歡樂(lè)槍戰(zhàn)》的技術(shù)實(shí)現(xiàn)方案。
游戲簡(jiǎn)介
《保衛(wèi)豆豆-歡樂(lè)槍戰(zhàn)》是一款北京華夏樂(lè)游科技股份基于Cocos引擎研發(fā)的休閑射擊亂斗小游戲,融合了射擊、MOBA、吃雞等熱門玩法。
?
游戲特點(diǎn)
?
- 萌寵射擊,實(shí)時(shí)競(jìng)技
- 四人亂斗,雙人組隊(duì)
- 多個(gè)英雄,身懷絕技
本文主要從三個(gè)方面來(lái)進(jìn)行分享,分別是:
?
- ECS架構(gòu)
- 網(wǎng)絡(luò)同步機(jī)制
- 技術(shù)難點(diǎn)及解決方案
一、ECS架構(gòu)
1、ECS架構(gòu)目的:
降低不斷增長(zhǎng)的代碼庫(kù)的復(fù)雜度。
2、游戲原型需求:
?
- 子彈:移動(dòng)、碰撞
- 英雄:移動(dòng)、碰撞、發(fā)射子彈
- 炮臺(tái):發(fā)射子彈
3、傳統(tǒng)架構(gòu)的弊端
要實(shí)現(xiàn)游戲原型,按照我們之前的做法,是用一個(gè)類來(lái)實(shí)現(xiàn)一種游戲?qū)嶓w的所有功能,這個(gè)類既有狀態(tài),又有行為。代碼復(fù)用使用繼承來(lái)解決。如果用這種做法,那么類大概長(zhǎng)這個(gè)樣子:
?
大家可以看到,父類會(huì)有很多共享的屬性和方法,子類繼承父類去做具體的事情。但是這種做法有很多弊端,比如說(shuō),隨著項(xiàng)目規(guī)模的增長(zhǎng),代碼庫(kù)復(fù)雜度也不斷增長(zhǎng),父類會(huì)越來(lái)越復(fù)雜,子類的功能越來(lái)越不明確,與多個(gè)類相關(guān)的代碼你不能太確切知道應(yīng)該放在哪里,拓展功能的時(shí)候極其不靈活,如果后期需要增加新功能的話,我們需要對(duì)整個(gè)繼承樹進(jìn)行功能重構(gòu)才能使其比較合理
在經(jīng)歷過(guò)幾個(gè)項(xiàng)目之后,我們回頭反思,發(fā)現(xiàn)之前的做法,違反了很多面向?qū)ο笤O(shè)計(jì)原則。比如說(shuō):
?
- 單一責(zé)任原則(Single responsibility principle)每個(gè)類都應(yīng)該只有單一的功能,并且該功能應(yīng)該由這個(gè)類完全封裝起來(lái)。
- 組合重用原則(Composite Reuse Principle)默認(rèn)情況下應(yīng)當(dāng)使用組合,只有在必須時(shí)才使用繼承。
在總結(jié)了從前的項(xiàng)目經(jīng)驗(yàn),并參考了大量技術(shù)文章后,我們找到了一種架構(gòu),把大量的模塊進(jìn)行拆分解耦,然后再集成起來(lái),這就是我們接下來(lái)要介紹的ECS架構(gòu)。
4、ECS架構(gòu)
ECS分別是:
?
- Entity(實(shí)體)
- Component(組件)
- System(系統(tǒng))
看到實(shí)體和組件大家可能覺得比較熟悉,但是這里要注意,這跟我們引擎中的實(shí)體組件框架可不是一回事,接下來(lái)我為大家簡(jiǎn)單介紹一下ECS架構(gòu)的元素。
(1)ECS架構(gòu)元素:
Component:組件,存儲(chǔ)游戲狀態(tài)
Entity:實(shí)體,組件的集合
System:系統(tǒng),實(shí)現(xiàn)游戲行為
World:系統(tǒng)和實(shí)體的集合,就是我們的游戲世界,他們的關(guān)系大概是這個(gè)樣子的:
?
我們可以看到,游戲世界中有很多System,每個(gè)System負(fù)責(zé)實(shí)現(xiàn)一種游戲行為,同時(shí)有很多組件,每種組件中會(huì)有一些游戲狀態(tài),實(shí)體上可以掛載一個(gè)或多個(gè)組件,實(shí)體和System聚合成了我們的游戲世界。
(2)ECS架構(gòu)設(shè)計(jì):
這個(gè)架構(gòu)有個(gè)基礎(chǔ)原則:
?
- 組件只有狀態(tài),沒有行為
- 系統(tǒng)只有行為,沒有狀態(tài)
剛看到這個(gè)原則的時(shí)候,大家可能會(huì)有一些疑問(wèn),什么是游戲行為呢?游戲行為,其實(shí)就是根據(jù)一定的規(guī)則去修改游戲狀態(tài)。比如說(shuō)移動(dòng),就是根據(jù)實(shí)體的方向和移動(dòng)速度去改變這個(gè)實(shí)體的位置。如果系統(tǒng)沒有游戲狀態(tài),它如何去實(shí)現(xiàn)游戲行為呢?
這就是ECS架構(gòu)最重要的職責(zé)了:為系統(tǒng)篩選出它關(guān)心的實(shí)體子集,只展示給它關(guān)心的游戲狀態(tài)。具體我們是怎么做的呢?
首先把可能單獨(dú)使用的游戲狀態(tài)歸納為一個(gè)個(gè)組件:
?
比如最常見的位置、方向我們可以歸納為變換組件;移動(dòng)速度這個(gè)組件可能會(huì)在移動(dòng)系統(tǒng)中單獨(dú)使用,所以我們把它歸納到移動(dòng)組件中;碰撞組件則有碰撞盒的大小;攻擊組件有攻擊方向,這樣我們就把各種屬性給拆開了。
接著,我們?cè)谙到y(tǒng)實(shí)現(xiàn)的時(shí)候,要向框架聲明我關(guān)心哪些“組件元組”(Component Tuple)
什么是“組件元組”?還是舉剛剛移動(dòng)的例子。移動(dòng)系統(tǒng)的移動(dòng)行為,應(yīng)該是關(guān)心實(shí)體的位置、方向以及移動(dòng)速度,就是我們歸納的變換組件和移動(dòng)組件,那么只要一個(gè)實(shí)體同時(shí)掛載這2個(gè)組件,它就可以被移動(dòng)系統(tǒng)遍歷到,系統(tǒng)就會(huì)進(jìn)行操作從而實(shí)現(xiàn)移動(dòng)行為。
?
最關(guān)鍵的一點(diǎn),“組件元組”其實(shí)就是用來(lái)實(shí)現(xiàn)框架篩選實(shí)體的功能,實(shí)體只需要根據(jù)自身功能需求掛載相應(yīng)的組件元組就可以了。比如說(shuō)子彈它有移動(dòng)和碰撞的功能,那么就掛載上變換、移動(dòng)和碰撞這3個(gè)組件。
?
最終實(shí)現(xiàn)的效果就是移動(dòng)系統(tǒng)遍歷了英雄和子彈實(shí)體,在他們身上實(shí)現(xiàn)了移動(dòng)的行為。攻擊系統(tǒng)遍歷了英雄和炮臺(tái)實(shí)體,然后他們就可以發(fā)射子彈。
(3)ECS架構(gòu)實(shí)例:
接下來(lái),我們看一下比較復(fù)雜的碰撞邏輯,這里我們可以對(duì)碰撞進(jìn)行拆解:
首先是碰撞的觸發(fā)系統(tǒng)。當(dāng)碰撞發(fā)生時(shí)將產(chǎn)生一個(gè)碰撞事件,然后這個(gè)系統(tǒng)只干這件事。剩下的碰撞處理呢,對(duì)于子彈來(lái)說(shuō),會(huì)有一個(gè)碰撞后銷毀系統(tǒng),它會(huì)在碰撞之后把子彈銷毀。對(duì)于英雄來(lái)說(shuō),他有一個(gè)碰撞后的損血系統(tǒng),通過(guò)這種方式,我們就可以把碰撞進(jìn)行拆分,再通過(guò)剛剛的方式集成在一起。
(4)ECS架構(gòu)作用:
這種架構(gòu)可以讓每個(gè)開發(fā)人員負(fù)責(zé)不同模塊的開發(fā),有效地提高多人開發(fā)效率。最重要的就是模塊的復(fù)用,可以便于功能拓展。如果你想改變一個(gè)實(shí)體的功能,只需要添加或者移除實(shí)體的組件就可以了。
比如說(shuō):一個(gè)英雄死亡之后,他應(yīng)該失去移動(dòng)功能,那么在英雄死亡之后,我們只需要把移動(dòng)組件給移除就可以了,等他復(fù)活的時(shí)候再給他加回來(lái)。可以看到,這種方式非常方便。既然這么方便了,我們就可以做出一個(gè)編輯器,把這種能力開放給策劃人員。
實(shí)際上,暴雪就專門為Overwatch開發(fā)了一套Statescript的腳本語(yǔ)言,它用起來(lái)就是一個(gè)可視化的編輯器,策劃人員可以在這個(gè)編輯器中編輯每個(gè)英雄在各種游戲狀態(tài)中擁有什么游戲能力,程序只要實(shí)現(xiàn)具體的功能模塊,然后開放給策劃人員使用,非常地靈活。
?
以下是我們?cè)趯?shí)踐過(guò)程中參考的技術(shù)文章:
[參考文檔]
《守望先鋒》架構(gòu)設(shè)計(jì)與網(wǎng)絡(luò)同步
http://gad.qq.com/article/detail/28682
《守望先鋒》回放技術(shù)-陣亡鏡頭、全場(chǎng)最佳和亮眼表現(xiàn)
http://gad.qq.com/article/detail/29595
《守望先鋒》中的網(wǎng)絡(luò)腳本化的武器和技能系統(tǒng)
http://gad.qq.com/article/detail/28219
淺談《守望先鋒》中的ECS構(gòu)架
https://blog.codingnow.com/2017/06/overwatch_ecs.html
二、網(wǎng)絡(luò)同步機(jī)制
1、常見同步機(jī)制:
常見的網(wǎng)絡(luò)同步機(jī)制可以分為以下三種:
?
- 確定性幀同步(Deterministic lockstep)
- 快照插值(Snapshot interpolation)
- 狀態(tài)同步(State synchronization)
(1)確定性幀同步
服務(wù)端:收集并轉(zhuǎn)發(fā)玩家輸入數(shù)據(jù),不運(yùn)算游戲邏輯
客戶端:在玩家輸入數(shù)據(jù)以后各自運(yùn)算游戲邏輯
優(yōu)點(diǎn):只有玩家輸入會(huì)被傳輸,數(shù)據(jù)流量非常小;代碼都是寫在客戶端上的,所以代碼復(fù)雜度較低。
缺點(diǎn):買手機(jī)游戲平臺(tái)對(duì)網(wǎng)絡(luò)延遲要求非常高;每個(gè)機(jī)器浮點(diǎn)數(shù)運(yùn)算不一致,需要將浮點(diǎn)數(shù)運(yùn)算轉(zhuǎn)換成整數(shù)運(yùn)算;斷線重連時(shí)間較長(zhǎng);因?yàn)橛螒蜻壿媽懺诳蛻舳?#xff0c;所以不是很安全。
(2)快照插值
服務(wù)端:運(yùn)算游戲邏輯,將快照發(fā)送給客戶端。
快照,就是我這一幀所有游戲?qū)嶓w的游戲狀態(tài)。
客戶端:不運(yùn)算游戲邏輯,收到快照以后進(jìn)行差值平滑播放。
實(shí)際上,客戶端只是一個(gè)播放器。
優(yōu)點(diǎn):客戶端運(yùn)算量小;斷線重連容易實(shí)現(xiàn);游戲邏輯全在客戶端,所以非常安全。
缺點(diǎn):帶寬占用非常大。
所以這種方式之前多用于像CS這種局域網(wǎng)對(duì)戰(zhàn)。
(3)狀態(tài)同步
服務(wù)端:運(yùn)算游戲邏輯,將玩家輸入和部分狀態(tài)發(fā)送給客戶端
客戶端:在玩家輸入時(shí),不等服務(wù)器就立馬運(yùn)算游戲邏輯,就有點(diǎn)像單機(jī)游戲了,但這種運(yùn)算結(jié)果未經(jīng)過(guò)服務(wù)器,不一定是正確的,所以它實(shí)際上是一個(gè)游戲邏輯的預(yù)測(cè)。在收到服務(wù)器數(shù)據(jù)后,會(huì)對(duì)預(yù)測(cè)結(jié)果進(jìn)行校驗(yàn),如果錯(cuò)誤,就需要平滑地將其糾正到正確的狀態(tài)。
這里說(shuō)一下校驗(yàn)的過(guò)程,其實(shí)就是先回滾再前滾。
服務(wù)端下發(fā)的數(shù)據(jù)是之前一個(gè)時(shí)間點(diǎn)的數(shù)據(jù),我們本地賦值以后相當(dāng)于回滾到之前的時(shí)間,然后我們會(huì)一幀幀的運(yùn)算到當(dāng)前的時(shí)間,這就叫前滾,最后將計(jì)算結(jié)果與預(yù)測(cè)結(jié)果進(jìn)行比較,可以看到校驗(yàn)的計(jì)算量是非常大的。
優(yōu)點(diǎn):客戶端可以進(jìn)行游戲邏輯預(yù)測(cè);網(wǎng)絡(luò)游戲體驗(yàn)好;以服務(wù)器數(shù)據(jù)為準(zhǔn),比較安全。
缺點(diǎn):代碼復(fù)雜度高;客戶端運(yùn)算量大;因?yàn)橛锌蛻舳祟A(yù)測(cè),所以客戶端之間是不完全同步的。
2、小游戲平臺(tái)特點(diǎn)
一開始我們的項(xiàng)目采用的是狀態(tài)同步的方式,但由于我們的項(xiàng)目是針對(duì)小游戲平臺(tái)的,小游戲平臺(tái)有以下幾個(gè)特點(diǎn):
- 運(yùn)算性能較差,客戶端計(jì)算量不能太大
- Javascript代碼很容易被破解,玩家想要作弊的話很容易
- 網(wǎng)絡(luò)連接只能使用TCP,所以帶寬占用不能太高
3、歡樂(lè)槍戰(zhàn)的實(shí)現(xiàn)方案
(1)帶寬優(yōu)化
基于小游戲平臺(tái)的特點(diǎn),我們項(xiàng)目從狀態(tài)同步開始做簡(jiǎn)化,一直簡(jiǎn)化到以下這種實(shí)現(xiàn)方案:
?
- 服務(wù)端:運(yùn)算游戲邏輯,將變化的狀態(tài)發(fā)送給客戶端
- 客戶端:不運(yùn)算游戲邏輯,收到數(shù)據(jù)以后進(jìn)行差值平滑播放
- 優(yōu)化了帶寬占用的快照插值
這個(gè)大家可能看著就有點(diǎn)眼熟了,其實(shí)就是優(yōu)化了帶寬占用的快照插值。這種方案最關(guān)鍵的一點(diǎn)是,你要把帶寬優(yōu)化下來(lái)。而帶寬優(yōu)化最關(guān)鍵的,是只有在必要的情況比如游戲開始和斷線重連時(shí)才發(fā)送全量狀態(tài),平時(shí)玩的過(guò)程中,只發(fā)送變化的狀態(tài)。
另外一方面是數(shù)據(jù)壓縮,比如方向,剛開始我們用的是方向向量,但其實(shí)用弧度制乘以一千就可以了,這樣就把兩個(gè)Float優(yōu)化成一個(gè)Short。
經(jīng)過(guò)帶寬優(yōu)化成果:
上行:2~15pkg/s,流量占用:0.1 KB/s
下行:0~15pkg/s,流量占用:2.5 KB/s
這個(gè)流量占用對(duì)于目前的手機(jī)網(wǎng)絡(luò)來(lái)說(shuō),是完全可以接受的。
(2)網(wǎng)絡(luò)抖動(dòng)優(yōu)化
介紹完了帶寬優(yōu)化,接下來(lái)我們來(lái)聊聊網(wǎng)絡(luò)抖動(dòng)。
網(wǎng)絡(luò)抖動(dòng)指的是,網(wǎng)絡(luò)的傳輸是不穩(wěn)定的,服務(wù)端每個(gè)邏輯幀會(huì)發(fā)送一個(gè)包,它發(fā)送的頻率是穩(wěn)定的,但是對(duì)于客戶端,可能在一個(gè)邏輯幀內(nèi)收不到包,也可能收到多個(gè)包。
這在游戲中的體現(xiàn)就是,玩家在移動(dòng)過(guò)程中,這一幀沒有收到包,就停下來(lái)了,下一幀收到2個(gè)包,就跳過(guò)去了,體現(xiàn)出走走停停的狀態(tài)。
對(duì)于這種網(wǎng)絡(luò)抖動(dòng),最常見的優(yōu)化方法是航位推測(cè)法。
航位推測(cè)法(Dead Reckoning):
?
- 客戶端和服務(wù)端約定至少每500ms同步一次
- 客戶端若沒有按時(shí)收到移動(dòng)狀態(tài),則用最后一次收到的移動(dòng)狀態(tài)繼續(xù)預(yù)測(cè)一段時(shí)間
- 服務(wù)端若沒有按時(shí)收到玩家輸入,則用最后一次收到的玩家輸入繼續(xù)運(yùn)算一段時(shí)間
用這種方案優(yōu)化之后,走走停停的現(xiàn)象就基本沒有了。
?
抖動(dòng)緩存法
另一種優(yōu)化方案是抖動(dòng)緩存,這是指收到包后不立馬處理,而是放入抖動(dòng)緩存中,延遲一段時(shí)間后再取出。
?
這種優(yōu)化方案關(guān)鍵點(diǎn)在于緩存的大小。如果緩存太小,對(duì)于抖動(dòng)還是比較敏感,抗抖動(dòng)效果比較弱,緩存太大,玩家的延遲又特別高,所以你需要根據(jù)算法動(dòng)態(tài)調(diào)整緩存的大小以適應(yīng)網(wǎng)絡(luò)環(huán)境。
(3)全區(qū)全服
?
- 所有玩家都在同一個(gè)大區(qū)里
- 前臺(tái)服務(wù)器處理登錄等戰(zhàn)斗外邏輯
- 游戲服務(wù)器處理戰(zhàn)斗邏輯
(4)分地域部署
我們的項(xiàng)目是實(shí)時(shí)競(jìng)技游戲,對(duì)于延遲比較敏感,因此我們的游戲服務(wù)器采用了分地域部署。服務(wù)器入口使用的是阿里云的“云解析DNS”服務(wù),按照地域自動(dòng)分配游戲服務(wù)器(華北、華東、華南、西南),玩家在進(jìn)行快速匹配戰(zhàn)斗時(shí),會(huì)根據(jù)地域分配服務(wù)器,同一地域玩家進(jìn)入該地域所屬服務(wù)器。
以下是我們?cè)诰W(wǎng)絡(luò)優(yōu)化方面參考的文章,都是干貨,如果感興趣可以去了解一下。
總結(jié)
以上是生活随笔為你收集整理的Cocos 技术派:实时竞技小游戏技术实现分享的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 搭载「光线追踪」技术的次时代主机会有怎样
- 下一篇: 机器学习将在游戏开发中的6种应用