インスペクターで設定できる色々な値(カスタム属性:アトリビュート)

2024年4月4日

Unityでは、ゲームオブジェクトを選択、自作のスクリプトをコンポーネントとしてアタッチすることができます。
自作の場合、初期値を設定・調整することでゲームバランスを考えたり、自作コンポーネントを再利用することができるようになります。

カスタム属性:アトリビュート

Unityのアトリビュート(Attributes)は、クラス、メソッド、変数などの宣言に追加情報を付与するために使用される特殊なマークです。これらは、コンパイラやUnityエディターに対して、コードの一部をどのように扱うべきか指示を与えるために使用されます。アトリビュートは角括弧([])で囲まれ、宣言の直前に配置されます。

Unityでは、いくつかの組み込みアトリビュートがあり、スクリプトの振る舞いを制御したり、インスペクターでの表示をカスタマイズしたりするのに役立ちます。以下はUnityでよく使用されるアトリビュートの例です。

  • SerializeField: 通常、プライベートフィールドはUnityエディターのインスペクターに表示されませんが、このアトリビュートを使用すると、プライベートフィールドでもインスペクターに表示させることができます。
  • HideInInspector: パブリックフィールドや[SerializeField]が適用されたフィールドでも、インスペクターから隠したい場合に使用します。
  • Range(min, max): 数値フィールドに対してスライダーを使用する範囲を指定します。インスペクターでの数値の入力を範囲内に制限します。
  • RequireComponent(type): あるコンポーネントがアタッチされているGameObjectに別の特定のコンポーネントも一緒にアタッチされるように要求します。これは依存関係を明示するのに役立ちます。
  • ContextMenu("MethodName"): インスペクターのコンテキストメニューにメニューオプションを追加し、指定したメソッドを呼び出せるようにします。
  • ExecuteInEditMode: スクリプトがエディター内でのみ動作し、ゲームがプレイ中でなくてもスクリプトのUpdateなどのメソッドが呼び出されるようにします。

これらのアトリビュートを使用することで、Unityでの開発プロセスがより効率的で、コントロールしやすいものになります。アトリビュートを適切に使用することで、コードの可読性が向上し、他の開発者がコードを理解しやすくなるほか、Unityエディターでの作業がより柔軟になります。

フィールド値

publicアクセス

スクリプト

public int hp;

インスペクター

privateアクセス

スクリプト

プライベートアクセスでは、属性[SerializeField]をつけることでインスペクターに表示できます
また、これはC#の仕様ですがprivateアクセス修飾子は省略することもできます。(privateがある場合と同意)

[SerializeField]
private int hp;

インスペクター

配列値

publicアクセス

スクリプト

public int[] scores = new int[5];

インスペクター

スクリプト(要素数をインスペクターで設定する場合)

public int[] scores;

インスペクター(要素数は、Scoresの数値設定で決めます)

List<>値

publicアクセス

スクリプト

public List<int> scores;

インスペクター(配列の場合と同じく要素数は、Scoresの値で設定します)

プロパティ値

publicアクセス

スクリプト

[field: SerializeField]
public int Hp { get; set; }

インスペクター

列挙値

publicアクセス

スクリプト

public enum Job
{
    Warrior,
    Wizard,
    Hero,
}

// インスペクターではプルダウンメニューで選択できるようになります
public Job job;
}

インスペクター

Vector3値

publicアクセス

スクリプト

public Vector3 vector3;

インスペクター

LayerMask値

publicアクセス

スクリプト

public LayerMask layerMask;

インスペクター(プルダウンメニューからマスクを1つ以上選択することができます)

Color値

publicアクセス

スクリプト

public Color color;

インスペクター(カラーバーをクリックすると、ピッカーが表示されてビジュアルでの設定も可能になります)

画像

publicアクセス

スクリプト

public Sprite sprite;

