第22章 SpriteAnimationPreview(スプライト一覧の表示) - エディター拡張入門

第22章 SpriteAnimationPreview(スプライト一覧の表示)


22.1 概要

スプライトを登録している AnimationClip をインスペクターのプレビュー画面で一覧表示してみます。

AnimationClip に登録されているスプライトの確認方法

AnimationClip に登録されているスプライトの確認方法はいくつかあります。

Animation ウィンドウで確認する

正規で行える唯一の確認方法です。

Window → Animation からウィンドウを表示できる

図22.1: Window → Animation からウィンドウを表示できる

ですが、この方法だとアニメーションを行うゲームオブジェクトを選択しないと Animation ウィンドウでは確認できません。

AnimationClip をインスペクターの Debug モードで確認する

Inspector ウィンドウ右上の ≡ ボタンを押して Debug を選択する

図22.2: Inspector ウィンドウ右上の ≡ ボタンを押して Debug を選択する

すると、Pptr Curve Mapping というプロパティーでスプライトを参照している配列を確認できます。このスプライトが AnimationClip で使用されているスプライトです。

もっと手軽にスプライトを確認する方法を確立する

本章では CustomPreview (カスタムプレビュー) を使用してインスペクター上で確認するようにします。

22.2 CustomPreview の作成

カスタムプレビューを作成するのは簡単です。作り方は、カスタムエディターを作成する方法と同じで、ObjectPreview を継承したクラスを作成し、CustomPreview の属性を付加します。

[CustomPreview(typeof(AnimationClip))]
public class SpritePreview : ObjectPreview
{
}

次に、インスペクターにプレビューを追加するには HasPreviewGUIGetPreviewTitle を設定する必要があります。

[CustomPreview(typeof(AnimationClip))]
public class SpritePreview : ObjectPreview
{
    private GUIContent previewTitle = new GUIContent("Sprites");

    public override bool HasPreviewGUI()
    {
        return true;
    }

    public override GUIContent GetPreviewTitle()
    {
        return previewTitle;
    }
}
カスタムプレビューが追加された状態。プレビュータイトルをクリックすことによって他のプレビューへと変更できる

図22.3: カスタムプレビューが追加された状態。プレビュータイトルをクリックすことによって他のプレビューへと変更できる

カスタムプレビューは複数持つことができる

カスタムエディターとは違い、1つのオブジェクトに対して複数のプレビューを持つことができます。

イメージとしては、インスペクターに表示されているエディターオブジェクトには必ず「デフォルトプレビュー」というものが存在します。それらに付属するものとしてカスタムエディターは実装されます。その状態が「fireball」と「Sprites」が表示されている 図22.3 です。

イメージは次の画像のようになります。

デフォルトプレビューとカスタムプレビュー。複数のプレビューを持つことができる。

図22.4: デフォルトプレビューとカスタムプレビュー。複数のプレビューを持つことができる。

プレビューの表示

プレビューを行うには OnPreviewGUI を使用します。試しに次のコードを追加してプレビューとして使用可能な範囲を確かめてみます。

public override void OnPreviewGUI(Rect r, GUIStyle background)
{
    GUI.Box(r, "表示領域");
}
赤線で囲まれた部分が使用可能な範囲

図22.5: 赤線で囲まれた部分が使用可能な範囲

さて、簡単にではありますが、カスタムプレビューの基礎となる機能を触ることができました。次は AnimationClip に登録されているスプライトを表示していきます。

22.3 AnimationClip が参照しているスプライトを取得する

まずはスプライトを取得します。

スプライトは ObjectReferenceKeyframe で管理されている

何をアニメーションさせるかで使用するキーフレームが変化します。AnimationClip には ObjectReferenceKeyframe と Keyframe の2種類のキーフレームがあります。普段良く目にするのは Keyframe の Float 型の値を保持するキーフレームです。それに対し、ObjectReferenceKeyframe は オブジェクトの参照を保持するキーフレームとなります。

ObjectReferenceKeyframe と Keyframe の違い

Position を操作するのは Keyframe、Sprite を操作するのは ObjectReferenceKeyframe

図22.6: Position を操作するのは Keyframe、Sprite を操作するのは ObjectReferenceKeyframe

保持する値が Float か参照かの違いはありますが、他は変わりありません。

ObjectReferenceKeyframe の参照を取得するには

例えば、SpriteRenderer の Sprite プロパティーで「アニメーション設定されているスプライトの一覧を取得する」ためには、AnimationClip に設定されている幾つものアニメーションカーブのプロパティーから、スプライトをアニメーションさせる特定のプロパティーを得なければいけません。そのための機能として EditorCurveBinding があります。

EditorCurveBinding

EditorCurveBinding は特定のアニメーションカーブを取得するためのキーとなるものです。例えば、本章のように SpriteRenderer の Sprite をアニメーションさせたい場合は、次のコードになります。

EditorCurveBinding.PPtrCurve("", typeof(SpriteRenderer), "m_Sprite");

これは、ルートのゲームオブジェクトにアタッチされている SpriteRenderer コンポーネントの m_Sprite(Sprite)プロパティー、のことを指しています。

AnimationUtility.GetObjectReferenceCurve

AnimationClip と EditorCurveBinding を使用して Sprite の参照が格納されている ObjectReferenceKeyframe を取得するために AnimationUtility.GetObjectReferenceCurve を使用します。

