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

        在《Endless End》裡,角色通過把箱子推到某些特定的地方來觸發機關的啟動是其中一個重要的解謎元素。因此,我們就要做一些能被玩家檢測到,並在接觸後與玩家向同一方向移動的「箱子」。

        首先,還是先分析一下這些「箱子」的需求:

        1. (核心需求)玩家正常狀態下無法穿過,而當玩家接觸到箱子時,玩家自動進入「推動狀態」。當玩家在推動狀態下繼續向石頭方向水平移動時,箱子就會「被推動」(玩家速度下降,箱子與玩家保持同一速率向同一方向移動)

        2. 當玩家目前正在推動的箱子接觸到了其他的箱子,由於當前正在推動的箱子被「擋住」了,因此也再無法推動

        3. 箱子是玩家進入裡世界後可以穿越的「場景物品」之一,因此箱子需要檢測玩家的「表/裡狀態」來決定自身是否能被碰撞/推動。

        4. 假如有兩個貼在一起的箱子,玩家從外部無法推動的情況;玩家可以進入裡世界並穿越到兩個箱子的中間,從中間將兩個箱子推開。

        具體示圖意如下:

  • 紅色線是地面
  • 紅色小人是玩家
  • 藍色的是箱子

        箱子的推動邏輯需要兩步,一是創建一個控制箱子自身的腳本、二是對玩家的水平移動腳本進行修改。

        首先對於箱子自身。在物體的結構上,除了為箱子添加一個單獨的Layer以及加一個Collider2D以外,就沒有其他的東西了。

        相關字段:

        腳本內容主要分為兩部分:

        1. 射線檢測:向左右發出一道極短距離的射線,檢測自身以外Layer為箱子的物體是否存在。如果存在,代表箱子在該方向上與另一個箱子緊密相鄰,箱子無法向該方向移動。

    • 當左射線檢測不為空,代表左邊有另一個箱子擋路,因此canPushLeft = false;當右射線檢測不為空,代表右邊有另一個箱子擋路,因此canPushRight = false。
    • 代碼:

        2. 移動方法:由玩家傳遞一個速度以及x軸值並調用,實現「被玩家推動」的效果。

    • Push方法由玩家調用,在Push時,檢測canPushLeft和canPushRight兩個bool值以及角色的水平移動方向。
      • 當canPushLeft == true,而角色水平移動方向為左方(moveX < 0)時,箱子才能向左移動。
      • 當canPushRight == true,而角色水平移動方向為右方(moveX > 0)時,箱子才能向右移動。
    • 代碼:

        3. 另外,箱子除了要實現上述兩個部分的內容(兩側箱子射線檢測、由玩家調用的推動以及自身移動的方法)外 ,正如開篇提到的其中一個需求所說的,玩家可以通過進入裡世界來無視箱子的碰撞。因此箱子還需要實現一個修改自身碰撞檢測的方法

    • 通過Physic2D.IgnoreLayerCollision來忽略角色Layer的檢測,並將該方法加到一個委托裡面。然後當玩家進入裡世界後,進行廣播並調用該委托。
      • 要注意的是,IgnoreLayerCollision裡的參數是(int layer1, int layer2, bool ignore),比起命名的IgnoreLayerCollision,其實際意義更像是SetCollisionBetweenTwoLayers。
        • 另外,不能直接聲明一個LayerMask並公開在Inspector面板,從外面設置對應的層,然後傳遞到IgnoreLayerCollision裡。
        • 而是需要使用LayerMask.NameToLayer,來根據Layer的名字轉換成對應的(int)Layer,這個結果才能正常被IgnoreLayerCollision所使用。
    • 代碼:

        其次是對於角色控制器的水平移動方法中的修改。

    • 當角色在表世界時,對其面朝的方向發射一道距離極短的射線,檢測玩家推動範圍裡是否存在有箱子Layer的物體。
      • 如果成功檢測,代表玩家推動範圍裡有箱子。
        • 先把箱子物體上的箱子控制器引用保存起來。
        • 使玩家進入推動狀態(最優先動畫Layer設為推動層)
        • 而當玩家進行水平移動(moveX != 0)時,嘗試通過調用箱子控制器中的Push方法,將自身的水平移動方向以及移動速度傳遞過去。
          • 如果箱子是處於可以推動的狀態,那箱子將會與玩家保持同樣的速度和方向移動
          • 如果箱子是處於不可推動的狀態,箱子由於自身的碰撞體原因,玩家也無法穿過箱子,顯示出一種「玩家推不動箱子」的狀態。
    • 代碼:

        總而言之,這個箱子的推動功能實現出來效果非常符合我的預期和項目需求,必可活用於下一次。雖然在實現的過程中也踩過一些坑。

        比如Physic2D.IgnoreLayerCollision的無效。我最開始就是將LayerMask聲明出來,並公開到Inspector面板上,然後想著使用的時候直接將LayerMask轉型為int,然後將值傳給IgnoreLayerCollision方法,結果發現(int)LayerMask的結果是一個4位數,直接就越界了,最後Google了一下才發現需要使NameToLayer(“Layer名”)才可以正常使用。

        通過解決一個又一個的坑,能感覺到自己確實是在進步,感覺很好。