TagHandleとは?

TagHandleはUnity 2023.2で追加された、Tag情報を表すための軽量な値型です。Unity 6(6000.0系)であれば問題なく使えます。 これを使うと、CompareTagに文字列ではなくTagHandleを渡せるようになり、内部での文字列比較を回避できます。

考え方としてはAnimator.StringToHashやShader.PropertyToIDと同じで、「事前に解決して整数(ハンドル)にしておき、毎フレームの呼び出しではそれを使い回す」というアプローチです。

なぜTagHandleが必要なのか

CompareTagは内部で最適化されているとはいえ、引数で受け取った文字列をUnity側のタグテーブルと突き合わせる処理が毎回走ります。

Update内で毎フレーム呼ばれる衝突判定など、呼び出し回数が多い箇所ではこの文字列照合が積み重なります。

体感差が出にくい場面も多いですが、弾幕系や大量のオブジェクト判定では意外と無視できないコストになります。

そこで、文字列をあらかじめTagHandleに変換しておき、CompareTagにはそのハンドルを渡すようにすると、ハンドル同士の比較だけで済むので余分な処理が省けます。

基本的な使い方

TagHandleはTagHandle.GetExistingTagでタグ名から取得します。 取得はキャッシュ可能なので、Awakeなど初期化時に一度だけ呼び出すのがおすすめです。

using UnityEngine;

public class EnemyDetector : MonoBehaviour
{
    // タグハンドルをキャッシュしておく
    private TagHandle _enemyTag;

    private void Awake()
    {
        // "Enemy"タグのハンドルを取得
        _enemyTag = TagHandle.GetExistingTag("Enemy");
    }

    private void OnTriggerEnter(Collider other)
    {
        // 文字列ではなくTagHandleを渡して比較
        if (other.CompareTag(_enemyTag))
        {
            Debug.Log("敵に触れた");
        }
    }
}

CompareTagはGameObjectとComponent双方にオーバーロードが用意されているので、ColliderやTransformから直接呼び出せます。

複数タグを扱う場合

複数タグがある場面では、ハンドルをまとめてフィールドに宣言しておくと扱いやすいです。 判定対象が増えるほどキャッシュしたTagHandleを使い回す形がシンプルで読みやすく、修正にも強くなります。

using UnityEngine;

public class HitDetector : MonoBehaviour
{
    private TagHandle _enemyTag;
    private TagHandle _bulletTag;

    private void Awake()
    {
        _enemyTag = TagHandle.GetExistingTag("Enemy");
        _bulletTag = TagHandle.GetExistingTag("Bullet");
    }

    private void OnTriggerStay(Collider other)
    {
        // 毎フレーム呼ばれる箇所こそハンドル化の恩恵が大きい
        if (other.CompareTag(_enemyTag))
        {
            // 敵との接触処理
        }
        else if (other.CompareTag(_bulletTag))
        {
            // 弾との接触処理
        }
    }
}

文字列がコード中に散らばらなくなるので、タイプミスの検出もしやすくなります。

従来のCompareTag(string)との性能差

体感レベルでの差は通常それほど大きくありませんが、毎フレーム数百〜数千回CompareTagが呼ばれるようなケースでは無視できない差になります。

体感差が出るのは弾幕系やMMOの大量判定など特殊ケースに限られるので、まずはプロファイラで実際にコストが出ているかを確認することをおすすめします。

おおまかな指針としてはこんなところです。

  • 1〜数回しか呼ばれない処理 → 従来のCompareTag(string)でも十分
  • OnTriggerStay、OnCollisionStay、シューティングの弾判定など高頻度処理 → TagHandle版に置き換える価値あり

プロファイラでCompareTagがホットになっている場合は、まずTagHandle版への置き換えを検討してみてください。

注意点

GetExistingTagは未登録タグでExceptionを投げる

GetExistingTagという名前のとおり、Tag Managerに存在しないタグ名を渡すとUnityExceptionが発生します。

タイポやTag未登録の状態でリリースしないよう、Awakeで取得して早期に気づくようにしておくと安全です。

存在チェックを省略してハンドルを取得したいときはTagHandle.GetTagが使えますが、これは主にエディタ拡張などの特殊用途向けです。 通常のゲームロジックではGetExistingTagを使い、未登録に気づきやすくしておくと安心です。

過剰な最適化はしない

CompareTag自体すでに十分軽い処理なので、すべての呼び出し箇所をTagHandle化する必要はありません。 プロファイラで実際にボトルネックになっている箇所だけを置き換える、というスタンスで十分です。

まとめ

TagHandleはCompareTagの引数を文字列からハンドルに置き換えるための軽量な型で、Unity 2023.2以降で利用できます。

  • TagHandle.GetExistingTagで一度だけ取得してキャッシュする
  • 高頻度に呼ばれるCompareTagの引数として渡す
  • 文字列照合が消えるぶん、毎フレーム処理での負荷が軽くなる

ホットな衝突判定があるプロジェクトから、ぜひ試してみてください。