[技術交流] 手游回合制游戲戰斗機制歸納式設計

>>>  技術話題—商業文明的嶄新時代  >>> 簡體     傳統

GameRes發布,文/猴與花果山,轉載請注明作者和出處


手游的回合制游戲,我就不多介紹了,最近的《秦時明月》,早些時候的《我叫MT》等。從一個單純的策劃角度來說,你可能覺得開發這樣的游戲,為戰斗過程中加入一些有趣的buff、有趣的玩法會更有趣,但事實上在實現過程中,因為種種原因最終都沒了下文,其實核心原因很多時候是在策劃對于游戲設計的規劃上出現了問題。

首先我們來暢想一下,一個秦時明月類型的游戲,或者我叫MT類型的游戲,其中有什么背景角色,無所謂,我增加一個卡牌可以吧?一個英雄——伊夫里特(我想這個夠出名了吧,不知道就白度去吧),為了體現這個卡牌的價值,作為游戲設計師,我希望加入一些特殊功能,讓這個卡牌或者英雄與眾不同,那么如果游戲允許加入被動特技,伊夫里特的被動技能我想可以是:受到火焰傷害的時候,免疫受傷效果,將原本傷害的一半轉化為治療。這是一個很刁的效果,至于伊夫里特因此會被定義為多少星什么顏色怎么個價錢的英雄,這里不作討論,核心是,要讓這玩意兒發揮,我們還可以為游戲的戰斗加入地形影響,你可以想象,當戰斗進行到第3回合開始,場景發生了變化,開始著火了,每回合對所有角色造成40點火焰傷害,而此時你的伊夫里特,相當于每回合恢復20點生命,因為戰場的需要,讓伊夫利特再次增值。

我相信從設計的角度來說,這樣的idea是絕妙的,因為它可以讓游戲增色不少:

1,我們有了英雄的被動,讓英雄除了數字以外,更加體現出獨一無二性。
2,我們有了地形、天氣系統,讓戰斗戰場更具特色,火山口和城堡內相比,不再只是背景貼圖變化了,火山口會著火,就像城堡內會有亂箭射出。
3,英雄被動配合地形,讓養成更多的英雄有了意義。

以上這種腦袋隨便一拍就能想到的主意緣何無人能做出來呢?事實上我們真的在動手的時候,會發現一個核心的問題——當我們在服務器上作數據計算的時候,又如何讓客戶端來重演一次服務器的計算呢

事實上這個問題大家解決的辦法都是一樣的——

1,策劃歸納好戰斗中會發生的事情。
2,客戶端把這些事情都寫好,就猶如寫一段腳本一樣,然后服務器告訴客戶端發生了什么事情,客戶端去重現,一個(或多個)回合的戰斗,都能序列化為一個1維數組。

我們就拿MT來舉例:

第2步策劃工作,策劃總結出來有這樣幾個情況:

1,角色發動攻擊:造成單個角色受到傷害、死亡。
2,角色發動大招:根據大招造成若干個角色受傷、死亡。
3,角色獲勝:戰斗結束。

第2步顯而易見,我們做個簡單的演算:

1,A1角色攻擊B1角色,造成300點傷害。
2,B1角色攻擊A1角色,造成150點傷害。
3,A2角色攻擊B1角色,造成200點傷害,B1角色死亡。
4,戰斗結束。

很簡單的1維數組做到了。的確,在這種結構下,在復雜一點增加大招也沒問題,包括秦時明月也是如此。當然,我們很多策劃都能用Excel做出這樣的戰斗模擬。那么僅僅使用這樣的機制,是否能夠實現我們之前說的伊夫里特的擴展呢?我相信這個沒問題,因為那并不是很頭疼的事情,因此我們讓策劃把想法更進一步的擴展一下:

我們的策劃又設計了幾個英雄,他們的數值我就不管了,我只來說說他們的被動:

1,盾牌哥哥,被動——格檔:受到的傷害若小于300,則完全抵消。
2,騎士姐姐,被動——護衛:每回合我方后排角色受到的第一下傷害,都會被騎士姐姐援護掉,騎士姐姐受到該傷害50%的損傷。

你要知道,光是這兩個英雄的被動,是無法滿足大佬們的,因為大佬們知道,主流賺錢的卡牌游戲中,還有一個叫做“緣分”的東西,那么盾牌哥哥和騎士姐姐如果有緣我們怎么做才是有趣的?是他倆一起上互相增加20%防御力么?我覺得更好的設計是:

