Unity插件Odin入门

Posted 北海6516

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity插件Odin入门相关的知识,希望对你有一定的参考价值。

商店地址:Odin
Odin是一个对编辑器进行拓展的插件,可以序列化各种数据,方便的制作出各种编辑器界面,如下:


导入插件后,如图Tool–Odin Inspector–Getting Started可以查看Odin提供的概览界面。

点击Open Attributes Overview会显示属性,字段编辑相关的示例,可以让我们方便的编辑Inspector界面的内容。点击Leran More会显示一些窗口相关的示例,方便自定义一些弹窗界面。

概览的下方提供了一些Scene样例,方便进一步学习。

首先,查看字段相关的实例,如上图,左侧是分类,右侧上方是Inspector界面上绘制出的内容,右侧下方是对应的代码,可直接复制使用。
Odin提供了100多个特性(Attribute),只需要把特性加到字段上就可以显示上方的样式。特性简单说就是用来标记元素的,如字段,方法,类。这么多特性很难记住,一般是需要了再去查找合适的样式。

	//AssetsOnly表示只能拖拽Assets目录下的资源,场景中的资源是无法拖动
	[AssetsOnly]
	public GameObject SomePrefab;
	
	//SceneObjectsOnly相反,只能拖拽Scene场景中的资源
	[SceneObjectsOnly]
	public GameObject SomeSceneObject;

下面总结一些比较常用的特性

1.限制数值范围,滑块,进度条

    [Range(0, 100)]
    public int Field = 2;
    
    [MinValue(0)]
    public int IntMinValue0;

    [MaxValue(0)]
    public int IntMaxValue0;

    [ProgressBar(0, 100)]
    public float ProgressBar = 50;

2.数值变化时触发特定方法

	[OnValueChanged("OnValueChanged")]
    public int DelayedField;
    
    //ShowInInspector用于将属性显示到界面上
    [ShowInInspector]
    [OnValueChanged("OnValueChanged")]
    public string DelayedProperty  get; set; 

    private void OnValueChanged()
    
        Debug.Log("Value changed!");
    

3.颜色

	//字段添加颜色
    [GUIColor(0.3f, 0.8f, 0.8f, 1f)]
    public int ColoredInt1;
    
    //调色板
    [ColorPalette("Fall")]
    public Color Color1;
    
    //按钮添加颜色
    [GUIColor(0, 1, 0)]
    [Button("ButtonName", ButtonSizes.Small)]
    private void ButtonMethod()
    
    

4.提示信息

    //HideLabel用于隐藏字段名
    [Title("Vector3标题")]
    [HideLabel]
    public Vector3 WideVector1;
    
    //Space用于添加一行空隙
    [Space]
    [InfoBox("提示1")]
    public int Int1;
    
    //MyGameObject为空时才会提示
    [Required]
    public GameObject MyGameObject;

5.输入校验

    [ValidateInput("HasMeshRenderer")]
    public GameObject DynamicMessage;
    
    private bool HasMeshRenderer(GameObject gameObject, ref string errorMessage)
    
        if (gameObject == null) return true;

        if (gameObject.GetComponentInChildren<MeshRenderer>() == null)
        
            errorMessage = "\\"" + gameObject.name + "\\" 必须包含MeshRenderer组件";
            return false;
        

        return true;
    
    
    
    [ValidateInput("CheckSpace", "字符串不能有空格", InfoMessageType.Warning)]
    public string Message = "Dynamic";

    private bool CheckSpace(string value)
    
        return value.IndexOf(' ') < 0;
    

6.下拉列表

    [ValueDropdown("TextureSizes")]
    public int SomeSize1;
    
    private static int[] TextureSizes = new int[]  256, 512, 1024 ;
    
    
    [ValueDropdown("FriendlyTextureSizes")]
    public int SomeSize2;
    
    private static IEnumerable FriendlyTextureSizes = new ValueDropdownList<int>()
    
         "Small", 256 ,
         "Medium", 512 ,
         "Large", 1024 ,
    ;
    

    [ValueDropdown("TreeViewOfInts", ExpandAllMenuItems = true)]
    public List<int> IntTreview = new List<int>()  1, 2, 7 ;
    
    private IEnumerable TreeViewOfInts = new ValueDropdownList<int>()
    
         "Node 1/Node 1.1", 1 ,
         "Node 1/Node 1.2", 2 ,
         "Node 2/Node 2.1", 3 ,
         "Node 3/Node 3.1", 4 ,
         "Node 3/Node 3.2", 5 ,
         "Node 1/Node 3.1/Node 3.1.1", 6 ,
         "Node 1/Node 3.1/Node 3.1.2", 7 ,
    ;

