タイトル: オブジェクトの取得とコンポーネントの取得
SEOタイトル: Unity GameObject/Component 取得完全ガイド(Find は重い/SerializeField 推奨)
| この記事の要点 |
|
GameObject 取得の主要 API
| API | 戻り値 | コスト | 用途 |
|---|---|---|---|
GameObject.Find("Name") | GameObject | ★★★(重い、シーン全走査) | 起動時 1 回限定 |
GameObject.FindGameObjectWithTag("Tag") | GameObject | ★★(Find より速い) | タグ付け済オブジェクト取得 |
GameObject.FindGameObjectsWithTag("Tag") | GameObject[] | ★★ | 同タグ複数取得 |
FindObjectsOfType<T>() | T[] | ★★★(極重) | シーン横断検索 |
FindFirstObjectByType<T>() | T | ★★ | Unity 2022.3+ 推奨 |
[SerializeField] T field | T | ★(直接代入) | 推奨 |
サンプル: 各取得方法
using UnityEngine;
public class FindSample : MonoBehaviour
{
// ★ 推奨: Inspector でアサイン
[SerializeField] private GameObject player;
[SerializeField] private Camera mainCam;
void Start()
{
// 名前で検索(重い、起動時のみ)
var enemy = GameObject.Find("Enemy");
// タグで検索(マシだが it も重い)
var goal = GameObject.FindGameObjectWithTag("Goal");
// 全タグ一致を配列取得
var enemies = GameObject.FindGameObjectsWithTag("Enemy");
// 型から検索(最も重い)
// Unity 2022.3 以降は FindFirstObjectByType を使う
var manager = FindFirstObjectByType<GameManager>();
// ★ Camera.main は内部キャッシュ済(GameObject.Find より速いが過信注意)
var cam = Camera.main;
}
}
Component 取得の主要 API
| API | 探索範囲 | 備考 |
|---|---|---|
GetComponent<T>() | 同じ GameObject | 基本 |
GetComponentInChildren<T>() | 自身+子孫 | 非アクティブ含めるオプションあり |
GetComponentInParent<T>() | 自身+祖先 | 同上 |
GetComponents<T>() | 同 GO の T 全部 | 配列で返る |
GetComponentsInChildren<T>() | 自身+子孫の全 T | UI のリスト取得に便利 |
TryGetComponent<T>(out T x) | 同 GO | 未存在で null を返さず bool。GC ゼロ |
キャッシュ推奨パターン
Update 内で毎フレーム GetComponent を呼ぶのは厳禁です。Awake / Start で取得して private フィールドに保持してください。
public class PlayerController : MonoBehaviour
{
// ★ 1. SerializeField で Inspector からアサインが最速・最安全
[SerializeField] private Rigidbody rb;
[SerializeField] private Animator anim;
// ★ 2. 同 GameObject 内ならコードで Awake キャッシュも可
private AudioSource _audio;
void Awake()
{
// null チェックは TryGetComponent が便利
if (!TryGetComponent(out _audio)) {
Debug.LogWarning("AudioSource not found, adding one.");
_audio = gameObject.AddComponent<AudioSource>();
}
}
void Update()
{
// ✅ キャッシュ済を使う
rb.AddForce(Vector3.up);
anim.SetFloat("Speed", rb.velocity.magnitude);
// ❌ NG: 毎フレーム取得
// GetComponent<Rigidbody>().AddForce(Vector3.up);
}
}
子孫からの取得
// 自分含む子の AudioSource を 1 つ取得
var aud = GetComponentInChildren<AudioSource>(includeInactive: true);
// 子孫の Renderer を全部
var renderers = GetComponentsInChildren<Renderer>();
foreach (var r in renderers) r.enabled = false;
UnityEvent と デリゲートでイベント連携
毎回 Find で参照する代わりに、イベント駆動にすると依存が逆転し、テストも容易になります。
using UnityEngine;
using UnityEngine.Events;
public class Health : MonoBehaviour
{
[SerializeField] private UnityEvent onDead;
public void TakeDamage(int dmg)
{
// ...
if (dmg >= 100) onDead.Invoke();
}
}
// → 受け取り側を Inspector でアサインしておけば Find 不要
パフォーマンス比較(概略)
| 呼び方 | 相対コスト | 毎フレーム呼んで良い? |
|---|---|---|
| 直接 SerializeField 参照 | 1 | はい |
| キャッシュ済の参照 | 1 | はい |
GetComponent<T> | ~10 | 避ける |
GetComponentInChildren | ~30 | 避ける |
FindGameObjectWithTag | ~50 | NG |
GameObject.Find | ~200 | NG |
FindObjectsOfType | ~500+ | 絶対 NG |
Camera.main の注意
Camera.main は Unity 内部で "MainCamera" タグ付きカメラを返します。初回呼び出しはキャッシュ生成があるため、頻繁にアクセスするなら自分でキャッシュしてください。
private Camera _cam;
void Awake() { _cam = Camera.main; }
void Update() {
var world = _cam.ScreenToWorldPoint(Input.mousePosition);
}
シングルトンと DI の検討
頻繁に「ゲーム全体で 1 つ」のオブジェクトにアクセスするなら、シングルトンや DI コンテナ(VContainer / Zenject)を使うほうが、Find より高速かつテストしやすくなります。
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
void Awake()
{
if (Instance != null && Instance != this) { Destroy(gameObject); return; }
Instance = this;
DontDestroyOnLoad(gameObject);
}
}
// 利用側
GameManager.Instance.AddScore(10);
FAQ
Q: GameObject.Find が動かない
A: 非アクティブなオブジェクトは Find できません。タグ検索(FindGameObjectWithTag)でも同様です。FindObjectsOfType(true) の引数で includeInactive を渡してください。
Q: SerializeField と public、どちらを使う?
A: [SerializeField] private が推奨。外部から書き換えられず、Inspector では編集可能というカプセル化が両立します。
Q: Find は何回までなら許容?
A: 1 シーンの起動時に数回程度なら問題なし。Update / FixedUpdate 内では絶対 NG です。