這篇在講什麼

推箱子是《Endless End》裡很重要的一個互動元素。它不是單純讓物件被碰撞後亂滑,而是要能精準地跟玩家輸入同步,並且在被其他箱子擋住時穩定停下來。

我希望箱子有什麼行為

我先把需求整理成幾點:

  1. 玩家碰到箱子並持續朝同方向移動時,能進入推動狀態
  2. 箱子要和玩家維持一致的移動方向與節奏
  3. 如果前方被其他箱子擋住,就不能再推
  4. 在特殊表裡世界切換狀態下,要能改變是否參與碰撞

而當箱子彼此卡住時,則必須停下來:

箱子本身需要哪些結構

從物件結構來看,箱子其實不複雜:

  • 一個箱子物件
  • 一個 collider
  • 一支控制腳本

需要的狀態大多圍繞在左右是否可推、速度與碰撞控制上。

箱子怎麼判斷自己能不能被推

我最後採用的方式,是讓箱子自己往左右各打一條短 raycast,專門檢查同樣屬於箱子 layer 的物件。

如果左邊有箱子,就代表不能再往左推;右邊同理。

這樣箱子本身就可以維護:

  • canPushLeft
  • canPushRight

而不是把所有判斷都丟回玩家控制器。

推動時怎麼處理位移

真正推動時,我沒有完全交給物理力去處理,而是讓玩家端呼叫箱子的 Push(...) 方法,把方向與速度傳進去。

箱子再根據:

  • 玩家目前移動方向
  • canPushLeft
  • canPushRight

來決定要不要真的更新自己的位置。

這種做法的優點是:

  • 行為可控
  • 箱子和玩家節奏比較容易同步
  • 不會太依賴物理模擬的偶發性

表裡世界切換時怎麼處理碰撞

這個專案裡,玩家可以在表裡世界之間切換,而箱子也是會跟著改變碰撞狀態的物件之一。

我的做法是切 layer collision 規則,讓箱子自己決定是否忽略和玩家的碰撞。

這裡特別要注意的一點是:

  • Physics2D.IgnoreLayerCollision(...) 需要的是 layer index
  • 不是直接把 LayerMask 當成 layer index 傳進去

這是當時我踩過的一個小坑。LayerMask 的值本質上是 bitmask,不等於實際 layer 編號。

玩家端要配合什麼

玩家控制器也需要做前向檢查。

當玩家朝某個方向移動時,先往前打一條短 raycast,確認前方是否是箱子。若命中:

  • 取得該箱子的控制腳本
  • 讓角色切進推動狀態
  • 呼叫箱子的 Push(...)

這樣責任就很清楚:

  • 玩家判斷「我現在要不要推」
  • 箱子判斷「我現在能不能動」

我自己的整理

這篇最後留下來的核心判斷是:解謎型推箱子,不一定要追求完全真實的物理感,反而更該追求行為穩定與除錯容易。

只要玩家預期能被穩定滿足,這種可控位移通常比純物理更適合。

先記住的重點

  • 推箱子的核心不是碰撞,而是可控且穩定的互動節奏
  • 玩家負責決定是否嘗試推,箱子負責決定自己能否移動
  • 箱子自己做左右阻擋檢查,通常比全部堆在玩家端更清楚
  • 使用 IgnoreLayerCollision 時,要分清 layer index 和 layer mask