預(yù)制件-在運(yùn)行時(shí)實(shí)例化預(yù)制件
想要在運(yùn)行時(shí)實(shí)例化復(fù)雜的游戲?qū)ο蠡蛴螒驅(qū)ο蟮募蠒r(shí),預(yù)制件非常方便。與使用代碼從頭開(kāi)始創(chuàng)建游戲?qū)ο笙啾?,使用代碼實(shí)例化預(yù)制件有許多優(yōu)點(diǎn),因?yàn)槟梢裕?/p>
使用一行代碼實(shí)例化一個(gè)預(yù)制件。從頭開(kāi)始創(chuàng)建等效的游戲?qū)ο笮枰嗟拇a行。
使用?Scene 視圖、Hierarchy 窗口和?Inspector?快速輕松地設(shè)置、測(cè)試和修改預(yù)制件。
無(wú)需更改代碼即可更改所實(shí)例化的預(yù)制件。無(wú)需更改任何代碼,即可將簡(jiǎn)單的火箭變成增壓火箭。
注意:可以從以下頁(yè)面下載一個(gè)包含所有示例的 Unity 項(xiàng)目:
InstantiatingPrefabsExamples.zip
實(shí)例化預(yù)制件的基礎(chǔ)知識(shí)
要在運(yùn)行時(shí)實(shí)例化預(yù)制件,代碼需要對(duì)該預(yù)制件的引用。要進(jìn)行此應(yīng)用,可以在代碼中創(chuàng)建一個(gè)公共變量來(lái)保存預(yù)制件引用。代碼中的公共變量在 Inspector 中顯示為可分配的字段。然后,可以在 Inspector 中分配要使用的實(shí)際預(yù)制件。
下面的腳本示例有一個(gè)公共變量“myPrefab”,這是對(duì)預(yù)制件的引用。該腳本在?Start()?方法中創(chuàng)建該預(yù)制件的實(shí)例。
using UnityEngine;?
public class InstantiationExample : MonoBehaviour
{
? ?
// 引用預(yù)制件。在 Inspector 中,將預(yù)制件拖動(dòng)到該字段中。
? ?
public GameObject myPrefab;
? ?
// 該腳本將在游戲開(kāi)始時(shí)簡(jiǎn)單地實(shí)例化預(yù)制件。
? ?
void Start()
? ?
{
? ? ? ?
// 實(shí)例化為位置 (0, 0, 0) 和零旋轉(zhuǎn)。
? ? ? ?
Instantiate(myPrefab, new Vector3(0, 0, 0), Quaternion.identity);
? ?}
}
要使用此示例,請(qǐng)執(zhí)行以下操作:
在項(xiàng)目中創(chuàng)建一個(gè)新的 C# 腳本,并將其命名為“InstantiationExample”。
將上面的腳本示例復(fù)制并粘貼到新腳本中,然后保存。
使用GameObject > Create Empty?菜單創(chuàng)建空游戲?qū)ο蟆?/p>
通過(guò)將腳本拖動(dòng)到空游戲?qū)ο笊?,將腳本作為一個(gè)組件添加到新的游戲?qū)ο蟆?/p>
創(chuàng)建任何預(yù)制件,然后將其從?Project 窗口拖動(dòng)到腳本組件的?My Prefab?字段中。

