第2章 標準で使えるエディター拡張機能 - エディター拡張入門

第2章 標準で使えるエディター拡張機能

Unity エディターは、すでに内部でエディター拡張の機能を使い、さまざまな拡張を行っています。ユーザーは属性(Attribute)を付けるだけで、それらの拡張にアクセスすることができ、自由にカスタマイズを行えます。この章ではすでに用意されているものを使ってみて「エディター拡張ではこういうことができる」ということを知りましょう。

2.1 インスペクターの見た目を変える

Range

int、float、long、double など数値をスライダーで変更できるようにするための機能です。

開発時には「なんとなくな値」で「それとない動き」を作ることはよくあります。それを普段のインスペクター表示で行うには、不向きなところが出てきます。それの解決策の1つとしてスライダーで操作できるようにして操作性を上げる方法があります。

左がデフォルト、右が Range で変更したもの

図2.1: 左がデフォルト、右が Range で変更したもの

using UnityEngine;
using System.Collections;

public class NewBehaviourScript : MonoBehaviour
{
    [Range(1, 10)]
    public int num1;

    [Range(1, 10)]
    public float num2;

    [Range(1, 10)]
    public long num3;

    [Range(1, 10)]
    public double num4;
}

Multiline / TextArea

デフォルトでは一行の TextField ですが、複数行の TextArea に変更できます。Multiline と TextArea は、ほぼ機能は同じですが、Multiline は「幅に合わせて自動改行されない」「スクロールバーが表示されない」といった制限があります。特に理由のない限り TextArea を使うことをオススメします。

上が Multiline。下が TextArea。TextArea はスクロールバーが表示される

図2.2: 上が Multiline。下が TextArea。TextArea はスクロールバーが表示される

using UnityEngine;
using System.Collections;

public class NewBehaviourScript : MonoBehaviour
{
    [Multiline(5)]
    public string multiline;

    [TextArea(3, 5)]
    public string textArea;
}

2.2 インスペクターで扱う機能を追加する

ContextMenuItem

インスペクターに表示されている変数にコンテキストメニューを追加します。少し手順が必要な要素を自動化したり、他の変数も一緒に値を変更しなければいけない時に使用してみるといいかもしれません。また、コンポーネント単位で値をリセットできる「Reset」機能はありますが、各変数に対してのリセット機能がないので ContextMenuItem で実装してみるのもいいかもしれません。

プロパティー名を右クリックするとコンテキストメニューが表示できる

図2.3: プロパティー名を右クリックするとコンテキストメニューが表示できる

using UnityEngine;

public class NewBehaviourScript : MonoBehaviour
{
    [ContextMenuItem ("Random", "RandomNumber")]
    [ContextMenuItem ("Reset", "ResetNumber")]
    public int number;

    void RandomNumber ()
    {
        number = Random.Range (0, 100);
    }

    void ResetNumber ()
    {
        number = 0;
    }
}

ColorUsage

色の変更にはカラーピッカーを使用します。ColorUsage は、カラーピッカーでアルファの使用を有効/無効にできたり、HDR 用のカラーピッカーに変更できます。

左からデフォルト、アルファなし、HDR カラーピッカー

図2.4: 左からデフォルト、アルファなし、HDR カラーピッカー

using UnityEngine;

public class NewBehaviourScript : MonoBehaviour
{
    public Color color1;

    [ColorUsage (false)]
    public Color color2;

    [ColorUsage (true, true, 0, 8, 0.125f, 3)]
    public Color color3;
}

2.3 インスペクターの見た目を整える

プロパティーに直接作用しませんが、見た目を見やすくしたり装飾を行うことが可能です。

Header

プロパティーをある程度にまとめてヘッダーを付けることにより、わかりやすくすることが可能です。

ヘッダーを付けることによって区切りがつき、見やすくなる

図2.5: ヘッダーを付けることによって区切りがつき、見やすくなる

using UnityEngine;
using System;

public class NewBehaviourScript : MonoBehaviour
{
    [Header("Player Settings")]
    public Player player;

    [Serializable]
    public class Player
    {
        public string name;

        [Range(1,100)]
        public int hp;
    }

    [Header("Game Settings")]
    public Color background;
}

Space

縦に余白を設けることができます。プロパティーとの間に余白を設けることで、見やすくしたいときに便利です。

