實戰教程:手把手教你做一款《憤怒的小鳥》
今天為大家分享一篇Unity 2D教程,使用Unity 2D功能來實現一款類似《憤怒的小鳥》的遊戲。其中用到的背景圖片、石頭、小鳥等資源均來自Asset Store資源商店的2D Sprites Pack,涉及到的功能包括2D物理、2D碰撞器及LineRenderer等。
該遊戲最終運行效果如下:
溫馨提示,學習本教程需要瞭解Unity編輯器基本操作及腳本程式設計概念。本文先為大家分享上半部分,示例工程使用Unity5.6.1f1,實現內容包括設置背景、地面、彈弓及其與彈子之間的物理交互。
準備工作
為“Floor1”添加Edge Collider 2D組件,調整碰撞器的座標讓其位於草皮與地面交界處,如下圖所示:
在層級視圖中選中“Floor1”物件,按下快速鍵Ctrl/Cmd + D根據需要複製多個草地物件,並依次調整各自的座標讓這些草地鋪滿整個螢幕下方。
添加彈弓
選中Sprites資料夾下的CatapultSprite圖片,將其切割為左右兩部分,並分別將切割後的圖片錨點移動至彈弓的皮筋所在位置,以便於後面繪製皮筋紋理。
切割完畢後將兩張圖片分別添加至場景,放置於草地上的合適位置。然後為場景添加彈子,也就是Sprites資料夾下的AsteroidSprite圖片,並將其縮放為合適的大小。
為彈子添加Rigidbody2D組件,這裡希望彈子在空中飛行時不受重力的影響,
這裡希望彈子在落地後會慢慢停止而非不停朝前運動,所以要為草皮使用物理材質,讓草皮擁有一點摩擦力,以阻止彈子前進。新建Physical Material 2D物理材質,並將其Friction設為5。
添加拉弓效果
新建空遊戲對象Slingshot作為彈弓的父節點,將之前的兩部分彈弓移到該物件下成為其子節點。然後新建兩個LineRenderer對象作為彈弓左右兩條弓,並新建三個空遊戲物件分別標記彈弓兩邊節點與彈子起始位置。Slingshot物件結構如下圖:
新建C#腳本Slingshot添加到Scripts資料夾下,Slingshot腳本的作用是處理彈子與彈弓之間的物理交互,生成彈弓被彈子拉扯的軌跡,並將彈子飛出。在Start方法中設置所有Line Renderer的Sorting Layer,並計算彈弓兩邊節點的中點位置。Start方法代碼如下:
Update方式實現了一個簡易的遊戲狀態機,當彈弓為空閒狀態時,玩家點擊螢幕會將彈子放置到待發射位置,並繪製彈弓兩邊的弦。如果玩家點擊並拖拽彈子,則改變彈弓狀態為UserPulling。代碼如下:
當彈弓處於UserPulling狀態時,就表示玩家正在拉扯彈弓。此時要計算彈子與彈弓中點之間的距離,如果距離大到一定程度就不允許玩家使勁了,同時繪製出彈弓的兩條弦。將允許的最大距離設為1.5f,代碼如下:
控制彈弓最遠拉拽距離的原理如下圖,其中B為彈弓兩個節點的中點,C為玩家拖拽彈子的位置(超過了設置的最大值1.5f),A是彈子最終可以到達的位置:
用笛卡爾坐標系來表示上圖,則三者之間的座標關係如下:
此時可以根據數學公式算出點A的座標,所以可以得出可以拖拽的最遠座標值為:
varmaxPosition = (location - SlingshotMiddleVector).normalized * 1.5f +SlingshotMiddleVector;
當玩家鬆開彈弓後,需要檢測玩家拉拽彈弓的力量,如果力量夠大,則彈子起飛,否則彈子將回到起始位置。代碼如下:
其中ThrowBird函數用來讓彈子起飛,計算彈子起飛的加速度,並再飛行後為其添加重力作用,最後拋出事件通知腳本彈子已起飛。ThrowBird函數代碼如下:
DisplaySlingshotLineRenderers函數用來繪製彈弓的兩條弓,SetSlingshotLineRenderersActive則用於設置兩條弓是否可見。兩個函數代碼如下:
DisplayTrajectoryLineRenderer函數則用於繪製彈子飛行方向的軌跡,根據抛物線原理進行計算。代碼如下:
到此彈子與彈弓的交互工程就完成了,此時運行場景,效果如下:
在上半部分解決了最棘手的彈弓與彈子之間的物理交互問題,我們實現了彈弓與彈子之間的物理交互。後面我們將講解,為彈子添加拖尾效果,加入彈子與障礙物的交互,加入相機跟隨效果及遊戲勝負判定。
添加彈子拖尾效果
在層級視圖選中彈子物件,在檢視視圖中為其添加Trail Renderer組件:
新建C#腳本命名為Bird,將該腳本添加到彈子物件上。在Start函數中設置彈子的初始狀態,放大彈子的碰撞器以方便玩家點擊,並控制拖尾效果是否顯示。BirdState是用來標誌彈子狀態的枚舉,共有飛行前及飛行中兩種狀態。Start函數代碼如下:
在腳本中添加FixedUpdate函數,來檢測彈子是否已被彈弓射出,如果已被射出且彈子速度非常小,就表示彈子已經落地。落地兩秒後從場景中移除彈子物件。代碼如下:
最後是供彈弓腳本調用的OnThrow函數,在彈子被射出時將其碰撞器設為原始大小,為其加上重力作用並顯示拖尾效果。代碼如下:
彈子射出後的拖尾效果如下:
顯示彈子飛行軌跡
在層級視圖中選中彈弓對象,為其新建空遊戲對象命名為trajectoryLineRenderer,並將該對象賦給SlingShot腳本的TrajectoryLineRenderer欄位。在trajectoryLineRenderer物件上添加LinerRenderer元件。現在彈弓物件的層級結構如下:
SlingShot腳本中的DisplayTrajectoryLineRenderer函數用於繪製彈子飛行軌跡。拉拽彈弓時,會預先顯示彈子的飛行軌跡,效果如下:
添加障礙物
新建空遊戲對象作為障礙物父節點,然後將Sprites資料夾下的PlankSprite拖拽至該對象下方,設置Tag為“Brick”,並為該圖片添加BoxCollider 2D及Rigidbody 2D組件。新建C#腳本命名為Brick,該腳本用於檢測彈子與障礙物的碰撞,並在碰撞發生後減去相應的生命值,減至0時從場景中移除障礙物。腳本代碼如下:
將新建的Brick腳本添加到障礙物子物件上,並在層級視圖中複製多個障礙物,調整各個障礙物的座標。擺成如下圖的形式:
添加目標
障礙物建好之後,下面來添加射擊目標。將Sprites資料夾下的BirdEnemyIdleSprite添加到障礙物父節點下,與障礙物為同一層級,將遊戲對象重命名為Pig,設置其Tag為“Pig”。並為其添加Circle Collider 2D與Rigidbody 2D組件。新建Pig腳本用於檢測碰撞,如果目標與彈子發生碰撞,則直接死亡。如果目標是與其它物件發生碰撞,則計算傷害,並在傷害減至0時從場景中移除目標。腳本代碼如下:
將Pig腳本添加到目標物件上,然後複製兩個目標,調整目標的座標位置如下:
將Sprites資料夾下的BirdEnemyDeathSprite圖片分別賦給3個Pig腳本的SpriteShowWhenHurt欄位,在目標被射中時會更換圖示表示受傷。
設定遊戲邊界
在場景中新建3個Quad對象,分別作為遊戲的左、右及上方邊界,位於背景圖後面。將其材質設置為半透明,並為其添加Box Collider 2D組件,勾選碰撞器的Is Trigger屬性。
新建腳本Destroyer用於在任意物件碰撞到邊界時銷毀物件,腳本代碼如下:
當彈子飛出邊界後會被直接銷毀。
添加相機跟隨
新建腳本CameraFollow,用於跟隨彈子射出時移動相機,並限定相機移動範圍,以避免移出遊戲邊界。將該腳本添加到場景中的主相機上,腳本代碼如下:
添加遊戲勝負判定
用遊戲管理器來管理遊戲狀態,控制彈弓狀態,觸發彈弓發射事件,並更改相機是否跟隨的狀態,最後負責遊戲勝負的判定。在Start函數中,遊戲管理器會獲取所有類型的物件,並設置遊戲與彈弓的初始狀態。新建腳本GameManager,代碼如下:
在Update函數中管理遊戲狀態,控制遊戲開始、進行中與遊戲結束後的操作。遊戲開始前,玩家點擊螢幕後將第一個彈子移動到彈弓初始位置就位,然後等待玩家拉拽彈弓後射出彈子。Update函數代碼如下:
AllPigsDestroyed函數用於檢測是否所有目標都被銷毀,代碼如下:
AnimateCameraToStartPosition函數用於移動相機位置,相機在跟隨射出的彈子移動到螢幕右側後,對目標進行判斷,如果所有目標被摧毀,則玩家勝利且遊戲結束。否則就將相機移動至起始位置,繼續下一次射擊。如果沒有可供射擊的彈子,則玩家失敗。函數代碼如下:
AnimateBirdToSlingshot函數用於將彈子移動到彈弓的起始拉拽位置,彈子就位後將彈弓改為啟動狀態,可以繪製彈弓兩邊的弦。代碼如下:
Slingshot_BirdThrown是BirdThrown事件的回呼函數,用於告訴相機需要跟隨的彈子。函數代碼如下:
最後的OnGUI函數用於在遊戲介面上顯示一些遊戲狀態相關的文字資訊,代碼如下:
最後是項目中定義的常量與枚舉,常量主要包括彈子的最小速度、彈子的最小及最大半徑,這些也可以直接在Bird腳本中定義。單獨列出來以方便後面進行維護,新建Constants腳本,代碼如下:
枚舉則用來定義彈弓狀態、彈子狀態以及遊戲狀態,新建Enums腳本,代碼如下:
到此整個教程就結束了,在場景中另外添加兩個彈子。運行遊戲,效果如下:
結語
本教程為大家介紹了如何在Unity中實現一款類似《憤怒的小鳥》的簡單遊戲,設計了一個關卡,添加了勝負判斷條件。大家還可以在此基礎上繼續完善,設計多個不同的關卡,為目標受傷添加音效,添加關卡時間限制等等。
Slingshot物件結構如下圖:新建C#腳本Slingshot添加到Scripts資料夾下,Slingshot腳本的作用是處理彈子與彈弓之間的物理交互,生成彈弓被彈子拉扯的軌跡,並將彈子飛出。在Start方法中設置所有Line Renderer的Sorting Layer,並計算彈弓兩邊節點的中點位置。Start方法代碼如下:
Update方式實現了一個簡易的遊戲狀態機,當彈弓為空閒狀態時,玩家點擊螢幕會將彈子放置到待發射位置,並繪製彈弓兩邊的弦。如果玩家點擊並拖拽彈子,則改變彈弓狀態為UserPulling。代碼如下:
當彈弓處於UserPulling狀態時,就表示玩家正在拉扯彈弓。此時要計算彈子與彈弓中點之間的距離,如果距離大到一定程度就不允許玩家使勁了,同時繪製出彈弓的兩條弦。將允許的最大距離設為1.5f,代碼如下:
控制彈弓最遠拉拽距離的原理如下圖,其中B為彈弓兩個節點的中點,C為玩家拖拽彈子的位置(超過了設置的最大值1.5f),A是彈子最終可以到達的位置:
用笛卡爾坐標系來表示上圖,則三者之間的座標關係如下:
此時可以根據數學公式算出點A的座標,所以可以得出可以拖拽的最遠座標值為:
varmaxPosition = (location - SlingshotMiddleVector).normalized * 1.5f +SlingshotMiddleVector;
當玩家鬆開彈弓後,需要檢測玩家拉拽彈弓的力量,如果力量夠大,則彈子起飛,否則彈子將回到起始位置。代碼如下:
其中ThrowBird函數用來讓彈子起飛,計算彈子起飛的加速度,並再飛行後為其添加重力作用,最後拋出事件通知腳本彈子已起飛。ThrowBird函數代碼如下:
DisplaySlingshotLineRenderers函數用來繪製彈弓的兩條弓,SetSlingshotLineRenderersActive則用於設置兩條弓是否可見。兩個函數代碼如下:
DisplayTrajectoryLineRenderer函數則用於繪製彈子飛行方向的軌跡,根據抛物線原理進行計算。代碼如下:
到此彈子與彈弓的交互工程就完成了,此時運行場景,效果如下:
在上半部分解決了最棘手的彈弓與彈子之間的物理交互問題,我們實現了彈弓與彈子之間的物理交互。後面我們將講解,為彈子添加拖尾效果,加入彈子與障礙物的交互,加入相機跟隨效果及遊戲勝負判定。
添加彈子拖尾效果
在層級視圖選中彈子物件,在檢視視圖中為其添加Trail Renderer組件:
新建C#腳本命名為Bird,將該腳本添加到彈子物件上。在Start函數中設置彈子的初始狀態,放大彈子的碰撞器以方便玩家點擊,並控制拖尾效果是否顯示。BirdState是用來標誌彈子狀態的枚舉,共有飛行前及飛行中兩種狀態。Start函數代碼如下:
在腳本中添加FixedUpdate函數,來檢測彈子是否已被彈弓射出,如果已被射出且彈子速度非常小,就表示彈子已經落地。落地兩秒後從場景中移除彈子物件。代碼如下:
最後是供彈弓腳本調用的OnThrow函數,在彈子被射出時將其碰撞器設為原始大小,為其加上重力作用並顯示拖尾效果。代碼如下:
彈子射出後的拖尾效果如下:
顯示彈子飛行軌跡
在層級視圖中選中彈弓對象,為其新建空遊戲對象命名為trajectoryLineRenderer,並將該對象賦給SlingShot腳本的TrajectoryLineRenderer欄位。在trajectoryLineRenderer物件上添加LinerRenderer元件。現在彈弓物件的層級結構如下:
SlingShot腳本中的DisplayTrajectoryLineRenderer函數用於繪製彈子飛行軌跡。拉拽彈弓時,會預先顯示彈子的飛行軌跡,效果如下:
添加障礙物
新建空遊戲對象作為障礙物父節點,然後將Sprites資料夾下的PlankSprite拖拽至該對象下方,設置Tag為“Brick”,並為該圖片添加BoxCollider 2D及Rigidbody 2D組件。新建C#腳本命名為Brick,該腳本用於檢測彈子與障礙物的碰撞,並在碰撞發生後減去相應的生命值,減至0時從場景中移除障礙物。腳本代碼如下:
將新建的Brick腳本添加到障礙物子物件上,並在層級視圖中複製多個障礙物,調整各個障礙物的座標。擺成如下圖的形式:
添加目標
障礙物建好之後,下面來添加射擊目標。將Sprites資料夾下的BirdEnemyIdleSprite添加到障礙物父節點下,與障礙物為同一層級,將遊戲對象重命名為Pig,設置其Tag為“Pig”。並為其添加Circle Collider 2D與Rigidbody 2D組件。新建Pig腳本用於檢測碰撞,如果目標與彈子發生碰撞,則直接死亡。如果目標是與其它物件發生碰撞,則計算傷害,並在傷害減至0時從場景中移除目標。腳本代碼如下:
將Pig腳本添加到目標物件上,然後複製兩個目標,調整目標的座標位置如下:
將Sprites資料夾下的BirdEnemyDeathSprite圖片分別賦給3個Pig腳本的SpriteShowWhenHurt欄位,在目標被射中時會更換圖示表示受傷。
設定遊戲邊界
在場景中新建3個Quad對象,分別作為遊戲的左、右及上方邊界,位於背景圖後面。將其材質設置為半透明,並為其添加Box Collider 2D組件,勾選碰撞器的Is Trigger屬性。
新建腳本Destroyer用於在任意物件碰撞到邊界時銷毀物件,腳本代碼如下:
當彈子飛出邊界後會被直接銷毀。
添加相機跟隨
新建腳本CameraFollow,用於跟隨彈子射出時移動相機,並限定相機移動範圍,以避免移出遊戲邊界。將該腳本添加到場景中的主相機上,腳本代碼如下:
添加遊戲勝負判定
用遊戲管理器來管理遊戲狀態,控制彈弓狀態,觸發彈弓發射事件,並更改相機是否跟隨的狀態,最後負責遊戲勝負的判定。在Start函數中,遊戲管理器會獲取所有類型的物件,並設置遊戲與彈弓的初始狀態。新建腳本GameManager,代碼如下:
在Update函數中管理遊戲狀態,控制遊戲開始、進行中與遊戲結束後的操作。遊戲開始前,玩家點擊螢幕後將第一個彈子移動到彈弓初始位置就位,然後等待玩家拉拽彈弓後射出彈子。Update函數代碼如下:
AllPigsDestroyed函數用於檢測是否所有目標都被銷毀,代碼如下:
AnimateCameraToStartPosition函數用於移動相機位置,相機在跟隨射出的彈子移動到螢幕右側後,對目標進行判斷,如果所有目標被摧毀,則玩家勝利且遊戲結束。否則就將相機移動至起始位置,繼續下一次射擊。如果沒有可供射擊的彈子,則玩家失敗。函數代碼如下:
AnimateBirdToSlingshot函數用於將彈子移動到彈弓的起始拉拽位置,彈子就位後將彈弓改為啟動狀態,可以繪製彈弓兩邊的弦。代碼如下:
Slingshot_BirdThrown是BirdThrown事件的回呼函數,用於告訴相機需要跟隨的彈子。函數代碼如下:
最後的OnGUI函數用於在遊戲介面上顯示一些遊戲狀態相關的文字資訊,代碼如下:
最後是項目中定義的常量與枚舉,常量主要包括彈子的最小速度、彈子的最小及最大半徑,這些也可以直接在Bird腳本中定義。單獨列出來以方便後面進行維護,新建Constants腳本,代碼如下:
枚舉則用來定義彈弓狀態、彈子狀態以及遊戲狀態,新建Enums腳本,代碼如下:
到此整個教程就結束了,在場景中另外添加兩個彈子。運行遊戲,效果如下:
結語
本教程為大家介紹了如何在Unity中實現一款類似《憤怒的小鳥》的簡單遊戲,設計了一個關卡,添加了勝負判斷條件。大家還可以在此基礎上繼續完善,設計多個不同的關卡,為目標受傷添加音效,添加關卡時間限制等等。