Unityエディタ拡張勉強会について

今回Unityエディタ拡張の勉強会の内容についてです。

先日、Donutsさんのとこで拡張部分の勉強会に参加してきました。
今回どんな内容だったのかをざらっと進めていきます。

まずはエディタ拡張とは何ってとこから

簡単に言ってしまえば、Unityをより使いやすくし、開発効率をあげること

それでなにができんだよってとこについて
  ・ショートカットキーの追加
  ・独自のInspectorの実装
  ・Assetインポート時の動作変更
  ・prefab部分のセーブ機能 
など色々あります。
Inspectorの拡張例
Transformのエディタ拡張 - G.D.M
開発しながらアニメを見るエディタ拡張例
【Unity】僕の考えた最強のエディタ拡張【AdventCalendar】 - 代官山らへんで働くengineerのUnityブログ
これを聞いたときはまじで笑いました

Attribute

まずこれは何ぞやってとこだけど・・・
  ・フィールド変数(プロパティ)や関数、クラスに対し、情報を付加できること
使い方について
[Attribute名]変数、関数、クラスの宣言で付加する
インスペクターに変数を表示、非表示とクラスの表示、非表示について

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AttributeTest : MonoBehaviour
{
     //変数の表示
 [SerializeField] private int privateParam;

    //変数の非表示
    [HideInInspector] public int  publicHideParm;

    // [System.Serializable]でクラス表示
    [System.Serializable]  
 public class SerializableClass
    {
        public int filedParam;
     }
     public SerializableClass serializableclass;

 // [System.Serializable]を適用しないと非表示 
 public class NormalClass
    {
        public int filedParam;
     }
     public NormalClass serializableclass;

}

その他のAttributeについて
コンポーネントを自動で加える

[RequireComponent(typeof(型名))]
public class Hoge : MonoBehaviour 

「必要なスクリプトをアタッチし忘れて参照出来ない」みたいな事を無くす時に使える

●変数に説明書きを加える
●マウスオーバで表示

[Tooltip("説明")] public string num;

f:id:kkam0707:20171126150428p:plain

●複数行に対応

[MultiLine] public string num;

f:id:kkam0707:20171126150706p:plain

さっそく本題に・・・

今回はReadOnlyAttributeというAttributeを自作しました。
どういうものか?
 ・Inspectorで確認できるが、編集はできない
 ・本来は存在しない機能
必要なクラス
 ・Attribute名を決めるクラス
 ・Attributeの内容を決めるクラス
 ・Attributeを使うクラス

まずは先にAseetsの中にEditorフォルダーを作ってください。
Editorフォルダーを作ることによって読み込む必要がないものは無視されます。

では中身に・・・
Attributeを作る
今回中身の詳細については割愛させていただきます。

Attribute名を決めるクラス(ReadOnlyAttribute.cs)

public class ReadOnlyAttribute : PropertyAttribute {}

Attributeの振る舞いを決めるクラス(ReadOnlyDrawer.cs)
※Assets/Editorフォルダを作り、その中に入れること