図2.6:

using UnityEngine;

public class NewBehaviourScript : MonoBehaviour
{
    [Space(16)]
    public string str1;

    [Space(48)]
    public string str2;
}

Tooltip

プロパティーに対する説明をインスペクター上で確認したいときに使用します。

「Tooltip」にマウスカーソルを乗せるとツールチップが表示される

図2.7: 「Tooltip」にマウスカーソルを乗せるとツールチップが表示される

using UnityEngine;

public class NewBehaviourScript : MonoBehaviour
{
    [Tooltip("これはツールチップです")]
    public long tooltip;
}

HideInInspector

本来 public 変数はインスペクターに表示されるが str2が表示されていない

図2.8: 本来 public 変数はインスペクターに表示されるが str2が表示されていない

using UnityEngine;

public class NewBehaviourScript : MonoBehaviour
{
    public string str1;

    [HideInInspector]
    public string str2;
}

2.4 インスペクターを少し便利にする

RequireComponent

最低1つは指定されたコンポーネントがアタッチされてなければいけない」という制限を行う属性です。

他のコンポーネントが、必須となるスクリプトを作成することはよくあります。例えば、Animator に関するスクリプトを作成した時は、必ず Animator コンポーネントが必要となります。必須なコンポーネントをアタッチするのを忘れてしまうと、当然ながらエラーが発生します。このエラーを事前に防ぐために RequireComponent を使用します。

RequireComponent が付いたスクリプトをアタッチすると、自動的に RequireComponent で指定しているコンポーネントもアタッチされます。すでにアタッチされている場合は何も行いません。そして指定されたコンポーネントを削除しようとすると「削除できません」という内容のダイアログが表示されます。

Animator コンポーネントを削除しようとするとダイアログが表示される

図2.9: Animator コンポーネントを削除しようとするとダイアログが表示される

using UnityEngine;

[RequireComponent(typeof(Animator))]
public class NewBehaviourScript : MonoBehaviour
{
    Animator animator;

    void Awake ()
    {
        animator = GetComponent<Animator> ();
    }
}

DisallowMultipleComponent

1つのゲームオブジェクトに同じコンポーネントを複数アタッチすることを禁止する属性です。

1つだけアタッチされてればいいのに気付かずに、同じコンポーネントを2つ以上アタッチしている場合があります。2つ以上アタッチされている状態でゲームを再生すると上手く動作せず「コードは正しいのになぜか動作しない」という原因不明の状態に陥りやすくなります。同じコンポーネントが2つアタッチされていた、というのは意外と気付きにくいものです。この問題を回避するために DisallowMultipleComponent を使用します。

同じスクリプトをアタッチしようとするとダイアログが表示される

図2.10: 同じスクリプトをアタッチしようとするとダイアログが表示される

継承クラスでも DisallowMultipleComponent は効果を発揮します。

using UnityEngine;

public class NewBehaviourScript : Base
{
}
using UnityEngine;

[DisallowMultipleComponent]
public class Base : MonoBehaviour
{
}

FormerlySerializedAs

変数名を変更した時に新しい変数名へデータの移行を行うための属性です。

インスペクターに表示されるような、シリアライズされているデータは変数名をパスとして保存されています。ここで変数名の変更を行うとシリアライズされていたデータが引き継げずに初期化されてしまいます。これはパスから値を参照できなくなったためです。変数名が変更されたという検知をするのは不可能でこれはどうしようもない問題です。この問題を回避するために FormerlySerializedAs を使用します。

using UnityEngine;

public class NewBehaviourScript : MonoBehaviour
{
    [SerializeField]
    string hoge;
}

インスペクター上で値を入力します。

hoge から fuga へと変数名を変更します。

sing UnityEngine;

public class NewBehaviourScript : MonoBehaviour
{
    [SerializeField]
    string fuga;
}

するとインスペクターで入力した文字列は引き継がれずに何も表示されません。

そこで FormerlySerializedAs を使用することで引き継がれるようになります。

using UnityEngine;
using UnityEngine.Serialization;

public class NewBehaviourScript : MonoBehaviour
{
    [SerializeField]
    [FormerlySerializedAs("hoge")]
    string fuga;
}