インスペクター(スプライトに変換した画像をドラッグ&ドロップすることで登録できます

連続した値

publicアクセス

スクリプト

public AnimationCurve curve;

インスペクター(線形のグラフを登録できます)

グラフ部分をクリックすると入力画面になります

スクリプト

値の取得ができます
x軸を0から1と考え、例えば.0.3の場所の値を取得する場合、次のようになります

public AnimationCurve curve;

void Start()
{
    Debug.Log(curve.Evaluate(0.3f));
}

値の取得ができます
キーフレームを追加するとその数だけ値を取得することができます
キーフレームは配列に格納されます

public AnimationCurve curve;

void Start()
{
    for (int i = 0; i < curve.length; i++)
    {
        Debug.Log(curve.Evaluate(0.3f));
    } 
}

コンポーネント

publicアクセス

スクリプト

public Transform tran;

インスペクター

サンプル

Cubeを1つ作ります
Positionに適当な値を代入してみます
この値を取得するサンプルになります

空のゲームオブジェクトを1つ作成して、作成したInspectorSetスクリプトをアタッチします
TranのところにHierarchyからCubeゲームオブジェクトをドラッグ&ドロップして登録します

確認のためのコードを追記します

public Transform tran;

void Start()
{
    Debug.Log(tran.position);
}

実行してみましょう
登録したCubeのポジションが表示されるはずです

(10.00, 20.00, 30.00)

参考)Transform型のところをGameObject型で宣言した場合の同一コード
このコードに変更した場合には、再度CubeをInspectorウィンドウでドラッグ&ドロップしてください。変更直後は、Noneになっています

public GameObject obj;

void Start()
{
    Debug.Log(obj.transform.position);
}

参考)GameObject.Findを使った場合
Inspectorウィンドウでドラッグ&ドロップできません
HierarchyウィンドウからCubeという名前のオブジェクトを探してくる用途に使います

Findを使う場面

  • 動的に存在するオブジェクト(リアルタイムで追加変更される敵やアイテム)を取得する必要がある場合
  • Prefabにアタッチされるスクリプトはこの書き方しかできません(Prefabが最初からシーンにないため)

Findを使うデメリット

スクリプト中にマジックナンバー(今回は"Cube"という固有の名前)が入るので、たとえば名前を変更した場合にコードの変更も必要になります。Inspectorで設定する場合は、自動的に差し替えられます

void Start()
{
    GameObject obj = GameObject.Find("Cube");

    Debug.Log(obj.transform.position);
}

自作クラスのインスタンス

publicアクセス

スクリプト

System.Serializable属性により、シリアル化します。インスタンスはメモリ上に作成されているため、Inspectorで読み書きできる固定情報にするためにシリアル化が必要になります。

[System.Serializable]
public class Player
{
    public string name;
    public int hp;
}

public class InspectorSet : MonoBehaviour
{
    public Player player;
}

スクリプト(インナークラスにした場合)

クラスの中にクラスを記述することができます
このインナークラスは、外部クラスから直接アクセスできません
必要に応じて使いましょう

[System.Serializable]
public class Player
{
    public string name;
    public int hp;
}

public Player player;

インスペクター

自作クラス(ジェネリッククラス)のインスタンス

unity 2020.1以降

publicアクセス

スクリプト

Playerクラスのhpの型をインスタンス作成時に決定することができます。

[System.Serializable]
public class Player<T>
{
    public string name;
    public T power;
}

public class InspectorSet : MonoBehaviour
{
    public Player<int> player1;
    public Player<float> player2;

    private void Start()
    {
        // コードの中で代入した場合、インスペクターの設定値よりこちらが適用されます
        // ジェネリッククラスを使うことで、player1のpowerはint型、player2のpowerはfloat型にすることができます
        player1.power = 10;
        player2.power = 10.3f;
    }
}

Player1のPowerに1.5など小数を入力しようとしても受け付けません( . が入力できません)
試してみましょう

自作クラスのList<>

publicアクセス

スクリプト

PlayerクラスとList<>の組み合わせも可能です。

[System.Serializable]
public class Player
{
    public string name;
    public int hp;
}

public class InspectorSet : MonoBehaviour
{
    public List<Player> players;
}

UnityEvent

publicアクセス

スクリプト

インスペクターにビジュアルで実行されるスクリプトをセットすることができるようになります。

using UnityEngine.Events;

public class InspectorSet : MonoBehaviour
{
    public UnityEvent sampleEvent;
}

UnityEvent(ビジュアルで設定してみる)

publicアクセス

スクリプト

インスペクターにビジュアルで実行されるスクリプトをセットすることができるようになります。

using UnityEngine.Events;

public class InspectorSet : MonoBehaviour
{
    public UnityEvent sampleEvent;

    public void ShowHello()
    {
        Debug.Log("Hello");
    }
}

+をクリックして、設定を追加する

実行したいスクリプトがアタッチされているゲームオブジェクトをNone(Object)にD&D