using UnityEditor; // UnityEditorの名前空間を使用する
[CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
public class ReadOnlyDrawer : PropertyDrawer
{
  public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
  {
    GUI.enabled = false;
    EditorGUI.PropertyField(position, property, label, true);
    GUI.enabled = true;
  }
}

Attributeを使うクラス(ReadOnlyAttributeTest.cs)

public class ReadOnlyAttributeTest : MonoBehaviour
{
 [SerializeField] private int attack;
 [SerializeField] private int deffence;
 // Inspectorに表示するが、編集はできない
 [ReadOnlyAttribute] public int rank;
 void Update()
 {
  rank = attack * deffence;
 }
}

・適当なオブジェクトにReadOnlyAttributeTestをアタッチ
・PlayしてInspectorのAttackとDeffenceを変更する→Rankが変わる

CustomEditor

  • AttributePropertyはプロパティ単位で適用される
  • クラス全体のInspectorでの見え方を変更するAttribute
  • 標準のTransformやCollider、Cameraの見え方もCustomEditorで作られる

やること
 • CustomEditorでInspectorにReadOnlyで表示させる

用意するクラス
 • CustomEditorの内容を定義するクラス
 • CustomEditorを適用するクラス

CustomEditorの内容を定義するクラス(ReadOnlyCustomEditor.cs)
※Assets/Editorフォルダの中に入れること

using UnityEditor;
[CustomEditor(typeof(CustomEditorTest))]
public class ReadOnlyCustomEditor : Editor
{
  private CustomEditorTest editorTest;
  void OnEnable()
  {
    editorTest = (CustomEditorTest)target;
  }
  public override void OnInspectorGUI()
  {
    base.OnInspectorGUI();
           
    EditorGUILayout.LabelField("Rank",editorTest.rank.ToString());
  }
}


CustomEditorを適用するクラス(CustomEditorTest.cs)

public class CustomEditorTest : MonoBehaviour
{
  [SerializeField] private int attack;
  [SerializeField] private int deffence;
  // プロパティは標準でInspector非表示
  public int rank {
   get {
     return attack * deffence;
     }
  }
}

オブジェクトにアタッチするとこのようになる。
f:id:kkam0707:20171126160341p:plain

特定シーンから再生する仕組み
• 特定のシーンを再生したい需要
• Titleから始める
• GameManagerのあるシーンから始める

• 毎回現在のシーンを閉じて、特定のシーンを開いて...は非効率
• 再生後に編集中のシーンに戻りたい
→SceneStarterが欲しい

これらをどうするか?・・・
 エディタ拡張で解決する

前準備
• MainシーンとTitleシーンを作る
• Scenesフォルダを作りその中にシーンファイルを移動する

• 再生可能なシーンリストに加える
• UnityメニューのFile -> Build Setting -> Scenes In Buildにシーンファイルを
ドラッグ&ドロップ

• Mainシーンを開いておく

• ショートカットキーでTitleシーンから再生することを目指す

タイトルシーンを再生する(TitleStarter.cs)
※Assets/Editorフォルダに入れておく

using UnityEditor;
using UnityEditor.SceneManagement;
public class TitleStarter : Editor
{
  private const string StartScene = "Assets/Scenes/Title.unity”;
  [MenuItem("Tools/Play From Title %@")]
  public static void PlayFromTitle()
  {
    if(EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) 
     {
    // シーンに遷移
    EditorSceneManager.OpenScene(StartScene);
    // 自動でPlay
    EditorApplication.isPlaying = true;
     }
  }
}

• MainシーンでCommand(Ctrl) + @
→Titleシーンが開く

少しだけ中身の詳細について
[MenuItem(“パス ショートカットキー")]
  • メニューに追加するAttribute
  • Attributeが付いた関数を実行する

ショートカットキー
  • %:Command(Mac), Ctrl(Windows)
  • #:Shift
  • &:Alt
  • _:キーのみ
  • _@で@のみ
  • 2つまでの複合もできる
   • %#@

さらに先ほどの部分をもう少し使いやすくする。
TitleStarter.csに追加してください

再生後は編集していたシーンに戻る

//1.名前空間を追加
using UnityEngine;
using UnityEngine.SceneManagement;

//2.定数を追加
private const string SaveKey = "SaveScene”;
//3.PlayFromTitle関数を編集
if(EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) 
{
  // 以下の処理を追加
  PlayerPrefs.SetString(SaveKey, SceneManager.GetActiveScene().path);
  // シーンに遷移
  EditorSceneManager.OpenScene(StartScene);
  // 自動で遷移
  EditorApplication.isPlaying = true;
}

再生後は編集していたシーンに戻る
 ・再生後に戻る処理を加える

[InitializeOnLoadMethod]
private static void gameEnd()
{
  var scene = PlayerPrefs.GetString(SaveKey, "");
  if(string.IsNullOrEmpty(scene)) {
    return;
  }
  EditorApplication.playmodeStateChanged = onPlayModeStateChanged;
}
private static void onPlayModeStateChanged()
{
  // 終了時に
  if(!EditorApplication.isPlaying) 
  {
    // このイベントを削除
    EditorApplication.playmodeStateChanged = null;
    // 戻る
    var scene = PlayerPrefs.GetString(SaveKey, "");
    PlayerPrefs.DeleteKey(SaveKey);
    EditorSceneManager.OpenScene(scene);
  }
}

PlayerPrefas

• 永続的データを管理するUnity内蔵クラス
• セーブデータやハイスコアの保存

• Key-Value方式で保存
• Keyはstringで指定
Valueはint, float, stringを保存可能

メソッド
保存:PlayerPrefs.SetString(Key, value)
取得:PlayerPrefs.GetString(Key)
存在の確認:PlayerPrefs.HasKey(Key)
ファイルに保存:PlayerPrefs.Save()
Keyと値削除:PlayerPrefs.DeleteKey(key)
全て削除:PlayerPrefs.DeleteAll()

保存場所

Mac
• /Library/Preferences内にunity.[company name].[product name].plist
Windows
• HKCU\Software\[company name]\[product name]

• 直接変更すればセーブデータのリセットや、値の変更も可能だが...

• 効率的に編集するため、エディタ拡張でPlayerPrefsエディタを作る

PlayerPrefasエディタ用のWindowを作る
PlayerPrefsEditor.cs
※Assets/Editorフォルダに入れておく

using UnityEngine;
using UnityEditor;
public class PlayerPrefsEditor : EditorWindow
{
  private string key;
  private string value;
  [MenuItem("Tools/PlayerPrefs/OpenEditor")]
  static void OpenEditor()
  {

   EditorWindow.GetWindow<PlayerPrefsEditor>("PlayerPrefsEditor");

  }
}

string型のPlayerPrefsエディタを作る
OnGUI関数を追加する

void OnGUI()
{
  GUILayout.Label("Key");
  key = GUILayout.TextField(key);
  GUILayout.Label("Value");
  value = GUILayout.TextField(value);
  if(PlayerPrefs.HasKey(key))
   {
   var data = PlayerPrefs.GetString(key);
   GUILayout.Label("Saved Value: " + data);
  }
  if(GUILayout.Button("Save"))
   {
   PlayerPrefs.SetString(key, value);
   PlayerPrefs.Save();
  }
}

OnGUI関数を編集する

GUILayout.BeginHorizontal(); // 追加
GUILayout.Label("Key");
key = GUILayout.TextField(key);
GUILayout.Label("Value");
value = GUILayout.TextField(value);
GUILayout.EndHorizontal(); // 追加
if(PlayerPrefs.HasKey(key)) 
 {
  var data = PlayerPrefs.GetString(key);
  GUILayout.Label("Saved Value: " + data);
}
if(GUILayout.Button("Save")) 
{
  PlayerPrefs.SetString(key, value);
  PlayerPrefs.Save();
}
// 追加
if(GUILayout.Button("Delete")) 
{
  PlayerPrefs.DeleteKey(key);
  PlayerPrefs.Save();
}

オススメエディタ拡張の紹介
Odin - Inspector and Serializer
https://www.assetstore.unity3d.com/jp/#!/content/89041
• 有料($22.50)
• Inspectorの操作性を向上させる数多くの機能を搭載
主な機能
• 配列の要素削除や並び替え
• ReadOnlyAttribute
• 値の範囲を設定
コンポーネントの参照が必要だがされていない場合にエラーメッセージ

Editor Console Pro
https://www.assetstore.unity3d.com/jp/#!/content/11889
• 有料($30.00)
• Debug.Logの拡張版
主な機能
• 検索・フィルター・色付け
• 値の監視
• ログのエクスポート

Advanced PlayerPrefs Window
https://www.assetstore.unity3d.com/jp/#!/content/7070
• 有料($9.00)
• 高機能PlayerPrefsエディタ
• データのインポート/エクスポート

Inspector Navigator
https://www.assetstore.unity3d.com/jp/#!/content/26181
• 無料
• オブジェクト選択の履歴を残す
• ショートカットキーで選択状態を移動

ReferenceViewer
https://github.com/anchan828/unitejapan2014/tree/master/ReferenceViewer

  ・アセットの参照を検索する

FrameCapturer
GitHub - unity3d-jp/FrameCapturer: export framebuffer, GBuffer or any RenderTextures from Unity to file. supported format: png, exr, gif, webm, mp4
  ・ゲーム画面を録画する


MissiongList
http://developer.wonderpla.net/entry/blog/engineer/Unity5_Search_MissingAssets/
  ・Missingになっているプロパティを検索する

壊れたAnimationClipを少しリカバリーしてみるEditor拡張
Unity 壊れたAnimationClipを少しリカバリーしてみるEditor拡張 - 渋谷ほととぎす通信
  • 階層構造を変更しても大丈夫

自作したエディタ拡張に関する記事46選+スライド
【Unity】自作したエディタ拡張に関する記事46選+スライド - コガネブログ

  ・ 小さなエディタ拡張を大量に作成し紹介


今回ざっとこのようなことを学んできました。
初めてエディタ部分に触れたので理解できないこともいくつかあったのですが、エンジニアの方に聞いたら
分かりやすく教えていただけのでとっても実りのあるものになりました。
今回お世話になったDonutsさんのホームページを貼っておきますので気になった方は、12月18日に
シェーダの勉強会があるのでぜひ参加してみてはいかがでしょうか?

そしてそして最後の懇親会では人事の方とも楽しく会話ができそしてピザが美味しかったです(笑)

www.donuts.ne.jp