啟動(dòng)運(yùn)行模式時(shí),應(yīng)該會(huì)看到預(yù)制件在場(chǎng)景中實(shí)例化為位置 (0, 0, 0)。
只需將其他預(yù)制件拖到 Inspector 的?My Prefab?字段中,即可更改要進(jìn)行實(shí)例化的預(yù)制件,而無(wú)需更改腳本。
因?yàn)榈谝粋€(gè)示例非常簡(jiǎn)單,相比您自己手動(dòng)將預(yù)制件自己放置到場(chǎng)景中,似乎并沒(méi)有任何優(yōu)勢(shì)。但是,能夠使用代碼實(shí)例化預(yù)制件將實(shí)現(xiàn)強(qiáng)大的功能,可以在游戲或應(yīng)用程序正在運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建游戲?qū)ο蟮膹?fù)雜配置,如下文中的示例所示。
常見(jiàn)情況
為了說(shuō)明在運(yùn)行時(shí)實(shí)例化預(yù)制件的優(yōu)勢(shì),下面介紹了預(yù)制件非常有用的幾種基本情況:
通過(guò)在不同位置(例如在網(wǎng)格或圓形結(jié)構(gòu)中)多次復(fù)制單個(gè)預(yù)制件來(lái)構(gòu)建一個(gè)結(jié)構(gòu)。
從發(fā)射器發(fā)射飛彈預(yù)制件。飛彈預(yù)制件可能是一個(gè)復(fù)雜的配置,其中包含網(wǎng)格、剛體、碰撞體、音頻源、動(dòng)態(tài)光源以及一個(gè)具有自己的軌跡粒子系統(tǒng)的子游戲?qū)ο蟆?/p>
車輛、建筑物或角色(例如機(jī)器人)分解成許多部分。在這種情況中,示例腳本將刪除完整正常的機(jī)器人預(yù)制件,并替換為殘骸機(jī)器人預(yù)制件。這種殘骸預(yù)制件由機(jī)器人的單獨(dú)破碎部分組成,每個(gè)部分都具有自己的剛體和粒子系統(tǒng)。通過(guò)這種方法,只需一行代碼即可將機(jī)器人炸成許多碎片,這種情況下會(huì)將原始游戲?qū)ο筇鎿Q為一個(gè)殘骸預(yù)制件。
以下各節(jié)說(shuō)明如何實(shí)現(xiàn)這些情況。
構(gòu)建結(jié)構(gòu)
可以使用代碼幾乎立即在特定配置中創(chuàng)建預(yù)制件的許多副本。使用代碼以這種方式生成結(jié)構(gòu)的過(guò)程稱為程序化生成。以下示例將創(chuàng)建一堵墻的實(shí)例。
要嘗試該示例,請(qǐng)創(chuàng)建以下腳本,將其命名為?Wall,并將其放置在場(chǎng)景中的空游戲?qū)ο笊稀?/p>
using UnityEngine;?
public class Wall : MonoBehaviour
{
??
public GameObject block;
??
public int width = 10;
??
public int height = 4;
?
??
void Start()
? {
? ? ??
for (int y=0; y<height; ++y){
? ? ? ? ??
for (int x=0; x<width; ++x)
{? ? ? ? ? ? ? ??
Instantiate(block, new Vector3(x,y,0), Quaternion.identity);
}}}}
完成上述操作后,應(yīng)該會(huì)在 Inspector 中看到?Block?變量,字段中包含文字?None。值為“None”表示還沒(méi)有為該變量分配任何預(yù)制件。

在將預(yù)制件分配給?Block?變量之前,以上示例腳本將無(wú)效。要?jiǎng)?chuàng)建一個(gè)簡(jiǎn)單的 Block 預(yù)制件,請(qǐng)執(zhí)行以下操作:
1.選擇?GameObject > 3D Object > Cube。
2.將立方體從?Hierarchy?窗口中拖入?Project 窗口中的?Assets?文件夾。****這將創(chuàng)建一個(gè)預(yù)制件資源。
3.將預(yù)制件重命名為“Block”。
4.現(xiàn)在?Block?預(yù)制件作為資源存在了,因此可以安全地從層級(jí)視圖中刪除立方體。
現(xiàn)在已經(jīng)創(chuàng)建了 Block 預(yù)制件,接下來(lái)可以將其分配給?Block?變量。選擇原始游戲?qū)ο螅ǜ郊恿恕癢all”腳本的游戲?qū)ο螅?。然后將“Block”預(yù)制件從?Project 窗口?拖入“Block”變量字段(其中顯示“None”)。

完成此設(shè)置后,單擊?Play?就會(huì)看到 Unity 使用預(yù)制件來(lái)構(gòu)建一堵墻:

這是一種可以在 Unity 中反復(fù)使用的靈活工作流程模式。因?yàn)樵诖四_本中使用的是預(yù)制件,所以無(wú)需改動(dòng)腳本即可輕松替換或編輯預(yù)制件來(lái)修改墻的磚塊屬性。還可以在場(chǎng)景中的其他游戲?qū)ο笊鲜褂?Wall 腳本,并為這些游戲?qū)ο蠓峙洳煌念A(yù)制件,以使用不同類型的預(yù)制件構(gòu)建各種墻。
可以使用代碼將游戲?qū)ο蠓胖迷诰W(wǎng)格、圓形圖案、隨機(jī)分散的形狀或任何其他配置中(只要您認(rèn)為適合所要?jiǎng)?chuàng)建的任何游戲或應(yīng)用程序)。下面是另一個(gè)示例,顯示了如何以圓形形式放置實(shí)例:
using UnityEngine;?
public class CircleFormation : MonoBehaviour
{
??
// 以圓形形式實(shí)例化預(yù)制件
??
public GameObject prefab;
??
public int numberOfObjects = 20;
??
public float radius = 5f;
??
void Start(){
? ? ??
for (int i = 0; i < numberOfObjects; i++){
? ? ? ? ??
float angle = i * Mathf.PI * 2 / numberOfObjects;
? ? ? ? ??
float x = Mathf.Cos(angle) * radius;
? ? ? ? ??
float z = Mathf.Sin(angle) * radius;
? ? ? ? ??
Vector3 pos = transform.position + new Vector3(x, 0, z);
? ? ? ? ??
float angleDegrees = -angle*Mathf.Rad2Deg;
? ? ? ? ??
Quaternion rot = Quaternion.Euler(0, angleDegrees, 0);
? ? ? ? ? Instantiate(prefab, pos, rot);}}}

