Unityエディター拡張のカスタムプレビュー

Unityエディター拡張のカスタムプレビュー:

はじめに

こんにちは、ソーシャルゲーム事業部のUnityエンジニアのです。

この記事はカヤックUnityアドベントカレンダー2018の16日目の記事になります。

今日の記事ではUnityエディター拡張のカスタムプレビューを紹介していきたいと思います。

カスタムプレビュー

TextureやMaterialなどプレビューできるものを選択すると、インスペクターのプレビューウィンドウで中身を確認できます。
プレビューできないものは、エディター拡張を使ってカスタムプレビューを作れば、プレビューができるようになります。

ここから、簡単なSpriteプレビュアーを作りながら、カスタムプレビューを作るためによく使う関数を見てみましょう

最低限の関数

コンポーネントのコード

using UnityEngine; 
 
public class SpritePreviewer : MonoBehaviour 
{ 
    public Sprite sprite; 
} 
カスタムエディターのコード。Editorフォルダ内へ入れます。

この中で注目してほしいのはOnPreviewGUIです。描画の処理はこの関数に書きます

[CustomEditor(typeof(SpritePreviewer))] 
public class SpritePreviewerEditor : Editor 
{ 
    private GUIContent _title = new GUIContent("スプライトプレビュアー"); 
 
    // プレビューウィンドウを表示するかどうか 
    public override bool HasPreviewGUI() 
    { 
        return true; 
    } 
 
    // 名前通り、プレビューウィンドウ名を設定する関数 
    public override GUIContent GetPreviewTitle() 
    { 
        return _title; 
    } 
 
    // プレビューウィンドウで描画させたい内容はここで書く 
    public override void OnPreviewGUI(Rect r, GUIStyle background) 
    { 
        var previewer = target as SpritePreviewer; 
        GUI.DrawTexture(r, AssetPreview.GetAssetPreview(previewer.sprite)); 
    } 
} 
SpritePreviewerコンポーネントをGameObjectに追加して、Sprite画像をコンポーネントにつけたら、こうなりました。

2018-11-15 15 17 41


OnPreviewSettings

AnimationClipのプレビューみたいに、動画の再生や再生速度の調整などのUIはOnPreviewSettings関数で実装されています。
例としては、スプライトを半分のサイズで描画する切り替えボタンを作ります。

private bool _halfSize = false; 
 
    // プレビューウィンドウで描画させたいものはここで書く 
    public override void OnPreviewGUI(Rect r, GUIStyle background) 
    { 
        var previewer = target as SpritePreviewer; 
        var rect = _halfSize ? new Rect(r.x, r.y, r.width / 2, r.height / 2) : r; 
        GUI.DrawTexture(rect, AssetPreview.GetAssetPreview(previewer.sprite)); 
    } 
 
    // プレビューウィンドウのヘッダーバーをカスタムする関数 
    public override void OnPreviewSettings() 
    { 
        _halfSize = GUILayout.Toggle(_halfSize, "0.5x"); 
    } 
off on
2018-11-19 18 09 53
2018-11-19 18 10 10

CustomPreviewAttributeとObjectPreview

新コンポーネントではなく、SpriteRendererに直接プレビューウィンドウを追加すればいいじゃないかと思って、Typeを変更してみたら

//[CustomEditor(typeof(SpritePreviewer))] 
[CustomEditor(typeof(SpriteRenderer))] 
public class SpritePreviewerEditor : Editor 
{ 
    ... 
 
    public override void OnPreviewGUI(Rect r, GUIStyle background) 
    { 
//     var previewer = target as SpritePreviewer; 
        var previewer = target as SpriteRenderer; 
        var rect = _halfSize ? new Rect(r.x, r.y, r.width / 2, r.height / 2) : r; 
        GUI.DrawTexture(rect, AssetPreview.GetAssetPreview(previewer.sprite)); 
    } 
} 


2018-11-19 19 41 35

なんか、インスペクターが変わりました。

何故かと言うと、CustomEditorアトリビュートはプレビューだけではなく、エンジン内部で実装されたインスペクターなどの要素も上書きするからです。

