陰干し中のゲーム開発メモ

ゲームを作るために役に立ったり立たなかったりする技術を学んでいきます!

【Unity】ランタイム中にのみ値が変更できなくなるアトリビュートを作ってみよう!

シリアライズされた変数が Inspector 上に表示されるけど値の変更が出来ないアトリビュートと、
ランタイム中のみ値の変更が出来ないアトリビュートを作った話。


この記事にはUnity2020.1.14f1を使用しています。

コンポーネントのシリアライズされた変数は Inspector 上に表示されて、値を変更することができるよね。
ランタイム中には現状の値が表示されているので、値の変化を確認することだってできる。
そのため、ランタイム中の値の変化を見たい場合は、変数をシリアライズしておけば、 Inspector 上で見れて便利。

でも、”値を見る”ためにシリアライズした変数を Inspector 上で変更できるようになっているのは、あんまり良くないなぁ。
ちょっと一手間加えてみましょうー。

Disable するアトリビュートを作る

わざわざ各コンポーネント用のエディタ拡張するのはめんどうくさい……。
やりたいことはそんなに複雑ではないと思うので、”アトリビュート”として作っていろんなところで汎用的に使えるものを作ってみましょう!

まずは DisableAttribute という特に何もしないアトリビュートを作ってみましょうか。
DisableAttribute.cs

using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif // UNITY_EDITOR
public class DisableAttribute : PropertyAttribute
{
} // // class DisableAttribute
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(DisableAttribute))]
public class DisableDrawer : PropertyDrawer
{
    public override void OnGUI(Rect aPosition, SerializedProperty aProperty, GUIContent aLabel)
    {
        EditorGUI.PropertyField(aPosition, aProperty, aLabel, true);
    }
} // class DisableDrawer
#endif // UNITY_EDITOR

これに Inspector 上ではプロパティを変更できなくするための処理を加えていきましょう。
その処理は既に Unity 側で用意してくれているよ。
BeginDisabledGroup()EndDisabledGroup() だ。
この関数の間に描画されたプロパティは "Disable" 状態になり、値が変更が出来なくなるってしろものさー!
docs.unity3d.com
docs.unity3d.com

早速使ってみよう!

DisableAttribute.cs

#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(DisableAttribute))]
public class DisableDrawer : PropertyDrawer
{
    public override void OnGUI(Rect aPosition, SerializedProperty aProperty, GUIContent aLabel)
    {
        EditorGUI.BeginDisabledGroup(true);
        EditorGUI.PropertyField(aPosition, aProperty, aLabel, true);
        EditorGUI.EndDisabledGroup();
    }
} // class DisableDrawer
#endif // UNITY_EDITOR

これで DisableAttribute クラスが完成したぞ!
サンプルのコンポーネントを作って、動作を確認してみよう!

Sample.cs

using UnityEngine;
public class Sample : MonoBehaviour
{
    [SerializeField]
    private string m_parameterA = "default";
    [SerializeField, Disable]
    private string m_parameterB = "default";
} // // class Sample

f:id:urahimono:20201123152314j:plain

DisableAttribute が設定されている変数は Inspector 上で値を変更できなくなっている。
成功ですねー!

ランタイム中のみ Disable するアトリビュートを作る

もう一手間加えたアトリビュートも作ってみましょうか。
先ほど作った DisableAttribute は Inspector 上では一切値が変更出来ないので、スクリプト上でしか値を設定することが出来ないよね。
でも、初期値だけは Inspector 上で設定したい、ゲームが実行しているときは値を見るだけ、そんな風にアトリビュートを改良出来ないかな?

先ほど使った BeginDisabledGroup() は、
引数として渡す値が true なら "Disable" 状態、 false なら "Disableにはならない"。
すなわち、"現在ゲームが動いているか?"を判定したフラグを渡してあげれば、望んだ形にアトリビュートを改良できるはずだ!
現在ゲームが動いているかを判定するものも Unity 側で用意されているぞ!
EditorApplication.isPlaying だ!
ゲーム実行中にはこのプロパティが true になっているよ。
docs.unity3d.com

このプロパティを使う形で 'DisableAttribute' を改良した RuntimeDisableAttribute を作ってみよう!
RuntimeDisableAttribute.cs

using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif // UNITY_EDITOR
public class RuntimeDisableAttribute : PropertyAttribute
{
} // // class RuntimeDisableAttribute
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(RuntimeDisableAttribute))]
public class RuntimeDisableDrawer : PropertyDrawer
{
    public override void OnGUI(Rect aPosition, SerializedProperty aProperty, GUIContent aLabel)
    {
        EditorGUI.BeginDisabledGroup(EditorApplication.isPlaying);
        EditorGUI.PropertyField(aPosition, aProperty, aLabel, true);
        EditorGUI.EndDisabledGroup();
    }
} // class RuntimeDisableDrawer
#endif // UNITY_EDITOR

'DisableAttribute' の BeginDisabledGroup() で渡す値だけを変えた簡単もの。
さっそくサンプル用のコンポーネントで試してみよう!

Sample.cs

using UnityEngine;
public class Sample : MonoBehaviour
{
    [SerializeField]
    private string m_parameterA = "default";
    [SerializeField, Disable]
    private string m_parameterB = "default";
    [SerializeField, RuntimeDisable]
    private string m_parameterC = "default";
} // // class Sample

f:id:urahimono:20201123152506j:plain
f:id:urahimono:20201123152513g:plain
ゲーム実行中には "Disable" 状態になっているね!

ただ、気になる点がある。
Unity の再生ボタンを押してしばらくの間、まだ "Disable" 状態になっていないのだ!
どうやら Unity のゲーム実行ステートは、

  • ゲームを実行していない
  • ゲームを実行ステートに移行中
  • ゲームを実行中

と、段階を踏んでゲームを実行中となっているようだ。
ただ、こちらからすれば
「再生ボタンを押したらゲームを実行するんだから、すぐにでも "Disable" 状態になっていてほしい!」
というの思いがある。

ということは、 EditorApplication.isPlaying ではダメなのだ!
別のものに変えよう。
もちろんいいものを Unity 側は用意してくれている。
EditorApplication.isPlayingOrWillChangePlaymode というものがあるのさ!
随分と長ったらしい名前のプロパティだが、これはゲーム実行中+ゲーム実行に移行中のときも 'true' を返してくれるという、今回の悩みをすべて解決してくれるようなプロパティだ。
docs.unity3d.com

先ほどの RuntimeDisableAttribute で使ってみよう。

RuntimeDisableDrawer.cs

#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(RuntimeDisableAttribute))]
public class RuntimeDisableDrawer : PropertyDrawer
{
    public override void OnGUI(Rect aPosition, SerializedProperty aProperty, GUIContent aLabel)
    {
        EditorGUI.BeginDisabledGroup(EditorApplication.isPlayingOrWillChangePlaymode);
        EditorGUI.PropertyField(aPosition, aProperty, aLabel, true);
        EditorGUI.EndDisabledGroup();
    }
} // class RuntimeDisableDrawer
#endif // UNITY_EDITOR

f:id:urahimono:20201123152716g:plain

うまく動作しているようだね。
えかったえかった。

こういう簡単なエディタの拡張をできる余裕がいつも欲しいものですな!