(:3[kanのメモ帳]

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

(:3[kanのメモ帳]


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

    

エディタ再生中に変更した値を、再生終了後もそのまま保持する属性【Unity】【エディタ拡張】【属性】


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



この記事でのバージョン
Unity 2018.4.01


はじめに

UnityではInspector上で色々な値を変える事が出来ますが、

基本的に再生中に変更した値は、再生終了時に元に戻ります。

f:id:kan_kikuchi:20190514162054g:plain


コンポーネントの右上にある歯車から、再生中にCopy Componentをして数値をコピー、

再生終了後にPaste Componetで値を上書きして戻すという事も可能ですが、これもちょっと面倒です。


f:id:kan_kikuchi:20190514162240j:plain


何かいい方法はないかと調べた所、皆さんご存知、テラシュールブログさんにて

自作の属性を使い「エディタでゲーム再生中に変更した値を停止しても消さない」

という方法が紹介されていました。




これをそのまま使えばいいのですが、色々と変更したい所や追加したい事があったので、

上記を参考に「エディタ再生中に変更した値をエディタ停止後もそのまま保持する属性」

を作ってみました!イメージとしては以下のような感じ。


f:id:kan_kikuchi:20190514141459g:plain


PersistentAmongPlayModeAttribute

さっそくコードですが、

今回はちょっと長いので、全文の表示はせずにリンクだけ載せています。


エディタ再生中に変更した値をエディタ停止後もそのまま保持する属性


上記の3つのスクリプトを作成し、

PersistentAmongPlayModeAttribute以外をEditorに配置すれば準備完了。


f:id:kan_kikuchi:20190514140840j:plain


使い方は簡単、変数にPersistentAmongPlayModeという属性を設定するだけ。

[PersistentAmongPlayMode]
public int PublicInt = 1;
  
[SerializeField, PersistentAmongPlayMode]
private int _privateInt = 1;


これで、再生を終了しても値が戻らなくなります。


f:id:kan_kikuchi:20190514141459g:plain


コードの詳細は省きますが、簡単に処理の流れを箇条書きにすると

  • 再生停止ボタンを押した瞬間を検知(この段階では値の変更は戻ってない)
    • シーン上の全コンポーネントを取得
      • さらに全変数を取得、PersistentAmongPlayModeが付いてる変数の値を保存
  • 再生が終了した瞬間を検知(この段階で値の変更が戻ってる)
    • シーン上の全コンポーネントを取得
      • さらに全変数を取得、PersistentAmongPlayModeが付いてる変数に保存してた値を戻す


という感じになっています。


注意点とその対処法

この属性を使う上で注意が必要なのが、

Inspector上だけではなく、プログラムから値を変更しても保持するという事です。

//エディタ再生中に変更した値をエディタ停止後もそのまま保持される値
[SerializeField, PersistentAmongPlayMode]
private int _privateInt = 1;

private void Awake() {
  //プログラムから値を変更
  _privateInt = 10;
}

f:id:kan_kikuchi:20190515055124g:plain


そもそも、この属性を使いたい時はInspectorで値を調整したい時だと思うので、

いっそInspectorでだけ値を変更できるようにした方が良いかもしれません。


具体的には調整用の値をまとめたクラスを作成し、調整値はgetterだけ作成する感じです。

/// <summary>
/// 調整用の値をまとめたクラス
/// </summary>
public class AdjustValue : MonoBehaviour{
  
  //エディタ再生中に変更した値をエディタ停止後もそのまま保持される値
  [SerializeField, PersistentAmongPlayMode]
  private int _privateInt = 1;
  public  int  PrivateInt => _privateInt;//getterしかないので、外部のプログラムからは変更は出来ない
  
}
//調整用の値をまとめたクラス(Inspectorで設定)
[SerializeField]
private AdjustValue _adjustValue = null;
  
private void Awake() {
  //値の取得は外部からも可能、変更は出来ない
  int privateInt = _adjustValue.PrivateInt;
}

f:id:kan_kikuchi:20190515062102j:plain


また、MonoBehaviourを継承していないクラスの変数には対応していません。

例えば、先ほどのAdjustValueを何も継承しないようにすると、

Inspector上に表示され、変更も出来ますが、

PersistentAmongPlayModeを付けても、値の変更は停止時に戻ってしまいます。

/// <summary>
/// 調整用の値をまとめたクラス
/// </summary>
[Serializable]
public class AdjustValue{ //MonoBehaviourを継承していない
  
  [SerializeField, PersistentAmongPlayMode]
  private int _privateInt = 1;
  public  int  PrivateInt => _privateInt;
  
}

f:id:kan_kikuchi:20190516063120j:plain


変更点と機能詳細

最初に述べた通り、参考元から色々と変更や追加をしています。

その「何を変えたか」を説明しつつ、ついでに機能の詳細についても説明していきます。


エラーへの対応

シーン再生中にコンポーネントが削除されたりすると、エラーが出ていたので、

それが出ないようにしました。

//終了ボタンを押した時に存在しなかった(シーン再生中に削除されたとかで)やつはスルー
if (!_valueDictDict.ContainsKey(component.GetInstanceID())) {
  return;
}



obsoleteの対応

再生開始した時など、エディターの状態が変わった事を知りたい場合に

以前はEditorApplication.playmodeStateChangedというイベントが使えたのですが、

Unity2017以降でobsolete(非推奨)になってしまいました。



なので、代わりに追加されたplayModeStateChangedを使い、obsoleteの警告が出ないようにしました。

//プレイモードが変更された時の処理を設定
EditorApplication.playModeStateChanged += state => {

  //終了ボタンを押した時に、その時の値を保存
 if (state == PlayModeStateChange.ExitingPlayMode) {
    _valueDictDict.Clear();
    ExecuteProcessToAllMonoBehaviour(SaveValue);
  }
  //実際に終了した時(シーン再生前の値に戻った時)に、保存してた値を反映
  else if (state == PlayModeStateChange.EnteredEditMode) {
    ExecuteProcessToAllMonoBehaviour(ApplyValue);
  }

};



Public以外の変数への対応

参考元の方ではPublicな変数にしか属性は使えなかったのですが、

privateなどのPublic以外の変数でも使えるようにしました。

//Publicとそれ以外のフィールドに対して処理を実行
ExecuteProcessToAllPersistentAmongPlayModeField(component, action,BindingFlags.Instance | BindingFlags.Public    | BindingFlags.Static);
ExecuteProcessToAllPersistentAmongPlayModeField(component, action,BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod );



エディタ再生中に値を変更してもシーンを保存出来るように

エディタ再生中にPersistentAmongPlayModeが付いた属性が変更された場合、

エディタ停止後にシーンに変更の米印が付くようにし、値の変更を保存出来るようにしました。

(参考元はこれがなかったので、再生終了後すぐに違うシーンに行くと値の変更が戻っていた)

//値の変更があったら保存出来るようにするため、シーンに変更があったこと(米印)を設定
if (isChangedValue) {
  EditorSceneManager.MarkAllScenesDirty();
}

f:id:kan_kikuchi:20190515062418j:plain
f:id:kan_kikuchi:20190515062440g:plain


なお、シーンの変更を保存せずに再読込みすれば、変更前の値に戻すことも可能です。


Inspector上の表記変更

PersistentAmongPlayModeの属性が付いているものは、Inspector上で判別できた方が良いと思ったので、

注意書きのようなものが表示されるようにしました。


f:id:kan_kikuchi:20190515063259j:plain


なお、文言を変更したい場合はPersistentAmongPlayModeDrawerを修正してください。

//注釈表示
EditorGUI.HelpBox(position, label.text + "は、再生中に変更しても停止時に戻りません", MessageType.Info);


また、そもそもこれが必要ない場合は

PersistentAmongPlayModeDrawer自体を削除すると表示されなくなります。