private Sprite[] GetSprites(AnimationClip animationClip)
{
  var sprites = new Sprite[0];

  if (animationClip != null)
  {
    var editorCurveBinding =
      EditorCurveBinding.PPtrCurve("", typeof(SpriteRenderer), "m_Sprite");

    var objectReferenceKeyframes =
      AnimationUtility.GetObjectReferenceCurve(animationClip, editorCurveBinding);

    sprites = objectReferenceKeyframes
       .Select(objectReferenceKeyframe => objectReferenceKeyframe.value)
       .OfType<Sprite>()
       .ToArray();
  }

  return sprites;
}

22.4 スプライトの表示(その1)

各スプライトとスプライト名が表示されている

図22.7: 各スプライトとスプライト名が表示されている

スプライトを表示していきます。限られた範囲の中で複数のスプライトを表示するには、1つの GUI 要素に使用できる大きさ(Rect)を求めなければいけません。ですがそれは面倒なので GUI.SelectionGrid を使用しましょう。

public override void OnPreviewGUI(Rect r, GUIStyle background)
{
    var sprites = GetSprites(target as AnimationClip);

    var guiContents = sprites
       .Select(s => new GUIContent(s.name, AssetPreview.GetAssetPreview(s)))
       .ToArray();

    GUI.SelectionGrid(r, -1, guiContents, 2, EditorStyles.whiteBoldLabel);
}

GUI.SelectionGrid はかなり優れもので、決められた Rect 値の範囲内で均等になるように GUIContent を配置できます。


22.5 スプライトの表示(その2)

Unity 標準の表示機能でスプライトを表示

図22.8: Unity 標準の表示機能でスプライトを表示

図22.8 のような表示の仕方は見たことがあるかもしれません。この表示はスプライトを複数選択した時にプレビューとして表示されるものとほぼ同じものです。(図22.9)

スプライトを複数選択した状態

図22.9: スプライトを複数選択した状態

今回は AnimationClip を選択した時に、複数のスプライトを選択したと判断させる実装を施します。

Initialize メソッド

CustomPreview クラスには初期化を行う Initialize メソッドが存在します。オーバーライドが可能な作りになっているので少し手を加えます。

public override void Initialize(Object[] targets)
{
    base.Initialize(targets);

    var sprites = new Object[0];

    foreach (AnimationClip animationClip in targets)
    {
        ArrayUtility.AddRange(ref sprites, GetSprites(animationClip));
    }

    m_Targets = sprites;
}

通常の初期化を行った後、ターゲットとなるオブジェクトを差し替えます。m_Targets がプレビューで扱う UnityEngine.Object の配列です。

あとは OnPreviewGUI を実装するだけです。OnPreviewGUIm_Targets の要素の数だけ呼び出されます。OnPreviewGUI が呼び出された時の m_Targets の要素はそれぞれ target に格納され、取得できる r (Rect) はプレビュー範囲全体ではなくすでに計算された1要素分だけの範囲となります。

m_Targets に要素が1つの時と、6つの時の引数 r で得られる範囲

図22.10: m_Targets に要素が1つの時と、6つの時の引数 r で得られる範囲

次にスプライトを描画しますが、GUI クラスにスプライトを描画する機能は存在しません。そこで、オブジェクトに対してプレビュー用のテクスチャを生成&取得できる AssetPreview.GetAssetPreview を使用します。

public override void OnPreviewGUI(Rect r, GUIStyle background)
{
    var previewTexture = AssetPreview.GetAssetPreview(target);
    EditorGUI.DrawTextureTransparent(r, previewTexture);
}

初回だけプレビュー画像が表示されない

ここで AssetPreview.GetAssetPreview の特徴を掴んでおきましょう。GetAssetPreview で作成するプレビュー用のテクスチャはキャッシュされています。もし、オブジェクトに対するテクスチャがキャッシュされていない場合(つまり初回)はテクスチャの生成を行うために GetAssetPreview は null を返します。null を返した時のその状態が 図22.11 です。

テクスチャが null なためスプライト名だけが表示されている

図22.11: テクスチャが null なためスプライト名だけが表示されている

本来であれば AssetPreview.IsLoadingAssetPreview を使用してロード中(生成中)かどうかを判断し、スキップする仕組みを実装しなければいけません

if (AssetPreview.IsLoadingAssetPreview (target.GetInstanceID ())) {
    var previewTexture = AssetPreview.GetAssetPreview (target);
    EditorGUI.DrawTextureTransparent (r, previewTexture);
}

ですが、この問題を回避するにはもう1つ簡単な方法で回避することもできます。プレビュー前にプレビュー用のテクスチャをキャッシュさせればいいので適当なところで AssetPreview.GetAssetPreview を実行します。

public override void Initialize(Object[] targets)
{
    base.Initialize(targets);

    var sprites = new Object[0];

    foreach (AnimationClip animationClip in targets)
    {
        ArrayUtility.AddRange(ref sprites, GetSprites(animationClip));
    }

    //ここでスプライトのプレビュー用テクスチャを生成&キャッシュさせる
    foreach (var sprite in sprites)
    {
        AssetPreview.GetAssetPreview(sprite);
    }

    m_Targets = sprites;
}

これで、テクスチャが null でプレビューが表示されないという問題は解決しました。

第21章 パーティクルを制御する 第23章 SpriteAnimationPreview(スプライトアニメーション)