No Functionにプルダウンメニューから[InspectorSet(コンポーネント)] -> [ShowHello(publicメソッド名)]を選択

イベントの呼び出しコードを追加。実行してみる

using UnityEngine.Events;

public class InspectorSet : MonoBehaviour
{
    public UnityEvent sampleEvent;

    public void ShowHello()
    {
        Debug.Log("Hello");
    }

    void Start()
    {
        sampleEvent.Invoke();
    }
}

結果

UnityEvent(引数あり。ビジュアルで設定してみる)

publicアクセス

スクリプト

引数を渡す場合、UnityEventのジェネリックを使う必要があります。

デフォルトで MonoBehaviour の UnityEvent は動的に void 関数をバインドします。UnityEvent は 4 つまで引数を伴う関数バインドをサポートしていますので、これは動的実行のケースである必要はありません。これを実行するために、複数の引数をサポートするカスタム UnityEvent クラスを定義する必要があります。これは以下のように、本当に簡単にできます。

ここで、わざわざUnityEvent<>を継承し、派生クラスから宣言する必要があるのは、UnityEvent<>が抽象クラス(abstract クラス)のためです(Unity 2019.4で確認)

抽象クラスからは、直接インスタンスを作成できない仕様になっています

using UnityEngine.Events;

[System.Serializable]
public class SampleEvent : UnityEvent<string>
{
}

public class InspectorSet : MonoBehaviour
{
    public SampleEvent sampleEvent;

    public void ShowHello(string msg)
    {
        Debug.Log("Hello " + msg);
    }
}

追加情報)Unity 2020.2で確認したところ、UnityEvent<>は、抽象クラスではなくなっているようです。
なので、次のように直接宣言することができます

using UnityEngine.Events;

public class InspectorSet : MonoBehaviour
{
    public UnityEvent<string> sampleEvent;

    public void ShowHello(string msg)
    {
        Debug.Log("Hello " + msg);
    }
}

+をクリックして、設定を追加する

実行したいスクリプトがアタッチされているゲームオブジェクトをNone(Object)にD&D

No Functionにプルダウンメニューから[InspectorSet(コンポーネント)] ->Dynamic stringの [ShowHello(publicメソッド名)]を選択
Dynamic stringは、実行時に引数の値を受け渡すとき(今回)に選択します。

イベントの呼び出しコードを追加。実行してみる

using UnityEngine.Events;

[System.Serializable]
public class SampleEvent : UnityEvent<string>
{
}

public class InspectorSet : MonoBehaviour
{
    public SampleEvent sampleEvent;

    public void ShowHello(string msg)
    {
        Debug.Log("Hello " + msg);
    }

    void Start()
    {
        sampleEvent.Invoke("World");
    }
}

結果

設計時に値を固定にしたいときは、次のように設定します。

固定設定を空白に追加します

UnityEvent(引数に自作クラスを使う。ビジュアルで設定してみる)

publicアクセス

スクリプト

引数を自作クラスの型で渡す場合、UnityEvnetのジェネリックを使います。
継承するUnityEventの型パラメータをPlayerとします

using UnityEngine.Events;

public class Player
{
    public int hp;
}

[System.Serializable]
public class SampleEvent : UnityEvent<Player>
{
}

public class InspectorSet : MonoBehaviour
{
    public SampleEvent sampleEvent;

    public void ShowHp(Player p)
    {
        Debug.Log("Player HP =  " + p.hp);
    }
}

+をクリックして、設定を追加する

実行したいスクリプトがアタッチされているゲームオブジェクトをNone(Object)にD&D

No Functionにプルダウンメニューから[InspectorSet(コンポーネント)] ->Dynamic stringの [ShowHello(publicメソッド名)]を選択
Dynamic stringは、実行時に引数の値を受け渡すとき(今回)に選択します。

イベントの呼び出しコードを追加。実行してみる

using UnityEngine.Events;

public class Player
{
    public int hp;
}

[System.Serializable]
public class SampleEvent : UnityEvent<Player>
{
}

public class InspectorSet : MonoBehaviour
{
    public SampleEvent sampleEvent;

    Player player = new Player();

    private void Start()
    {
        player.hp = 10;

        sampleEvent.Invoke(player);
    }

    public void ShowHp(Player p)
    {
        Debug.Log("Player HP =  " + p.hp);
    }
}

結果

C#,Unity

Posted by hidepon