7.分组

    //水平分组
    [HorizontalGroup] 
    public float num;
    
    [HorizontalGroup, Button(ButtonStyle.Box)]
    private void Full(float a, float b, out float c)
    
        c = a + b;
    
    
    //Box分组
    [BoxGroup("Titles")]
    public int A;

    [BoxGroup("Titles")]
    public int B;
    
    //按钮分组
    [ButtonGroup]
    private void C()  

    [ButtonGroup]
    private void D()  

8.集合


1.注意序列化字典必须继承SerializedMonoBehaviour,List不需要

public class Odin学习 : SerializedMonoBehaviour

    public Dictionary<int, Material> IntMaterialLookup;
    
    [OnInspectorInit]
    private void CreateData()
    
        IntMaterialLookup = new Dictionary<int, Material>()
        
             1, ExampleHelper.GetMaterial() ,
             7, ExampleHelper.GetMaterial() ,
        ;
    


2.List不加特性也可以使用,拖动左侧的滑块可以调整元素的顺序,TableList可以将List转为表格的形式,点击加号左边的按钮可以切换会原来列表的形式。

    public List<float> FloatList;
    
    [Range(0, 1)]
    public float[] FloatRangeArray;
    
    [TableList(ShowIndexLabels = true, AlwaysExpanded = true)]
    public List<SomeCustomClass> TableListWithIndexLabels = new List<SomeCustomClass>()
    
        new SomeCustomClass(),
        new SomeCustomClass(),
    ;
    
    [Serializable]
    public class SomeCustomClass
    
        [TableColumnWidth(57)]
        [PreviewField(Alignment = ObjectFieldAlignment.Center)]
        public Texture Icon;

        [TextArea]
        public string A, B;
    

9.条件

    public bool IsToggled;

    [DisableIf("IsToggled")]
    public int DisableIfToggled;
    
    [EnableIf("IsToggled")]
    public int EnableIfToggled;
    
    [DisableInEditorMode]
    public GameObject A;

    [DisableInPlayMode]
    public Material B;

    [HideIf("IsToggled")]
    public Vector3 HiddenWhenToggled;

    [ShowIf("IsToggled")]
    public Vector2 VisibleWhenToggled;

10.资源列表

	//显示该路径下的材质,路径前面的Assets不用写
    [AssetList(Path = "Materials/")]
    public List<Material> AssetList;
    
    [AssetList(AssetNamePrefix = "Line")]
    public List<Material> MaterialsStartingWithLine;

11.窗口

public class Odin窗口 : OdinEditorWindow

    [MenuItem("Tools/简单窗口")]
    private static void OpenWindow()
    
        var window = GetWindow<Odin窗口>();
        window.position = GUIHelper.GetEditorWindowRect().AlignCenter(500, 500);
    

    [EnumToggleButtons]
    public ViewTool SomeField;


Unity3D Editor Undo回退效果实现3 Odin相关

环境:Unity2021.1.14 Odin3.0.4 语言:C#

面向:UnityEditor/Odin进阶开发人员

总起

我们在前两篇文章中讲解了Unity中Undo原理并进行了实现,这次我们来看看Odin是如何接入Undo。

实际上Odin本身是基于Unity的IMGUI,所以它Undo的底层实现就是使用的Unity中的Undo。

Odin实现Undo

先来看看以下实现:

using System;
using Sirenix.OdinInspector;
using Sirenix.OdinInspector.Editor;
using UnityEngine;

public class TestOdinUndo : MonoBehaviour

    private TestOdinUndoData testOdinUndoData = new TestOdinUndoData();

    private PropertyTree _propertyTree;
    public PropertyTree propertyTree
    
        get
        
            if (_propertyTree == null)
            
                _propertyTree = PropertyTree.Create(testOdinUndoData);
            
            return _propertyTree;
        
    

    [OnInspectorGUI]
    public void OnInspectorGUI()
    
        propertyTree.Draw(false);
    


[Serializable]
public class TestOdinUndoData

    public int iValue;

效果如下,iValue能够正确显示出来,不过Undo功能因为Draw中传的是false所以没有生效。

 

接下来我们尝试在外面包裹一层ScriptableObject转成Unity的Object,再包裹一层SerializedObject进行传入:

using System;
using Sirenix.OdinInspector;
using Sirenix.OdinInspector.Editor;
using UnityEditor;
using UnityEngine;

