[C#/Unity] 《Endless End》中的梯子實現總結

        由於《Endless End》需要體現「一層一層往上走」這個印象,因此,一些允許玩家垂直移動的道具就成為了必要場景物品。對於這一點,我做了兩個東西讓玩家「垂直移動」,一個是升降機,而另一個就是這一篇總結中要說的,玩家可以上下攀爬的梯子。

        那這個梯子要怎麼做呢?首先,我先對於這個梯子的具體需求進行分析。

  1. (核心需求)玩家在梯子頂端/底端可以水平/垂直移動,進行垂直移動後,玩家進入「攀爬」狀態;而在梯子頂端與底端之間的區域,玩家只能進行垂直移動。
  2. 我們不是一個動作遊戲。因此,我們的遊戲沒有跳躍,也不需要玩家在梯子中間部分的範圍進行左右移動,玩家也不可能在不經過梯子頂部/底部的前提下直接進入梯子中間部分。
  3. 梯子的貼圖雖然不與地面的貼圖重疊,但是當玩家在經過梯子頂部時,玩家仍然可以選擇水平走過這個梯子,而不是直接垂直往下掉落,當然,如果玩家只是處於梯子頂部且Input.GetAxisRaw(“Vertical”) == 0,那玩家也不會進入攀爬的狀態。如下圖:

        4. 玩家進入梯子並開始攀爬時,由於不能水平移動,因此需要在代碼層面上直接使玩家的position.x 與梯子的 position.x 保持一致。否則觀感會很奇怪。

        總體的示意圖如下:

  • 綠色的是地面
  • 黑色間條的是梯子
    • 橙框部分是梯子中部範圍
    • 藍框部分是梯子頂端
    • 紅框部分是梯子底端
  • 粉紅色的是玩家

我前後嘗試過兩種實現方式,效果類近:

        1. 射線檢測流:玩家向角色上下方各發出一條只檢測梯子LayerMask的射線;同時梯子身上只有一個觸發器。

  • 相關字段: 
  • 當隨便一條射線的檢測結果不為空,代表玩家「處於梯子的範圍內」,設置可攀爬標志符(bool canClimb)為true,否則canClimb = false
    • 然後根據canClimb進行邏輯判斷。
      • 當canClimb == true
        •  代表有梯子在檢測範圍內,通過RaycastHit2D.transform得到梯子的位置信息。
        • 接收垂直輸入
        • 將角色剛體的重力設為0,清空加速度
        • 根據玩家所在的梯子部分進行不同的處理
          • 在梯子中部
            • 禁止水平移動
            • 同步角色與梯子在x軸上的位置
            • 將角色的最優先動畫Layer設為攀爬層
          • 在梯子底部和頂部
            • 允許水平移動
            • 只有垂直輸入不為0時,才將角色的最優先動畫Layer設為攀爬層
        • 無論在哪個部分
          • 都需要根據垂直輸入 != 0的值設置到攀爬動畫的bool值裡
          • 根據垂直輸入的值進行攀爬(使用Vector2.MoveTowards)
      • 當canClimb == false
        • 允許水平移動
        • 重置重力為默認值
        • 清除加速度
        • 將最優先動畫Layer設為步行層
  • 代碼:
  • 射線檢測流的問題:從頂部至中部移動/從底部至中部移動都沒有問題;但是從中部移動至頂部/從中部移動至底部時,以中部至頂部為例,由於上方向射線高於角色貼圖,因此會在角色貼圖尚未走到「視覺上的梯子頂部」時就已經無法檢測到梯子,從而進入up_Ray.collider == null && down_Ray.collider != null的情況,而在這個情況中,玩家可以水平移動,導致下圖的情況出現。(如果單純的把觸發器範圍拉高,玩家的下方向射線又會使玩家在明顯高於地面的情況下仍然可以向上攀爬)
      • 比較好的解決方案是使射線以角色的腳部為中心發出,同時使梯子的上邊界貼近上層的地面,那角色就會在爬到剛好踩著梯子的位置可以開始水平移動;
      • 除此以外,還要盡可能使下邊界貼近下層的地面,但是又要留一點空間,給角色的向下射線留一個「無法檢測到梯子的空間」,否則角色到了下層地面時,因為兩個射線檢測對象都不為空,而判斷角色在梯子中部而無法水平移動!
      • 但同時又不能把向下射線的offset往下調,否則就會使剛剛對梯子上邊界和向上射線的調整前功盡棄(導致角色明顯超過地面高度時仍然可以繼續向上攀爬)。
      • 也是因為這個原因,我最後選擇了第二個實現方式。

        2. 觸發器檢測流:在梯子身上創建兩個空的子物體,分別代表頂部和底部,在父物體(自身),以及兩個子物體各添加一個觸發器和腳本,並在腳本中設置enum(Top, Middle, Bottom)標示當前物體是梯子的哪個部分。實現思路參考了這段教程https://www.youtube.com/watch?v=fQixU-qVSDk&t=1s

  • 相關字段:
  • 梯子的構造:
  • 梯子的有三個部分(頂/中/底),每一個部分都有一個觸發器和腳本。
    • 頂部觸發器與上層地面保持水平,同時高度盡量小一點,剛好能容納玩家的腳部觸發器;腳本中的Part標志為Top。
    • 中央觸發器上下邊界應剛好與頂部和底部觸發器重疊;腳本中的Part標志為Middle。
    • 底部觸發器高度為貼圖底部至下層地面的距離;腳本中的Part標志為Bottom。
    • 當玩家的腳部觸發器進入梯子的對應部分後,梯子將玩家對應的「所在梯子部分」字段設為true,離開時則設為false。
    • 然後回到角色控制的腳本中,根據角色當前處於梯子的位置情況進行判斷和控制。
      • 當角色到達了頂點/底端且不在梯子的中間部分(!isInLadderMiddle && (isInLadderBottom || isInLadderTop))
        • 允許水平移動
        • 角色還在梯子範圍裡,重力保持為0
        • 清空加速度
        • 假如在這種情況下,玩家進行了水平輸入,水平移動應優先於垂直移動。
          • 在頂端時,直接返回,不再執行垂直輸入判斷。
          • 在底端時,使重力恢復且向下方施加單位加速度,使玩家盡快落到地面,然後返回,不再執行垂直輸入判斷。
        • 假如玩家沒有進行水平輸入,則將玩家最優先動畫Layer設為攀爬層。
        • 判斷玩家的垂直輸入(moveY)
          • 如果moveY != 0
            • 如果玩家當前是梯子頂端(isInLadderTop == true)而且正在按著上鍵(moveY > 0)
              • 由於頂端不能在向上移動,因此將最優先動畫Layer設為行走層
              • 然後向上施加一個力,使玩家彈至頂部觸發器的頂部,確保玩家可以水平移動並離開梯子範圍
            • 否則,則是正常攀爬,並設置攀爬動畫的bool為true。
          • 否則,moveY == 0,攀爬動畫bool設為false,如果玩家在梯子頂端,將最優先動畫Layer設為行走層。
      • 當角色不在梯子的任何範圍(!isInLadderMiddle && !isInLadderTop && !isInLadderBottom)
        • 可以水平移動
        • 重力為默認重力
        • 清空加速度
      • 當角色在梯子中間(isInLadderMiddle)
        • 禁止水平移動
        • 重力為0
        • 清空加速度
        • 最優先動畫Layer設為攀爬
        • 如果能檢測到垂直輸入(moveY != 0)
          • 使角色與梯子的x軸保持一致
          • 攀爬
        • 根據垂直輸入設置攀爬動畫的bool值
  • 代碼:

        其實無論是射線檢測流還是觸發器檢測流,最後的效果都是差不多的。只是在批量生產和穩定性上,觸發器檢測流會稍優於射線檢測流,前者雖然物體多,但是每次生產只要調節好各部分觸發器的邊界即可;而後者還要對角色的射線屬性(距離、offset)進行測試,稍微麻煩一點。

        不過兩者都能解決遊戲裡對梯子的功能需求;手感還不錯,也不會出現奇怪的bug,當然還存在很大的優化和改進空間,自己還要繼續努力學習。

1 thought on “[C#/Unity] 《Endless End》中的梯子實現總結”

  1. Pingback: [C#/Unity] 《Endless End》中的升降機實現總結 - LoneliNerd's Study Log

Comments are closed.