獨立遊戲開發中的物理系統
注:本文選自機械工業出版社出版的《獨立遊戲開發:基礎、實踐與創收》一書的小節,略有改動。經出版社授權刊登於此。
Unity物理系統更準確的叫法應該是物理引擎(Physics Engine),該引擎是採用NVIDIA的PhysX物理引擎實現的,為避免與遊戲引擎本身的名字衝突,本書還是稱其為物理系統。所謂物理系統,是指在遊戲物件上實現加速度、碰撞、重力、摩擦力及各種外力作用的一系列功能集合。Unity物理系統又分為2D和3D兩種型別,兩者在使用上大體相似,主要區別是3D物理系統多了一個維度。
Unity物理系統沒有總開關,只要在遊戲物件上附加並正確設定了物理元件(如Rigidbody、Collider、Joint、Effector等元件),即使用了物理系統功能。下面我們繼續開發案例遊戲,並基於物理系統實現主角的移動、跳躍、自由落體及更復雜的碰撞檢測等功能。
遊戲物件調整
對於Road,我們需要將其調整為一個有一定距離且主角可以站立的路面。首先將Road的Sprite Renderer元件Draw Mode屬性選擇為Tiled(在該屬性下,影像會根據遊戲物件尺寸自動填充,就像連續的瓦片一樣),然後在場景中拖曳Road的左右邊框(需要確保工具欄中的變換工具為Rect Tool狀態),適當增大其寬度後即可得到一個連續的路面。接下來調整碰撞器範圍,在Box Collider 2D元件中單擊Edit Collider按鈕後,碰撞器範圍即進入可編輯狀態,調整完後再次單擊Edit Collider按鈕即可。我們還需要取消碰撞器的Is Trigger屬性,以保證主角與路面的碰撞不可穿透,此時儘管Road並未附加Rigidbody 2D元件,但它相當於一個Static狀態的剛體。另外,之前的Road指令碼已經不適用了,我們將其對應指令碼元件從檢視視窗移除,並將該指令碼檔案從專案視窗刪除即可。如圖1和圖2所示:圖1展示了檢視視窗中Road的相關元件情況,標註框中為相關的調整項;圖2展示了Road在場景檢視中的情況,注意其碰撞器範圍是一個極細的矩形綠色框(圖中可能不容易看出來,請讀者結合實際操作檢視),我們將該範圍上邊框調整在Road高度二分之一的位置,對應馬路中央,也是遊戲角色的水平落腳點。
圖1 Road遊戲物件相關元件情況
圖2 調整後的Road遊戲物件
注:在圖1中,有一個三角形警告符號,其內容提示我們:當前本Sprite影像資源的匯入設定可能會造成Tiled模式下的繪製錯誤。但很明顯,我們這裡並未出現繪製錯誤,筆者在實際工作中也尚未遇到過此類錯誤,忽略該警告即可。或者,可在該Sprite的影像資源匯入設定中,將Mesh Type屬性設定為Full Rect以消除該警告。
對於Player,我們需要讓其擁有重力以及合適的碰撞範圍。首先將Rigidbody 2D元件的Gravity Scale屬性設定為4,以接受該值大小的重力等級。接著重新選擇碰撞器,由於主角有一個近似圓形的外觀,因此可用圓形的Circle Collider 2D元件替換Box Collider 2D元件,並適當調整其範圍大小,如圖3所示。
圖3 調整後的Player遊戲物件
對於RoadBlock,可用類似方法調整其碰撞範圍並刪除RoadBlock指令碼即可,具體步驟這裡不再贅述。
渲染順序修正
我們先執行遊戲,可以看到主角會因重力向路面下落,最終被錯誤地顯示在馬路後面,如圖4所示。要修正此問題,我們需要了解下Sprite Renderer元件的Sorting Layer與Order in Layer屬性:Sorting Layer屬性中可新增一系列特定名稱的排序分組,Unity將按照組順序依次渲染其中的Sprite;當多個Sprite同屬一個Sorting Layer分組時,則可通過Order in Layer屬性的值大小來決定它們的渲染順序。
值得注意的是,Soring Layer與Layer雖然只差一個單詞,但在Unity中它們是兩個不同的概念,可閱讀書中第5章中有關Layer的簡要介紹。另外讀者應知曉,Sprite Renderer元件功能不屬於物理系統功能。
下面,我們開始調整Sorting Layer與Order in Layer。首先,在Sorting Layer中新增一個Player分組:任意選擇一個遊戲物件,在檢視視窗中單擊Sorting Layer屬性右側的Default按鈕,並在展開的下拉選單中選擇Add Sorting Layer選項,即可開啟標籤與層的有關設定,其中,Sorting Layers欄下預設僅有一個Default分組,右下角的加減號(“+ -”)可增減分組,拖曳左側的等號(“=”)則可調整組順序,這裡我們新增一個Player分組,並保持現有順序即可。然後為Player的Sprite選擇該分組:單擊Player遊戲物件,直接將其Sorting Layer屬性右側的選項選擇為剛才新增的Player分組即可。接下來,Road與RoadBlock之間同樣需要調整渲染順序:將RoadBlock拖曳到Road上,可看到錯誤的前後關係,此時保持兩者的Sorting Layer同屬預設Default分組,我們保持Road的Order in Layer屬性為0,再將RoadBlock的Order in Layer屬性設為1,即可修正渲染順序(RoadBlock為0,Road為-1也可以)。圖5展示了調整後的執行效果。
圖4 錯誤的渲染順序
圖5 修正的渲染順序
基於物理系統的移動
這裡我們修改PlayerController指令碼,將當前基於Transform元件的移動替換為基於物理系統功能的移動,如程式碼1所示。
程式碼1 PlayerController.cs
- using UnityEngine;
- public class PlayerController : MonoBehaviour
- {
- // 用於引用Player的Rigidbody 2D元件
- private Rigidbody2D body;
- // 表示主角的移動速度
- public float speed;
- private void Start()
- {
- // 獲取Player的Rigidbody 2D元件
- body = GetComponent<Rigidbody2D>();
- }
- private void FixedUpdate()
- {
- KeyboardControl();
- }
- private void KeyboardControl()
- {
- // 通過鍵盤左右鍵輸入乘以速度變數得出水平速度
- float sp = speed * Input.GetAxis("Horizontal");
- // 根據水平速度和應有的垂直速度影響剛體速度
- body.velocity = new Vector2(sp, body.velocity.y);
- }
- }
上述程式碼中的GetComponent是一個泛型方法,用於獲取已附加元件,尖括號內為該元件的具體型別,這裡我們獲取Player的Rigidbody 2D元件,並由body變數引用。velocity是Rigidbody 2D元件中一個屬性,代表當前剛體的移動速度,我們把一個匿名二維向量賦值給它,即可實現剛體速度驅動遊戲物件的移動。在這個匿名二維向量中,X維度值即左右方向鍵輸入值與speed變數的積,Y維度值則對應剛體當前在該維度應有的速度(也就是說,我們僅控制水平速度,而不直接控制垂直速度,垂直速度將由外力實現,例如,下落時由物理系統重力產生向下的速度;跳躍時由人物跳躍力產生向上的速度)。
注:對精準名詞解釋一下,velocity表示速度,speed表示速率。速度是有方向和大小的向量,而速率是沒有方向的值。在沒有特別說明時,本書把兩者都稱為速度。
基於物理系統的跳躍與碰撞
我們繼續編寫PlayerController指令碼程式碼,為主角新增跳躍能力,並使用更復雜的碰撞檢測來判定其是否站立在地面上,如程式碼2所示。
程式碼2 PlayerController.cs
- using UnityEngine;
- public class PlayerController : MonoBehaviour
- {
- // 用於引用Player的Rigidbody 2D元件
- private Rigidbody2D body;
- // 表示主角的移動速度
- public float speed;
- private void Start()
- {
- // 獲取Player的Rigidbody 2D元件
- body = GetComponent<Rigidbody2D>();
- }
- private void FixedUpdate()
- {
- KeyboardControl();
- }
- private void KeyboardControl()
- {
- // 通過鍵盤左右鍵輸入乘以速度變數得出水平速度
- float sp = speed * Input.GetAxis("Horizontal");
- // 根據水平速度和應有的垂直速度影響剛體速度
- body.velocity = new Vector2(sp, body.velocity.y);
- }
- }
在上述程式碼中,我們新增了onGround與jumpPower變數,並使用了OnCollisionStay2D與OnCollisionExit2D方法。onGround變數是一個布林值,用於說明主角是否站立在地面上(或其他可站立物體上),當該值為真時,我們使用GetAxis("Vertical")獲取上下方向鍵輸入值,並在輸入值大於0時(向上的方向)呼叫AddForce方法在剛體上增加一個瞬時的力,該力是一個匿名二維向量,作為引數傳遞給AddForce方法,我們這裡只需要一個向上的力以產生向上的速度,因此該二維向量的X維度值設為0,Y維度值則設為代表跳躍力大小的jumpPower變數。
OnCollisionStay2D方法會在Player始終與碰撞物件接觸時連續執行,我們使用它檢測主角是否站立在地面上,其中,contactCount屬性儲存了Player與某個碰撞物件之間碰撞點的數量(多數情況下為1),我們用cnum變數儲存該數量,並結合for迴圈與GetContact方法遍歷所有的碰撞點;contact變數則用於依次儲存遍歷結果,每一個碰撞點都有一個normal屬性,該屬性是一個法線向量(這裡該向量是一個長度為1,並與Player碰撞點切線垂直的二維向量),當該向量Y維度值為1時,Player的碰撞點必然在正下方,即站立在地面上。這裡我們將該值的判定標準設為0.8,以考慮碰撞點稍稍偏離正下方的情況。OnCollisionExit2D方法會在Player脫離任意碰撞時執行,我們直接在其中將onGround變數賦值為假,即說明Player處於懸空狀態。
若此時執行遊戲,主角將會以滾動方式移動,這不是我們想要的效果。可在Rigidbody 2D元件中展開Constraints選項並勾選Freeze Rotation Z屬性以解決此問題,如圖6所示。最後在檢視視窗的Player Controller指令碼元件中,為Speed與Jump Power屬性分別設定一個合適的值(這裡我們設定為3和150),即可執行遊戲測試最終效果。
圖6 在Rigidbody 2D元件中鎖定Z軸旋轉
相關文章
- B站上的獨立遊戲開發者遊戲開發
- 獨立遊戲開發入門指南遊戲開發
- 被Facebook開除的獨立遊戲開發者遊戲開發
- Playdew:在巴基斯坦開發獨立遊戲遊戲
- 2019 indiePlay中國獨立遊戲大賽報名開啟,在這裡發現更多優秀的獨立遊戲!遊戲
- 有趣的獨立遊戲在這裡聚集!2022 indiePlay中國獨立遊戲大賽報名開始 !遊戲
- 遊戲雜談:淺析很多獨立遊戲開發者堅持獨立而不願合作的原因遊戲開發
- 頂級獨立遊戲開發者談獨立遊戲在當前環境下的生存機會遊戲開發
- 獨立遊戲開發者:目前獨立遊戲市場飽和靠打超低折銷售遊戲開發
- 破繭成蝶:獨立遊戲《Cocoon》的開發故事遊戲
- 三位獨立遊戲開發者的勇者之路遊戲開發
- Valve正在開發《Dota自走棋》獨立版本遊戲遊戲
- 12.22GWB獨立遊戲開發者沙龍遊戲開發
- 國內獨立遊戲開發者報告 2020遊戲開發
- 獨立遊戲精神在這裡傳承!2023 indiePlay中國獨立遊戲大賽報名開始!遊戲
- Steam新推薦系統引獨立遊戲開發者不滿遊戲開發
- 探索古印度神話領域的獨立遊戲開發遊戲開發
- 任天堂官方:獨立開發者如何開發、發售Switch遊戲遊戲
- 國產獨立遊戲的中場戰事遊戲
- 微軟為獨立遊戲開發者提供平臺微軟遊戲開發
- 在WeGame試玩節,看到中國獨立遊戲的發展GAM遊戲
- 2023 GWB騰訊獨立遊戲大獎賽揚帆起航,助力獨立遊戲開發者追夢逐浪遊戲開發
- 獨立遊戲開發者的Kickstarter眾籌成功經驗談遊戲開發
- 獨立遊戲開發是如何讓我進退兩難的?遊戲開發
- 從業遊戲 22 年給獨立開發者的建議遊戲
- 獨立遊戲團隊發展策略:在系列作中創立自己的IP宇宙遊戲
- 中國獨立遊戲圖鑑:活下去遊戲
- 那些獨立遊戲中的境界(1):以水彩之名遊戲
- 獨立開發者如何開發出熱門好評Quest VR遊戲VR遊戲
- 《新神》開發者:獨立遊戲開發是種什麼樣的體驗?遊戲開發
- 2020 indiePlay中國獨立遊戲大賽報名開始!遊戲
- 2021 indiePlay中國獨立遊戲大賽報名開始遊戲
- 《硬核機甲》製作人自述獨立遊戲開發者之路遊戲開發
- 經典老遊戲,在獨立開發者手中復甦遊戲
- 獨立遊戲開發的低門檻一去不返遊戲開發
- 給國內獨立遊戲開發者的一些建議遊戲開發
- 獨立遊戲開發者:我是如何與騰訊打交道的?遊戲開發
- 萬款獨立遊戲吐血篩選,爆款獨立遊戲彙總指南遊戲