Unity 在腳本取得遊戲物件或組件的方法


建立時間: 2022年10月22日 23:12
更新時間: 2023年9月14日 08:36

說明

已經開發 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 設定音效檔案
preview player inspector

你也可以改建立兩個變數

player.cs

public class Player : MonoBehaviour
{
    /// <summary>
    /// 死亡音效
    /// </summary>
    [SerializeField] private AudioClip _deathClip;
    /// <summary>
    /// 受傷音效
    /// </summary>
    [SerializeField] private AudioClip _hurtClip;
}

取得關閉遊戲物件

當遊戲物件關閉時,使用下列第3,4點跟 Find 有關的方法會找不到遊戲物件,此時可以建立一個空遊戲物件,並且是啟動的狀態,寫一個腳本放在這個空的遊戲物件,接著就可以使用此方法取得關閉的遊戲物件。

Active false game object

get active false game object by a script

using UnityEngine;

/// <summary>
/// 用來控制多個 UI 面板
/// </summary>
public class UIPanels : MonoBehaviour
{
    /// <summary>
    /// 關卡選擇 UI 面板
    /// </summary>
    [SerializeField]
    private GameObject levelSelectionPanel;

    /// <summary>
    /// 關卡選擇 UI 面板
    /// </summary>
    public GameObject LevelSelectionPanel
    {
        get => levelSelectionPanel;
    }
}

優點

  1. 可以在 Inspector 直接設定內容,在 Play 時設定的內容會回到 Play 之前的狀態
  2. 可以取得關閉的遊戲物件

缺點

  1. 只要改變數名稱,就需要重新在 Inspector 設定
  2. 如果參考依賴其他遊戲物件,其他遊戲物件刪除或改變時,要注意參考遺失的問題

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),當標籤名稱被修改後,很有可能會沒改到程式碼的標籤名稱,少量用標籤還可以,別過度依賴標籤會比較好。

Game Object Tag

以下範例出自 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>();
    }
}

參考

觀看次數: 1536
findloadresourceresourcestagunity
按讚追蹤 Enjoy 軟體 Facebook 粉絲專頁
每週分享資訊技術

一杯咖啡的力量,勝過千言萬語的感謝。

支持我一杯咖啡,讓我繼續創作優質內容,與您分享更多知識與樂趣!