您在這裡

在 2D 橫向捲軸遊戲裡上下樓梯

1 篇文章 / 0 新
在 2D 橫向捲軸遊戲裡上下樓梯

在 2D 橫向捲軸動作類或冒險類遊戲中,其實「樓梯」並沒有想像中的常見,特別是強調平台跳躍的 2D 遊戲中通常完全不會出現樓梯。其中一個重要原因,就是樓梯相關的人物動態實作起來並沒有想像中的簡單;但萬一遊戲規劃上或場景的合理性上需要大量使用樓梯系統呢?德國獨立遊戲團隊 DigiTales 的 Julian 前陣子分享了一篇這樣的文章,我們取得翻譯授權整理如下,供有需要的開發者參考。

文章原作:Julian Colbus
原文連結:http://digitales.games/blog/tips/stairs-movement
遊戲連結:https://store.steampowered.com/app/1364100/Lacuna__A_SciFiNoirAdventure/

※ ※ ※

前提

 

上下樓梯功能長久以來對我來說猶如芒刺在背。我們在 2017 下半年時首度把這功能實作進當時的原型裡,而這些程式碼直到前一陣子都沒有什麼改變。就算以功能原型的角度來看,那個實作僅勉強堪用,當然也不該保留到遊戲上市版本之中。

不過呢,正因為它造成許多程式臭蟲並凸顯出了各種容易踩到的坑,我現在可以跟各位談談在設計自己的系統時,該注意哪些地方。我說「設計」是因為我會著重在遊戲設計的部份,關於程式怎麼寫只會大概提一下概念,因此就不把實際的程式碼拿出來講了。不然這篇本來就已經夠長的文章包準會一發不可收拾。

首先我要先快速說明我們移動系統的重點功能。如果你的需求完全不一樣,那接下來的解決方案可能對你不太適用。

  1. 遊戲本身是人物可以行走和衝刺2D 橫向捲軸不能跳躍和奔跑,也就是說人物只能從底端和頂端兩處走進樓梯。
  2. 在樓梯移動時有專屬的動畫,不沿用平時的水平行走和衝刺。
  3. 樓梯有兩種:正向梯(直立)和側向梯(對角方向)。後者可再分為「左上至右下」與「左下至右上」兩種方向。
  4. 樓梯每一階的大小相同,以配合各種玩家角色動畫。樓梯長度不拘。
  5. 人物模組的碰撞區(與樓梯動作有關)在大腿附近。不過從概念上來說,這並不是什麼必要的條件。
  6. 沒有戰鬥和其他因素會對在樓梯上移動的人物施加外力。
  7. 設計目標是為了做出直觀不易出錯又賞心悅目的樓梯動作。

瞭解所有前提之後,我們這就來開始疊代,把冒出來的問題一個個解決掉吧!首先從側向梯(對角方向)開始。各位之後會發現我們即將設下的原則大多也適用於正向梯(直立)。

 

第一部:走上樓梯

 

首要之務就是讓人物登上樓梯。我們先做出非常基本的第一代樓梯:當玩家即將通過樓梯時,會先觸發一個碰撞區,將人物切換為樓梯上的操控方式(我們稱之為「穿過檢查點」,穿過一詞與後面需要的功能相關,詳後述)。樓梯的另一端則有另一個檢查點切換回一般操控。


人物的碰撞區為綠色,穿過檢查點為淺藍色。

第一個要解決的問題乍看之下可能覺得很難發生,但實際上相當重要,需要及早考慮:人物碰撞區可能不會在正確的時機觸發檢查點,尤其在低影格率、而玩家移動速度又和影格率脫鉤的時候(當然,遊戲邏輯與影格率理應脫鉤)。當影格率比較低的時候,每張畫面之間的移動距離就會變大,而這個距離越大,玩家人物就越容易衝過檢查點。

在影格率高的情況下,人物會逐漸接近並在適當距離觸發檢查點:

然而在低影格率時,可能會造成人物碰撞區的尾巴去觸發檢查點:

