2011年5月22日 星期日

Unity 隱藏3D模型及 GetComponentsInChildren 的使用

有時遊戲設計的需求,在場景中的3D模型物件未必都是一開始就一直是顯示的狀態,有可能會因為某些事件的發生而必須將物件隱藏,或因為某些事件的發生必須使該物件再次顯示出來,通常在大部份的狀況,我們可能會先製作一個 Prefabs ,在需要該物件時利用 Instantiate() 建立一個實例物件,使物件在場景中運作,在不需要該物件時利用 Destroy() 將此物件銷毀,可是如果我們的需求是必須持續使用該物件,物件本身帶有某些資料值會持續使用到,那麼就不能隨意將其銷毀,但此時需有一段時間不能使該物件出現在畫面上,那麼我們應該怎麼做呢?

物件的隱藏與顯示,這個定義上很廣泛,主要就是讓鏡頭裡看不到此物件,以下就來討論幾種方式:

將物件移出鏡頭外
通常遊戲場景都會有地板、背景等,我們如果暫時需要將物件隱藏掉,可暫時改變物件在3D空間的座標位置,將它藏在地板下、背景後,甚至是鏡頭後方,這樣就可以很輕易的隱藏畫面中的物件,不過,有些缺點,就是當需要再次將物件顯示在畫面之中時,必須將物件放回正確的位置,如果遊戲場景及鏡頭都是定點固定不動的,那麼將物件放回原來位置並不難,但如果遊戲場景、鏡頭都是依照遊戲進行在不斷運作改變方向及內容的話,此時在處理物件座標定位上可能就要多費點功夫了。

統一儲存相關資訊
如上面第一段所提到的,在需要時產生物件,在不需要顯示時銷毀物件,需要延續使用的相關數據,統一儲存在某個記憶位置、檔案、資料庫...中,如此做法其實相當直接,不需要的東西就丟,但也有同樣的問題,當該物件於下次需要顯示時,我們必須要很清楚知道他應該出現的位置,然後在這個位置產生物件,使遊戲進行不會錯亂掉,但對於有使用物理碰撞反彈等等的遊戲,那就很難去預測到該出現的正確位置,而且如果這些位置數據還要自己寫程式計算的話,Unity 的物理引擎反倒顯得無用武之地了。

關閉物件,使物件無法運作
當遊戲進行到某事件發生時,我們不想將物件銷毀,而只想讓物件在目前位置消失並停止運作,等待之後事件發生時再顯示出來,此時我們就可以利用 GameObject.active 將該物件關閉,或使用 GameObject.SetActiveRecursively() 直接將該物件及他的子物件全部關閉,使他停止運作,因為物件被停止了,所以此時他將不會顯示在畫面上,而物件本身帶有的 Component 也都會停止運作,待之後事件發生需要顯示該物件時再將其開啟,不過此時很容易發生一種情形是,GameObject.Find() 要找回此物件並將其開啟時,卻發現找不到,因為 Find() 只會幫你找出正在活動中的物件,所以在將物件關閉前,我們必須將此物件放至預先定義好的變數成員中,使我們的程式保有他的參照,在需要開啟時才能利用此參照找回這個已被關閉的物件;因為已持有他的參照,所以此物件雖然是在關閉的狀態,但我們仍然能對他控制,使他改變位置或變更其他數據,但也因為是關閉的狀態,所以我們付予他的任何主動功能動作將不會運作。



只控制 renderer 的開關
這是最直接的方式,利用 renderer.enabled 來控制模型渲染是否啟用,關閉了就只是看不到該物件,但全部的 Component 仍然正常運作中,待需要顯示時再將它開啟,不過通常一個模型並不單單只有一個物件,例如一個人物可能分為上半身、下半身、身體、手、腳、頭...等等,甚至是更細部,所以我們可能就會想利用類似這樣的方式將子物件的模型渲染都關閉..
for(var _child : Transform in parentObject) {
    _child.renderer.enabled = false;
}
但是如果整個模型構造不只一層,而是多層次的樹狀結構,那麼我們可能就需要用巢狀式的 for in 迴圈一層一層的執行 renderer.enabled = false ,此時又必須考慮到每個模型的樹狀結構都不同,總不能為每個模型都客制化寫一個巢狀迴圈讓他們去做隱藏,這樣子寫程式就太笨了,於是我們可能會寫一個 script 專門做這個動作,不過我們並不知道模型的結構會有幾層,這麼做還真是不好處理;幸好 Unity 有個 GameObject.GetComponentsInChildren() 幫我們解決了這個問題,在這部份,我們不再去考慮模型本身是否有子物件或者是有幾層的結構,一句話就能取得全部並控制他們;官方說明頁面的範例,我照樣的改到我的程式中並無法取得我需要的東西,所以在此提供我的寫法以供參考...
var meshObject : Transform;
_components : Component[] = meshObject.GetComponentsInChildren(Renderer);
for(var _comp : Component in _component){
    _comp.renderer.enabled = false;
}
如此,我們很方便的就能為物件隱藏或顯示,但物件在遊戲場景中仍正常動作中,如果設計了一些物理運作或碰撞事件,那麼物件碰到某物件或移動到某個區域就自己隱藏起來,隱藏後仍繼續活動,可能又碰到了某物件或到了某個特定區域又再自己顯示出來,如此下去,我們可以省掉許多程式運算及控制,只要在適當的時候告訴物件應該隱藏或顯示就行了。

GetComponentsInChildren() 不只是用在物件的 renderer 的控制上,還可用在任何 Component 的控制上,所以我們可以將數個相關的物件都放到一個空物件中,以群組的方式利用 GetComponentsInChildren() 來統一管理並控制其中的 Component 及 script 中的變數成員及函式。