實(shí)例化飛彈和爆炸
在此情況中:
1.玩家按下發(fā)射按鈕時(shí),“Launcher”游戲?qū)ο髮?shí)例化一個(gè)飛彈預(yù)制件。該預(yù)制件包含一個(gè)網(wǎng)格、一個(gè)剛體和一個(gè)碰撞體,因此它可以在空中飛行并檢測(cè)何時(shí)發(fā)生碰撞。
2.飛彈與物體碰撞,然后實(shí)例化爆炸預(yù)制件。爆炸預(yù)制件包含粒子系統(tǒng)效果,以及一個(gè)對(duì)周圍游戲?qū)ο笫┝Φ哪_本。
按照與上面的 Block 預(yù)制件相同的方式,無(wú)論飛彈預(yù)制件有多復(fù)雜,僅用一行代碼即可實(shí)例化飛彈。在實(shí)例化預(yù)制件之后,還可以修改實(shí)例化的游戲?qū)ο蟮娜魏螌傩?。例如,可以設(shè)置飛彈的剛體的速度。
除了易于使用之外,還可以稍后修改預(yù)制件,而無(wú)需改動(dòng)代碼。因此,如果飛彈為火箭,那么稍后可以添加粒子系統(tǒng),從而產(chǎn)生云跡。完成此操作后,所有實(shí)例化的火箭都會(huì)具有粒子軌跡。
以下腳本顯示了如何使用?Instantiate()?函數(shù)來(lái)發(fā)射飛彈。
using UnityEngine;?
public class FireProjectile : MonoBehaviour
{
? ?
public Rigidbody projectile;
? ?
public float speed = 4;
? ?
void Update(){
if (Input.GetButtonDown("Fire1")){
? ? ? ? ??
Rigidbody p = Instantiate(projectile, transform.position, transform.rotation);
? ? ? ? ? ?p.velocity = transform.forward * speed;}}}
在代碼中,預(yù)制件變量類型是剛體,而不是游戲?qū)ο?。這樣有兩個(gè)有用的效果:
1.只能為此變量分配具有剛體組件的游戲?qū)ο蟆_@很有用,因?yàn)樗兄诖_保您為變量分配了正確的游戲?qū)ο蟆?/p>
2.Instantiate 方法返回對(duì)新實(shí)例上的剛體組件的引用。這很有用,因?yàn)檫@樣可以輕松地在實(shí)例化剛體之后立即設(shè)置剛體的速度。
生成公共預(yù)制件變量時(shí),變量類型可以是游戲?qū)ο?,也可以是任何有效的組件類型(內(nèi)置的 Unity 組件或您自己的 MonoBehaviour 腳本之一)。
對(duì)于游戲?qū)ο箢愋偷淖兞?,可以將任何游戲?qū)ο蠓峙浣o該變量,并且 Instantiate 函數(shù)將返回對(duì)新游戲?qū)ο髮?shí)例的引用。
對(duì)于組件類型變量(例如剛體、碰撞體和光源),只能將該組件類型的游戲?qū)ο蠓峙浣o變量,并且 Instantiate 函數(shù)將返回新游戲?qū)ο髮?shí)例上對(duì)該特定組件的引用。
以下腳本(放置在飛彈預(yù)制件上)執(zhí)行以下操作:在飛彈碰撞物體后,在飛彈的當(dāng)前位置實(shí)例化爆炸,然后刪除飛彈游戲?qū)ο蟆?/p>
using UnityEngine;?
public class Projectile : MonoBehaviour{
??
public GameObject explosion;
??
void OnCollisionEnter(){
? ? ? Instantiate(explosion,transform.position,transform.rotation);
? ? ? Destroy(gameObject);}}

