FeastSC 发表于 2022-1-5 17:25

Unity3D Editor ControlID 简单介绍

环境:Unity2021.1.14 Odin3.0.4 语言:C#
面向:Editor开发人员
总起

当我在工具代码中看到这行代码时:
GUIUtility.hotControl = id;

我产生了一个疑问,ControlID是什么?

在http://49.233.81.186/guicreation.html中的介绍也只是寥寥几笔:

为每个GUI分配一个ControlID,此ID使每个GUI独立,如果没有正确分配这个ID,你最终会遇到多个GUI操作的冲突。比如当鼠标拖动GUI.Window时,范围选择工具起作用了。

生成/获取ControlID:
int id = GUIUtility.GetControlID(FocusType.Passive, rect);

通过ControlID控制样式:
EditorStyles.objectFieldThumb.Draw(rect, content, id);

而使用GUIUtility.hotControl可以找出当前处于焦点的ControlID,或者使用GUIUtility.keyboardControl你可以知道键盘的焦点。
一个例子

在Odin的使用中我发现了以下的bug(或者说设计如此):



我设计了这么个界面,Data中显示Datas的其中一个数据,然后选择显示哪个数据,由ChooseIndex决定。
代码如下:
using System;
using Sirenix.OdinInspector;
using UnityEngine;

public class TestControlID : MonoBehaviour
{
   
    public class Data
    {
      
      public int testInt;

      public Mode mode = Mode.Show;
    }

    public enum Mode
    {
      Show,
      DontShow,
    }

   
    public int chooseIndex;

    public void Choose()
    {
      if (chooseIndex >= 0 && chooseIndex < datas.Length)
      {
            data = datas;
      }
    }

    public Data data;

    public Data[] datas;
}

先把数据全选成NotShow,之后选择第一个Data将 Mode选择Show,再切到第二个,这时会神奇的发现第二个Data也“粘连”修改成了Show。

这个问题最终原因就是出在ControlID上:
1. Odin使用的enum popup有两段操作,鼠标按下和抬起都会使值触发变化;
2. 鼠标按下已经使值从DontShow改成了Show,造成了TestInt被隐藏,这导致了当前Mode框的ControlID发生了变化;
3. 鼠标抬起也会触发值的变化,但此时的ControlID已经发生了变化,所以OdinSelector<T>.confirmedPopupControlId虽然被记录下来,但并没有触发;
4. 等到我们改换显示第二个数据,因为计算出来ControlID是一致的,所以第二段此时才进行触发;

产生“粘连”修改问题,也很好解决:
1. 禁用这种二段触发的popup,可以使用Unity原生popup;
2. ControlID在计算的时候可以率先分配一个不共用的id段;
3. 会被隐藏的属性都放到mode下面,这样ControlID不会发生变化。

这个例子直接放到InspectorWindow上进行切换是不会有问题的,Unity的处理方式实际就是第2种。

不过落到具体的方式上Unity2021和Unity2017还有所不同,2021采用了切换时会一直增长的方式,而2017则会为每个GameObject分配单独的ControlID。
总结

ControlID的具体算法因为是写在C++层的,我还是不是很了解,有大佬知道的话可以指引一下。
大体的作用实际就是标明每个控件的唯一ID,但是这种做法也会有局限,就是缓存下来的这个ID可能会随时变换,这是一个不稳定的ID。
页: [1]
查看完整版本: Unity3D Editor ControlID 简单介绍