1,當騎士姐姐護衛盾牌哥哥的時候,若最終收到的傷害小于600將被完全抵消。
2,當盾牌哥哥在場時,騎士姐姐受到攻擊后可以攻擊3個目標,而不是單體,受到攻擊的同時會提高盾牌哥哥防御力20。

假如一個程序員并不了解我很早以前就說過的buff機制及其實現原理的話,在看到這個設定的時候,他足夠有判斷能力的話,就會想到——你是一個異想天開的設計師,和你合作的話,會有更多意想不到的效果要去實現,你有了盾牌哥哥和騎士姐姐,那一定會有想都想不到的牧師弟弟和戰士妹妹,他會果斷的告訴你這玩意兒做不了,最后你的游戲成了MT,只有你拍一我拍一,所有效果都和傷害掛鉤。

假如一個了解和熟悉我的Buff機制的人會去如何做這些邏輯呢?事實上很簡單,回合制游戲中最佳的buff回掉點就只有這么幾個:

1,回合開始時:在每個回和開始時執行,比如HoT技能、DoT技能,對于一個回合制游戲來說,回合開始時生效是最合理的(ATB游戲不在此討論范圍)。
2,角色攻擊時:當角色攻擊命中每個目標的時候回調,用于左右最后的攻擊效果,如造成爆擊時吸收造成傷害50%的血量恢復自己。
3,角色受擊時:當角色被攻擊時的回調,用于左右最后的攻擊效果,例如盾牌哥哥的,受到傷害低于300時,受到傷害=0。
4,角色擊殺前:當角色即將死亡的時候發生的事情,比如我們設計了一個技能叫手下留情,她的作用是永遠不會把目標生命打到1以下(請別在這里思考為什么設計這么一個技能)。
5,角色死亡后:當角色被擊殺時發生效果,如:每殺死一個角色獲得一層狂怒,增加傷害30%。
6,Buff結束時:在Buff結束時候做出的回調,比如:3回合后召喚隕石攻擊所有場上的角色。

事實上,Buff機制合理運用,在邏輯層上,是完美無缺的,這些效果都是輕而易舉就能實現的,但是這里還是有一個核心的問題,也是最難解決的問題——我如何將這個回合的戰斗告訴客戶端?

我們繼續演算盾牌哥哥和騎士姐姐的浪漫,他們在冒險的過程中遇到了3個怪物,史來姆A\B\C,史來姆A\B\C又都帶有特技:

史來姆A:受到傷害時有20%的幾率反彈30%的傷害量。
史來姆B:受到傷害固定為1。
史來姆C:每當一個史來姆受到攻擊的時候,疊加一層粘液,每層粘液提高自身5%行動速度,但降低5%傷害,每3層粘液可以使攻擊產生一層風怒,每層風怒可以使攻擊產生額外一擊。

戰斗開始了,會發生什么?我們大概演算幾步:

1,回合1開始,盾牌哥哥攻擊,史來姆B受到了1點傷害(582點被吸收)。史來姆C獲得了1層粘液。
2,騎士姐姐攻擊,史來姆A受到了傷害447點,史來姆A發生了反擊效果,騎士姐姐受到傷害134點,騎士姐姐得到了順勢劈的效果(下次攻擊命中3個目標),盾牌哥哥得到了提起護盾效果(防御力提高20點)。史來姆C獲得了一層粘液。
3,史來姆A攻擊,騎士姐姐護衛,受到0點傷害(177點被化解),騎士姐姐獲得了順勢劈效果,盾牌哥哥得到了提起護盾效果。
4,史來姆B攻擊,盾牌哥哥受到了0點傷害(22點被化解)。
5,史來姆C攻擊,盾牌哥哥受到了742點傷害,盾牌哥哥給跪了。
6,回合2開始,騎士姐姐攻擊,史來姆A受到了491點傷害,史來姆B受到了1點傷害(442點被吸收),史來姆C受到了222點傷害。史來姆C獲得3層粘液,史來姆C獲得一層風怒(下次攻擊2段傷害)。
7,史來姆C攻擊,騎士姐姐受到615點傷害,史來姆C風怒攻擊,騎士姐姐受到了667點傷害,騎士姐姐給跪了
10,戰斗結束,玩家戰敗。

我們從上面這段演算,可以看到這樣一個復雜的流程:


這個相比一條線下來的MT模式來說,復雜了太多太多。他完全是多條線發展的,那么我們在這個過程中再看看,我們通常所采用的那種歸納方式還有意義嗎?我們仍然可以把每一個方塊發生的事情歸納起來,因此我們有這些事件:

1,攻擊:某角色攻擊了另外一個角色。
2,反擊:某角色反擊了另外一個角色。
………………