這會導致行走動畫和樓梯圖像不同調。因此我們在第二代樓梯中,必須要從接下來的兩種解法中擇一實作:

  1. 單純地在開始或結束樓梯上移動的瞬間,將玩家人物傳送到正確的「進入點」或「離開點」位置。因為傳送的距離只會在低影格率的情況下才比較大,當遊戲已經執行不太順暢的時候,這個傳送效果也不會太突兀。
  2. 讓物理運算與畫面繪製脫鉤,這樣自然就不會遇到影格率問題,因為碰撞的邏輯與運算與畫面更新切開了。如果你使用 Unity,FixedUpdate() 就可以處理這個問題,它每秒都會執行固定的次數,而不受影格率影響(預設為每秒 50 次,這個值可以調整)。

不論你使用兩者中哪個方法(第二個比較推薦),在本文中會保持使用「進入點」與「離開點」這兩個用詞,畢竟不論方法為何,它們在概念上仍然是相通的。


此處箭頭表示在兩連續影格之間的物體移動

現在讓我們來討論下一個比較明顯的問題。要是玩家想要能穿過樓梯呢?我們的設計到目前為止,人物一走到碰撞區就會走進樓梯。

第三代解決這個問題的做法,是在樓梯入口定義一個不同的碰撞區(稱為「進入範圍」)。只有在樓梯底端按住方向鍵上或在頂端按住方向鍵下,人物才會開始上下樓梯。左右移動只會讓人物穿過進入點,繼續一般的水平移動。

現在問題變成了一旦玩家在進入範圍內按住上或下,人物就會在當下所在位置直接開始進行上下樓梯的動作,而非從確切的進入點開始。就算他們先傳送到進入點(我們在第二代中做了這個功能),也會因為進入範圍太寬,而使傳送效果過於顯目:

為了解決這個問題,我們在第四代讓玩家於進入範圍內按住上或下時,人物會走到實際的進入點。例如當人物在樓梯底端入口的右側時,按住上會先讓人物走到進入點,然後才上樓梯。為了這個目的,我們需要增加一些只有「玩家人物在進入範圍內」條件成立時,才會有作用的額外操作方式。


圖內的箭頭顯示玩家目前按下的按鍵

當然,現在我們不再需要傳送了,但也正因為玩家可以穿過樓梯,就導致從另一方向接近樓梯進入點時,人物從一般走路動畫切換到上樓梯動畫不自然的問題。所以到了第五代,我們把進入範圍和穿過檢查點結合起來!檢查點需要更新成能視玩家人物接近的方向來調整位置,好讓人物不論從哪邊接近,都能在恰好的位置觸發檢查點。

總合運作起來如下:若玩家在進入範圍內按住上/下,人物會走向進入點,若在人物碰撞區碰到檢查點的時候,方向鍵仍是按住的,就會開始進入到在樓梯上移動的狀態。


註:穿過檢查點隨時都會依據玩家接近的方向,切換到與玩家相對的那一側,才能讓人物的碰撞區與檢查點在完全正確的時機接觸,並開始在樓梯上的移動。

現在玩家可以穿過每一個樓梯的進入點了。但要是上下樓梯是繼續向左或向右的唯一途徑呢?如果我們有時候就是要第一代的運作方式,強迫朝著樓梯按住左或右的玩家進入樓梯呢?依照你的關卡編排方式,某些樓梯可能是朝某方向前進的唯一通路,但其他樓梯僅是分支的通路。

因此,我們在第六代分出了兩種樓梯進入點(「可穿過」與「不可穿過」)。為此我們加入了一個叫做「可穿過」的布林值,供我們個別套用在每一道樓梯物件的兩端上。若某個進入點為不可穿過,那麼穿過檢查點就不會動態換邊,因為該進入點只能從一邊接近。

新增這項設計後,我們必須將玩家在意圖進入不同類型的樓梯時,可能會使用的按鍵和按鍵組合全都納入考量。在樓梯底端按上,和在樓梯頂端按下,至此已都有預期中的效果了。不過,當從左側走向不可穿過的樓梯時,「按住右」也會是玩家表達進入樓梯意圖的行為(反之亦然),因為這種樓梯不能穿過。現在當玩家觸發穿過檢查點的時候,只要按住下面個別狀況中的特定按鍵時,人物就會走上樓梯:


此例中的樓梯若是可穿過,玩家就必須按住上才會進入樓梯,或是按左/右穿過樓梯。


在可穿過的樓梯頂端也是如此,按住下進入,按住左 / 右是穿過。


