(:3[kanのメモ帳]

個人ゲーム開発者kan.kikuchiのメモ的技術ブログ。月木更新でUnity関連がメイン。

(:3[kanのメモ帳]


本ブログの運営者kan.kikuchiが個人で開発しているゲームです!


Scripting Define Symbolsを劇的に使いやすくするエディタ拡張【Unity】【エディタ拡張】


このエントリーをはてなブックマークに追加



この記事でのバージョン
Unity 5.2.2f1 Personal

はじめに

2015年の記事100本目!そしてUnity Advent Calendar 2015 14日目!



前回、13日目はShaulaさんのUnityで各種定数を定義して使うでした。

定数と言えば、今回の記事でも使っている

ConstantsClassCreatorが定数クラスを自動生成するためのクラスだったりします……!


さてさてそんな今回はScripting Define Symbolsを劇的に使いやすくするエディタ拡張の話です。

※これは個人の感想であり感じ方には個人差があります( ´・◡・`)b


Scripting Define Symbols

UnityではScripting Define Symbolsを使う事で、プロジェクト内で共通のシンボルを定義できます。

そんなもんは知らんという方は、以下の記事を参照の事。



ただこのScripting Define Symbolsには以下のような欠点があり、正直使い難いです。

  • 全プラットフォームに一括設定ができない
  • 羅列されるので設定されてる項目がわかり難い
  • なんのシンボルか説明をコメントでかけない
  • 未定義にする場合消すので、再設定がめんどい


また、UnityではなくC#の仕様なのですが、

CやC++のように、Defineで定数を設定できません。


なので、それらを解決するエディタ拡張を作ったわけです。


これを使うと以下のよう事が可能になります。

  • シンボルの確認、作成、削除、編集が簡単に
  • シンボルを消さずに有効、無効の切り替え
  • なんのシンボルか説明が書けるように
  • 全プラットフォーム一括設定(個別も可)
  • defineによる定数宣言の代替(主にデバッグ用)


例えば、RPGの開発中にPlayerのHPを一時的に10に書き換える

みたいな時は以下のような感じになります。



なお、左側にあるのがUnityで、右側にあるのがMonoDevelopです。


導入

コードはプロジェクトごとGitHubにあげました。

GitHub?という方はDownload ZIPという所をポチっとな。



Assetsディレクトリ内にあるEditorディレクトリをプロジェクトへ突っ込めば、とりあえず使えます。

ちなみに、SymbolsEditor-Editor-SymbolEditor.csみたいな構成でも大丈夫です。


f:id:kan_kikuchi:20151130135908p:plain


これでTools-Open-SymbolEditorWindow

が表示されるようになるはず。


f:id:kan_kikuchi:20151201132926p:plain


このSymbolEditorWindowを押すと以下のようなウィンドウが開きます。


f:id:kan_kikuchi:20151201132938p:plain


このウィンドウを使ってシンボルをイジっていくわけです。

画像のように常にSceneの隣とかに置いておくと使いやすいかも。


使い方

では次に、SymbolEditorWindowの使い方です。


シンボルの追加、削除

シンボルを追加するにはまず、空になっているSymbolの所に任意の文字列を入力します。

この文字列は数字及び大文字の英字と_だけ使えるようになっています。


f:id:kan_kikuchi:20151201132938p:plain f:id:kan_kikuchi:20151202135151p:plain


さらにSymbolの隣にチェックマークを入れるとそのシンボルを有効に出来ます。


f:id:kan_kikuchi:20151203133559j:plain



上部に「保存されていない情報があります」

と表記されている状態だとまだは保存されていないので、


f:id:kan_kikuchi:20151203133728j:plain


下部のSaveで保存し、追加完了です。

なお、Current Targetが今選択しているプラットフォームにのみ保存を行い、

All Targetは全プラットフォームに同じ設定を保存します。


f:id:kan_kikuchi:20151203133844j:plain


保存しないと実行時に変更が消えてしますでの注意が必要です。


また、Symbolの下にあるValueはSymbolに対応する値で、

Infoはそのシンボルが何のためのモノなのかを説明する欄です。

どちらとも空欄でも問題ありません。


f:id:kan_kikuchi:20151203134234j:plain


なお、プラットフォーム別に設定するのはSymbolが有効か否かだけでValueやInfoは常に共通です。


Valueについて詳細は次項で。


Symbolに対応する値

最初の例、RPGの開発中にキャラクターのHPを一時的に書き換えたいみたいな時に

#define DEBUG_HP 10

#ifdef DEBUG_HP
  hp = DEBUG_HP;
#endif


みたいにできると、切り替えや値の変更が楽で、

デバッグコードを消し忘れたみたいなことも少なくなると思います。


ただ前述通り、C#ではdefineで定数の宣言はできないので、

DefineValueというクラスに、シンボルと同名の定数を宣言するようにしてみました。


例えば以下の画像の設定で、その下にあるコードみたいな感じ。


f:id:kan_kikuchi:20151203134820p:plain

/// <summary>
/// Symbolに対応した値を定数で管理するクラス
/// </summary>
public static class DefineValue{

  public const string DEBUG_HP = "10";

}


このDefineValueを使うと先ほどのコードは以下のように書き換えられます。

#ifdef DEBUG_HP
  hp = int.Parse (DefineValue.DEBUG_HP); 
#endif


なお、DefineValueで宣言している定数は全てstringなので他の型だと変換が必要になります。


このDefineValueは以前作ったConstantsClassCreatorというもので自動で生成しています。



ConstantsClassCreatorとは、簡単に言えば定数クラスを自動生成するクラスです。


なお、DefineValue.csの書き出し先は

ConstantsClassCreatorのEXPORT_DIRECTORYPATH で設定しています。


Symbolの無効と削除

Symbolを無効する場合はチェックを外しSaveするだけです。


削除もSymbolの右にある×を押してSaveするだけです。


f:id:kan_kikuchi:20151203135536j:plain


削除もSaveしなければ反映されないので、

間違えて消した場合でも、Save前にResetすれば問題ありません。


Reset

Saveする前の状態に戻すボタンです。


f:id:kan_kikuchi:20151203135927j:plain


All Invalid


全Symbolを無効にするボタンです


f:id:kan_kikuchi:20151203135934j:plain


Symbolの追加等と同様にSaveしなければ反映されません。


All Valid

全Symbolを有効にするボタンです


f:id:kan_kikuchi:20151203135940j:plain


これもSymbolの追加等と同様にSaveしなければ反映されません。


All Delete

全Symbolを削除するボタンです


f:id:kan_kikuchi:20151203135945j:plain


もちろんこれもSymbolの追加等と同様にSaveしなければ反映されません。


設定ファイルの保存場所

Symbolの名称、有効か否か、ValueとInfoの情報は

EditorUserSettingsで保存しています。


保存されている場所はLibrary/EditorUserSettings.assetです。


f:id:kan_kikuchi:20151203140004p:plain


EditorUserSettingsについては以下の記事を参照の事。



普通はLibraryディレクトリをバージョン管理に含まないらしいので、

複数人で開発をする時にデバッグコードの設定状況が変わる点には注意が必要です。


クラス

ではでは、コードの説明です。

ScriptingDefineSymbolsEditorでは自動生成しているDefineValueを含めると

クラスは5つあり、それぞれの機能は以下の通りです。


DefineSymbol

symbol名、対応する値、説明を持ったSymbolのクラス


SymbolEditor

DefineSymbolの操作を行うクラス


SymbolEditorWindow

symbolを操作する時に使うウィンドウの描画を行っているクラス


DefineValue

Symbolに対応する値を定数で宣言しているクラス


ConstantsClassCreator

定数クラスを自動生成するためのクラス。DefineValue を作るために使用。


要点

大まかな処理は以下の通りです。

  1. 読み込み
  2. 表示
  3. 編集
  4. 保存


大まか過ぎる気はしますが、気にせず要点だけを順に説明していきます。


読み込み

まず最初にPlayer Settingsに保存されているSymbolを読み込みます。


f:id:kan_kikuchi:20151204133914p:plain


PlayerSettingsの情報を読み込むには、以下のようにします。

//現在選ばれているプラットフォームを取得
BuildTargetGroup targetGroup = EditorUserBuildSettings.selectedBuildTargetGroup; 

//プラットフォームを指定してPlayerSettingsに設定されているSymbolsを取得
string settingSymbols  = PlayerSettings.GetScriptingDefineSymbolsForGroup (targetGroup );


読み込んだ時は全Symbolがまとまった文字列なのでSplitで;区切りの配列にしています。

string[] settingSymbols = PlayerSettings.GetScriptingDefineSymbolsForGroup (_targetGroup).Split(new []{SYMBOL_SEPARATOR}, StringSplitOptions.None); 


次にScriptingDefineSymbolsEditorが

EditorUserSettingsに保存しているSymbolも読み込みます。


EditorUserSettingsの情報を読み込むには、EditorUserSettings.GetConfigValueを使います。

//EditorUserSettingsに保存されているシンボル名一覧があれば、リストに追加
string savingSymbolKeys = EditorUserSettings.GetConfigValue (SYMBOL_KEY_LIST_SAVE_KEY);


このPlayerSettingsとEditorUserSettingsから読み込んだkeyから重複している部分を省き

symbolごとにDefineSymbol のインスタンスを作成

valueやinfoが保存されていればそれも設定しています。


さらにPlayerSettingsに保存されていたか否かで、現在有効になっているか判定し、

有効ならばwindowの上に来るようにソートしています。

//空白及び重複しているシンボル名を除き、アルファベット順にソート
symbolKeyList = symbolKeyList.Where(symbolKey => !string.IsNullOrEmpty(symbolKey)).Distinct().ToList();
symbolKeyList.Sort ();

//各シンボルkeyからシンボルを作成
_symbolList = new List<DefineSymbol> ();
foreach (string symbolKey in symbolKeyList) {

  bool isEnabled = settingSymbols.Contains (symbolKey);
  DefineSymbol symbol = new DefineSymbol (symbolKey, isEnabled);

  //EditorUserSettingsに保存されていなかったkeyは前のValueとInfoが残っている可能性があるので削除
  if(!savingSymbolKeyList.Contains(symbolKey)){
    symbol.DeleteValueAndInfo ();
  }

  _symbolList.Add (symbol);
}

//有効になっているシンボルを上に表示するように
_symbolList = _symbolList.OrderBy (symbol => symbol.IsEnabled ? 0 : 1).ToList();



表示

ウィンドウの表示設定はSymbolEditorWindow のOnGUI内で行っています。

と言っても、ただひたすらEditorGUI ○○みたいなのでUIを設定しているだけですが……。


Unity5だとScopeとやらで綺麗(?)に出来るらしいです。



そのうち直したい(:3っ)∋〜


なお、以下のように無意味に括弧がついてるのはインデントを下げて分かりやすくするためだす。

EditorGUILayout.BeginHorizontal ();
{//無意味な括弧
  //ここのインデントが下がる
}//無意味な括弧閉じ
EditorGUILayout.EndHorizontal(); 



編集

変更があったかどうかは

EditorGUIのBeginChangeCheck と EndChangeCheck をつかって確認しています。

//内容の変更チェック開始
EditorGUI.BeginChangeCheck ();

/*中略*/

//内容が変更されていれば、シンボルを編集
if (EditorGUI.EndChangeCheck ()){
  SymbolEditor.EditSymbol (symbolNo, symbolKey, symbolValue, symbolInfo, isEnabled);
}


Window上で変更があった場合、

その内容をEditSymbol から該当するDefineSymbolに伝えている感じです。

/// <summary>
/// シンボルを修正
/// </summary>
public static void EditSymbol(int symbolNo, string symbolKey, string symbolValue, string symbolInfo, bool isEnabled){
  _isEdited = true;

  //新たなシンボル作成
  if(symbolNo >= _symbolList.Count){
    DefineSymbol newSymbol = new DefineSymbol (symbolKey, isEnabled);
    _symbolList.Add (newSymbol);
  }

  DefineSymbol symbol = _symbolList[symbolNo];
  symbol.Edit (symbolKey, symbolValue, symbolInfo, isEnabled);
}


ただし、この時点ではPlayerSettingsやEditorUserSettingsに保存していません。


保存

各DefineSymbolのkeyを一つの文字列にまとめ、

PlayerSettingsとEditorUserSettingsの両方に保存を行っています

なお、PlayerSettingsに保存するのはWindow上で有効になっているkeyだけです。


PlayerSettingsに保存するにはPlayerSettings.SetScriptingDefineSymbolsForGroup

EditorUserSettingsに保存するにはEditorUserSettings.SetConfigValueを使います。

//PlayerSettingsに保存 
PlayerSettings.SetScriptingDefineSymbolsForGroup (targetGroup, enabledSymbols ); 

//EditorUserSettingsに保存 
EditorUserSettings.SetConfigValue (key, Value ); 


また、InfoやValueが設定されている場合はEditorUserSettingsに保存しています。


さらに、有効になっているkeyをDictionaryにまとめて、最後にDefineValue を書き出しています。


おわりに

余談ですが、これを作ったあとに先駆者を発見してやっちまった感がありましたね……。

ググルノダイジ。



こちらは設定ファイルをxmlで書き出しているので、分かり易い&編集し易いかもしれません。

でも機能が微妙に違うからせーふーせーふ!⊂(^ω^)⊃セフセフ!!


さて次回、15日目のUnity Advent Calendar 2015 は、

noshipuさんの記事になります!