游戲在真實的環(huán)境中,有些特殊情況需要處理,本文介紹技能模塊是如何處理人為作弊和現實中的網絡導致的一些問題。
主要介紹四個部分:
- 防外掛
- 網絡延遲問題解決
- 網絡卡頓和抖動
- 流量優(yōu)化
注意,本文默認介紹的是玩家的技能處理,也就是技能的控制端在玩家的客戶端。對于控制端在服務器的小怪,基本沒有前三個問題。
每個游戲的技能系統(tǒng)的實現不同,處理方式也有可能不太一樣,本文所使用的技能系統(tǒng)參考之前寫的文章:http://www.lxweimin.com/p/551f02f95727
1.防外掛
由于技能是客戶端先行,因此技能模塊很多邏輯是放在客戶端的,由客戶端控制技能流程并且通知服務器執(zhí)行相應的功能。由此可見,技能是由客戶端發(fā)起的,服務端必須對收到的技能執(zhí)行命令進行驗證以保證技能確實是可用的,防止玩家通過外掛重復發(fā)送技能釋放消息無限次釋放技能。
每個游戲技能系統(tǒng)實現不同,可能對應的邏輯也不太一樣。基本原則是:
1.服務端保存技能釋放可用性的相關信息,比如技能CD、技能藍量等。
2.技能結算在服務端執(zhí)行,客戶端管理技能執(zhí)行流程。
3.服務端每次真正的釋放技能之前,對技能進行判斷是否可用。
4.服務端收到的技能執(zhí)行消息后,根據實現系統(tǒng)的規(guī)則進行相應驗證。
下面,以我們的技能系統(tǒng)為例,介紹我們是如何實現防外掛的。
在我們系統(tǒng)中,技能同步包括三類同步消息:
- 技能根節(jié)點enter (root_enter): 表示技能樹根結點進入執(zhí)行,表示一個技能樹開始。
- 技能葉子節(jié)點enter(action_enter): 表示技能的執(zhí)行節(jié)點進入執(zhí)行,表示一個技能執(zhí)行模塊開始執(zhí)行,有一個執(zhí)行模塊有后搖時間,根據后搖時間,他會自動結束。
- 根節(jié)點exit(root_exit) :表示技能樹結束。
服務端會接受到客戶端發(fā)來的這三種技能消息,其中,服務端收到root_enter和action_enter消息后,需要對消息的真實性進行驗證,root_exit表示技能結束,和防外掛沒有關系,無需驗證。
由于技能的信息都是以root_enter來控制的,比如技能CD、技能耗藍和沉默/暈眩等導致的技能是否可用,因此,當服務端收到root_enter的時候,首先要判斷這個技能是否真的可以釋放,判斷后進入相關邏輯。
action_enter是技能真正的執(zhí)行消息,技能模塊并沒有方法判斷一個執(zhí)行節(jié)點(技能樹葉子節(jié)點)是否可用,因此,當收到action_enter的消息時,只能根據root節(jié)點的信息進行判斷。我們進行了兩種判斷:
- 判斷一:在root_enter和root_exit執(zhí)行期間,表示正在執(zhí)行這個大技能,收到action_enter后,判斷這個action是否屬于正在執(zhí)行的大技能葉子節(jié)點,若不屬于,不能執(zhí)行。
- 判斷二:我們還判斷了action_enter消息對應的執(zhí)行節(jié)點的順序,保證執(zhí)行節(jié)點是按照合法的順序執(zhí)行的,而不能一直執(zhí)行某一個特別牛逼的葉子節(jié)點。
- 此外,服務端執(zhí)行action節(jié)點的時候,不能同時執(zhí)行多個,每次只能執(zhí)行一個action節(jié)點,并且需要持續(xù)相應的時間(action節(jié)點的后搖時間),上一個action節(jié)點執(zhí)行結束后才能執(zhí)行下一個節(jié)點。
基于以上機制,可以保證服務端收到的技能消息只有合法的消息才可以執(zhí)行。
2.網絡延遲
在真實的網絡環(huán)境中,網絡延遲是難免的。造成的結果是,服務端執(zhí)行邏輯一直晚于客戶端。這種網絡延遲并不會造成錯誤,但是在網絡延遲大的時候會造成表現和結算不能對應上。為了解決著這個問題,一般采取的方式是基于網絡延遲時間讓服務端加快執(zhí)行速度,去追趕客戶端。
以技能結算為例,當server端收到action_enter消息后,根據當前時間和客戶端開始時間可以計算出網絡延遲,將服務端的前搖時間減少兩個網絡延遲,當客戶端收到技能結算消息時,正好是客戶端的技能結算時間,這樣,在玩家控制的客戶端,看到的就是完美對應上的效果。參考文章:技能模塊的同步
3.網絡波動和卡頓
對于手機游戲來說,網絡波動和卡頓也是難免的,這種情況造成的結果就是,原本按一定的順序以一定的時間間隔到達服務器/客戶端的消息,可能同時到達了服務器/客戶端,或者時間間隔忽大忽小。
一般來說,當服務器由于網絡卡頓同時接收到多個技能開始執(zhí)行的消息時,可以通過兩種方式進行處理。
1.接到技能執(zhí)行消息后馬上執(zhí)行,這樣導致的問題時可能在同一幀收到多條技能執(zhí)行消息,并且在同一幀執(zhí)行多個技能。這個策略的好處是處理方式比較簡單,而且能讓服務器盡快的跟客戶端同步。但是為了防止玩家使用外掛同時發(fā)送多個技能執(zhí)行請求,這個策略是不可行的。
2.當接收到多條技能執(zhí)行消息時,按序依次執(zhí)行技能,每個技能的執(zhí)行時間結束后,才執(zhí)行下一條技能。這種策略的問題是,若服務器的延遲與客戶端較大,如果玩家一直在不停的放技能,會導致服務器與客戶端的延遲越來越大。
為了解決此類問題,我們在客戶端和服務端采取不同的處理方式。
3.1 proxy服務端(主控端是玩家控制的客戶端)
服務器是技能真正執(zhí)行的地方,需要保證技能正確的執(zhí)行。因此,服務端基于第二種方式解決網絡卡頓,同時增加了一些邏輯,以保證服務端不會和客戶端延遲過大。
3.1.1 消息隊列
proxy服務端以隊列的形式保存下來收到的技能消息并依次執(zhí)行。
當服務端收到技能同步消息后,就將消息存入隊列,技能執(zhí)行模塊就根據隊列依次執(zhí)行,其中,action_enter會執(zhí)行一段時間,后搖時間點到達后結束。root_enter和root_exit對技能執(zhí)行狀態(tài)進行控制,執(zhí)行即結束,不存在持續(xù)時間。
3.1.2 防止服務端延遲過大
在某些情況下,服務端可能遲于戶端較長時間。比如網絡卡頓,導致客戶端多次釋放技能的消息同時到達服務端。為了解決這種問題,我們通過兩種機制,讓服務端追趕客戶端。
- 當接收到一個新的root_enter信息時,馬上清空掉隊列中的所有技能消息,執(zhí)行隊列中對應的root_exit消息。然后執(zhí)行新的root_enter信息。此策略表現是:當玩家執(zhí)行一個新的技能,服務端之前還沒有執(zhí)行的技能就不再執(zhí)行了。
- 葉子節(jié)點的持續(xù)時間(后搖時間)根據網絡延遲進行一定的減少,給定一個系數比如0.8,一方面保證服務端不會快速的執(zhí)行多個action節(jié)點,同時可以讓服務端盡快的追上客戶端。
3.2 proxy客戶端 (主控端是玩家控制的客戶端或者是服務器控制的怪物)
客戶端只是執(zhí)行技能的表現,在網絡條件較差的情況下,我們只要保證游戲的正確性(不出錯誤,不影響服務器正確運行,網絡條件變好后游戲可以正確運行)即可,可以一定程度的降低游戲的表現效果。
因此,在我們游戲中,客戶端的proxy端采用的是接到消息后馬上執(zhí)行的策略。客戶端接到既能執(zhí)行信息,那么就把之前正在執(zhí)行的技能停止掉,然后執(zhí)行新的就好了。
4 流量優(yōu)化
流量的優(yōu)化基本上沒有太多通用的技巧,最基本也是最重要的就是:不要同步沒有意義的消息。
這句話是廢話,但是也是流量優(yōu)化的指導方向。聽起來很簡單,但是實現起來非常難,甚至想不同步冗余信息是不可能的。
為什么說這件事很難,甚至是不可能的呢?
一,只有梳理清楚了執(zhí)行邏輯,才能確定哪些同步消息是必要的,哪些是冗余的。因此,執(zhí)行邏輯一定要清晰,這樣哪些消息必須同步,哪些消息不需要同步才能區(qū)分。
二,比如一條消息,某些情況是不用同步的,有的情況又要同步。那么發(fā),還是不發(fā)。再細節(jié)一點,比如一個參數,有的情況不需要同步,有的情況需要同步。如果對每種情況進行特殊化編寫代碼,代碼可讀性可能較差,如果發(fā)送一個通用的同步消息,可能消息量比較大。那么,優(yōu)化做到什么程度?大概做到游戲流量可以接受的成都就好了。
還有些tips可以減小流量信息,比如:
- 有些常見的string,甚至是所有的string,可以將其轉為一個int,客戶端服務端都知道這個int值代表哪個string即可。
- 有些float,可以通過乘以100轉為int傳到客戶端,客戶端再除以100,將float轉為int進行傳輸。
等...
4.其他
本文基于底層的消息是保證消息順序、保證不掉包、保證消息不被篡改、保證消息沒有重發(fā)的。這件事,本身實現起來可能更加復雜。