public class TestOdinUndo : MonoBehaviour

    // 包裹的第二层,SerializedObject内部是序列化的数据,方便Unity处理
    private SerializedObject _serializedObject;

    public SerializedObject serializedObject
    
        get
        
            if (_serializedObject == null)
            
                _serializedObject = new SerializedObject(testOdinUndoWrapData);
            
            return _serializedObject;
        
    

    // 包裹的第一层,是个ScriptableObject
    private TestOdinUndoWrapData _testOdinUndoWrapData;

    private TestOdinUndoWrapData testOdinUndoWrapData
    
        get
        
            if (_testOdinUndoWrapData == null)
            
                _testOdinUndoWrapData = ScriptableObject.CreateInstance<TestOdinUndoWrapData>();
                _testOdinUndoWrapData.data = testOdinUndoData;
            
            return _testOdinUndoWrapData;
        
    

    private TestOdinUndoData testOdinUndoData = new TestOdinUndoData();

    private PropertyTree _propertyTree;
    public PropertyTree propertyTree
    
        get
        
            if (_propertyTree == null)
            
                // 创建PropertyTree使用SerializedObject
                _propertyTree = PropertyTree.Create(serializedObject);
            
            return _propertyTree;
        
    

    [OnInspectorGUI]
    public void OnInspectorGUI()
    
        // 调用Draw,传入true
        propertyTree.Draw(true);
    


public class TestOdinUndoWrapData : ScriptableObject

    public TestOdinUndoData data;


[Serializable]
public class TestOdinUndoData

    public int iValue;

按照以上即可实现Undo的功能。

事实上Unity Inspector处理各个字段绘制时也使用的是SerializedObject,我们在使用IMGUI写Editor时Unity也是建议使用该对象的。

Odin的序列化

我们先来看以下代码:

using Sirenix.OdinInspector;

public class TestOdinSerialize : SerializedMonoBehaviour

    public int iValue;

    public float fValue;

 

把它做成Prefab保存,我们可以看到iValue和fValue都是由Unity本身进行序列化的:

 使用OdinSerialize标签:

using Sirenix.OdinInspector;
using Sirenix.Serialization;

public class TestOdinSerialize : SerializedMonoBehaviour

    [OdinSerialize]
    public int iValue;

    public float fValue;

保存后结果:

 

可以看到Odin序列化中也出现了iValue字段,同时Unity也会序列化该字段。

我们通过Serialization Debugger也可以看到该结果。

 

想要禁用Unity的序列化只需要打上NonSerialized,这样就只会有Odin的序列化:

using System;
using Sirenix.OdinInspector;
using Sirenix.Serialization;

public class TestOdinSerialize : SerializedMonoBehaviour

    [NonSerialized]
    [OdinSerialize]
    public int iValue;

    public float fValue;

总结一下:

  1. Unity能序列化的字段,Odin不会主动序列化;
  2. 使用OdinSerialize和NonSerialized两个标签强制使用Odin序列化;
  3. 当Unity和Odin都能序列化的时候,则会产生两份序列化数据,所以要想好使用哪种序列化,可以使用Serialization Debugger进行确认。

然后根据官网整理了两个重点:

  1. 当子对象中有需要Odin的序列化时,需要从父对象一路下来都要指定为Odin序列化,否则会使用Unity的序列化;
  2. Odin序列化的是最佳实践是:尽量使用Unity的序列化,因为Odin的序列化会产生两次序列化过程比较消耗性能。

Odin序列化的一些细节

Odin在处理序列化时实际上是把数据转存到SerializationData上。

在默认情况下会有一个List<SerializationNode>记录想要序列化的数据,然后再由Unity进行序列化,这是之前说的Odin会产生两次序列化过程的本质。

针对这套流程我做了一个简单的小实验:

List<SerializationNode> nodes = new List<SerializationNode>();
nodes.Add(new SerializationNode() Name = "iValue", Entry = EntryType.Integer, Data = "3");

using (var context = Cache<DeserializationContext>.Claim())
using (var reader = new SerializationNodeDataReader(context))
using (var resolver = Cache<UnityReferenceResolver>.Claim())

    reader.Nodes = nodes;
    context.Value.IndexReferenceResolver = resolver.Value;
    UnitySerializationUtility.DeserializeUnityObject(this, reader);

执行这套流程,我们可以看到iValue变成3。

通过这样的实现我们主动构建了这套Node的List,然后再进行反序列化到对象上。

或许通过这套流程我们能实现json转成Node保存到Unity asset中?我还没有做更加深入的研究,或许等哪天需要吧,这边暂时只提一个思路。

以上是关于Unity插件Odin入门的主要内容,如果未能解决你的问题,请参考以下文章

Unity3D Odin Inspector 简单介绍与入门

很好用的Unity编辑器扩展工具 Odin Inspector教程

Unity3D Editor Undo回退效果实现3 Odin相关

Unity3D Editor Undo回退效果实现3 Odin相关

Unity3D Odin Inspector Attribute回调的实现原理

Unity3D Odin Inspector Attribute回调的实现原理