表演的藝術,妖尾回合制戰鬥系統客戶端設計

妖尾歷經幾年開發,終於在今年6月底順利上線,筆者從2017年初參與開發,主要負責妖尾戰鬥系統開發。戰鬥做爲遊戲的核心玩法系統,涉及不少技術點,但願能借幾篇文字,系統性總結MMORPG戰鬥系統的開發經驗。
本文主要從宏觀層面總結回合制遊戲戰鬥的美術資源規範,系統框架設計和主要技術點,好比斷線重連,技能表演等。html

系列博文傳送門:
記錄戰鬥記錄你,詳解妖尾戰鬥錄像系統緩存

美術資源規範

  • 模型

模型分爲低模(1500-2000面)、高模(6000-10000面)兩種規格,戰場單位統一使用低模,但在合體技等鏡頭動畫表演使用高模。主角模型是由頭、上衣、武器、下裝4部分組成的,遊戲中經過網格、貼圖合併成1個完整模型進行展現,這樣能夠實現部件換裝。非主角模型比較簡單,直接加載完整模型。網絡

非主角高低模

主角高低模

  • 掛點

高低模都有頭、腳、血量、受擊、左右手、左右腳等掛點,高模相比低模額外多了表情掛點(下文解釋掛點做用)。架構

  • 材質球

高模跟低模使用不同的材質球。低模全身只用了一種材質球,而高模用了兩種材質球,臉部和身體分別是不一樣材質球,臉部材質球實現了uv動畫用於作表情變化。高低模的身體材質球都實現了描邊,高模額外開啓了自陰影。高模貼圖爲256x256大小png圖片,低模爲128x128大小png圖片,貼圖都是寬高相等的POT尺寸,這樣Android/IOS能夠分別使用ECT2/PVRTC壓縮格式。框架

高低模材質貼圖

  • 表情實現

人物表情是經過shader uv動畫實現的,索引0-3從分別對應下面貼圖的4個表情。因爲shader是項目TA編寫輸出的,要讓動做美術可以控制表情變化,咱們定了個表情掛點位置映射索引的規則,表情掛點x軸數值除以100向下取整即爲索引,動做美術在動畫時間軸裏只須要編輯表情掛點的位置,經過程序轉換設置shader參數,就能控制表情變化。異步

  • 動畫

戰鬥單位的動畫狀態機具備很是多的狀態,有多達60+多個動畫,但經常使用動畫只有其中幾個,因此戰鬥單位不會在進入戰場時一次性加載全部動畫,默認只加載站立、受擊、奔跑、死亡等4種動畫。其餘動畫則每回合按需加載,咱們會按角色預先存儲動做和對應資源路徑的配置表,須要用到的動做查表獲取路徑加載資源,做爲AnimationClip加載到RuntimeAnimatorController上。另外,像受擊浮空等動畫還須要處理好依賴,相關的過渡動畫也要一併加載。編輯器

戰鬥動畫狀態機

  • 技能

技能是使用Flux編輯器製做出來的,經過時間軸上建立多個Sequance軌道來組成一段技能表演,每一個Sequance腳本負責1種表現,如角色移動、播放特效等,Sequence腳本共同做用就能表現出一段技能。1個技能最終生成動做、音頻共2個Prefab。1個戰鬥單位擁有的技能也很是多,不會在進入遊戲時一次性加載,也是每回合按需加載要用到的技能。ide

用Flux編輯物理普攻技能

  • Buff

Buff相比技能表現要簡單,由於最多隻有添加、持續、觸發、移除等4個階段須要作表現,每一個buff prefab掛相應的4個腳本,配置特效資源,人物動做,替換材質便可。工具

Buff Prefab的配置腳本

  • 美術資源流水線

主角模型與非主角模型的資源提交規範稍有不一樣,但製做流水線基本是同樣的。美術提交包括模型fbx、動做fbx、動畫機,材質球、貼圖等資源,經過工具腳本進行資源檢查、預處理,生成預製件到指定目錄。性能

美術提交資源一覽

美術資源流水線

圖解戰鬥框架

一套戰鬥框架其實包括了不少內容,一篇文章難以講清全部細節。不過筆者嘗試畫圖總結了戰鬥按功能劃分的各個模塊,但願儘可能講清基本模塊的內容,模塊之間的關係,從而在宏觀層面瞭解戰鬥系統。

戰鬥框架圖

骨架——戰鬥狀態機

戰鬥狀態機

