8
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Unityで「エディタからのみ外部アクセス可能なメンバ変数」を実現する方法の考察

Last updated at Posted at 2015-08-17

概要

Twitter上で以下のようなポストがRTされてて気になったので検証してみた結果をまとめる。

色んな手段が考えられるが、とりあえず以下の点を重視。
  • 定義側の記述を簡単実現できる
  • 呼び出し側に特殊な操作が不要

基本

「C#だしCondition属性使えないかなー」とか思ったけど以下の制約で断念。
Setterのみならまだ何とかなりそうだが、できればGetter/Setterそろえて実装できる方法が便利。

  • 基本的にメソッドのみでプロパティに適用できない
  • メソッドの返り値はvoidに限定され、out引数も使えない

参考 : https://msdn.microsoft.com/ja-jp/library/Aa664622(v=VS.71).aspx

ということで#if UNITY_EDITOR ~ #endifでプロパティを括ってみる。
参考 : http://docs.unity3d.com/ja/current/Manual/PlatformDependentCompilation.html

HogeHoge.cs
using UnityEngine;
using System.Collections;

public class HogeHoge : MonoBehaviour
{
    [SerializeField]
    int piyo;

#if UNITY_EDITOR

    // エディタから参照するためのプロパティ
    public int Piyo
    {
        get { return this.piyo; }
        set { this.piyo = value; }
    }

#endif
	
	// Update is called once per frame
	void Update ()
    {
        // 実行時の参照(本来は使って欲しくない参照)
        // ビルド時にエラーになる
        Debug.Log(this.Piyo);
	}
}
HogeHogeEditor.cs
using UnityEngine;
using UnityEditor;
using System.Collections;

[CustomEditor(typeof(HogeHoge))]
public class HogeHogeEditor : Editor 
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        if (GUILayout.Button("Test"))
        {
            var instance = this.serializedObject.targetObject as HogeHoge;

            // プロパティを通してprivateな変数にアクセスできる
            instance.Piyo++;
            Debug.Log(instance.Piyo);
        }
    }
}

これでビルドした時にはPiyoがHogeHogeより除去されるので、実機実行時にPiyoを参照するようなコードを書いていた場合はコンパイルエラーで止まる。
image

改良

しかし、UNITY_EDITORエディタでのプレビュー実行時にも有効なため、
このままだとビルドするまでは不正な参照を発見できない
Jenkinsなどで定期ビルドをしているような環境ならこれでも十分かもしれないが、
そんなのが無い環境ではちとめんどい。

なので呼び出し時に「呼び出し元がエディタ側のコードかどうか」を確認するコードも追加してみる。

まずEditor側のアセンブリ(≒DLL)にAssemblyIsEditorAssembly付与する。
これはUnity側で用意している「Editor用アセンブリであること」を示す属性である。
(意味的に初期値で設定されていて欲しいが何故か設定されていない)
参考 : http://docs.unity3d.com/jp/current/ScriptReference/AssemblyIsEditorAssembly.html
なお、アセンブリに対する指定はアセンブリ内のコードの何れかに記述されていればよいので、どのファイルでも良い。
できればAssembly.csなど専用ファイルを作ってそこに記述するのがお行儀が良いが、今回はさぼってHogeHogeEditor.csに記述した。

HogeHogeEditor.cs
using UnityEngine;
using UnityEditor;
using System.Collections;

// アセンブリに「エディタ拡張用のアセンブリであること」を示す属性を付与
// こちらのアセンブリに含まれる何れかのコードに1つ記述されていれば良い
[assembly: AssemblyIsEditorAssembly]

[CustomEditor(typeof(HogeHoge))]
public class HogeHogeEditor : Editor 
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        if (GUILayout.Button("Test"))
        {
            var instance = this.serializedObject.targetObject as HogeHoge;

            // プロパティを通してprivateな変数にアクセスできる
            instance.Piyo++;
            Debug.Log(instance.Piyo);
        }
    }
}

次にアクセス制限をするために、呼び出し元のアセンブリ情報を確認する実装を追加。

HogeHoge.cs
using UnityEngine;
using System.Collections;

public class HogeHoge : MonoBehaviour
{
    [SerializeField]
    int piyo;

#if UNITY_EDITOR

    // エディタから参照するためのプロパティ
    public int Piyo
    {
        get
        {
            // 呼び出し元メソッド情報を取得
            var method = new System.Diagnostics.StackFrame(1).GetMethod();

            // 「AssemblyIsEditorAssembly属性が付与されているアセンブリ = エディタ用」なので、
            // 呼び出し元メソッドを定義しているクラスが含まれるアセンブリを確認する
            if (method.DeclaringType.Assembly.IsDefined(typeof(AssemblyIsEditorAssembly), false) == false)
            {
                Debug.Assert(false, "Invalid Access! From {0}::{1}", method.DeclaringType, method.Name);
            }

            return this.piyo;
        }
        set
        {
            // 必要ならばこちらにもgetと同様の処理を書く
            // (多くの場合、set側にこそ記述をすべき)
            this.piyo = value;
        }
    }

#endif
	
    // Update is called once per frame
    void Update ()
    {
        // 実行時の参照(本来は使って欲しくない参照)
        // エディタでの実行時にもアサートを吐く
        Debug.Log(this.Piyo);
     }
}

これで、実行時にエディタ側以外のコードからPiyoが呼ばれるとAssertが発生するようになり、
不正利用の発見が容易になる。
image

なお、呼び出し元確認部分のコードを汎用メソッドとして用意すると使いまわす上で便利だが、
その場合はStackFrameを取得する際の引数skipFramesの値がずれるので注意すること。
StackFrameクラスのリファレンス : https://msdn.microsoft.com/ja-jp/library/ws7f30w6.aspx

蛇足的おまけ

EditorApplication.isPlayingを使うことで似たような挙動をより簡単に実現できるが、
あくまで「実行中か」で判断するため、
Editor上でプレビュー実行中にInspectorなどのEditor側のコードでアクセスした場合でも不正扱いになる
という欠点がある。
この誤爆的挙動を良しとするならば、こっちを選択するのもありかもしれない。

HogeHoge.cs
using UnityEngine;
using System.Collections;

public class HogeHoge : MonoBehaviour
{
    [SerializeField]
    int piyo;

#if UNITY_EDITOR

    // エディタから参照するためのプロパティ
    public int Piyo
    {
        get
        {
            // 実行時の呼び出しか確認
            // ただし、Editorでのプレビュー実行中にEditor側のコードでここにきてもisPlaying = trueになる模様
            // よって誤爆の危険がある
            if (UnityEditor.EditorApplication.isPlaying)
            {
                // 呼び出し元メソッド情報を取得
                var method = new System.Diagnostics.StackFrame(1).GetMethod();
                Debug.Assert(false, "Invalid Access! From {0}::{1}", method.DeclaringType, method.Name);
            }

            return this.piyo;
        }
        set
        {
            // 必要ならばこちらにもgetと同様の処理を書く

            this.piyo = value;
        }
    }

#endif
	
    // Update is called once per frame
    void Update ()
    {
        // 実行時の参照(本来は使って欲しくない参照)
        // エディタでの実行時にもアサートを吐く
        Debug.Log(this.Piyo);
     }
}
8
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?