如果是不可穿過,那按住右和上都會視為玩家有上樓梯的意圖。


在不可穿過的樓梯頂端同理,按住左和下都會走下樓梯。

這操作起來可能已經感覺不差了,但根據玩家操控的實作方式,如果讓玩家在走上樓梯時有好幾種按鍵組合可按,那這又會衍生出另一類的問題了。

為了要在第七代搞定這些事情,我們必須在不同類型的樓梯進入點處定義哪些按鍵會互相取消,還有哪些按鍵一起按不能把移動速度疊加上去。我們也必須一個個設定動畫,如果有兩個按鍵會互相取消(例如玩家同時按住左和右),那不該造成人物模型原地走動。為了解決所有可能的問題,關鍵就在於區分上行梯(左下到右上)和下行梯(左上到右下)。舉例來說吧,若玩家在下行梯的範圍內按住左(遠離樓梯)和下(在本例中視同走向樓梯),可能就會導致「原地踏步」的動畫。又或者他們會為了移動速度加倍而按住下(走向樓梯)和右(也是走向樓梯)。我們必須將所有類型樓梯可能會遇上的所有按鍵組合都想清楚,才不會捅出麻煩來。也許各位已經自行想出了能解決這個問題的精妙系統,不然就和我一樣,得在實際的移動邏輯之外手動定義一籮筐的布林組合。


在這情況中,按住左、按住下、還有按住左+下都應該要有同樣的結果。


下+右和左+右都彼此衝突。這兩種組合都會讓人物停止。

我呼籲各位千萬不要在這個步驟偷懶,一定要算到所有小細節。這不只能避免出錯,也能讓移動操控變得更直觀。

好啦。走上側向梯的部分搞定了。那正向梯的部分有什麼特殊規則嗎?

其實我們只需要加入一些小調整,就可以在第八代加入正向梯了,因為基本的邏輯都通用。在我們的遊戲中,做法是讓正向梯只有中間一小段寬度可以使用,而非整個樓梯面。因此,它們有兩個位置明確的進入點,就和側向梯一樣。這也代表玩家隨時可以穿過前梯,意即它們的頂端和底端都是可穿過的,因為樓梯圖像的寬度必定比進入範圍還寬。當然了,在某些情況下玩家可能穿過正向梯後就立刻遇到牆壁而無法前進。其餘部分(走到樓梯上的行為)運作模式如出一轍。更棒的是,我們不需要去顧慮玩家按住左 / 右來嘗試走上正向梯的狀況。


在進入範圍內按住上會讓人物走向進入點並開始走上樓梯。


在進入範圍內按住下會讓人物走向進入點並開始走下樓梯。

 

第二部:樓梯上的走動

 

恭喜你走到這一步了,值得掌聲鼓勵一下。我發誓,進入樓梯是這個系統最複雜的部分。現在我們就來定義在各類樓梯上的移動吧。

根據你遊戲的設定(例如你導入寫實的物理和重力),以垂直方式或對角線方式向上移動可能會造成一些問題,比如說你的人物從上方一走進樓梯就會滑下去。因此到了第九代,我們必須強迫人物在樓梯上貼著一直線線段來移動,而不使用物理碰撞來決定人物的移動或站立的位置。事實上,我們需要在人物進入樓梯時取消所有作用中的力,同時忽略所有在樓梯上期間被施加的力。好在我們的遊戲不用考慮戰鬥,或其他會對人物在樓梯上移動時施加外力的因素。

但我們要怎麼確保玩家移動的角度一定能走到底端或頂端?我們可以計算頂端和底端之間的角度,不過既然為了配合人物動畫,樓梯的每一階都已經一樣大了,所以不管是長是短,我們的側向梯都會是同樣的角度,那麼我們只要寫死就行了。


角度顯示為藍色。它不是碰撞區,只存在於程式碼中。

好啦,那我們的移動操控要怎麼在樓梯上運作?就到第十代來設計一下吧。玩家會預期上、右和上+右都應該要能走上樓(下樓梯時則相反)。記得不能疊加速度。

再一次地,我們也得考慮會互消的按鍵組合。例如上樓的時候,左和下是一樣的,都應該要能取消視為同樣的上和右。