架構圖紫色部分爲PlayMaker腳本集合,若是將戰鬥框架理解爲人,那PlayMaker狀態機就是人的骨架,它串聯了整個戰鬥流程。妖尾戰鬥採用了PlayMaker插件可視化編輯整個戰鬥流程,這樣易於編輯,追蹤整個戰鬥流程,直觀地將戰鬥分紅始化、表演、指令選擇三大戰鬥狀態。各戰鬥狀態基本爲線性流程,戰鬥狀態之間則經過全局事件進行轉移。

另一點是,咱們但願儘可能用lua實現戰鬥邏輯,PlayMaker插件原生只支持C#,爲了支持Lua,咱們實現了繼承C#狀態機行爲基類(FsmStateAction)的子類,該類負責驅動Lua腳本,Lua腳本實現跟FsmStateAction類一樣的接口和行爲,這樣就能夠用Lua編寫狀態機邏輯了,代碼基本實現以下:

namespace HutongGames.PlayMaker.Actions
{
    public class LuaFsmStateAction : FsmStateAction
    {
        public string luaFileName = "";
        private LuaTable _luaTable;
        private LuaFunction luaOnEnter = null;
        private LuaFunction luaOnExit = null;
        private LuaFunction luaOnUpdate = null;

        public override void OnEnter()
        {
            if (_luaTable == null && !string.IsNullOrEmpty(luaFileName))
            {
                LuaSupport.DoFile(luaFileName);
                LuaFunction luaFunction = LuaSupport.lua.GetFunction(luaFileName + ".create");
                if (luaFunction != null)
                {
                    _luaTable = luaFunction.Invoke<LuaFsmStateAction, LuaTable>(this);
                    luaFunction.Dispose();
                }
                if (_luaTable != null && _luaTable.IsAlive)
                {
                    luaOnEnter = _luaTable.GetLuaFunction("OnEnter");
                    luaOnExit = _luaTable.GetLuaFunction("OnExit");
                    luaOnUpdate = _luaTable.GetLuaFunction("OnUpdate");
                }
                else
                {
                    Debug.LogError("Cannot find lua class " + luaFileName);
                    Finish();
                    return;
                }
            }
            SafeCall(luaOnEnter);
        }

        public override void OnExit()
        {
            SafeCall(luaOnExit);
        }

        public override void OnUpdate()
        {
            SafeCall(luaOnUpdate);
        }

        private void SafeCall(LuaFunction func)
        {
            if (func != null && func.IsAlive)
            {
                func.Call();
            }
        }
     }
}

大腦——戰鬥控制器

戰鬥的核心管理器就是架構圖底下藍色部分的戰鬥控制器,它是戰鬥系統的大腦。戰鬥控制器負責接收協議數據,驅動戰鬥邏輯。

戰鬥控制器有兩種方式接收數據輸入。對於一般的聯網戰鬥,底層網絡層接收後臺協議數據,再傳輸給戰鬥控制器。妖尾還在新帳號進入遊戲時,設計了一場戰鬥用於展現關鍵劇情,這場戰鬥則是離線模擬戰鬥。咱們單獨實現了模擬戰鬥控制器,它負責根據策劃配表生成模擬協議數據,傳輸給戰鬥控制器驅動戰鬥邏輯。

  • 協議設計

整個戰鬥流程的協議設計以下圖所示,能夠分爲戰場初始化,等待加入戰場,戰前表演,回合選招,回合表演,戰鬥結束等6個階段。戰鬥控制器收到不一樣的協議包切換PlayMaker狀態,進而改變戰鬥流程。

一場戰鬥是由一組連續的協議數據組成的。若是因爲客戶端卡頓,切出後臺等緣由,出現前一個協議包還未處理表現完,下一個協議包已經到了,忽略協議包不處理,或者粗暴切斷當前邏輯,直接處理下一個協議包都是不可取的,可能致使戰鬥表現異常。所以戰鬥控制器設計了協議緩存隊列,用於緩存順序處理協議數據,然而緩存隊列並非簡單地順序處理數據就能萬事大吉了,若是不加以考慮處理斷線重連的狀況,就會碰到像戰鬥進度嚴重延遲,甚至卡死等狀況。

  • 斷線重連

戰鬥控制器的一大要務就是處理好戰鬥中的斷線重連,恢復並修正戰鬥流程。簡單來看,戰鬥中斷線重連有兩大類狀況:斷線重連後戰鬥已結束;斷線重連後戰鬥未結束。