請(qǐng)注意,上圖中顯示了在運(yùn)行模式下運(yùn)行的腳本,實(shí)例化的游戲?qū)ο蟪霈F(xiàn)在層級(jí)視圖中,并在名稱后附加了文字“(Clone)”。
用布娃娃或殘骸替換角色
在游戲中,通??赡軙?huì)希望將角色、車輛、建筑物或其他資源從“完好無(wú)損”狀態(tài)切換到“損毀”狀態(tài)。通常的做法不是嘗試修改游戲?qū)ο蟮耐旰脽o(wú)損版本(例如刪除腳本、添加剛體組件等),而是刪除整個(gè)完好無(wú)損游戲?qū)ο蟛⑵涮鎿Q為實(shí)例化的損毀預(yù)制件,這樣做的效率會(huì)更高得多。這樣可以提供很大的靈活性??梢詫?duì)損毀版本使用其他材質(zhì)、附加完全不同的腳本或者實(shí)例化某個(gè)預(yù)制件(其中包含分解為不同部分的游戲?qū)ο?,用于模擬原始游戲?qū)ο笃扑榈臍埡“姹荆V恍枰淮握{(diào)用 Instantiate() 就可以實(shí)現(xiàn)上述任意方案,從而將損毀版本引入場(chǎng)景中,同時(shí)刪除原始游戲?qū)ο蟆?/p>
最重要的是,您可以創(chuàng)建損毀版本,然后通過(guò)與原始游戲?qū)ο笸耆煌挠螒驅(qū)ο髞?lái)對(duì)損毀版本執(zhí)行 Instantiate()。例如,要?jiǎng)?chuàng)建一個(gè)可破壞的機(jī)器人,需要對(duì)兩個(gè)版本進(jìn)行建模:一個(gè)版本包含單個(gè)游戲?qū)ο螅ǜ郊恿司W(wǎng)格渲染器
以及用于機(jī)器人移動(dòng)的腳本),另一個(gè)版本包含多個(gè)可以由物理系統(tǒng)單獨(dú)控制的骨骼部件。使用僅包含一個(gè)游戲?qū)ο蟮哪P蜁r(shí),游戲運(yùn)行速度更快,因?yàn)槟P桶娜切螖?shù)量較少,因此其渲染速度比具有許多小部件的機(jī)器人要快。此外,機(jī)器人四處自由走動(dòng)時(shí),沒(méi)有理由將機(jī)器人拆分為單獨(dú)部件。
要構(gòu)建殘骸機(jī)器人預(yù)制件,可以執(zhí)行以下操作:
1.在偏好的 3D 建模軟件中適用大量不同的骨架部件對(duì)機(jī)器人建模,然后將機(jī)器人導(dǎo)出到 Unity 項(xiàng)目的 Assets 文件夾中。
2.在 Unity Editor 中創(chuàng)建一個(gè)空?qǐng)鼍啊?/p>
3.將模型從 Project 窗口拖入空?qǐng)鼍爸小?/p>
4.通過(guò)選中所有部件并選擇?Component > Physics > Rigidbody,將剛體添加到所有部件。
5.通過(guò)選中所有部件并選擇?Component > Physics > Mesh Collider(啟用?Convex?選項(xiàng)以獲得更快的性能),將碰撞體添加到所有部件。
6.確保將殘骸機(jī)器人的所有部件設(shè)為單個(gè)根游戲?qū)ο蟮淖哟?/p>
7.要獲得額外的特殊效果,請(qǐng)將類似煙霧的粒子系統(tǒng)添加為每個(gè)部件的子游戲?qū)ο蟆?/p>
8.現(xiàn)在獲得了一個(gè)具有多個(gè)可爆炸部件的機(jī)器人。這些部件可能會(huì)掉落到地面,因?yàn)樗鼈兪芪锢碓砜刂疲⑶颐總€(gè)部件都會(huì)產(chǎn)生一個(gè)粒子軌跡(由于附加了粒子系統(tǒng))。
9.單擊 Play 來(lái)預(yù)覽模型的反應(yīng)并進(jìn)行任何必要的調(diào)整。
10.將根游戲?qū)ο笸蟿?dòng)到?Project 窗口的?Assets?文件夾內(nèi)以創(chuàng)建一個(gè)新的預(yù)制件。
以下示例演示了如何在代碼中對(duì)上述步驟建模。
using UnityEngine;?
public class WreckOnCollision : MonoBehaviour
{
public GameObject wreckedVersion;
// 每幀調(diào)用一次 Update
??
void OnCollisionEnter(){
? ? ??
Destroy(gameObject);
? ? ? Instantiate(wreckedVersion,transform.position,transform.rotation);}}