那正向梯上的操控方式呢?就靠第十一代來搞定!

各位可能覺得按上和下不就沒事了嗎。然而迫使玩家要先按住上或下走到樓梯最末端後,才能左右移動,不僅反直覺,也讓人感覺遊戲不精緻。玩家也可能會想,為什麼在離頂端或底端還差 1 像素的地方按下左或右,卻是一點反應也沒有。因此我們必須分別在底部和頂部 1 公尺的地方定義「離開範圍」。玩家在離開範圍內按住左或右就會朝著樓梯的「離開點」移動。在抵達離開點後,他們就會走出樓梯並繼續朝著他們按住的方向走過去。也許各位注意到了,這就和進入範圍運作的方式完全一樣,讓玩家得以使用另外的按鍵走向進入點(也就是我們在很前面第三第四代加入的功能)。還有,我們得考慮會互相取消或疊加速度的按鍵組合。


藍色為樓梯頂和底的離開範圍。各位可以用碰撞區來處理,不過我們是從人物和最近的進出點的距離來算。

加入離開範圍後感覺精緻了許多,但這樣做也另外產生了一些問題,必須使用第十二代來解決。舉例來說,如果前梯只有 2 公尺或是更短,那玩家同時處於頂端和底端的離開範圍怎麼辦?那就把離開範圍設為樓梯總長的 20%,不要寫死成 1 公尺。


樓梯短,離開範圍就短

這樣解決了樓梯短的問題,但樓梯很長的時候又出了新問題,因為 20% 的離開範圍實在太長了。所以我們更新了十二代的規則,在第十三代中限制兩端離開範圍最長各為 1 公尺。

 

第三部:離開樓梯

 

就快完成了!現在進入樓梯和沿著樓梯走都很順利,離開樓梯其實也滿簡單的。

至前述設計為止,我們的人物會一直延續樓梯上的移動,直到永遠:

第十四代,我們設置了碰撞區讓玩家觸發的時候可以離開樓梯(也就是回到正常的行走操控,重新施加物理計算)。我們其實重複利用了頂端和底端的進出範圍碰撞區:

唯一的問題在於碰撞區過早觸發,在頂端的時候尤其嚴重,這是因為人物碰撞區高度的關係。我們把頂端的碰撞區上挪至足夠高度、也將底端碰撞區下移一點,讓人物上下樓梯到最末端的時候正好在碰撞區裡面。

另外,玩家在進入樓梯的過程中,會有一小段時間在已經切換成樓梯移動模式,但人物還是在碰撞區裡的狀態。這時若突然想要離開樓梯,就可能導致因為沒有再次觸發到碰撞區,而沿著樓梯角度而一直走下去的情況。在第十五代中,我們用和進入樓梯同樣的方式來解決:我們替所有例子(側向下梯/側向上梯/正向梯)一個一個定義離開樓梯的意圖,如同第六代那樣。當玩家在碰撞區內按下任一個合理的按鍵組合,他們就會離開樓梯。

到了第十六代,我們再次考慮低影格率時,可能會導致玩家衝過離開點的情況。如果你已經使用第二代時提過的,與影格率脫鉤的物理系統,這就不會是問題。若否,我們將人物傳送到離開點,就如同傳送到進入點那樣:

大功告成了!只需少少的十六次疊代,我們就成功做出了直觀、不易出錯又賞心悅目的樓梯動作。

在影片錄製後,我們又稍微改善了這個系統。我們改成使用 FixedUpdate() 而非較容易被注意到的傳送方式,並新增了在正向梯上站著不動的閒置動畫,它會根據先前移動方向而有所變化(面向或背對玩家視點)。

當然我們也很清楚,和那些程式大佬以 AI 為基礎所開發的 3D 複合地形移動系統比起來,這種移動系統根本就是小巫見大巫。不過這套系統我們用起來很順,應該也能輕鬆在任何類似的 2D 橫向捲軸遊戲中實作出來。各位若是覺得它還能更簡化、更強化、不管什麼化之類的,都歡迎提出來和我們交流交流。我們很樂意改善我們的程式和這篇指南,為其他開發者提供助益。

希望這篇文章有寓教於樂的功用,或至少能點出一個道理:看似平淡無奇的機制實作起來可能相當棘手。

Julian