第一種狀況比較簡單,斷線重連的登錄包帶有玩家是否處於戰鬥中的標誌位,若是當前不處於戰鬥中,前臺卻仍處於戰鬥場景中,則清掉全部戰鬥協議緩存,執行退出戰鬥的邏輯。

第二種狀況則要細分多種狀況討論。通常斷線重連後,戰鬥協議緩存隊列可能存有多個戰鬥協議,須要確認協議數據是否仍爲原來那場戰鬥的。簡單判斷原則就是,若是隊列中收到初始戰場包,且其戰場ID與以前協議不一樣,能夠認爲斷線重連回來後已開始了另外一場新戰鬥,舊戰鬥數據已失效,直接清出緩存,開始處理表現新戰鬥。

接着考慮斷線回來後還在原戰鬥的狀況,戰鬥設計上斷線重連必然會收到初始戰場包,戰場包帶有當前戰鬥階段的標誌位,根據標誌位便可還原戰場狀態:標誌位爲戰前表演,回合表演階段,該客戶端立刻發送表演結束Req,等待服務端通知下回合開始,避免拖慢戰鬥進度;標誌位爲回合選招階段,客戶端切爲選招界面,並根據階段開始時間戳修正剩餘選招時間。

肉身——戰鬥資源

戰鬥資源理所固然就是戰鬥系統的肉身了。管理資源的難點在於合理加載卸載,如人有四肢五官,協調越好,運動性能越強,越節省體力。

  • 資源管理策略

資源 加載策略 緩存策略
戰鬥場景 登陸預加載 常駐內存
全屏背景圖 根據場景切換 常駐一張圖
戰鬥HUD 高配登陸預加載;
低配入場預加載
高配常駐內存;
低配戰後卸載
功能模塊UI 戰中即時加載 出戰鬥卸載
通用特效 高配登陸預加載;
低配入場預加載
常駐內存
己方模型 進戰鬥預加載 高配緩存到下一場戰鬥,無命中則戰後卸載;
低配戰後卸載
敵方模型 進戰鬥預加載 戰後卸載
骨骼動畫 入場加載基本動畫,
其他動畫按需回合加載
戰後卸載
技能 回合按需加載 緩存一回合,不命中則淘汰
Buff 回合按需加載 戰後卸載

上圖簡述了戰鬥系統涉及的主要資源及加載緩存策略,一言蔽之,就是既要體面,又要節約。

咱們但願遊戲體驗儘可能流暢,在社區場景遭遇戰鬥時能秒切進入戰鬥,因此:

  1. 最基本的戰鬥場景和全屏圖資源,高低配機都會預加載好並常駐內存;
  2. 本着節約原則,其餘資源儘可能作到不影響體驗,用時加載,不用時戰後卸載。
  3. 骨骼動畫,技能,Buff等資源每回合根據表演內容再按需加載;
  4. 模型方面能夠預判像玩家本身,隊友等己方模型常常複用,高配機會緩存至下一場戰鬥,無命中再戰後卸載,戰鬥HUD也會常駐內存;低配機資源比較緊張,仍是用戰後即時卸載的策略。
  • 資源使用策略

另外,一場戰鬥表現少說也會涉及數十個資源的異步加載,若是每處表演邏輯都要異步等待資源加載回調,很容易致使回調地獄。所以戰鬥狀態機特地將資源加載,資源使用劃分紅兩個階段。每回合等待表演所需資源所有異步加載完畢,才能進入到表演階段,表演邏輯按同步方式使用資源便可。因爲資源加載粒度細分到以回合爲單位來加載,實測資源加載等待並不會影響戰鬥表演的流暢體驗。

  • 資源ab打包策略

講到資源管理,ab打包是個繞不開的話題。ab打包粒度越細,包數量越多,IO壓力大;ab打包粒度越粗,資源越冗餘,包體,熱更新資源量都會變大,說究竟是平衡的藝術。

資源 打包策略
戰鬥場景 單獨打包
全屏背景圖 每張圖單獨打包
戰鬥HUD HUD集合打包,HUD上的動態小圖標按類別集合打包
功能模塊UI 按模塊集合打包
通用特效 全部通用特效集合打包
主角模型 每一個主角各個模型部件單獨打包,各個骨骼動做單獨打包
非主角模型 每一個角色爲單位打包
技能 每一個技能單獨打包,技能引用資源分普通技能,合體技兩類,再按角色爲單位打包
Buff 全部Buff集合打包

