关于类DOTA游戏多样化技能系统的设计思考
在游戲里,每一個人物都有很多的技能。像DOTA,英雄聯盟一樣,技能也不都是單一的直線判斷,而是有很多的花樣。這類游戲的技能系統是如何設計的呢?
這里想從自動機的角度來抽象這個問題,以期得到一個更泛化的解決方案。因為我是引擎程序員而非game play,因此可能實際解決問題未必和我的想法一樣。
首先,學習過自動機理論或者是編譯原理,又或者對游戲人工智能方面有所了解的朋友一定聽說過自動機的概念。這里我不怎么嚴謹的描述一下什么是自動機:
(1)它擁有若干個狀態(State),其中有一個狀態是初始狀態,并對應若干結束狀態;
(2)它擁有一個符號集,并接受符號集內部的符號作為輸入;
(3)它擁有若干稱之為“狀態轉移函數”的邊,這些邊連接狀態,把當前狀態和輸入的符號作為參數,并實現從一個狀態到另一個狀態的轉移。
這樣的自動機一般是叫做有窮自動機,仔細看看,是不是和我們在LOL這類技能施放類的游戲的行為很類似?
試著把自動機里的一些概念映射到游戲里:
(1)在LOL這類游戲里,的確存在很多顯式的狀態的概念,譬如說死亡,減速,暈眩,這就是玩家當前的狀態。
(2)用戶的輸入可以作為符號集,這里的用戶輸入有時候并不是按下了某個鍵這樣簡單,而是更有意義的輸入:譬如某個技能是否已經釋放到了一個人身上,又或者某個AOE技能是否與某個角色的包圍盒發生了碰撞。
(3)狀態轉移函數則是設計師定義的技能施放機制,譬如寒冰的劍射中敵人后對目標產生暈眩效果,也就是使得敵方英雄從當前狀態轉化到了暈眩狀態。
經過這樣的抽象,整個英雄技能的施放就很有條理了:每個角色都有一個唯一的狀態機,它的當前狀態可以通過雙方玩家的輸入而發生轉移,轉移的規則作為一個個單獨的狀態轉移函數由策劃配表完成。
我們以LOL的幾個技能施放為例子,看看如何設計針對英雄自身的自動機。
先看光輝女郎LUX的被動技能的觸發機制:首先,敵方英雄當前狀態是正常狀態,接著LUX施放了一個傷害技能且該技能擊中了敵方英雄(也即是一個有效輸入),于是根據狀態轉移,這個技能使得敵方英雄掉血并且處于一個被標記的狀態,緊接著,LUX平A了敵方英雄一次,于是又有一個狀態轉移函數,使得敵方英雄在被標記的情況下掉了額外的生命值,同時使得該英雄重新回到了正常狀態。這是一個非常典型的一次技能判斷機制。
但這里就存在一個問題,有朋友就會說,這樣簡單的輸入->反饋式的邏輯誰不會寫啊,引入所謂的自動機有什么用?真實的MOBA類游戲里,很多技能的施放和產生的效果并不是僅僅由用戶的一個輸入行為決定,甚至不是一個狀態決定,一個結果或者一個狀態轉移很可能是由多個角色身上的已知狀態以及多個用戶的同時輸入來決定的,如何引入多輸入或者多狀態的狀態轉移函數呢?
我們再引入一類更為復雜的自動機,這類自動機叫做下推自動機。下推自動機和有窮自動機幾乎一樣,唯一的不同是,下推自動機除了狀態,輸入,和狀態轉移函數以外,還包含一個可操作的數據棧。這個數據棧的操作可由狀態轉移函數來進行操作,狀態轉移函數可以把用戶之前的輸入壓入棧里或者從棧里取出來使用。這樣,用戶先前的一些狀態就能夠被記錄下來而不用使用組合來生成新的狀態。
比如有一個技能,當施放時,如果敵方,同時處于暈眩+減速+血量低于百分之五十,就對敵人造成一擊必殺(死亡狀態),如果是先前的有窮自動機,我們要表示這樣一個狀態,可能需要重新定義一個新的狀態:X英雄一擊必殺的狀態(也就是暈眩+減速+血量低于百分之五十),一旦隨著游戲邏輯的復雜,這樣的狀態將會呈幾何級數增加。反過來,如果是使用下推自動機,我們則不需要定義新的狀態,只要有暈眩,減速和血量低于百分之五十這三個單獨的狀態,然后當暈眩發生時,狀態轉移函數從正常狀態轉移到暈眩,同時把暈眩狀態作為一個輸入壓入到數據棧里,緊接著,又發生了減速,那么從暈眩轉移到減速,再把減速壓入到數據棧里,此時血量低于百分之五十的話,可以再讓用戶進入血量低于百分之五十的狀態。當處于這一狀態時,再接受X英雄放了大招,這時候狀態轉移函數以X的大招作為輸入,當前處于血量低于百分之五十的狀態,同時從當前數據棧里找到了另外兩個必要條件:暈眩和減速,那么以此為依據,讓敵方英雄進入死亡狀態。
同時,我們的狀態轉移附加的事(比如施加暈眩狀態,手機游戲或者使其進入血量低于百分之五十),這可以抽象成一系列的原子行為被附著在狀態轉移函數中,而狀態轉移函數則負責依次觸發這類行為,而有些行為我們可以認為是同步的,有些則是異步的。譬如三秒的暈眩可以理解為一個異步操作,它分兩個階段執行,第一階段是開始時施加暈眩,第二階段是結束時取消暈眩,這類異步操作就進一步化為了兩個原子操作。狀態轉移函數的目的就變成了調度一系列行為的觸發,同時轉換(Switch)當前的狀態。
所以到目前為止,我們抽象出的結構有這么幾個:狀態,行為,輸入,狀態轉移函數(一系列的行為調度+一個狀態的切換),它的拓撲結構和圖的類型非常接近,因此可以讓策劃用配表的形式把這種圖的給表示出來并簡單的存儲在一個XML文件里。程序要做的事情則是通過策劃配表得到的XML文件重建這個設計好的自動機,并把相應輸入link到這個自動機的輸入端,同時把行為綁定為一些固化的執行函數。
這是一個比較基礎的玩法,如果想要玩的更復雜,也有一些腦洞可以開,比如說,程序員都聽說過正則表達式,以及可能不一定聽說過的上下文無關文法,這類表示方法能夠非常簡潔有效地表示一個技能的觸發規則,如果你的策劃能夠了解和熟悉這樣優雅的規則表示法,并且能夠把他想要設計的技能或者英雄用一個上下文無關文法或者正則表達式來表示出來,同時你又有足夠的能力設計出這樣的語法解析器,那么你完全可以把一個技能施放系統當做一個編譯器的前端解析器來實現。同時引入一些邏輯操作符來豐富規則,譬如與(AND)或(OR),或者閉包操作等。
除過這里介紹的自動機,實現類似行為的另一個常用結構是所謂行為樹(Behavior Tree),但從我的理解來看,這和編譯原理中講到的語法樹沒有太多不同。
所以其實現在很多游戲中需要考慮和解決的問題,在經典的算法和基礎計算機理論層面已經有很多聰明的先驅提出了很多優雅的算法,往往通過合理的抽象我們都能把游戲中的問題規約為這類經典問題來解決,而很多國內的game play程序員喜歡發明些trick來臨時解決,最后的結果往往就是補丁打補丁,代碼可讀性極差,還是因為忽略基礎理論和基本功不扎實。
總結
以上是生活随笔為你收集整理的关于类DOTA游戏多样化技能系统的设计思考的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 游戏中常用的寻路算法(5)预先计算好的路
- 下一篇: 使用行动列表去创造简单且可扩展的游戏AI