Unity3D開(kāi)發(fā)教程:憤怒的小鳥(niǎo)
一、前言
“憤怒的小鳥(niǎo)”在2009年12月發(fā)布,由于它的高度上癮的游戲,它很快成為有史以來(lái)最成功的移動(dòng)游戲。
在本教程中,我們將在“Unity”中實(shí)現(xiàn)“憤怒的小鳥(niǎo)”翻版。游戲中最復(fù)雜的部分是物理系統(tǒng),但是多虧了Unity,我們就不用擔(dān)心太多了。
像往常一樣,一切都會(huì)盡可能簡(jiǎn)單地解釋?zhuān)@樣每個(gè)人都能理解它。
以下是項(xiàng)目的預(yù)覽:
二、源碼
UI資源:
http://xiazai.jb51.net/202106/yuanma/picturezy_jb51.rar
源代碼:
http://xiazai.jb51.net/202106/yuanma/Game_AngryBirds_jb51.rar
三、正文 項(xiàng)目版本
Unity5.0.0f4
1.設(shè)置相機(jī)
點(diǎn)擊Main Cameras,在Hierarchy面板設(shè)置背景色以友好的藍(lán)色色調(diào)(紅色=187, 綠色=238, 藍(lán)色=255)并調(diào)整大小而位置如下圖所示:
2.地面設(shè)置
地面貼圖設(shè)置
為了防止版權(quán)問(wèn)題,我們不能在本教程中使用原“憤怒的小鳥(niǎo)”圖形。相反,我們將畫(huà)我們自己的Sprite,使他們看起來(lái)像原來(lái)的游戲。
讓我們從用我們選擇的繪圖工具開(kāi)始:
將其保存到我們的項(xiàng)目中后,我們可以在項(xiàng)目區(qū)可以看到:
然后在Inspector修改導(dǎo)入設(shè)置:
注:Pixels Per Unit像素轉(zhuǎn)到單位價(jià)值16這意味著16x16像素將適合在游戲世界的一個(gè)單位。我們將使用這個(gè)值作為我們所有的紋理。我們選擇16,因?yàn)轼B(niǎo)的大小將有一個(gè)16x16像素后,我們希望在游戲世界它有一個(gè)單位的大小。
好了,現(xiàn)在我們可以將圖片從項(xiàng)目區(qū)拖入到場(chǎng)景中:
讓我們看看Inspector把地面定位在(0, -2),所以作為不為y=0的都不是地面的一部分:
地面物體設(shè)置
現(xiàn)在地面只是一幅圖像,僅此而已。它不是物理世界的一部分,事物不會(huì)與它相撞,也不會(huì)站在它上面。我們需要添加一個(gè)Collider讓它成為物理世界的一部分,這意味著事物將能夠站在它的頂端,而不是掉進(jìn)它的正中。
添加BoxCollider2D組件:
3.邊界設(shè)置
創(chuàng)建空對(duì)象,命名為borders
位置歸零:
現(xiàn)在,我們將在我們的水平的左邊、右邊和頂部添加某種不可見(jiàn)的區(qū)域。每當(dāng)有東西進(jìn)入那個(gè)區(qū)域,它就應(yīng)該被摧毀。此類(lèi)行為可以通過(guò)Trigger,這幾乎只是一個(gè)Trigger它接收到碰撞信息,但不會(huì)與任何東西發(fā)生沖突。
添加碰撞器:
勾選
Is Trigger
之后,我們可以為級(jí)別的右側(cè)和頂部再添加兩個(gè)triggers :
如果我們看看場(chǎng)景然后,我們可以看到觸發(fā)器是如何與我們的背景很好地對(duì)齊的:
現(xiàn)在我們?nèi)匀槐仨毚_保任何進(jìn)入邊界的東西都會(huì)立即被銷(xiāo)毀。此類(lèi)行為可以通過(guò)腳本Borders:
創(chuàng)建腳本Borders.cs:
將其添加到邊界對(duì)象物體上面:
讓我們也將腳本移動(dòng)到一個(gè)新的Scripts文件夾,只是為了保持清潔:
編輯Borders.cs腳本:
using UnityEngine; using System.Collections; public class Borders : MonoBehaviour { // Use this for initialization void Start () { } // Update is called once per frame void Update () { } }
我們不需要啟動(dòng)或者更新函數(shù),所以讓我們移除它們。相反,我們將使用OnTriggerEnter2D函數(shù),每當(dāng)有東西進(jìn)入其中一個(gè)邊界觸發(fā)器時(shí),統(tǒng)一將自動(dòng)調(diào)用該函數(shù):
using UnityEngine; using System.Collections; public class Borders : MonoBehaviour { void OnTriggerEnter2D(Collider2D co) { } }
在這個(gè)函數(shù)中,無(wú)論什么東西進(jìn)入Triggers,我們都將Destroy這個(gè)物體:
using UnityEngine; using System.Collections; public class Borders : MonoBehaviour { void OnTriggerEnter2D(Collider2D co) { Destroy(co.gameObject); } }
保存腳本后,我們的邊界就完成了。我們稍后會(huì)看到,如果我們?cè)噲D將一只鳥(niǎo)射出水平之外,它就會(huì)消失。
4.云彩設(shè)置
我們將花幾分鐘額外添加云到背景,以使水平看起來(lái)更好。像往常一樣,我們首先畫(huà)一個(gè):
讓我們?cè)陧?xiàng)目區(qū),然后在Inspector修改云的導(dǎo)入設(shè)置:
現(xiàn)在我們要做的就是把它從項(xiàng)目區(qū)進(jìn)入場(chǎng)景幾次,將每一片云放置在我們想要的位置:
注意:只要使用一些重復(fù)的模式和一些非常顏色,我們可以使水平看起來(lái)相當(dāng)好,無(wú)需付出很大的努力。
5.彈弓設(shè)計(jì)
彈弓圖片
一個(gè)飛彈將產(chǎn)生新的鳥(niǎo)類(lèi),并允許用戶(hù)發(fā)射到水平。和往常一樣,我們將從畫(huà)Sprites開(kāi)始:
這里是導(dǎo)入設(shè)置:
稍后,我們將創(chuàng)建一個(gè)腳本,在彈弓的位置生成一只新的鳥(niǎo),或者確切地說(shuō)是在彈弓的Pivot位置生成一只鳥(niǎo)。
我們想要在彈弓頂部而不是中間處出現(xiàn),這就是為什么我們要在“導(dǎo)入設(shè)置”中設(shè)置Pivot在頂部。
下面的圖像顯示了中心和頂:
*注意:如果我們將數(shù)據(jù)透視設(shè)置為中心然后變換位置是彈弓中心的點(diǎn)。如果我們把Pivot 設(shè)為頂,然后變換位置是彈弓頂端的點(diǎn)。
好了,現(xiàn)在我們可以將彈弓拖到場(chǎng)景中去了(-22, 3):
生成鳥(niǎo)腳本
如前所述,我們的彈弓應(yīng)該是生成鳥(niǎo)。確切地說(shuō),它應(yīng)該在一開(kāi)始就生成一個(gè),然后等待用戶(hù)啟動(dòng)它,然后在所有的物理計(jì)算完成之后再生成另一個(gè)。(當(dāng)什么都不動(dòng)的時(shí)候).
我們可以通過(guò)腳本來(lái)實(shí)現(xiàn)這樣的行為。
添加腳本Spawn.cs:
我們可以雙擊腳本來(lái)打開(kāi)它:
using UnityEngine; using System.Collections; public class Spawn : MonoBehaviour { // Use this for initialization void Start () { } // Update is called once per frame void Update () { } }
這個(gè)啟動(dòng)函數(shù)在開(kāi)始游戲時(shí)由Unity自動(dòng)調(diào)用。這個(gè)更新函數(shù)被一次又一次地自動(dòng)調(diào)用,大約每秒60次。我們不需要它們中的任何一個(gè),這樣我們就可以從腳本中刪除它們。
還有另一種類(lèi)型的更新函數(shù),它被稱(chēng)為FixedUpdate…它也被一次又一次的調(diào)用,但是是在單位物理完全相同的時(shí)間間隔內(nèi)計(jì)算的,所以在做物理工作的時(shí)候使用FixedUpdate是一個(gè)好主意(我們很快就會(huì)這么做).
下面是修改后的腳本FixedUpdate腳本:
using UnityEngine; using System.Collections; public class Spawn : MonoBehaviour { void FixedUpdate() { } }
好的,讓我們添加一個(gè)變量,允許我們稍后指定BirdPrefab(我們想生的鳥(niǎo)):
using UnityEngine; using System.Collections; public class Spawn : MonoBehaviour { // Bird Prefab that will be spawned public GameObject birdPrefab; void FixedUpdate() { } }
以下是我們?nèi)绾紊伤姆椒ǎ?/pre>void spawnNext() { // Spawn a Bird at current position with default rotation Instantiate(birdPrefab,transform.position,Quaternion.identity); }生成的觸發(fā)區(qū)域現(xiàn)在我們不能只生一只又一只鳥(niǎo)。相反,我們將不得不生成一個(gè),然后等待它被發(fā)射。有幾種方法可以實(shí)現(xiàn)這一點(diǎn),但最簡(jiǎn)單的方法是使用Triggers.
Trigger是一個(gè)簡(jiǎn)單的Collider ,接收碰撞信息,但實(shí)際上并不是物理世界的一部分。所以如果我們添加一個(gè)Trigger然后,每當(dāng)有東西進(jìn)入Trigger、留在Trigger中或離開(kāi)Trigger時(shí),我們都會(huì)收到通知。然而,由于它只是一個(gè)Trigger,事情不會(huì)像普通Collider 那樣與它相撞(這很快就更有意義了).
我們可以將Trigger添加到彈弓中,方法是在Hierarchy面板中,然后點(diǎn)擊添加組件Circle Collider 2D,給它一個(gè)合適的半徑和中心然后啟用觸發(fā):
Is Trigger
我們還可以在場(chǎng)景中查看:
在添加觸發(fā)器之后,每當(dāng)有東西進(jìn)入時(shí),我們都會(huì)收到通知。(OnTriggerEnter2D),停留(OnTriggerStay2D)或離開(kāi)(OnTriggerExit2D)上面那個(gè)綠色的圓圈。
現(xiàn)在,我們可以通過(guò)創(chuàng)建一個(gè)使用中變量,然后將其設(shè)置為bool值,當(dāng)生下一只鳥(niǎo)的時(shí)候?yàn)閒alse,當(dāng)它離開(kāi)觸發(fā)器時(shí)為true:
using UnityEngine; using System.Collections; public class Spawn : MonoBehaviour { // Bird Prefab that will be spawned public GameObject birdPrefab; // Is there a Bird in the Trigger Area? bool occupied = false; void FixedUpdate() { } void spawnNext() { // Spawn a Bird at current position with default rotation Instantiate(birdPrefab, transform.position, Quaternion.identity); occupied = true; } void OnTriggerExit2D(Collider2D co) { // Bird left the Spawn occupied = false; } }之后我們可以修改我們的FixedUpdate函數(shù),因此每當(dāng)觸發(fā)區(qū)域不再被占用時(shí),它總是生成一只鳥(niǎo):void FixedUpdate() { // Bird not in Trigger Area anymore? if (!occupied) spawnNext(); }注:!occupied意思是還沒(méi)有被占用…我們也可以用if(occupied==false).我們的生成腳本現(xiàn)在可以正常工作了,但是讓我們?cè)偬砑右粋€(gè)特性。在射殺一只鳥(niǎo)之后,會(huì)有很多東西相互碰撞,墜落,滾來(lái)滾去,甚至爆炸。在最初的“憤怒的小鳥(niǎo)”游戲中,只有在水平上的所有東西停止移動(dòng)之后,才會(huì)產(chǎn)生一只新的鳥(niǎo)。
我們可以很容易地創(chuàng)建一個(gè)sceneMoving函數(shù),該函數(shù)查找場(chǎng)景中是否有任何對(duì)象仍在移動(dòng),而不僅僅是一點(diǎn)點(diǎn):
bool sceneMoving() { // Find all Rigidbodies, see if any is still moving a lot Rigidbody2D[] bodies = FindObjectsOfType(typeof(Rigidbody2D)) as Rigidbody2D[]; foreach (Rigidbody2D rb in bodies) if (rb.velocity.sqrMagnitude > 5) return true; return false; }注意:我們使用了FindObjectsOfType找到所有帶有剛體的物體,之后我們會(huì)檢查每個(gè)物體的velocity,如果這個(gè)剛體的sqrMagnitude大于5,說(shuō)明這個(gè)剛體還在移動(dòng)就返回True,沒(méi)有就返回false使用這個(gè)整潔的小腳本,我們可以輕松地修改FixedUpdate功能,因此只有在沒(méi)有任何移動(dòng)的情況下才會(huì)產(chǎn)生新的鳥(niǎo):
void FixedUpdate() { // Bird not in Trigger Area anymore? And nothing is moving? if (!occupied && !sceneMoving()) spawnNext(); }現(xiàn)在我們已經(jīng)完成了生成鳥(niǎo)的腳本,我們可以在Inspector面板看到彈弓上面掛載的腳本:
注意:我們還不能在沒(méi)有鳥(niǎo)的情況下測(cè)試生成鳥(niǎo)腳本,但是它確實(shí)工作得很好,我們將在創(chuàng)建鳥(niǎo)之后看到這一點(diǎn)。
6.鳥(niǎo)的設(shè)置
鳥(niǎo)的圖片
讓我們開(kāi)始更有趣的事情:鳥(niǎo)。我們首先畫(huà)一個(gè)16 x 16一只大圓身軀和一些小小的翅膀和眼睛的鳥(niǎo)的像素圖像:
我們將使用以下方法導(dǎo)入設(shè)置為此:
讓我們從項(xiàng)目區(qū)進(jìn)入場(chǎng)景若要從其中創(chuàng)建游戲?qū)ο?,?qǐng)執(zhí)行以下操作:
鳥(niǎo)的物理
讓我們?yōu)轼B(niǎo)添加碰撞器Circe Collider 2D:
現(xiàn)在有一個(gè)Physics Material 2D對(duì)撞機(jī)的縫隙,讓我們可以給鳥(niǎo)一些特殊的物理特性。在“Unity憤怒的小鳥(niǎo)”教程中,物理材料將是非常有用的,因?yàn)楝F(xiàn)在,如果這只鳥(niǎo)掉在地上,它看起來(lái)會(huì)是這樣的:
看起來(lái)有點(diǎn)不自然。相反,我們想讓這只鳥(niǎo)從下面的東西中跳出來(lái):
要在第二張圖片中創(chuàng)建彈跳效果,我們所要做的就是在項(xiàng)目區(qū)并選擇Create>Physics2D Material,說(shuō)出來(lái)鳥(niǎo)類(lèi)材料把它變成一個(gè)新的物理材料文件夾:
一旦被選中,我們就可以修改Inspector:
注:Bounciness值越大,鳥(niǎo)就越會(huì)反彈。
最后,我們可以再次選擇鳥(niǎo),然后拖動(dòng)鳥(niǎo)類(lèi)材料從項(xiàng)目區(qū)進(jìn)入Collider Material插槽:
這只鳥(niǎo)也應(yīng)該四處走動(dòng)。剛體負(fù)責(zé)物體的重力、速度和其他使物體運(yùn)動(dòng)的力。根據(jù)經(jīng)驗(yàn)法則,在物理世界里,所有應(yīng)該移動(dòng)的東西都需要一個(gè)剛體.:
注意:我們?cè)O(shè)置了Gravity Scale到4因?yàn)樗茏岠B(niǎo)飛得更快。
如果我們按下Play現(xiàn)在我們可以看到鳥(niǎo)從地上掉下來(lái)并彈跳起來(lái):
我們的鳥(niǎo)類(lèi)物理已經(jīng)完成了,但是有一個(gè)小的調(diào)整是我們必須在這里進(jìn)行的?,F(xiàn)在,如果我們?cè)趶椆猩傻脑?,由于它的剛體引力,它會(huì)立即墜落到地面。我們只希望用戶(hù)一開(kāi)火,鳥(niǎo)就會(huì)受到重力的影響,所以讓我們現(xiàn)在啟用Is Kinematic,然后在腳本中禁用它:
現(xiàn)在,剛體是運(yùn)動(dòng)學(xué)的,這意味著它不受重力或速度的影響,因此不會(huì)立即墜落。
注意:為了更清楚地說(shuō)明這一點(diǎn),任何像英雄、汽車(chē)或鳥(niǎo)之類(lèi)的東西都應(yīng)該有一個(gè)剛體,它是運(yùn)動(dòng)學(xué)的。我們只使能只要鳥(niǎo)還在彈弓里就能運(yùn)動(dòng)。
鳥(niǎo)預(yù)制體
如前所述,這只鳥(niǎo)從一開(kāi)始就不應(yīng)該出現(xiàn)在場(chǎng)景中。相反,彈弓應(yīng)該在需要的時(shí)候生成出一只新的鳥(niǎo)。為了使彈弓能夠生成鳥(niǎo),我們必須創(chuàng)建一個(gè)預(yù)制件 (換句話說(shuō),我們必須在我們的項(xiàng)目區(qū)有鳥(niǎo)的資源).要?jiǎng)?chuàng)建預(yù)制件,我們所要做的就是在hierarchy面板,將物體拖入到項(xiàng)目區(qū)的Prefabs文件夾中:
現(xiàn)在,我們可以在任何時(shí)候?qū)ⅧB(niǎo)加載到場(chǎng)景中,這意味著我們現(xiàn)在也可以從Hierarchy中刪除這個(gè)對(duì)象:
生成鳥(niǎo)
讓我們將預(yù)制體bird拖到到我們的Spawn.cs的腳本中的BirdPrefab插槽中:
如果我們按下Play現(xiàn)在我們可以看到彈弓是如何生出一只鳥(niǎo)的:
拉and釋放腳本
用戶(hù)應(yīng)該能夠把鳥(niǎo)在彈弓周?chē)缓筢尫潘员惆阉湎蛩M姆较颉?/p>我們將創(chuàng)造一個(gè)新的C#腳本給它起個(gè)名字PullAndRelease :
using UnityEngine; using System.Collections; public class PullAndRelease : MonoBehaviour { // Use this for initialization void Start () { } // Update is called once per frame void Update () { } }用戶(hù)將能夠拖動(dòng)鳥(niǎo)繞一個(gè)圓圈。每個(gè)圓都需要一個(gè)中心,在我們的例子中,它是鳥(niǎo)的生成位置,所以讓我們確保將它保存在一個(gè)變量中:using UnityEngine; using System.Collections; public class PullAndRelease : MonoBehaviour { // The default Position Vector2 startPos; // Use this for initialization void Start () { startPos = transform.position; } }*注意:我們還刪除了更新函數(shù)因?yàn)槲覀儾恍枰?/pre>好的,為了讓用戶(hù)把鳥(niǎo)拖曳成一個(gè)圓圈,我們必須找出這只鳥(niǎo)是否被點(diǎn)擊了。(確切地說(shuō):拖著)…我們還需要知道用戶(hù)是否釋放了鼠標(biāo),在這種情況下,我們必須在我們目標(biāo)方向發(fā)射鳥(niǎo)。
當(dāng)然,如果沒(méi)有這方面的功能,那就不是Unity了。Unity自動(dòng)調(diào)用Onmouseup和OnmouseDrag函數(shù),當(dāng)我們用鼠標(biāo)拖動(dòng)游戲?qū)ο蠡螂S后釋放鼠標(biāo)時(shí):
*注:鼠標(biāo)拖動(dòng),意思是用戶(hù)在GameObject上按住鼠標(biāo)按鈕,然后移動(dòng)鼠標(biāo)。移動(dòng)鳥(niǎo)真的很容易。我們所要做的就是將當(dāng)前的鼠標(biāo)位置轉(zhuǎn)換到游戲世界的某個(gè)點(diǎn),然后將鳥(niǎo)移到那里。當(dāng)然,只有在一定半徑內(nèi):
void OnMouseDrag() { // Convert mouse position to world position Vector2 p= Camera.main.ScreenToWorldPoint(Input.mousePosition); // Keep it in a certain radius float radius = 1.8f; Vector2 dir = p - startPos; if (dir.sqrMagnitude > radius) dir = dir.normalized * radius; // Set the Position transform.position = startPos + dir;}
*注意:我們可以使用ScreenToWorldPoint獲取到手指點(diǎn)擊的位置,但是這個(gè)位置是不固定的,在找到手指點(diǎn)擊的位置p之后,我們只需要計(jì)算從startPos到p的距離,如果這個(gè)距離太長(zhǎng)dir.sqrMagnitude > radius,就讓這個(gè)位置等于一個(gè)最大值dir = dir.normalized * radius
如果我們按下Play然后我們可以把鳥(niǎo)繞個(gè)圈:
把鳥(niǎo)射向一個(gè)方向也同樣容易。我們可以用我們的Onmouseup函數(shù)來(lái)知道鼠標(biāo)何時(shí)釋放。然后,我們將計(jì)算出從鳥(niǎo)到startPos然后使用rigidbody的AddForce在那里啟動(dòng)它的功能:
// The Force added upon releasepublic float force = 1300;void OnMouseUp() { // Disable isKinematic GetComponent<Rigidbody2D>().isKinematic = false; // Add the Force Vector2 dir = startPos - (Vector2)transform.position; GetComponent<Rigidbody2D>().AddForce(dir * force); // Remove the Script (not the gameObject) Destroy(this);}
*注意:如前所述,我們也將禁用運(yùn)動(dòng)學(xué)等使剛體再次受到重力和速度的影響。我們只需將當(dāng)前位置減去startPos…最后,我們刪除這個(gè)對(duì)象,這樣它就不能再被發(fā)射了。
如果我們按下Play然后我們就可以拉著這只鳥(niǎo)開(kāi)火了:
Feather Particle Effect羽毛的粒子效果
讓我們通過(guò)增加鳥(niǎo)的碰撞效果來(lái)使游戲更加流暢。一旦它第一次落地,它就應(yīng)該像這樣在自己周?chē)S意地長(zhǎng)出羽毛:
當(dāng)我們需要隨機(jī)粒子產(chǎn)生、旋轉(zhuǎn)和向某個(gè)方向移動(dòng)時(shí),就會(huì)使用粒子系統(tǒng)。粒子系統(tǒng)的一個(gè)簡(jiǎn)單的例子是煙霧,它產(chǎn)生灰色紋理,然后以錐狀向上移動(dòng)。
我們將修改我們的粒子系統(tǒng),使其不是使粒子向上飛,而是使它們飛向四面八方。我們還將修改一些更具體的東西,如大小,速度和旋轉(zhuǎn)。我們的羽毛沒(méi)有正確或錯(cuò)誤的粒子系統(tǒng),所以你可以隨意使用它,直到它看起來(lái)像你想讓它看起來(lái)那樣。以下是我們得出的結(jié)論:
這是我們用來(lái)做這件事的圖像:
*注意:右擊圖像,選擇另存為.。并將其保存在項(xiàng)目的Assets/Sprites文件夾。
導(dǎo)入設(shè)置:
之后,我們可以從項(xiàng)目區(qū)進(jìn)入我們粒子系統(tǒng)所以它使用圖像對(duì)所有的粒子。
現(xiàn)在我們可以將場(chǎng)景中的羽毛對(duì)象拖入到我們項(xiàng)目區(qū)的Prefabs文件夾,做成一個(gè)預(yù)制體:
然后我們可以在Hierarchy中刪除羽毛游戲?qū)ο?/p>
最后一件事是給我們的鳥(niǎo)添加一個(gè)腳本,這樣羽毛粒子系統(tǒng)就會(huì)在發(fā)生碰撞時(shí)產(chǎn)生。讓我們?cè)陧?xiàng)目區(qū)然后創(chuàng)建新腳本…我們給它起個(gè)名字CollisionSpawnOnce。我們也會(huì)把它移到我們的Sprits文件夾,然后雙擊它以打開(kāi)它:
using UnityEngine; using System.Collections; public class CollisionSpawnOnce : MonoBehaviour { // Use this for initialization void Start () { } // Update is called once per frame void Update () { } }我們不需要啟動(dòng)或更新函數(shù)。相反,我們將使用OnCollisionEnter2D函數(shù)和effect公共游戲?qū)ο髴?yīng)生成的預(yù)制件的變量:using UnityEngine; using System.Collections; public class CollisionSpawnOnce : MonoBehaviour { // Whatever should be spawned (Particles etc.) public GameObject effect; void OnCollisionEnter2D(Collision2D coll) { // Spawn Effect, then remove Script Instantiate(effect,transform.position,Quaternion.identity); Destroy(this); } }*注意:為了確保只產(chǎn)生一次效果,我們將從Destroy(this)(這只會(huì)關(guān)閉腳本,而不是整只鳥(niǎo))。保存腳本后,我們可以看到Effect插槽…現(xiàn)在我們可以拖著羽毛粒子系統(tǒng)預(yù)制件項(xiàng)目區(qū)進(jìn)入Effect插槽:
如果我們按下Play然后把這只鳥(niǎo)發(fā)射到地上,然后我們就可以看到它周?chē)挠鹈谏桑?/p>
路徑
我們還會(huì)給我們的鳥(niǎo)添加另一個(gè)效果,讓它看起來(lái)更流暢:一條白點(diǎn)的軌跡,顯示鳥(niǎo)的軌跡:
首先,我們需要一些大小不同的跟蹤圖像:
我們會(huì)用同樣的導(dǎo)入設(shè)置對(duì)于每一幅圖像:
我們希望能夠在我們想要的任何時(shí)候產(chǎn)生軌跡部分,這意味著我們將需要三個(gè)預(yù)制件。因此,讓我們選擇三個(gè)圖像并將其拖到場(chǎng)景中,然后拖回到Prefabs文件夾。直到我們有三個(gè)預(yù)制體:
現(xiàn)在我們只需要一個(gè)腳本來(lái)生成一個(gè)又一個(gè)的TRAIL元素,大約每秒鐘一次。讓我們創(chuàng)建一個(gè)新的C#腳本給它起個(gè)名字Trail :
using UnityEngine; using System.Collections; public class Trail : MonoBehaviour { // Use this for initialization void Start () { } // Update is called once per frame void Update () { } }我們可以移除更新因?yàn)槲覀儾恍枰?。讓我們添加一個(gè)public GameObject[]保存所有跟蹤元素的變量。我們將使用Array,這意味著它不僅僅是一個(gè)GameObject:using UnityEngine; using System.Collections; public class Trail : MonoBehaviour { // Trail Prefabs public GameObject[] trails; // Use this for initialization void Start () { } }我們還需要一個(gè)函數(shù)來(lái)生成下一條線索。例如,它應(yīng)該產(chǎn)生第一個(gè)TRAIL元素,然后下一次應(yīng)該生成第二個(gè),然后是第三個(gè),然后是第一個(gè)。這可以通過(guò)使用實(shí)例化有一個(gè)額外的計(jì)數(shù)器變量:using UnityEngine; using System.Collections; public class Trail : MonoBehaviour { // Trail Prefabs public GameObject[] trails; int next = 0; // Use this for initialization void Start () { } void spawnTrail() { Instantiate(trails[next], transform.position, Quaternion.identity); next = (next+1) % trails.Length; } }我們將其設(shè)置為0,這意味著trails spawnTrail中的第一個(gè)元素被調(diào)用。然后使用next+1來(lái)增加next。為了保持它在trails數(shù)組的范圍內(nèi),我們還將使用% trails.Length,它使用模(%)運(yùn)算。對(duì)于那些不了解模的人,這里有一個(gè)更明顯的版本:void spawnTrail() { Instantiate(trails[next], transform.position, Quaternion.identity); next = next + 1; if (next == trails.Length) next = 0; }現(xiàn)在我們有了一個(gè)生成軌跡函數(shù),我們可以使用它生成一個(gè)新的trail 元素通過(guò)使用InvokeRepeting函數(shù)100 ms生成一個(gè):using UnityEngine; using System.Collections; public class Trail : MonoBehaviour { // Trail Prefabs public GameObject[] trails; int next = 0; // Use this for initialization void Start () { // Spawn a new Trail every 100 ms InvokeRepeating("spawnTrail", 0.1f, 0.1f); } void spawnTrail() { Instantiate(trails[next], transform.position, Quaternion.identity); next = (next+1) % trails.Length; } }現(xiàn)在,小徑元素會(huì)一直生成,甚至當(dāng)鳥(niǎo)不飛的時(shí)候也是如此。讓我們添加一個(gè)小小的修改,只在鳥(niǎo)飛得足夠快的情況下才會(huì)產(chǎn)生軌跡:void spawnTrail() { // Spawn Trail if moving fast enough if (GetComponent<Rigidbody2D>().velocity.sqrMagnitude > 25) { Instantiate(trails[next], transform.position, Quaternion.identity); next = (next+1) % trails.Length; } }好的,讓我們保存腳本。在這里,我們將從我們的三條小徑預(yù)制體中拖到插槽中:
如果我們按下Play然后我們就可以看到這只鳥(niǎo)射擊后的蹤跡:
7.木片
讓我們添加一些結(jié)構(gòu),如石頭,冰和木材,我們的Unity2D憤怒的小鳥(niǎo)游戲更加豐富。
我們先畫(huà)一塊木片:
注意:右擊圖像,選擇另存為。并將其保存在項(xiàng)目的Assetes/Sprits文件夾。
這里是導(dǎo)入設(shè)置:
現(xiàn)在我們可以把它拖到場(chǎng)景把它放在地上的某個(gè)地方:
木片應(yīng)該是物理世界的一部分,所以我們將一如既往地在添加Box Collider 2D組件:
木片也應(yīng)該能夠四處移動(dòng)?,F(xiàn)在它不會(huì)自己移動(dòng),但是如果鳥(niǎo)飛進(jìn)它,它就會(huì)移動(dòng)。它也應(yīng)該受到重力的影響,所以我們需要的是剛體…我們可以通過(guò)選擇添加組件->物理二維->Rigidbody 2D…我們也會(huì)增加Mass到4所以它更重了一點(diǎn):
現(xiàn)在我們有一塊木頭,它是物理世界的一部分!
對(duì)于這個(gè)略有不同的木片,我們將重復(fù)相同的工作流程:
這是我們的游戲如何看待添加第二塊木材和旋轉(zhuǎn)第一個(gè)90°:
8.石頭
為了在我們的游戲中有幾種不同的結(jié)構(gòu),我們還將添加兩種不同類(lèi)型的石頭:
操作流程和以前一樣,這次我們將Mass設(shè)置為10:
下面是我們的游戲中有一些石頭的樣子:
注:我們?cè)俅螌?shí)現(xiàn)了一些體面的外觀與基本的形狀,只有少數(shù)顏色和抖動(dòng)。
9.冰
冰的圖片
我們將為我們的游戲增加一個(gè)結(jié)構(gòu):冰。不過(guò),這一次會(huì)更有趣一些。像往常一樣,我們首先畫(huà)一塊冰:
這個(gè)導(dǎo)入設(shè)置與以往相同:
冰物理
添加 Boxcollider2D組件 剛體組件:
冰應(yīng)該很滑,所以讓我們右擊項(xiàng)目區(qū)并選擇創(chuàng)造->物理二維材料給它起個(gè)名字冰IceMaterial:
設(shè)置參數(shù):
之后,我們可以在Hierarchy面板中選擇冰然后將IceMaterial從項(xiàng)目區(qū)拖入到BoxCollider2D>Material插槽:
撞擊時(shí)摧毀冰
如果被足夠的力量撞擊,我們也希望冰層被摧毀,因?yàn)檫@就是冰的自然作用。我們添加腳本BreakOnImpact.cs腳本:using UnityEngine; using System.Collections; public class BreakOnImpact : MonoBehaviour { // Use this for initialization void Start () { } // Update is called once per frame void Update () { } }我們不需要啟動(dòng)或者更新函數(shù),所以讓我們移除這兩個(gè)函數(shù)。我們需要一種方法來(lái)估計(jì)碰撞的力量。我們將保持簡(jiǎn)單,并將速度與質(zhì)量相乘:float collisionForce(Collision2D coll) { // Estimate a collision's force (speed * mass) float speed = coll.relativeVelocity.sqrMagnitude; if (coll.collider.GetComponent<Rigidbody2D>()) return speed * coll.collider.GetComponent<Rigidbody2D>().mass; return speed; }*注:Collision2D繼承OnCollisionEnter2D功能,我們將獲取一個(gè)估計(jì)碰撞的力量,它包含方向與速度相乘。如果我們只關(guān)心速度,那么我們可以使用coll.relativeVelocity.sqrMagnitude…現(xiàn)在,如果造成碰撞的對(duì)象有剛體然后我們把速度乘以剛體的質(zhì)量…否則我們只返回速度。好了,現(xiàn)在我們可以用OnCollisionEnter2D函數(shù)以獲得有關(guān)碰撞的通知。然后,我們將比較碰撞的力量和一個(gè)可配置的變量。如果它比力大,那么冰就會(huì)破裂:
using UnityEngine; using System.Collections; public class BreakOnImpact : MonoBehaviour { public float forceNeeded = 1000; float collisionForce(Collision2D coll) { // Estimate a collision's force (speed * mass) float speed = coll.relativeVelocity.sqrMagnitude; if (coll.collider.GetComponent<Rigidbody2D>()) return speed * coll.collider.GetComponent<Rigidbody2D>().mass; return speed; } void OnCollisionEnter2D(Collision2D coll) { if (collisionForce(coll) >= forceNeeded) Destroy(gameObject); } }如果我們保存腳本,請(qǐng)按Play把鳥(niǎo)碰撞冰層,然后它就會(huì)破裂:
現(xiàn)在,我們可以花幾分鐘來(lái)復(fù)制這些結(jié)構(gòu),并將它們放在一起,這樣我們就可以在它們之間添加豬了:
10.綠豬
鳥(niǎo)兒想要消滅所有的豬,所以讓我們把一些豬加入到我們的游戲中,這樣鳥(niǎo)兒就不會(huì)覺(jué)得無(wú)聊了。
我們首先畫(huà)一個(gè):
導(dǎo)入設(shè)置:
添加碰撞器和剛體:
如果有足夠大的力量襲擊豬,豬就會(huì)死。幸運(yùn)的是,我們已經(jīng)有了一個(gè)腳本。給我們的pig物體添加腳本BreakOnImpact.cs,并且設(shè)置Force Needed的值:
*注意:能夠重用這樣的腳本是基于組件的開(kāi)發(fā)。
現(xiàn)在我們可以復(fù)制這頭豬并把它移到一些結(jié)構(gòu)之間:
如果我們按下Play然后我們就可以試著消滅豬了:
11.橡膠
我們將為我們的游戲添加最后一個(gè)功能:彈弓橡膠,所以拖拽和釋放鳥(niǎo)看起來(lái)要好得多:
我們首先畫(huà)一半的橡膠,這幾乎只是一條粗線:
這里是導(dǎo)入設(shè)置:
*注意:這次我們?cè)O(shè)置了Pivot到右(邊),正確的讓一些旋轉(zhuǎn)變得更容易。
現(xiàn)在我們可以從項(xiàng)目區(qū)進(jìn)入場(chǎng)景兩次,說(shuō)出其中一個(gè)左橡膠,另一個(gè)右橡膠把它們放在彈弓的上部:
我們要確保左邊的那個(gè)總是畫(huà)出來(lái)的。后面彈弓和右彈弓總是被拉進(jìn)去的。前面其中的一部分。我們可以再加兩個(gè)分類(lèi)層對(duì)于我們的游戲,但我們將保持簡(jiǎn)單,只需更改層序到-1左邊的橡膠和1右邊的橡膠:
現(xiàn)在橡膠看起來(lái)就像在外彈弓:
在開(kāi)始編寫(xiě)腳本之前,讓我們?cè)贖ierarchy然后將這兩個(gè)對(duì)象設(shè)置為slingshot的子對(duì)象:
*注意:每當(dāng)我們移動(dòng)彈弓時(shí),橡膠部件就會(huì)隨之移動(dòng)。
讓我們創(chuàng)建一個(gè)新的C#腳本給它起個(gè)名字Rubber:
using UnityEngine; using System.Collections; public class Rubber : MonoBehaviour { // Use this for initialization void Start () { } // Update is called once per frame void Update () { } }這個(gè)腳本的目的是讓兩個(gè)橡膠部分跟隨鳥(niǎo),直到它離開(kāi)生成鳥(niǎo)的觸發(fā)圈。我們需要兩個(gè)變量leftRubber 和rightRubber,讓我們?cè)谝院笾付ㄏ鹉z。我們不需要啟動(dòng)或者更新函數(shù),讓我們刪除掉它們:
using UnityEngine; using System.Collections; public class Rubber : MonoBehaviour { // The Rubber objects public Transform leftRubber; public Transform rightRubber; }現(xiàn)在,有一些稍微復(fù)雜一些的功能。我們將需要一個(gè)功能,定位橡膠在彈弓和鳥(niǎo)的位置,我們必須先將橡膠旋轉(zhuǎn)到鳥(niǎo)的方向,然后根據(jù)鳥(niǎo)的距離使橡膠變成或變短:void adjustRubber(Transform bird, Transform rubber) { // Rotation Vector2 dir = rubber.position - bird.position; float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg; rubber.rotation = Quaternion.AngleAxis(angle, Vector3.forward); // Length float dist = Vector3.Distance(bird.position, rubber.position); dist += bird.GetComponent<Collider2D>().bounds.extents.x; rubber.localScale = new Vector2(dist, 1); }注意:首先我們計(jì)算從鳥(niǎo)到橡膠的距離,然后計(jì)算這個(gè)方向的角度。然后我們,通過(guò)Quaternion.AngleAxis(angle, Vector3.forward)將橡膠旋轉(zhuǎn)到這個(gè)角度。最后,我們計(jì)算從鳥(niǎo)到橡膠的距離,之后,我們將橡膠的縮放設(shè)置為這個(gè)距離加上碰撞器的x的長(zhǎng)度既dist += bird.GetComponent().bounds.extents.x這個(gè)OnTriggerStay2D功能將通知我們,每當(dāng)鳥(niǎo)改變它的位置時(shí),它還在彈弓。我們可以使用這個(gè)函數(shù)來(lái)調(diào)整左右橡皮筋:
using UnityEngine; using System.Collections; public class Rubber : MonoBehaviour { // The Rubber objects public Transform leftRubber; public Transform rightRubber; void adjustRubber(Transform bird, Transform rubber) { // Rotation Vector2 dir = rubber.position - bird.position; float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg; rubber.rotation = Quaternion.AngleAxis(angle, Vector3.forward); // Length float dist = Vector3.Distance(bird.position, rubber.position); dist += bird.GetComponent<Collider2D>().bounds.extents.x; rubber.localScale = new Vector2(dist, 1); } void OnTriggerStay2D(Collider2D coll) { // Stretch the Rubber between bird and slingshot adjustRubber(coll.transform, leftRubber); adjustRubber(coll.transform, rightRubber); } }快好了。我們將再增加一個(gè)離開(kāi)時(shí)候觸發(fā)的事件,使橡皮筋在發(fā)射后變短:void OnTriggerExit2D(Collider2D coll) { // Make the Rubber shorter leftRubber.localScale = new Vector2(0, 1); rightRubber.localScale = new Vector2(0, 1); }現(xiàn)在,我們可以通過(guò)首先選擇彈弓對(duì)象中的游戲?qū)ο?。層次性然后點(diǎn)擊添加組件->Scitps->Rubber…我們亦會(huì)把這兩個(gè)橡膠拖到相應(yīng)的槽內(nèi):
如果我們按下Play現(xiàn)在我們可以看到小鳥(niǎo)和彈弓之間的橡皮筋:
當(dāng)然,現(xiàn)在我們也可以玩一輪憤怒的小鳥(niǎo)了:
總結(jié)
到這里本篇文章就結(jié)束了,我們剛剛創(chuàng)建了一個(gè)小游戲憤怒的小鳥(niǎo)翻版,使用簡(jiǎn)單的形狀與顏色來(lái)達(dá)到一個(gè)良好的效果,廣泛的使用了Unity的2D物理引擎,添加了許多的效果
相關(guān)文章
C#監(jiān)控文件夾并自動(dòng)給圖片文件打水印的方法
這篇文章主要介紹了C#監(jiān)控文件夾并自動(dòng)給圖片文件打水印的方法,涉及C#針對(duì)文件夾及圖片操作的相關(guān)技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-05-05深入解析C#中的交錯(cuò)數(shù)組與隱式類(lèi)型的數(shù)組
這篇文章主要介紹了深入解析C#中的交錯(cuò)數(shù)組與隱式類(lèi)型的數(shù)組,隱式類(lèi)型的數(shù)組通常與匿名類(lèi)型以及對(duì)象初始值設(shè)定項(xiàng)和集合初始值設(shè)定項(xiàng)一起使用,需要的朋友可以參考下2016-01-01C# 并發(fā)控制框架之單線程環(huán)境下實(shí)現(xiàn)每秒百萬(wàn)級(jí)調(diào)度
本文介紹了一款專(zhuān)為工業(yè)自動(dòng)化及機(jī)器視覺(jué)開(kāi)發(fā)的C#并發(fā)流程控制框架,通過(guò)模仿Go語(yǔ)言并發(fā)模式設(shè)計(jì),支持高頻調(diào)度及復(fù)雜任務(wù)處理,已在多個(gè)項(xiàng)目中驗(yàn)證其穩(wěn)定性和可靠性2024-10-10