一般的に、上書きしないようにそのエディタークラスを継承すればいいですけど、Unity内部のSpriteRendererEditorはinternalで継承できないです。

今回はプレビューのカスタムだけ求めるので、CustomPreview*1ObjectPreviewを使えば解決できます。

// [CustomEditor(typeof(SpritePreviewer))] 
[CustomPreview(typeof(SpriteRenderer))] 
 
// CustomPreviewを使うために、ObjectPreviewを継承しなければいけない 
// public class SpritePreviewerEditor : Editor 
public class SpritePreviewerEditor : ObjectPreview 
{ 
    ... 
} 
変更前 CustomPreview
2018-11-19 19 41 35
2018-11-19 18 33 30
これでSpriteRendererでスプライトのプレビューができるようになりました。

最終のコードはこうなります

using UnityEngine; 
using UnityEditor; 
 
[CustomPreview(typeof(SpriteRenderer))] 
public class SpritePreviewerEditor : Editor 
{ 
    private GUIContent _title = new GUIContent("スプライトプレビュアー"); 
    private bool _halfSize = false; 
 
    // プレビューウィンドウを表示するかどうか 
    public override bool HasPreviewGUI() 
    { 
        return true; 
    } 
 
    // 名前通り、プレビューウィンドウ名を設定する関数 
    public override GUIContent GetPreviewTitle() 
    { 
        return _title; 
    } 
 
    // プレビューウィンドウで描画させたいものはここで書く 
    public override void OnPreviewGUI(Rect r, GUIStyle background) 
    { 
        var previewer = target as SpritePreviewer; 
        var rect = _halfSize ? new Rect(r.x, r.y, r.width / 2, r.height / 2) : r; 
        GUI.DrawTexture(rect, AssetPreview.GetAssetPreview(previewer.sprite)); 
    } 
 
    // プレビューウィンドウのヘッダーバーをカスタムする関数 
    public override void OnPreviewSettings() 
    { 
        _halfSize = GUILayout.Toggle(_halfSize, "0.5x"); 
    } 
} 

使用例

NGUIのUISpriteとUISpriteAnimationに基づいて実装したUISpriteAnimationプレビュアーを紹介します。
UISpriteAnimationはコマアニメを作るためのコンポーネントですが、エディター再生中しか動きを見れないので、いつでも見れるようにプレビュアーを作りました



11 -21-2018 11-29-08


サンプルコード

using UnityEngine; 
using UnityEditor; 
using System.Reflection; 
using System.Collections.Generic; 
 
[CustomEditor(typeof(UISprite))] 
// UISpriteInspectorのOnPreviewGUIを利用したいので継承する 
public class UISpriteAnimationPreviewer : UISpriteInspector 
{ 
    AnimationSetting _animSetting = null; 
 
    bool _isPlaying = false; 
    bool _hasAnimation = false; 
    float _speedScale = 1f; 
 
    protected override void OnEnable() 
    { 
        base.OnEnable(); 
        _hasAnimation = false; 
 
        var targetSprite = target as UISprite; 
        var spriteAnim = targetSprite.GetComponent<UISpriteAnimation>(); 
        if (spriteAnim != null) 
        { 
            _animSetting = new AnimationSetting(spriteAnim); 
            _hasAnimation = true; 
        } 
    } 
 