你會發現這樣歸納,幾乎每一個方塊都是一個東西,目前只有5張卡牌,如果我們有個哥不林小分隊,天知道天才的設計師們能想出什么花招。最要命的是,這么多東西,我們讓客戶端怎么去重現呢?事實上,最困難的地方,不是將邏輯重現給客戶端,而是在邏輯的基礎上,我們還有很多表現要傳遞給客戶端,這才是最頭疼的,也許一個方塊內的事情可以歸納為:

一次攻擊:誰攻擊了誰一次,造成了傷害多少。

但事實上從一個優秀設計師的角度來說,這應該歸納的并不是一次攻擊,而應該是:

1,攻擊者發動了某個視覺特效(如果游戲規定攻擊前要播放一下)
2,攻擊者做出了攻擊動作
3,受擊者身上出現了受擊特效,受擊者同時做出了受傷動作,跳出了傷害數字。

根據這個歸納,我們就有了這么3個事情(姑且稱之為事情,Thing吧)

1,某個角色身上播放了一個特效。
2,某個角色作了某個動作。
3,某個角色身上跳出了數字。

我們看,假如你這樣歸納的話,用在其它地方是不是會更合適?我的意思是,你并不需要增加很多未知的東西,比如我們可以試試看最長的第2段:

1,騎士姐姐的攻擊:播放特效-〉攻擊動作-〉受擊動作-〉跳數字-〉受擊特效
2,史來姆C獲得Buff:播放特效-〉跳數字(確切地說是文字)
3,史來姆A反彈:播放特效-〉反彈動作-〉受傷動作-〉跳數字-〉受擊特效
4,盾牌哥哥獲得Buff:播放特效-〉跳數字
5,騎士姐姐獲得Buff:播放特效-〉跳數字
利用這樣的歸納方法,我們很容易的就把整個一段很特殊的5個事情歸納成了3個已經歸納過的動作。

在這個基礎上,我們很容易就能確定出,服務器應該如何整理這個結構用來告訴客戶端(Haxe)
首先我們需要的是一個總管,來實現上面的樹結構:

class BattleAction
{
       public function new(_thing:Dynamic)
       {
               thing = _thing;
               nextFunc = new Array<BattleAction>();
       }
       
       public var thing:Dynamic;   //一個動作的描述
       public var nextFunc:Array<BattleAction>;  //我的后續動作數組
}



值得注意的是,除了Root部分(也可以說是第一層枝)這里,我們需要按順序來執行外,其他的地方,只要是同一支展開的,都是同時執行。我們通過這樣一個結構,整理出所有的Thing,形成一棵大樹丟給客戶端。而BattleAction中的那個Thing(Dynamic),便是每個動作,還是借著剛才的舉例:

1,角色做動作
class BattleThing_ChaDoAction
{

       public function new(_chaUid:String, _actionId:Int)
       {
               chaUid = _chaUid;
               actionId = _actionId;
       }

       public var chaUid:String;  //哪個角色
       public var actionId:Int;   //什么動作
}



2,角色播放特效
class BattleThing_PlayAnimOnCha
{

       public function new(_onChaUid:String, _effectName:String, _dummyPos:Int, _playTimes:Int = 1)
       {
               onChaUid = _onChaUid;
               effectName = _effectName;
               dummyPos = _dummyPos;
               playTimes = _playTimes;
       }
       
       public var onChaUid:String; //誰身上播放
       public var effectName;       //特效名
       public var dummyPos:Int;   //綁點
       public var playTimes:Int;        //播放次數,Magic:-1為循環
}


3,跳數字
class BattleThing_PopTextOnGuy
{

       public function new(_chaUid:String, _text:String, _fontName:String)
       {
               chaUid = _chaUid;
               text = _text;
               fontName = _fontName;
       }

       public var chaUid:String;  //誰身上
       public var text:String;      //什么內容
       public var fontName:String;  //什么字體
}


當客戶端獲得這棵樹的時候,我們根據類型來解析Dynamic,然后作出不同的動作,就可以完美的重現服務器上復雜的邏輯,從而實現出Buff機制在回合制游戲中的完美表現。當然這套機制中,起到核心作用的仍然是策劃對于游戲系統、功能的歸納能力,假如一個策劃只能提需求,是做不了這種項目的



GameRes游資網 2015-08-23 08:38:50

[新一篇] 次世代:游戲機界的世界大戰

[舊一篇] 人生蠻荒記(3):識己
回頂部
寫評論


評論集


暫無評論。

稱謂:

内容:

驗證:


返回列表