簡單羅列了戰鬥相關資源的ab打包策略,原則上是儘可能按資源使用耦合程度劃分打包,可能一塊兒使用的資源,打包到一塊兒,若是資源過多,就要進一步拆分ab包。再者,作好提早設計,確保打包策略在將來資源量堆起來後仍能適用。好比,主角模型的模型部件,骨骼動做很是多且在將來頗有可能新增,能夠每一個資源單獨打包;非主角模型模型,骨骼動做數量相對固定,就能以角色單位打包。規劃好ab打包策略後,跟美術約定好規則來提交資源目錄及資源,就能編寫工具根據配表,不一樣目錄執行不一樣的ab打包策略。

戰鬥表演

戰鬥表演大致分爲技能和Buff兩類表演。技能是有開始結束的一段表現,小到普通攻擊,大到多人合體技,都是技能表演;Buff則是附在單位上的持續性狀態表現,如人物的中毒,封印狀態表現。

  • 技能表演

正如前面的框架圖裏提到妖尾戰鬥有不少表演腳本,可綜合對角色,UI,場景,鏡頭,節奏作全方位的調度控制,從而表現一段技能。伽吉魯和蕾比兩個角色的合體技是很是有表明性的一段技能表演,涵蓋了對不少技能腳本的應用,簡單舉例講解這個合體技的實現,就能夠了解技能是怎麼編輯,表演的。下圖是合體技的遊戲表現:

整體來看,這個合體技由鏡頭動畫+技能打擊兩部分構成,這兩部分都是在同一條時間軸經過腳本組合運用編輯出來的,最後生成一個合體技預製件。

圖中紅色部分是鏡頭動畫實現腳本:

  1. 動畫掛靠腳本帶有Animator組件,控制相機和角色的位置旋轉。
  2. 動畫掛靠腳本帶有相機掛點,啓用場景鏡頭動畫相機並放到掛點下
  3. 動畫掛靠腳本帶有角色掛點,生成對應角色高模實例,放到掛點下,並播放合體技動做
  4. 動畫掛靠腳本每幀根據角色表情掛點更新角色表情
  5. 特寫角色特效腳本實例生成包括角色底下的IRON文字特效,光影粒子特效,速度線UI特效等,放置到相對鏡頭相機指定位置播放
  6. 線光源設置腳本修改戰鬥環境光
  7. 替換材質腳本替換伽吉魯模型材質
  8. 相機徑向模糊腳本啓用相機後處理特效

不難看出鏡頭動畫的主要邏輯是由動畫掛靠腳本實現的,主要鏡頭,角色走位調度由美術實現Animator進行控制。視鏡頭動畫的複雜效果,可能會堆多一些特寫特效腳本同步播放,豐富畫面效果。

戰鬥系統運用了幾個透視相機,按相機深度由低到高分別是:

  • 戰鬥背景圖相機
  • 戰鬥單位名字相機
  • 戰鬥UI相機
  • 戰鬥主相機
  • 戰鬥鏡頭動畫相機
  • 戰鬥鏡頭動畫UI相機

鏡頭動畫播放完,緊接着就是綠色部分腳本,配合完成技能釋放:

  1. 移動腳本控制伽吉魯跑到戰場中央
  2. 播放特效,動做腳本控制伽吉魯作出打擊表現
  3. 受擊腳本控制目標作出受擊動做,扣血反饋
  4. 震屏腳本,角色抖動腳本配合着增強打擊感
  5. 回位腳本控制伽吉魯回到本身的站點

技能釋放須要由更多的腳本組合完成,通常不須要美術產出不少資源,利用一些簡單攻擊特效,配置角色走位,動做,受擊,鏡頭控制就能作出漂亮打擊感的技能。

  • Buff表演

Buff表演相比技能表演更簡單,容易編輯,實現。每種Buff均可以分爲Buff添加,Buff持續,Buff觸發,Buff移除4個階段,視需求自由決定每一個階段是否有具體表現,Buff編輯器只需配置每一個階段的特效,人物動做,替換材質便可。下圖是反擊Buff的遊戲表現,4個階段都有特效表現。固然,也存在一些Buff是設計成徹底無表現的。

結語

至此本文就結束了,主要仍是就美術資源,資源管理,協議交互,戰鬥表演作了些介紹,內容並無涵蓋整個戰鬥系統,不過已經是戰鬥系統核心設計內容,特此記錄,也但願能提供一些經驗借鑑。

相關文章
相關標籤/搜索