卡牌戰鬥系統設計概述
同步方案:
在同步方案上採用的是狀態同步的方案,主要的考慮是狀態同步是網易多款實時手游均採用的方案,得到了驗證,風險性較小。客戶端接受用戶的操作輸入發送給伺服器,所有戰鬥邏輯在伺服器計算完成,伺服器將客戶端表現指令發送給客戶端執行。其中,為了精確匹配遠程攻擊和相應扣血,所有的遠程的扣血由客戶端判定命中後向伺服器請求觸發。
代碼架構:
戰鬥代碼結構
battle.lua是整個戰鬥邏輯的最上層。battle.lua處理消息的接受和分發、戰鬥的主邏輯think的執行,戰鬥開始和結束的管理。
fighter.lua管理每個玩家的行為和卡牌狀態。
base_entity.lua、entity.lua、entity_moveable.lua、tower.lua、arrow.lua、...等等是各類Entity的類,他們之間有繼承關係,處理Entity狀態機。
battle_base.lua是處理所有戰鬥的基礎功能類,功能包括Entity創建、Entity選擇、Entity管理、Entity死亡處理、數學運算處理、尋路入口。
battle_net.lua是處理所有戰鬥消息的發送。
skill_base.lua是處理所有技能的釋放,skill_.lua以及buff_.lua是所有技能和buff具體的實現。
astar_path_find.lua和collide.lua分別處理網格註冊管理、Entity碰撞。
AI:
(1)在人人的PVP戰鬥中,兩方玩家只需要將單位拖動到場上,上場後單位由AI託管。每個Entity的AI採用狀態機的方式來實現。Entity的Think函數在每幀中:1. 判定state是否滿足轉化條件,如果滿足進行state的轉化 2.執行state內的行為。Entity的主要的state包括:fixed、idle、ahead、walk、run、attack、pursue、dead、skill。
(2)在人機的PVP戰鬥中,有專門的機器人AI來模擬玩家的行為。首先,機器人在選牌階段,會盡量保持牌組的均衡,避免選出過於極端的陣容,具體的做法是會在可選的牌中選擇一個攻擊評分top2、防禦評分top2的卡牌,然後隨機選擇剩餘的牌。戰鬥中,AI每隔1s會進行一次think,每次think只會產生一次操作(為了模擬人類的思考間隔)。採用行為樹來實現具體的think,如下圖所示。
行為樹
另外,在選擇了要放置的卡牌之後,它的放置位置的選擇也比較重要。位置選擇的主要原則有:(1)進攻時,圍繞英雄或者聚集式放置單位(2)防守時,根據對方的來犯單位放置單位。英雄的技能的自動釋放也由AI機制進行管理,它的觸發由獨立的邏輯判定觸發,會根據當前場上英雄的技能的特性判定是否到達最佳的釋放時機,然後決定是否釋放。
(3)PVE戰鬥怪物和Boss的AI觸發條件和行為比較複雜,每個副本都需要可配置具體的AI,採用的ACT(Action、Condition、Trigger)方案,可以支持比較靈活的AI配置。每個怪物和Boss都可以配置多條ACT邏輯。它們的think函數每幀判定自己的ais里是否有Trigger被觸發,觸發後判定Condition是否通過,通過則觸發Action。
技能和buff系統:
由於我們的遊戲技能設計比較複雜,技能和buff系統是整個戰鬥最重要也是最龐大的部分。
從釋放方式上來說,分為:
main_skill --普攻技能init_skill --出生直接釋放一次的技能,沒有技能動作birth_skill --出生自動釋放一次的技能auto_skill --自動釋放技能manual_skill --手動釋放技能dead_skill --死亡釋放技能init_buff --出生自帶的buff
另外,技能可以擁有子技能(主技能釋放時會觸發子技能的釋放)、計數技能(根據使用次數觸發的替代技能)。
技能的具體類型分為16種:
attack =1, --攻擊create =2, --召喚move =3, --位移buff =4, --bufffield =5, --法術場dispel =6, --驅散taunt =7, --嘲諷heal =8, --治療field_arrow =9, --移動法術場field_aura =10, --光環evocate =11, --復活energy =12, --回費purge =13, --凈化gather =14, --聚人transfer =15, --傳送push =16, --推人
buff是技能系統的重要組成部分,具體類型分為19種:
fixed =1, --定身perporty =2, --屬性attack =3, --傷害heal =4, --治療soul_link =5, --靈活鏈接trigger =6, --觸發技能power_scale =7, --係數disapear =8, --消失dead =9, --定時死亡stealth =10, --隱身immune =11, --免疫subdue =12, --征服follow =13, --跟隨absorb =14, --吸住suck =15, --吸血dying =16, --垂死protect =17, --護盾ability =18, --基礎能力accum_power_scale =19, --累加係數
技能的釋放過程包括選擇目標、對選擇的目標釋放技能。選擇的類型包括:
RANGE_TYPE= {--主類型single =1, --單體circle =2, --圓形rect =3, --矩形self=4, --自己sector =5, --扇形focus =6, --當前目標}SUB_RANGE_TYPE= {--子類型random =1, --隨機closet =2, --最近exclude_target =3,--除了當前目標}
選擇目標的時候會採用主類型和子類型結合選擇出目標。另外,還支持一些特殊類型的選擇目標需求,包括:
SPECIAL_SELECT_TYPE= {random =1, --隨機random_buff =2, --篩選buffrandom_hp =3, --篩選hprandom_type =4, --篩選目標type}
以上是對技能系統的概述,還有很多細節,這裡不做過多的討論。
尋路碰撞系統:
尋路系統隨著地形的需求不同做了幾次迭代,也陸續實現了幾種尋路方式,最終地圖採用了無路障的方式,也就拋棄了尋路系統的使用。這裡還是說明一下我們在尋路系統上做的一些工作。最早,實現了astar的尋路演算法,astar的優點是能夠解決動態碰撞的問題,缺點在於計算量比較大(有考慮過預先bake路線)、路線比較直有明顯的走格子的痕迹。又嘗試了導航網格的尋路演算法,對於我們的障礙物比較大塊的地圖來說,導航網格對地圖的劃分比較少,計算量相對於astar演算法會比較小。另外,導航網格尋路的路線比較自然。但是它不能解決動態碰撞問題。
碰撞系統在效果上是要模擬皇室衝突兵種單位之間推來推去的效果。網上沒有現成的庫可以用,我自己實現了一個演算法。在設計這個演算法時,最擔心是性能問題,所以整個演算法的出發點是如何降低性能開銷。首先,我們把整個地圖劃分為1x1的格子單位,這個大小和我們的單位的直徑比較接近。單位在移動的過程中會更新自己的所在格子信息。單位移動時會觸發一次碰撞處理,根據自己當前的格子以及移動的方向,確定有可能產生碰撞的單位所在的格子,然後取出格子中的單位做碰撞處理。一個單位被碰撞後會產生位置的移動,會發起一次子碰撞處理,這是一個遞歸的過程。這個過程不加特殊處理很容易陷入死循環,我做了約束,一個單位在一次碰撞處理中,只會被移動一次。這個演算法在實際使用中,發現有些速度相同的單位疊在一起後,會陷入互相擠壓無法分開。為此,我加了一個簡單的處理,判定兩個單位是否處在重疊狀態,如果處在重疊狀態,會直接強制拉開。經過反覆的演算法調整,目前效果沒有出現比較明顯的問題。
天梯匹配的機器人難度控制:
在遊戲測試時,由於玩家的導量不足,需要有機器人去模擬玩家跟玩家作戰。機器人有兩大問題,第一就是如何儘可能的模擬玩家去作戰,第二就是如何給玩家匹配一個水平相當的機器人。這裡主要討論第二點,如何給玩家匹配水平相當的機器人。其他遊戲有很多可以參考的做法,比如經典的war3、dota。一般採用的方法分為兩類:1. AI分級 2.獲得錢的速度分級(屬性分級) 。AI分級比較難,而且很難做到天梯這麼多檔次的分級。屬性分級相當比較容易線性的調節。我們主要的做法是通過屬性分級來做。我以玩家和機器人作戰的勝率作為演算法的核心目標,為了讓玩家打電腦沒有那麼強的挫敗感,我們定的勝率是65%(是下一場的期望勝率,不是總體勝率)。這個勝率作為權重計算出玩家對電腦的凈勝場數。我們將電腦的難度分為上下一共20個level,每個level對應一定的屬性加成或者減少,根據凈勝場數得到對應的level。
凈勝場是比賽機制中常用的用來判定水平的標準、另外一個判定水平的標準是勝率。
推薦閱讀:
※遊戲線上的伺服器風險
※記錄一次伺服器宕機分析過程(2)-深入Lua GC
TAG:遊戲伺服器 |