    // プレビューウィンドウで描画させたい内容はここで書く 
    public override void OnPreviewGUI(Rect rect, GUIStyle background) 
    { 
        var t = target as UISprite; 
        AnimationSetting setting = _animSetting; 
 
        if (!Application.isPlaying && _hasAnimation) 
        { 
            var spriteAnim = setting.anim; 
 
            BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Instance; 
            var field = typeof(UISpriteAnimation).GetField("mSpriteNames",flags); 
            var spriteNames = field.GetValue(spriteAnim) as List<string>; 
 
            // UISpriteAnimationのUpdate関数を参考にして、 
            // 何枚目のUISpriteを表示するか経過時間で計算する 
            setting.delta += ((float)EditorApplication.timeSinceStartup - setting.lastTime) * _speedScale; 
            setting.lastTime = (float)EditorApplication.timeSinceStartup; 
 
            if (spriteNames.Count > 0) 
            { 
                if (_isPlaying) 
                { 
                    float rate = 1f / spriteAnim.framesPerSecond; 
                    if (rate < setting.delta) 
                    { 
                        setting.delta = Mathf.Repeat(setting.delta, rate); 
                        setting.index++; 
                    } 
                    setting.index %= spriteNames.Count; 
                    // プレビューウィンドウで表示するために、UISpriteのspriteNameを変える 
                    // 保存する時spriteNameの差分が出るかもしれない 
                    t.spriteName = spriteNames[setting.index]; 
                } 
 
                // どの画像を表示しているか分かるように、画像名を表示する 
                EditorGUI.DropShadowLabel(rect, spriteNames[setting.index]); 
                rect.height -= 15; 
            } 
            else 
            { 
                return; 
            } 
        } 
 
        // UISpriteのプレビューを利用して、spriteNameで指定しているUISpriteを描画させる 
        base.OnPreviewGUI(rect, background); 
    } 
 
    // プレビューウィンドウのヘッダーバーをカスタムする関数 
    public override void OnPreviewSettings() 
    { 
        base.OnPreviewSettings(); 
        if (!_hasAnimation) 
        { 
            return; 
        } 
 
        // 再生するボタン 
        var playButton = EditorGUIUtility.IconContent("preAudioPlayOn"); 
        var pauseButton = EditorGUIUtility.IconContent("preAudioPlayOff"); 
 
        EditorGUI.BeginChangeCheck(); 
        _isPlaying = GUILayout.Toggle(_isPlaying, _isPlaying ? playButton : pauseButton, (GUIStyle)"preButton"); 
        if (EditorGUI.EndChangeCheck()) 
        { 
            _animSetting.lastTime = (float)EditorApplication.timeSinceStartup; 
        } 
 
        // AnimationClipのような再生速度を制御するUI 
        // 実際の作業はUISpriteAnimationのFramerateで調整しているので、ここは練習だけ^_^ 
        var speedScale = EditorGUIUtility.IconContent("SpeedScale", "Speed Scale"); 
        if (GUILayout.Button(speedScale, (GUIStyle)"preButton")) 
        { 
            _speedScale = 1; 
        } 
        _speedScale = GUILayout.HorizontalSlider(_speedScale, 0f, 5f, (GUIStyle)"preSlider", (GUIStyle)"preSliderThumb"); 
 
        GUILayout.Box(_speedScale.ToString("0.000"), new GUIStyle("preLabel")); 
    } 
 
    public override bool HasPreviewGUI() 
    { 
        return true; 
    } 
 
    //常に再描画される必要があるかどうか 
    public override bool RequiresConstantRepaint() 
    { 
        return _isPlaying; 
    } 
 
    private class AnimationSetting 
    { 
        public int index; 
        public float delta; 
        public float lastTime; 
        public UISpriteAnimation anim; 
 
        public AnimationSetting(UISpriteAnimation anim) 
        { 
            index = 0; 
            delta = 0; 
            lastTime = (float)EditorApplication.timeSinceStartup; 
            this.anim = anim; 
        } 
    } 
} 
CanEditMultipleObjectsEditor.targetsを利用すれば、下のGIFのように複数のUISpriteAnimationのプレビューもできますが、
実際の作業に使われないし、実現するためにNGUIコードの改造も少し必要なので、今回割愛します。


11 -21-2018 11-19-11


おわりに

ということで、Unityエディター拡張のカスタムプレビューを紹介しました。

上記の例に限らず、パーティクルのプレビューとか、AnimationClipにあるSpriteアニメーションのプレビューとか、3Dモデルのプレビューとか、複数のプレビューを使って各アニメーションのプレビューとか、いろんなことができるでしょう。

明日は浅利さんによる「HTC Vive で両手を使った transform 操作を実現する」の話になります。

*1:CustomPreviewを使えば、1つのObjectで複数のプレビューを作ることができます。
オリジナルのエンクロージャ:
48698224-e6839f00-ec29-11e8-8a1a-ec740f4207a3.png

コメント

このブログの人気の投稿

投稿時間:2021-06-17 22:08:45 RSSフィード2021-06-17 22:00 分まとめ(2089件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)