电竞比分网-中国电竞赛事及体育赛事平台

分享

Unity檢視面板的繼承方法研究

 tiancaiwrk 2019-05-17

對于檢視面板 Inspector 的面板繼承方式對項目來說是很有必要的, 比如一個基類, 寫了一個很好看的檢視面板[CustomEditor(typeof(XXX))],

可是所有子類的面板無法直接繼承這個CustomEditor, 有些人的解決方案是把子類寫檢視面板的代碼獨立出來, 然后子類面板直接去調(diào)用這些Layout,

非常浪費人力物力.

  最近發(fā)現(xiàn)有個 DecoratorEditor 腳本, 它實現(xiàn)了對 Unity 自帶檢視面板的擴展, 看到它實現(xiàn)某個類型的面板Inspector的方法, 就是使用Editor.CreateEditor 這個API

創(chuàng)建了一個相應(yīng)的Editor, 然后調(diào)用它的OnInspectorGUI() 方法來繪制原有面板的, 于是可以從這個地方著手.

   從設(shè)計上來說 [CustomEditor(typeof(XXX))] 在耦合上并沒有太多的耦合, Unity 開發(fā)組的想法應(yīng)該就是一個Editor對應(yīng)一個組件, 它們對應(yīng)類型之間的繼承關(guān)系不應(yīng)該

互相影響, 保證泛用性和獨立性. 

  原始的Editor腳本和類型是這樣的:

基類 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class TestBaseClass : MonoBehaviour
{
    public float abc;
}
// 檢視面板
[CustomEditor(typeof(TestBaseClass))]
public class TestBaseClassInspector : Editor
{
    TestBaseClass _target;
    private void OnEnable()
    {
        _target = target as TestBaseClass;
    }
    public override void OnInspectorGUI()
    {
        _target.abc = EditorGUILayout.FloatField("基類變量 : ", _target.abc);
    }
}

子類1 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class TestCamera : TestBaseClass
{
    public Camera cam;
}
// 檢視面板
[CustomEditor(typeof(TestCamera))]
public class TestCameraInspector : Editor
{
    TestCamera _target = null;
    private void OnEnable()
    {
        _target = target as TestCamera;
    }
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
         
        _target.cam = EditorGUILayout.ObjectField("一次繼承變量 : ", _target.cam, typeof(Camera), true) as Camera;
    }
}

子類2 : 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class TestUntiy : TestCamera
{
    public int hahah;
}
// 檢視面板
[CustomEditor(typeof(TestUntiy))]
public class TestUntiyInspector : Editor
{
    TestUntiy _target = null;
    private void OnEnable()
    {
        _target = target as TestUntiy;
    }
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
        _target.hahah = EditorGUILayout.IntField("二次繼承變量 : ", _target.hahah);
    }
}

  

1
TestBaseClass
1
TestCamera : TestBaseClass
1
TestUntiy : TestCamera<br><br>非常簡單的繼承關(guān)系, TestUnity的檢視面板如下, 沒有繼承關(guān)系

 

  那么在繼承的設(shè)計上, 也應(yīng)該遵循Unity的設(shè)計原則, 在繼承類型 : Editor 以及 base.OnInspectorGUI(); 繪制基類方法上做文章.

如果使用簡單的繼承比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[CustomEditor(typeof(TestCamera))]
public class TestCameraInspector : TestBaseClassInspector
{
    TestCamera _target = null;
    private void OnEnable()
    {
        _target = target as TestCamera;
    }
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
        _target.cam = EditorGUILayout.ObjectField("一次繼承變量 : ", _target.cam, typeof(Camera), true) as Camera;
    }
}

  會出現(xiàn)很多問題, 如基類的OnEnable方法沒有被觸發(fā), 基類面板報錯等, 所有生命周期都要寫虛方法, 每個重寫方法都要注意, 很麻煩.

  而Editor.CreateEditor創(chuàng)建的Editor是有生命周期的. 創(chuàng)建一個中間類型 InspectorDecoratorEditor, 大家都繼承它就可以了, 而繪制基類對象的方法就改為

DrawBaseInspectorGUI<T>(), 這樣就能方便地自己定義需要繪制的基類了.

