Unity 在腳本取得遊戲物件或組件的方法
分類
說明
已經開發 Unity 約兩個月的時間,我學到了一些載入資源的方法,也就是在腳本取得遊戲物件或組件,每個方法都有其優缺點,大家可以看看哪個比較適合你
前兩個方法以載入音效為例,如果大家要練習,可以自行找 mp3 或 wav 做為要載入的資源
1. 在 Inspector 設定
這是我學習時最常見的作法,假設角色有多個音效需要播放,我建立一個 AudioClip
陣列,在 Inspector 拖曳音效檔
我擷取重點程式如下
player.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
/// <summary>
/// 角色音效
/// 0 死亡
/// 1 受傷
/// </summary>
[SerializeField] private AudioClip[] _audioClips;
private AudioSource _audioSource;
void Awake()
{
_audioSource = GetComponent<AudioSource>();
}
private void _damage()
{
_audioSource.clip = _audioClips[1];
_audioSource.Play();
}
private void _die()
{
_audioSource.clip = _audioClips[0];
_audioSource.Play();
}
}
將 player.cs 加在任何一個遊戲物件,在 Inspector 設定音效檔案
你也可以改建立兩個變數
player.cs
public class Player : MonoBehaviour
{
/// <summary>
/// 死亡音效
/// </summary>
[SerializeField] private AudioClip _deathClip;
/// <summary>
/// 受傷音效
/// </summary>
[SerializeField] private AudioClip _hurtClip;
}
取得關閉遊戲物件
當遊戲物件關閉時,使用下列第3,4點跟 Find 有關的方法會找不到遊戲物件,此時可以建立一個空遊戲物件,並且是啟動的狀態,寫一個腳本放在這個空的遊戲物件,接著就可以使用此方法取得關閉的遊戲物件。
using UnityEngine;
/// <summary>
/// 用來控制多個 UI 面板
/// </summary>
public class UIPanels : MonoBehaviour
{
/// <summary>
/// 關卡選擇 UI 面板
/// </summary>
[SerializeField]
private GameObject levelSelectionPanel;
/// <summary>
/// 關卡選擇 UI 面板
/// </summary>
public GameObject LevelSelectionPanel
{
get => levelSelectionPanel;
}
}
優點
- 可以在 Inspector 直接設定內容,在 Play 時設定的內容會回到 Play 之前的狀態
- 可以取得關閉的遊戲物件
缺點
- 只要改變數名稱,就需要重新在 Inspector 設定
- 如果參考依賴其他遊戲物件,其他遊戲物件刪除或改變時,要注意參考遺失的問題
2. 使用 Load 方法
Resource.Load
為了解決上述的缺點,可以使用這個做法
在 Assets 資料夾建立 Resources 資料夾
Resources 資料夾是一定要建立的,位置和名稱需一樣
Resources 裡面還可以再建立資料夾,依你的需求建立即可
例如我將我的音擋放在 Assets/Resources/Audios/Hurt.wav
接下來使用腳本示範如何用 Resources.Load
取得 AudioClip
player.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
/// <summary>
/// 受傷音效
/// </summary>
private AudioClip _hurtClip;
void Awake()
{
_hurtClip = Resources.Load<AudioClip>("Audios/Hurt");
}
}
這種作法比較像以前我在開發遊戲的時候,載入每一張圖片和音效的作法
Addressables.LoadAssetAsync()
這個是可以實現熱更新的方式,簡單來說就是可以物件打包上傳到伺服器,然後使用者可以遊戲中遠端下載物件的作法,但整個做法相當複雜,有興趣得讀者可以參考 Unity 使用 Addressables 實現熱更新
3. 尋找場景中的遊戲物件
GameObject.Find
用 GameObject.Find(name)
來找出場景中遊戲物件,參數 name 帶入場景中的物件名稱,需特別注意的是當找不到物件時會回傳 null,因為更改物件名稱忘記的改腳本很可能會發生,假設在某些特殊條件下才會進一步存取此物件,遊戲很可能會存在危機四伏的漏洞
詳細的說明可以參考官方文件 GameObject.Find
ExampleClass.cs
using UnityEngine;
using System.Collections;
// This returns the GameObject named Hand in one of the Scenes.
public class ExampleClass : MonoBehaviour
{
public GameObject hand;
void Example()
{
// This returns the GameObject named Hand.
hand = GameObject.Find("Hand");
// This returns the GameObject named Hand.
// Hand must not have a parent in the Hierarchy view.
hand = GameObject.Find("/Hand");
// This returns the GameObject named Hand,
// which is a child of Arm > Monster.
// Monster must not have a parent in the Hierarchy view.
hand = GameObject.Find("/Monster/Arm/Hand");
// This returns the GameObject named Hand,
// which is a child of Arm > Monster.
hand = GameObject.Find("Monster/Arm/Hand");
}
}
GameObject.FindWithTag
每個遊戲物件都可以設定 tag 標籤,可以用 GameObject.FindWithTag("tagName")
來取得遊戲物件,雖然相當方便,但如果 tag 一多可能會有點麻煩,你會在 Inspector 找你要的標籤找得有點心累,以我寫這篇文章的今天為止,內建 Unity 編輯器的 tag 只能新增或刪除,連換標籤排列順序都沒辦法,另一個問題同 GameObject.Find(name)
,當標籤名稱被修改後,很有可能會沒改到程式碼的標籤名稱,少量用標籤還可以,別過度依賴標籤會比較好。
以下範例出自 Unity 官方手冊 GameObject.FindWithTag
using UnityEngine;
using System.Collections;
public class ExampleClass : MonoBehaviour
{
public GameObject respawnPrefab;
public GameObject respawn;
void Start()
{
if (respawn == null)
respawn = GameObject.FindWithTag("Respawn");
Instantiate(respawnPrefab, respawn.transform.position, respawn.transform.rotation);
}
}
4. 尋找物件相關方法
需注意 Find 相關的方法很吃效能,所以注意盡量別在 Update()
這種循環的方法調用 Find 相關的方法,盡量用變數去儲存重複性的結果。
找單一腳本組件
單一腳本組件使用 FindObjectOfType()
,Unity 2023 年後的版本請改用 FindFirstObjectByType()
。
如果組件是唯一的,例如 Player, GameManager 這種在場景中只有唯一一個就很適合這種寫法。
ExampleClass.cs
using UnityEngine;
public class ExampleClass : MonoBehaviour
{
private GameManager gameManager;
void Example()
{
gameManager = FindObjectOfType<GameManager>();
}
}
找到全部腳本組件
如果組件有多個,就要改用 FindObjectsOfType()
,Unity 2023 年後的版本請改用 FindObjectsByType()
。
ExampleClass.cs
using UnityEngine;
public class ExampleClass : MonoBehaviour
{
// 僅供參考,並未實作 Brick 類別
private Brick[] bricks;
void Example()
{
bricks = FindObjectsOfType<Brick>();
}
}
參考
一杯咖啡的力量,勝過千言萬語的感謝。
支持我一杯咖啡,讓我繼續創作優質內容,與您分享更多知識與樂趣!