前言
拋開需求
技能框架
技能框架的設計
技能系統,很容易遭到程序員、策劃、測試,甚至於玩家的挑戰。一個技能框架的可讀性、擴展性、安全性以及健壯性都是極其重要的,我們作為一個設計者,必須要把這些問題都考慮在內。作為一個綜合性系統,程序、美術、音效,甚至於策劃精妙絕倫或著是與無理取鬧的需求,我們都必須要考慮進去。
在技能系統這一塊,也許算是我做的比較多的,也是思考的最為深入的一個Gameplay的系統。
所以忍不住想寫那麼一些東西,去分享一下我的想法、以及做法。
我們仔細想想我們曾經玩過的遊戲,當然,或多或少你是玩過遊戲的,不然我很懷疑你是怎麼做到遊戲程序員的。
技能究竟是什麼?
如下圖,在一個典型的技能里,它通常都包含有動作、特效、聲效,以及本次影響某些作用,以及持續影響某些作用或表現。至於持續影響的部分,則是可能有其他系統接管的,例如:BUFF系統(見鏈接)。
為什麼我們將一個技能分成兩種部分,一是表現上的,一個是數值上的。從一個設計者的角度而言,如果這次的技能發生了,並且確確實實的命中的目標,我們就應該進行這次的結算,不管表現是否由於什麼原因進行了中斷,或者跳過。並且,如果有伺服器進行參與的時候,我們總是應該以伺服器為準。
所以,為了更好的復用我們的代碼(客戶端以及伺服器同一套代碼時,或者是跨項目間的重用),我們在設計技能系統的時候,一般都會注意,將表現的控制,以及數值處理上的控制分開(也就是所謂的邏輯與表現分離)。對於一些非常成熟的項目框架,甚至於我們可能還會在我們的技能系統上繼承於某些Interface,例如,ISubSystem(ISubSystem可能是我們的框架提供出來,用於系統級擴展的一些介面),這種設計的框架,可能會給人一種,稍微優雅的直觀感受。這時,我們可能會忍不住的Show一下自己的設計,於是搞了一個對於我們系統稍作抽象的Interface,叫IAbilitySystem(派生於ISubSystem)。然後再寫一個叫做AbilitySystem去實現我們的技能系統,然後美曰其名的笑談:以後別人看不順眼我們的AbilitySystem,就可以寫一個叫做SkillSystem的玩意去替代,反正我們外部持有的對象是ISubSystem/IAbilitySystem。
這種情況下,你的代碼固然丑(很多程序員都認為別人的代碼很醜,沒有問題吧??),但也會因為設計良好而被人點贊。
你付出的,僅僅是對你願意暴露出去的API,再在interface聲明一次。麻煩?也許吧。所以這就是設計者該去權衡的地方了,一旦不當,就成了所謂的設計過度。
在現在這種情況下,我們可以簡單的分出幾個類型了,這裡不探討設計過度與否。
把這個ISubSystem擴展開,其實就很接近我們整個Gameplay框架的一個原型了。當然這已經不是我們聊的事情了。
比如一個回合制的戰鬥系統(夢幻模擬戰),跟一個即時戰鬥的ACT戰鬥系統(戰神),不管是設計難度還是實現難度,都相差十分巨大,巨大到跟你知道的你跟馬雲的差別一樣。
所以,不管是為了效率、實現難度,我們可能會針對不同的技能應用場合,設計不同的技能系統,並選擇一個最優解。儘管他們的大體框架很接近,但是細節上的實現卻是完全顛覆的。
我們以回合制(Turn-based)的遊戲來開始我們的話題。
思考下面的技能。
火球:角色朝前方發射一個火球,火球命中目標(角色)的時候,對目標造成傷害,火球消失。
大爆炸火球:角色朝前方發射一個火球,火球命中目標(障礙、角色)的時候,造成爆炸,對周圍的目標造成傷害,並且對爆炸波及的目標,造成持續性灼燒傷害。
如果是有實現過技能系統的同學,自然很容易就知道,技能火球的實現。
我們先來看看一個「十分切題」的代碼,這裡的十分切題並不意味著是最優解。
public class Fireball : IAbility { /// <summary> /// Config { /// float velocity; // 速度 /// float radius; // 火球半徑 /// string fxName; // 特效名稱 /// //其他 /// } /// </summary> public FireBallConfig Config { get; private set; }
/// <summary> /// 技能持有者 /// </summary> public IAbilityOwner Owner { get; set; }
public void Launch(Action<FireBall, List<IAbilityTarget>> onHit) { // 執行動作表現 // 延遲 0-n秒 執行特效表現 // 延遲 0-n秒 播放飛行聲效 // 命中後回調 } }
乍看之下,這種代碼好像沒有什麼問題。但是一旦選擇這種代碼,我們再去思考一下,需求人員如何跟我們進行數據以及功能的交換,美術人員如何參與技能表現的調優?
可能造成如下結果:
一:
策劃人員維護一張超級龐大的表,然後所有的技能配置項都填在裡面,每一行就是一個技能,但是由於欄位含義,勢必會造成一行中很多空缺欄位,維護難度,增刪難度,劇增。
二:
策劃人員維護一大堆技能表,不同的技能表分別配置不同類型的技能。很大情況下,策劃人員可能後期都在找這個技能配置在哪個表中。
三:
一旦美術同學發現哪裡需要調優,我們可能要先把美術同學的資源放進來,然後經過一大堆的遊戲邏輯,終於到了可以展示我們技能的場景,好不容易等到MP滿了,我們可以釋放我們的技能看看了(別懷疑,這種做法在以前很盛行,其實更多的主要是項目一直處於所謂的緊張期,當然還有很重要的一個原因,所使用的引擎太土鱉)。
四:
它無法量產。
五:
它的命名太過「策劃化」了,如果我們要實現一模一樣的功能,但是名字叫做冰刺(Icicle)的技能,然後用了火球(Fireball)這個名字的技能,總覺得有一種莫名的喜感,就是不知道策劃同學會不會無所謂就是了。
換句話說,如果這是一個大型項目,我覺得上面的代碼完全是技能系統的外行,很有可能一開始設計這種技能的人到了後期每天會被鞭一百次。當然,對於不注重技能擴展性,或者是技能所在的比重並不大的項目而言,這完全是沒有問題的,因為這都不是重點,也不是需要量產的功能,自然也就不是問題了。將就一下,隨便就過去了。
我覺得,作為一個合格的技能體系,它應該至少符合下面的幾點要求。
1.代碼儘可能優雅的。
2.框架是可以輕易擴展的。
3.可以量產的。
4.可以遊離於遊戲之外,單獨進行重放的。
那麼,有沒有那麼一個共同的抽象,可以讓我們做到這種技能體系呢?
我們嘗試把數值邏輯與表現邏輯分開。
在表現層上,我們需要一個工具/插件,它至少符合以下幾點:
1.支持保序的表現
2.可以擴展的
3.可以同時編輯我們不同類型的Track的
4.可以離線驗證我們技能
5.可以與數值邏輯部分交換上下文的事件系統(EventSystem、EventManager)
如果你使用Unity,可以考察一下uSequencer/Timeline系統。
如果你使用UE4,毫無疑問自然要考慮的就是Blueprint了。
假設我們在與策劃人員在參與討論的時候,已經對即將要去實現的技能體系有了大概的認知。
我們可以嘗試從以下方面去考慮一下,我們的技能能不能這樣。
1.對技能的特點進行抽象,要知道,策劃人員往往並不具備技能特徵抽象的能力,或者說習慣。
2.對技能需要配置的數據項進行歸納,往往這些數據項都是需要進行隨時配置的,我們要對策劃描述中出現的數值/詞語變得敏感。比如,速度、方向、重力、加速度、大小等物理特徵,都可能需要程序人員轉化成數值配置項。
3.嘗試著將技能套入我們的技能框架。
4.與策劃人員進行反覆溝通,看看能不能儘可能的切入我們已有的技能體系,而不要做一隻特立獨行的雞。功能需求,是需要溝通的,不是需求方讓你做什麼,你就做什麼,做出來了,就覺得很厲害,做不出來就不行。
5.給你編輯好的技能,一個合適的命名。合適的技能命名會具有比較高的辨識度,可以讓你看見它的時候,一下子反應出這是什麼,而不是一個黑人問號臉。
6.你的技能應該如何設計成可以獨立運行。有時候,我們雖然知道數值邏輯與表現邏輯需要分離,但是實現起來可能並不是那麼輕鬆的,你需要絞盡腦汁的將數據項從表現系統抽離。一個最漂亮的做法無異於:我們可以在一個空場景中,載入1到N個角色,這些角色,至少包含一個是技能發起方,其他的自然是可被搜尋的對象。這個技能施展的時候,這個技能發起方可以有一些動作,動作伴隨特效,然後他往前一砍,一個特效往前飛行,碰到某個可以被搜尋的對象的時候,這個對象會被捕獲到你的技能流程裡面,交由技能流程做處理,這個技能流程可能需要進行例如這個對象是否HP為0等等一系列是否符合捕獲條件的判斷。然後,這個對象頭上可能會冒出一些傷害數值,以及受擊特效,受擊動作,如果技能可能把一個角色打飛,可能這時候還會對應不同的後續處理。
7.往往一個技能表現會有兩部分。一部分是發起者(Sponsor)的,一部分則是回應者(Responders)的。對於類似於三國無雙這種遊戲而言,可能一刀下去就是一大片的。這種情況自然也要納入考慮。
8.更複雜的一些技能系統,可能會存在著連攜技能。就是你跟另一個合作夥伴同時在使用可以共鳴的一些技能的時候,會產生出雙人組合技,這種系統就要看具體的需求了。
我們看看能不能把技能Sponsor部分按照下面的方式劃分,還有聲效部分就不寫了。
對於Responders方,我們看看能不能分成這樣一種方式。
接下來,就是對我們抽象出來的節點做一個代碼實現的事情了。
這兩部分串列起來的方式,其實就是OnHit,每次OnHit就會觸發一類受擊表現。
然而,總會出現有非常規的問題,比如,霸體的受擊表現。
這裡可能就會出現一種情況,一種Attack表現,對應N中OnHit表現了。至於飄血、數值,走的又是另一套方式,一般可能會走EventMessage事件分發機制。這樣既可以解耦,也可以在技能編輯器下免去沒有數值處理對象的尷尬。
此外,我們還應該對技能描述嘗試抽象出一個個獨立的函數(或方法),這些方法我們可以重用在其他技能的Timeline(或BP)之中,例如,發射火球,這種功能,我們可以視為「Load and fire a VFX」;受擊動作、攻擊動作,可以視為「Play animation clip」等等。而且,這個才是所有技能設計之中,最應該優先分割、考慮的。也是我們未來可以真正復用到其他項目之中的技能功能子集。
TAG:技能 | 戰鬥分析 | 遊戲設計 |