public class InspectorDecoratorEditor : Editor
{
    public static readonly System.Type EndType = typeof(UnityEngine.MonoBehaviour);     // end type dont need show in inspector
    public static readonly System.Type BaseEditorType = typeof(UnityEditor.Editor);     // CustomEditor must inherit from it, filter
    public static readonly BindingFlags CustomEditorFieldFlags = BindingFlags.NonPublic | BindingFlags.Instance;    // flag
    // type cache[Assembly, [scriptType, customEditorType]]
    protected static Dictionary<Assembly, Dictionary<System.Type, System.Type>> ms_editorReferenceScript
        = new Dictionary<Assembly, Dictionary<System.Type, System.Type>>();
    protected List<Editor> m_inheritEditors = null;     // cached editors
    // ctor, use ctor instead Mono life circle, more user friendly
    public InspectorDecoratorEditor()
    {
        CacheEditorReferenceScript();
    }
    #region Main Funcs
    /// <summary>
    /// Cache all CustomEditor in current Assembly
    /// </summary>
    protected void CacheEditorReferenceScript()
    {
        var editorAssembly = Assembly.GetAssembly(this.GetType());      // editor may in diferent assemblies
        if(ms_editorReferenceScript.ContainsKey(editorAssembly) == false)
        {
            Dictionary<System.Type, System.Type> cachedData = new Dictionary<System.Type, System.Type>();
            var types = editorAssembly.GetExportedTypes();
            foreach(var editorType in types)
            {
                if(editorType.IsSubclassOf(BaseEditorType))
                {
                    var scriptType = GetTypeFormCustomEditor(editorType);
                    if(scriptType != null)
                    {
                        cachedData[scriptType] = editorType;
                    }
                }
            }
            ms_editorReferenceScript[editorAssembly] = cachedData;
        }
    }
    /// <summary>
    /// Draw a Target Type Inspector, call OnInspectorGUI
    /// </summary>
    /// <typeparam name="T"></typeparam>
    protected virtual void DrawBaseInspectorGUI<T>() where T : InspectorDecoratorEditor
    {
        if(m_inheritEditors == null)
        {
            m_inheritEditors = new List<Editor>();
            Dictionary<System.Type, System.Type> scriptEditorCache = null;
            if(ms_editorReferenceScript.TryGetValue(Assembly.GetAssembly(this.GetType()), out scriptEditorCache) && scriptEditorCache != null)
            {
                var baseType = target.GetType().BaseType;
                while(baseType != null && baseType != EndType)
                {
                    System.Type editorType = null;
                    if(scriptEditorCache.TryGetValue(baseType, out editorType) && editorType != null)
                    {
                        m_inheritEditors.Add(Editor.CreateEditor(targets, editorType));
                    }
                    baseType = baseType.BaseType;
                }
            }
        }
        if(m_inheritEditors.Count > 0)
        {
            for(int i = m_inheritEditors.Count - 1; i >= 0; i--)
            {
                var drawTarget = m_inheritEditors[i];
                if(drawTarget && drawTarget.GetType() == typeof(T))
                {
                    drawTarget.OnInspectorGUI();   // draw target type only, avoid endless loop
                    break;
                }
            }
        }
    }
    #endregion
    #region Help Funcs
    public static System.Type GetTypeFormCustomEditor(System.Type editorType)
    {
        var attributes = editorType.GetCustomAttributes(typeof(CustomEditor), false) as CustomEditor[];
        if(attributes != null && attributes.Length > 0)
        {
            var attribute = attributes[0];
            var type = attribute.GetType().GetField("m_InspectedType", CustomEditorFieldFlags).GetValue(attribute) as System.Type;
            return type;
        }
        return null;
    }
    #endregion
}

  

  

  修改后的Editor代碼如下, 修改的只有繼承類以及DrawBaseInspectorGUI<T>函數(shù), 注意這里對于T來說是沒有類型檢查的, 可是在函數(shù)中是有類型匹配的,

就算傳入錯誤類型也是安全的 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
[CustomEditor(typeof(TestBaseClass))]
public class TestBaseClassInspector : InspectorDecoratorEditor
{
    TestBaseClass _target;
    private void OnEnable()
    {
        _target = target as TestBaseClass;
    }
    public override void OnInspectorGUI()
    {
        _target.abc = EditorGUILayout.FloatField("基類變量 : ", _target.abc);
    }
}
[CustomEditor(typeof(TestCamera))]
public class TestCameraInspector : InspectorDecoratorEditor
{
    TestCamera _target = null;
    private void OnEnable()
    {
        _target = target as TestCamera;
    }
    public override void OnInspectorGUI()
    {
        DrawBaseInspectorGUI<TestBaseClassInspector>();
        _target.cam = EditorGUILayout.ObjectField("一次繼承變量 : ", _target.cam, typeof(Camera), true) as Camera;
    }
}
[CustomEditor(typeof(TestUntiy))]
public class TestUntiyInspector : InspectorDecoratorEditor
{
    TestUntiy _target = null;
    private void OnEnable()
    {
        _target = target as TestUntiy;
    }
    public override void OnInspectorGUI()
    {
        DrawBaseInspectorGUI<TestCameraInspector>();
        _target.hahah = EditorGUILayout.IntField("二次繼承變量 : ", _target.hahah);
    }
}

  然后看看檢視面板現(xiàn)在的樣子, 完美繪制了基類面板:

DrawBaseInspectorGUI<T>() 這個繪制基類的請求強大的地方就是可以選擇從哪個類型開始繪制, 比如
DrawBaseInspectorGUI<TestCameraInspector>();
換成
DrawBaseInspectorGUI<TestBaseClassInspector>();
那么 TestCameraInspector 這個檢視面板就被跳過去了 :

 

  雖然有很多方式能夠繪制或者繼承子類檢視面板, 不過這個應(yīng)該是個泛用度很高的方案. Over.

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多