ここで気を付けて欲しいのが、変数名の変更と FormerlySerializedAs 属性の追加、oldName の指定は同時に行わなければいけない点です。スクリプトのコンパイルによって必要ないデータは破棄されてしまい、変数名を変更(コンパイル1回目)したにもかかわらず、FormerlySerializedAs の指定を忘れていて、慌てて追加(コンパイル2回目)してもすでにデータは破棄されています。2回目のコンパイルで、データが破棄されるためです。

AddComponentMenu

Component メニューにメニュー項目を追加します。

スクリプトはすべて Component/Scripts メニューの中にまとめられます。汎用的なスクリプトコンポーネント群を作成した時、1つのカテゴリにまとめたい時があります。例えば UI に関するスクリプトを作成した時などです。

TweenColor/Position/Rotation/Scale.cs を作成した状態

図2.11: TweenColor/Position/Rotation/Scale.cs を作成した状態

この時に AddComponentMenu を使用すると別メニューとして作成できます。AddComponentMenu を使用したスクリプトは他の場所にメニューが作成されるので Component/Scripts メニューの中から削除されます。

using UnityEngine;

[AddComponentMenu("MyUI/Tween Color")]
public class TweenColor : MonoBehaviour
{
}
My UI下に移動し、カテゴライズできた

図2.12: My UI下に移動し、カテゴライズできた

2.5 ゲームの開発を楽にする

ExecuteInEditMode

ゲーム再生中でなくても MonoBehaviour を継承したコンポーネントの 主要な関数が呼び出されるようになります。呼び出されるタイミングはゲームオブジェクトが更新された時です。シーンアセットをダブルクリックして、シーンをロードした時には Awake と Start 関数が、インスペクターなどでコンポーネントの変数などを変更したら Update 関数が呼び出されます。また、OnGUI で実装した GUI がエディターの GUI 描画サイクルに則って常に表示されるようになります。

using UnityEngine;

[ExecuteInEditMode]
public class NewBehaviourScript : MonoBehaviour
{
    [Range(0,10)]
    public int number;

    void Awake ()
    {
        Debug.Log ("Awake");
    }

    void Start ()
    {
        Debug.Log ("Start");
    }

    void Update ()
    {
        Debug.Log ("Update");
    }
}

ContextMenu

コンポーネントのコンテキストメニューからメソッドを実行します。ContextMenuItem と名前と機能が似ていますが、追加するコンテキストメニューの場所が違います。

コンポーネントの歯車をクリックまたはコンポーネントのタイトルを右クリック

図2.13: コンポーネントの歯車をクリックまたはコンポーネントのタイトルを右クリック

using UnityEngine;

public class NewBehaviourScript : MonoBehaviour
{
    [Range (0, 10)]
    public int number;

    [ContextMenu ("RandomNumber")]
    void RandomNumber ()
    {
        number = Random.Range (0, 100);
    }

    [ContextMenu ("ResetNumber")]
    void ResetNumber ()
    {
        number = 0;
    }
}

SelectionBase

シーンビューでオブジェクトを選択した時に、「選択されるゲームオブジェクトを指定する」「ゲームオブジェクトの選択順を決める」時に使用します。

普段、シーンビューでオブジェクトを選択した時はルートのゲームオブジェクトが選択されます。

Cube をマウスでクリックした直後の状態

図2.14: Cube をマウスでクリックした直後の状態

次のスクリプトを記述し、子要素のゲームオブジェクトにアタッチします。

using UnityEngine;

[SelectionBase]
public class NewBehaviourScript : MonoBehaviour
{
}

そしてルートのゲームオブジェクトをクリックすると、子要素のゲームオブジェクトが選択されるようになります。

Cube をマウスでクリックした直後の状態

図2.15: Cube をマウスでクリックした直後の状態

そしてもう一度ゲームオブジェクトをクリックするとルートのゲームオブジェクトが選択されます。このように、SelectionBase 属性の付いたゲームオブジェクトで最下層の子要素から順に選択できます。順に選択していき、SelectionBase 属性の付いたゲームオブジェクトが存在しない場合はルートを選択するという仕様です。

クリックするごとに下から順に選択されていく

図2.16: クリックするごとに下から順に選択されていく

第1章 エディター拡張で使用するフォルダー 第3章 データの保存