深入瞭解GameObject
GameObject的屬性
Name下方的選項組,對應GameObject.tag,多用在控制碰撞和查找物體上。在創建標籤時可以使用系統自帶的標籤,也可以在下拉菜單中點擊New Tag來創造自定義標籤。
點擊Add Tag…後,Inspector會跳轉到Tags&Layers界面,在該界面下創建自定義標籤後,需要重新點選GameObject再將其tag設置爲新創建的Tag。
在腳本中我們可以使用Tag來查找GameObject,使用Tag來查找比使用Name來查找消耗的資源更少一些:
GameObject GameObject.FindGameObjectWithTag(string tag);
GameObject[] GameObject.FindGameObjectsWithTag(string tag);
CompareTag方法可以用於判斷物體的Tag:
bool GameObject.CompareTag(string tag);
這個函數比簡單的使用字符串判斷句this.tag == tag要高效的多。
CompareTag最常用於碰撞的判定,如下面的例子:
void OnCollisionEnter(collider other)
{
if(other.CompareTag("bullet"))
{
GameOver();
}
}
Tag右側的選項組,對應腳本中的GameObject.layer。它的作用涉及到了攝像機的渲染和物理碰撞的檢測。
同樣的我們可以自定義Layer。Unity最多支持32層Layer,因爲Unity經常需要使用Int32作爲掩碼來輸入Layer信息。比如說我需要使用射線檢測系統來跟蹤鼠標指向的目標,但只需要檢測Unit而不需要檢測其它物體,我們就可以將Unit設置爲單獨的一個Layer,比如User Layer8 = Unit,然後使用下面的代碼來實現:
readonly LayerMask mask = 1<<8;
void Update()
{
if(Input.GetMouseButtonDown(0))
{
RaycastHit hit;
if(Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition),out hit, mask.value));
{
Debug.Log("Hit on a Unit!");
DoSomething();
}
}
}
上面的代碼中,Raycast等射線檢測的知識將等到物理章節中解釋。作爲參數的mask作爲一個掩碼,從最低位到最高位分別表示Layers列表中的第0個元素到第31個元素,每一位如果是1則表示選中該層Layer,爲0表示未選中,而Raycast函數中接收mask傳入的掩碼,只對第8層進行檢測,從而實現目標效果。
層與層的碰撞可以在主界面菜單欄的"Edit"->“Project Settings”->“Physics"中修改,2D遊戲則是"Edit”->“Project Settings”->“Physics 2D”。在這個窗口頁面的最下方有一個碰撞檢測表格,在這個表格中勾除某些格就可以使對應的兩個層間不再發生碰撞,如圖:
在上圖的情況中,標籤爲New Layer的物體不再和其他標籤爲New Layer或Default的物體發生碰撞。
可以通過鎖定某一層來防止不小心移動該層的物體。具體的做法是在Unity主界面的工具欄中點擊Layers下拉菜單並改變對應層最右邊的鎖定選項。在這個菜單中的眼睛圖標代表了一個層的可見性,當該選項被勾除則這一層不會被渲染。後面的鎖是鎖定性,被勾選時該層物體不能被選中:
在改變一個父物體的Layer屬性時,會彈出如圖所示的窗口,詢問是否要將改變應用到所有子物體上。選擇Yes則子物體的Layer也會一起改變,選擇No則只改變父物體的Layer。
生成與銷燬GameObject
GameObject是不能使用new來實例化的,必須使用Instantiate函數:
GameObject Instantiate(GameObject obj);
GameObject Instantiate(GameObject obj, Transform father);
GameObject Instantiate(GameObject obj, Vector3 position, Quaternion rotation);
GameObject Instantiate(GameObject obj, Vector3 position, Quaternion rotation, Transform father);
class InstantiateTest : MonoBehavior
{
[SerilizedField]
private GameObject bullet;
void Start()
{
GameObject newBullet = Instantiate(bullet, transform.position, transform.rotation);
newBullet.GetComponent<Bullet>().Shoot();
}
}
- 使用Destroy銷燬GameObject
void Destroy(GameObject obj);
void Destroy(GameObject obj, float delay);
class ExplosionEffectManager : MonoBehavior
{
[SerilizedField]
private GameObjcet explosionEffect;
public void ShowEffect(Vector3 center)
{
GameObjcet newEffect = Instantiate(explosionEffect, center + Random.insideUnitSphere, transform.rotation);
Destroy(newEffect, 5f);
}
}
- 使用物品池來管理物體
每次調用Instantiate都會申請新的內存空間,而每次Destroy都會將這些內存遺棄等待GC處理。對於經常生成又經常被摧毀的物品,如子彈、特效等,快速而大量的Instantiate和Destroy會導致大量的GC消耗。這些物體最好使用一個物品池來管理,物品池可以是棧也可以是隊列或其它的什麼結構,其根本思路在於通過改變activeSelf而非直接使用Destroy來實現類似銷燬的效果,以此減輕GC負擔。
下面的例子是用一個棧封裝的子彈池,其它腳本在創建或銷燬子彈時不再使用Instantiate和Destroy,轉而使用BulletPool.Born和BulletPool.Dies:
public class BulletPool
{
public static Stack<GameObject> pool = new Stack<GameObjcet>();
public static GameObject Born(GameObject bullet, Vector3 position)
{
if(pool.Count == 0)
{
GameObject obj = Instantiate(bullet, position, new Quaternion(0,0,0,0));
obj.Initialize();
return obj;
}
else
{
GameObject obj = pool.Pop();
obj.SetActive(true);
obj.Initialize();
return obj;
}
}
public static void Dies(GameObject bullet)
{
pool.Push(bullet);
bullet.SetActive(false);
}
}