在 2D 横向卷轴游戏里上下楼梯
在 2D 橫向卷軸動(dòng)作類或冒險(xiǎn)類游戲中,其實(shí)“樓梯”并沒有想像中的常見,特別是強(qiáng)調(diào)平臺(tái)跳躍的 2D 游戲中通常完全不會(huì)出現(xiàn)樓梯。其中一個(gè)重要原因,就是樓梯相關(guān)的人物動(dòng)態(tài)實(shí)作起來并沒有想像中的簡單;但萬一游戲規(guī)劃上或場景的合理性上需要大量使用樓梯系統(tǒng)呢?德國獨(dú)立游戲團(tuán)隊(duì) DigiTales 的 Julian 前陣子分享了一篇這樣的文章,我們?nèi)〉梅g授權(quán)整理如下,供有需要的開發(fā)者參考。
前提
上下樓梯功能長久以來對(duì)我來說猶如芒刺在背。我們?cè)?2017 下半年時(shí)首度把這功能實(shí)作進(jìn)當(dāng)時(shí)的原型里,而這些程式碼直到前一陣子都沒有什么改變。就算以功能原型的角度來看,那個(gè)實(shí)作僅勉強(qiáng)堪用,當(dāng)然也不該保留到游戲上市版本之中。
不過呢,正因?yàn)樗斐稍S多程式臭蟲并凸顯出了各種容易踩到的坑,我現(xiàn)在可以跟各位談?wù)勗谠O(shè)計(jì)自己的系統(tǒng)時(shí),該注意哪些地方。我說“設(shè)計(jì)”是因?yàn)槲視?huì)著重在游戲設(shè)計(jì)的部份,關(guān)于程式怎么寫只會(huì)大概提一下概念,因此就不把實(shí)際的程式碼拿出來講了。不然這篇本來就已經(jīng)夠長的文章包準(zhǔn)會(huì)一發(fā)不可收拾。
首先我要先快速說明我們移動(dòng)系統(tǒng)的重點(diǎn)功能。如果你的需求完全不一樣,那接下來的解決方案可能對(duì)你不太適用。
?
- 游戲本身是人物可以行走和沖刺的?2D 橫向卷軸,不能跳躍和奔跑,也就是說人物只能從底端和頂端兩處走進(jìn)樓梯。
- 在樓梯移動(dòng)時(shí)有專屬的動(dòng)畫,不沿用平時(shí)的水平行走和沖刺。
- 樓梯有兩種:正向梯(直立)和側(cè)向梯(對(duì)角方向)。后者可再分為“左上至右下”與“左下至右上”兩種方向。
- 樓梯每一階的大小相同,以配合各種玩家角色動(dòng)畫。樓梯長度不拘。
- 人物模組的碰撞區(qū)(與樓梯動(dòng)作有關(guān))在大腿附近。不過從概念上來說,這并不是什么必要的條件。
- 沒有戰(zhàn)斗和其他因素會(huì)對(duì)在樓梯上移動(dòng)的人物施加外力。
- 設(shè)計(jì)目標(biāo)是為了做出直觀、不易出錯(cuò)又賞心悅目的樓梯動(dòng)作。
了解所有前提之后,我們這就來開始疊代,把冒出來的問題一個(gè)個(gè)解決掉吧!首先從側(cè)向梯(對(duì)角方向)開始。各位之后會(huì)發(fā)現(xiàn)我們即將設(shè)下的原則大多也適用于正向梯(直立)。
第一步:走上樓梯
首要之務(wù)就是讓人物登上樓梯。我們先做出非常基本的第一代樓梯:當(dāng)玩家即將通過樓梯時(shí),會(huì)先觸發(fā)一個(gè)碰撞區(qū),將人物切換為樓梯上的操控方式(我們稱之為“穿過檢查點(diǎn)”,穿過一詞與后面需要的功能相關(guān),詳后述)。樓梯的另一端則有另一個(gè)檢查點(diǎn)切換回一般操控。
?
第一個(gè)要解決的問題乍看之下可能覺得很難發(fā)生,但實(shí)際上相當(dāng)重要,需要及早考慮:人物碰撞區(qū)可能不會(huì)在正確的時(shí)機(jī)觸發(fā)檢查點(diǎn),尤其在低影格率、而玩家移動(dòng)速度又和影格率脫鉤的時(shí)候(當(dāng)然,游戲邏輯與影格率理應(yīng)脫鉤)。當(dāng)影格率比較低的時(shí)候,每張畫面之間的移動(dòng)距離就會(huì)變大,而這個(gè)距離越大,玩家人物就越容易沖過檢查點(diǎn)。
在影格率高的情況下,人物會(huì)逐漸接近并在適當(dāng)距離觸發(fā)檢查點(diǎn):
?
然而在低影格率時(shí),可能會(huì)造成人物碰撞區(qū)的尾巴去觸發(fā)檢查點(diǎn):
?
這會(huì)導(dǎo)致行走動(dòng)畫和樓梯圖像不同調(diào)。因此我們?cè)诘诙鷺翘葜?#xff0c;必須要從接下來的兩種解法中擇一實(shí)作:
?
- 單純地在開始或結(jié)束樓梯上移動(dòng)的瞬間,將玩家人物傳送到正確的“進(jìn)入點(diǎn)”或“離開點(diǎn)”位置。因?yàn)閭魉偷木嚯x只會(huì)在低影格率的情況下才比較大,當(dāng)游戲已經(jīng)執(zhí)行不太順暢的時(shí)候,這個(gè)傳送效果也不會(huì)太突兀。
- 讓物理運(yùn)算與畫面繪制脫鉤,這樣自然就不會(huì)遇到影格率問題,因?yàn)榕鲎驳倪壿嬇c運(yùn)算與畫面更新切開了。如果你使用 Unity,FixedUpdate()?就可以處理這個(gè)問題,它每秒都會(huì)執(zhí)行固定的次數(shù),而不受影格率影響(預(yù)設(shè)為每秒 50 次,這個(gè)值可以調(diào)整)。
不論你使用兩者中哪個(gè)方法(第二個(gè)比較推薦),在本文中會(huì)保持使用“進(jìn)入點(diǎn)”與“離開點(diǎn)”這兩個(gè)用詞,畢竟不論方法為何,它們?cè)诟拍钌先匀皇窍嗤ǖ摹?/span>
?
現(xiàn)在讓我們來討論下一個(gè)比較明顯的問題。要是玩家想要能穿過樓梯呢?我們的設(shè)計(jì)到目前為止,人物一走到碰撞區(qū)就會(huì)走進(jìn)樓梯。
第三代解決這個(gè)問題的做法,是在樓梯入口定義一個(gè)不同的碰撞區(qū)(稱為“進(jìn)入范圍”)。只有在樓梯底端按住方向鍵上或在頂端按住方向鍵下,人物才會(huì)開始上下樓梯。左右移動(dòng)只會(huì)讓人物穿過進(jìn)入點(diǎn),繼續(xù)一般的水平移動(dòng)。
現(xiàn)在問題變成了一旦玩家在進(jìn)入范圍內(nèi)按住上或下,人物就會(huì)在當(dāng)下所在位置直接開始進(jìn)行上下樓梯的動(dòng)作,而非從確切的進(jìn)入點(diǎn)開始。就算他們先傳送到進(jìn)入點(diǎn)(我們?cè)诘诙凶隽诉@個(gè)功能),也會(huì)因?yàn)檫M(jìn)入范圍太寬,而使傳送效果過于顯目:
?
為了解決這個(gè)問題,我們?cè)诘谒拇屚婕矣谶M(jìn)入范圍內(nèi)按住上或下時(shí),人物會(huì)走到實(shí)際的進(jìn)入點(diǎn)。例如當(dāng)人物在樓梯底端入口的右側(cè)時(shí),按住上會(huì)先讓人物走到進(jìn)入點(diǎn),然后才上樓梯。為了這個(gè)目的,我們需要增加一些只有“玩家人物在進(jìn)入范圍內(nèi)”條件成立時(shí),才會(huì)有作用的額外操作方式。
?
當(dāng)然,現(xiàn)在我們不再需要傳送了,但也正因?yàn)橥婕铱梢源┻^樓梯,就導(dǎo)致從另一方向接近樓梯進(jìn)入點(diǎn)時(shí),人物從一般走路動(dòng)畫切換到上樓梯動(dòng)畫不自然的問題。所以到了第五代,我們把進(jìn)入范圍和穿過檢查點(diǎn)結(jié)合起來!檢查點(diǎn)需要更新成能視玩家人物接近的方向來調(diào)整位置,好讓人物不論從哪邊接近,都能在恰好的位置觸發(fā)檢查點(diǎn)。
總合運(yùn)作起來如下:若玩家在進(jìn)入范圍內(nèi)按住上/下,人物會(huì)走向進(jìn)入點(diǎn),若在人物碰撞區(qū)碰到檢查點(diǎn)的時(shí)候,方向鍵仍是按住的,就會(huì)開始進(jìn)入到在樓梯上移動(dòng)的狀態(tài)。
?
注:穿過檢查點(diǎn)隨時(shí)都會(huì)依據(jù)玩家接近的方向,切換到與玩家相對(duì)的那一側(cè),才能讓人物的碰撞區(qū)與檢查點(diǎn)在完全正確的時(shí)機(jī)接觸,并開始在樓梯上的移動(dòng)。
現(xiàn)在玩家可以穿過每一個(gè)樓梯的進(jìn)入點(diǎn)了。但要是上下樓梯是繼續(xù)向左或向右的唯一途徑呢?如果我們有時(shí)候就是要第一代的運(yùn)作方式,強(qiáng)迫朝著樓梯按住左或右的玩家進(jìn)入樓梯呢?依照你的關(guān)卡編排方式,某些樓梯可能是朝某方向前進(jìn)的唯一通路,但其他樓梯僅是分支的通路。
因此,我們?cè)诘诹殖隽藘煞N樓梯進(jìn)入點(diǎn)(“可穿過”與“不可穿過”)。為此我們加入了一個(gè)叫做“可穿過”的布林值,供我們個(gè)別套用在每一道樓梯物件的兩端上。若某個(gè)進(jìn)入點(diǎn)為不可穿過,那么穿過檢查點(diǎn)就不會(huì)動(dòng)態(tài)換邊,因?yàn)樵撨M(jìn)入點(diǎn)只能從一邊接近。
新增這項(xiàng)設(shè)計(jì)后,我們必須將玩家在意圖進(jìn)入不同類型的樓梯時(shí),可能會(huì)使用的按鍵和按鍵組合全都納入考量。在樓梯底端按上,和在樓梯頂端按下,至此已都有預(yù)期中的效果了。不過,當(dāng)從左側(cè)走向不可穿過的樓梯時(shí),“按住右”也會(huì)是玩家表達(dá)進(jìn)入樓梯意圖的行為(反之亦然),因?yàn)檫@種樓梯不能穿過。現(xiàn)在當(dāng)玩家觸發(fā)穿過檢查點(diǎn)的時(shí)候,只要按住下面?zhèn)€別狀況中的特定按鍵時(shí),人物就會(huì)走上樓梯:
?
這操作起來可能已經(jīng)感覺不差了,但根據(jù)玩家操控的實(shí)作方式,如果讓玩家在走上樓梯時(shí)有好幾種按鍵組合可按,那這又會(huì)衍生出另一類的問題了。
為了要在第七代搞定這些事情,我們必須在不同類型的樓梯進(jìn)入點(diǎn)處定義哪些按鍵會(huì)互相取消,還有哪些按鍵一起按不能把移動(dòng)速度疊加上去。我們也必須一個(gè)個(gè)設(shè)定動(dòng)畫,如果有兩個(gè)按鍵會(huì)互相取消(例如玩家同時(shí)按住左和右),那不該造成人物模型原地走動(dòng)。為了解決所有可能的問題,關(guān)鍵就在于區(qū)分上行梯(左下到右上)和下行梯(左上到右下)。舉例來說吧,若QQ號(hào)買賣平臺(tái)玩家在下行梯的范圍內(nèi)按住左(遠(yuǎn)離樓梯)和下(在本例中視同走向樓梯),可能就會(huì)導(dǎo)致“原地踏步”的動(dòng)畫。又或者他們會(huì)為了移動(dòng)速度加倍而按住下(走向樓梯)和右(也是走向樓梯)。我們必須將所有類型樓梯可能會(huì)遇上的所有按鍵組合都想清楚,才不會(huì)捅出麻煩來。也許各位已經(jīng)自行想出了能解決這個(gè)問題的精妙系統(tǒng),不然就和我一樣,得在實(shí)際的移動(dòng)邏輯之外手動(dòng)定義一籮筐的布林組合。
我呼吁各位千萬不要在這個(gè)步驟偷懶,一定要算到所有小細(xì)節(jié)。這不只能避免出錯(cuò),也能讓移動(dòng)操控變得更直觀。
好啦。走上側(cè)向梯的部分搞定了。那正向梯的部分有什么特殊規(guī)則嗎?
其實(shí)我們只需要加入一些小調(diào)整,就可以在第八代加入正向梯了,因?yàn)榛镜倪壿嫸纪ㄓ谩T谖覀兊挠螒蛑?#xff0c;做法是讓正向梯只有中間一小段寬度可以使用,而非整個(gè)樓梯面。因此,它們有兩個(gè)位置明確的進(jìn)入點(diǎn),就和側(cè)向梯一樣。這也代表玩家隨時(shí)可以穿過前梯,意即它們的頂端和底端都是可穿過的,因?yàn)闃翘輬D像的寬度必定比進(jìn)入范圍還寬。當(dāng)然了,在某些情況下玩家可能穿過正向梯后就立刻遇到墻壁而無法前進(jìn)。其余部分(走到樓梯上的行為)運(yùn)作模式如出一轍。更棒的是,我們不需要去顧慮玩家按住左 / 右來嘗試走上正向梯的狀況。
?
第二步:樓梯上的走動(dòng)
恭喜你走到這一步了,值得掌聲鼓勵(lì)一下。我發(fā)誓,進(jìn)入樓梯是這個(gè)系統(tǒng)最復(fù)雜的部分。現(xiàn)在我們就來定義在各類樓梯上的移動(dòng)吧。
根據(jù)你游戲的設(shè)定(例如你導(dǎo)入寫實(shí)的物理和重力),以垂直方式或?qū)蔷€方式向上移動(dòng)可能會(huì)造成一些問題,比如說你的人物從上方一走進(jìn)樓梯就會(huì)滑下去。因此到了第九代,我們必須強(qiáng)迫人物在樓梯上貼著一直線線段來移動(dòng),而不使用物理碰撞來決定人物的移動(dòng)或站立的位置。事實(shí)上,我們需要在人物進(jìn)入樓梯時(shí)取消所有作用中的力,同時(shí)忽略所有在樓梯上期間被施加的力。好在我們的游戲不用考慮戰(zhàn)斗,或其他會(huì)對(duì)人物在樓梯上移動(dòng)時(shí)施加外力的因素。
但我們要怎么確保玩家移動(dòng)的角度一定能走到底端或頂端?我們可以計(jì)算頂端和底端之間的角度,不過既然為了配合人物動(dòng)畫,樓梯的每一階都已經(jīng)一樣大了,所以不管是長是短,我們的側(cè)向梯都會(huì)是同樣的角度,那么我們只要寫死就行了。
?
好啦,那我們的移動(dòng)操控要怎么在樓梯上運(yùn)作?就到第十代來設(shè)計(jì)一下吧。玩家會(huì)預(yù)期上、右和上+右都應(yīng)該要能走上樓(下樓梯時(shí)則相反)。記得不能疊加速度。
?
再一次地,我們也得考慮會(huì)互消的按鍵組合。例如上樓的時(shí)候,左和下是一樣的,都應(yīng)該要能取消視為同樣的上和右。
?
那正向梯上的操控方式呢?就靠第十一代來搞定!
各位可能覺得按上和下不就沒事了嗎。然而迫使玩家要先按住上或下走到樓梯最末端后,才能左右移動(dòng),不僅反直覺,也讓人感覺游戲不精致。玩家也可能會(huì)想,為什么在離頂端或底端還差 1 像素的地方按下左或右,卻是一點(diǎn)反應(yīng)也沒有。因此我們必須分別在底部和頂部 1 公尺的地方定義“離開范圍”。玩家在離開范圍內(nèi)按住左或右就會(huì)朝著樓梯的“離開點(diǎn)”移動(dòng)。在抵達(dá)離開點(diǎn)后,他們就會(huì)走出樓梯并繼續(xù)朝著他們按住的方向走過去。也許各位注意到了,這就和進(jìn)入范圍運(yùn)作的方式完全一樣,讓玩家得以使用另外的按鍵走向進(jìn)入點(diǎn)(也就是我們?cè)诤芮懊娴谌偷谒拇尤氲墓δ?#xff09;。還有,我們得考慮會(huì)互相取消或疊加速度的按鍵組合。
?
加入離開范圍后感覺精致了許多,但這樣做也另外產(chǎn)生了一些問題,必須使用第十二代來解決。舉例來說,如果前梯只有 2 公尺或是更短,那玩家同時(shí)處于頂端和底端的離開范圍怎么辦?那就把離開范圍設(shè)為樓梯總長的 20%,不要寫死成 1 公尺。
?
這樣解決了樓梯短的問題,但樓梯很長的時(shí)候又出了新問題,因?yàn)?20% 的離開范圍實(shí)在太長了。所以我們更新了十二代的規(guī)則,在第十三代中限制兩端離開范圍最長各為 1 公尺。
?
第三步:離開樓梯
就快完成了!現(xiàn)在進(jìn)入樓梯和沿著樓梯走都很順利,離開樓梯其實(shí)也滿簡單的。
至前述設(shè)計(jì)為止,我們的人物會(huì)一直延續(xù)樓梯上的移動(dòng),直到永遠(yuǎn):
?
在第十四代,我們?cè)O(shè)置了碰撞區(qū)讓玩家觸發(fā)的時(shí)候可以離開樓梯(也就是回到正常的行走操控,重新施加物理計(jì)算)。我們其實(shí)重復(fù)利用了頂端和底端的進(jìn)出范圍碰撞區(qū):
?
唯一的問題在于碰撞區(qū)過早觸發(fā),在頂端的時(shí)候尤其嚴(yán)重,這是因?yàn)槿宋锱鲎矃^(qū)高度的關(guān)系。我們把頂端的碰撞區(qū)上挪至足夠高度、也將底端碰撞區(qū)下移一點(diǎn),讓人物上下樓梯到最末端的時(shí)候正好在碰撞區(qū)里面。
?
可是,玩家在進(jìn)入樓梯的過程中,人物還是在碰撞區(qū)里,那他們可以決定不要上樓梯,在不再次觸發(fā)碰撞區(qū)的情況下穿過離開點(diǎn)嗎?當(dāng)然可以。在第十五代中,我們用和進(jìn)入樓梯同樣的方式來解決:我們替所有例子(側(cè)向下梯/側(cè)向上梯/正向梯)一個(gè)一個(gè)定義離開樓梯的意圖,如同第六代那樣。當(dāng)玩家在離開范圍內(nèi)按下任一個(gè)合理的按鍵組合,他們就會(huì)離開樓梯。
到了第十六代,我們?cè)俅慰紤]低影格率時(shí),可能會(huì)導(dǎo)致玩家沖過離開點(diǎn)的情況。如果你已經(jīng)使用第二代時(shí)提過的,與影格率脫鉤的物理系統(tǒng),這就不會(huì)是問題。若否,我們將人物傳送到離開點(diǎn),就如同傳送到進(jìn)入點(diǎn)那樣:
?
大功告成了!只需少少的十六次疊代,我們就成功做出了直觀、不易出錯(cuò)又賞心悅目的樓梯動(dòng)作。
在影片錄制后,我們又稍微改善了這個(gè)系統(tǒng)。我們改成使用 FixedUpdate() 而非較容易被注意到的傳送方式,并新增了在正向梯上站著不動(dòng)的閑置動(dòng)畫,它會(huì)根據(jù)先前移動(dòng)方向而有所變化(面向或背對(duì)玩家視點(diǎn))。
當(dāng)然我們也很清楚,和那些程式大佬以 AI 為基礎(chǔ)所開發(fā)的 3D 復(fù)合地形移動(dòng)系統(tǒng)比起來,這種移動(dòng)系統(tǒng)根本就是小巫見大巫。不過這套系統(tǒng)我們用起來很順,應(yīng)該也能輕松在任何類似的 2D 橫向卷軸游戲中實(shí)作出來。各位若是覺得它還能更簡化、更強(qiáng)化、不管什么化之類的,都?xì)g迎提出來和我們交流交流。我們很樂意改善我們的程式和這篇指南,為其他開發(fā)者提供助益。
希望這篇文章有寓教于樂的功用,或至少能點(diǎn)出一個(gè)道理:看似平淡無奇的機(jī)制實(shí)作起來可能相當(dāng)棘手。
總結(jié)
以上是生活随笔為你收集整理的在 2D 横向卷轴游戏里上下楼梯的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《堡垒之夜》中你可能没注意到的设计
- 下一篇: 近乎于“妖”!浅谈《山海镜花》的美术风格