FishPlayer

一个喜欢摸鱼的废物

0%

Unity Odin CustomValueDrawer 小坑

为了方便策划在 scriptable object 上配置我们的任务以及事件相关的文本,我做了一个简单的 struct 。里面包含了 I2 的 Lockey 和一个参数数组。
既然要方便策划配置,那肯定要做一个简单的预览,这样策划就能知道自己配置的文本参数是否正确。

既然要做预览,那肯定得得自己复写一下他的 property drawer了。对一个字段的编辑器绘制重写有两种方式,一种是继承 OdinValueDrawer class, 还有一种是 CustomValueDrawer Attribute.

https://odininspector.com/tutorials/how-to-create-custom-drawers-using-odin/how-to-create-a-custom-value-drawer
https://odininspector.com/attributes/custom-value-drawer-attribute

踩坑

我定义的结构是这样的。

在默认情况下他已经能在 inspector上画出我的 Lockey 和我的参数数组。
因为考虑到给整个结构做 OdinValueDrawer 需要自己画数组有点麻烦,所以我选择用 CustomValueDrawer 只重新画 LocalizedString。

LocalizedString 的重画我直接照搬了同事的,但是我发现了一个非常奇怪的问题。

我创建了一个下拉选框,然后在用户选中一个新的 lockey 的时候我对结构体中的 lockey 进行更新。但我发现在下拉选框的选中事件触发后进行赋值,绘制完成再返回新的结构,得到的是没有被更改的结果。
但是同样的绘制代码,在 OdinValueDrawer 中却能得到正确的结果!!!CustomValueDrawer 和 OdinValueDrawer 的原理是不一样的。

OdinValueDrawer 就是相当于一个更先进的 Property Drawer, 在这个绘制器里是可以获取到原本的实际的字段值的。

CustomValueDrawer 则像是一个函数,里面包含了绘制的方法,但是不能够直接操作原本的字段,需要最后把你更新到的值返回出去,这才能够赋值给实际的那个字段。

代码给出,有问题的地方使用注释标明了。

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117

[Serializable]
public struct LocalizationStringParmPack
{
[CustomValueDrawer("DrawPreviewText")]
public LocalizedString TextLoc;
public LocalizationParamsManager.ParamValue[] ParmsArray;
public override string ToString()
{
return this.GetRawEnglishString();
}

#if UNITY_EDITOR

static GUIStyle m_previewStyle;
static GUIStyle PreviewStyle
{
get
{
if (m_previewStyle == null)
{
m_previewStyle = new GUIStyle(EditorStyles.textArea);
m_previewStyle.wordWrap = true;
}
return m_previewStyle;
}
}

// use hacky stuff to get selected new lockey
static string s_wtfKey = string.Empty;
static bool s_hasChanged = false;
LocalizedString DrawPreviewText(LocalizedString value, GUIContent label)
{
using (new EditorGUILayout.VerticalScope(UnityEditor.EditorStyles.helpBox))
{
// draw lockey
using (new EditorGUILayout.HorizontalScope())
{
if (label != null)
{
EditorGUILayout.LabelField(label, GUILayout.Width(EditorGUIUtility.labelWidth));
}

// weird stuff happin in dropdown
if (EditorGUILayout.DropdownButton(new GUIContent(value.mTerm), FocusType.Keyboard))
{
var terms = LocalizationManager.GetTermsList();
var selector = new GenericSelector<string>("Select Term", terms);
selector.EnableSingleClickToSelect();
selector.SelectionConfirmed += s =>
{
if (s.Any())
{
s_wtfKey = s.First();
// WEIRD STUFF HERE
/* if I do
LocalizedString next = new LocalizedString(s.First());
and return 'next' and the end of method, it just doesnt work!!!

cuz the GenericSelector is actually a editor window, and
*/
s_hasChanged = true;
}
};
selector.ShowInPopup();
}

int mask = (value.mRTL_IgnoreArabicFix ? 0 : 1) +
(value.mRTL_ConvertNumbers ? 0 : 2) +
(value.m_DontLocalizeParameters ? 0 : 4);

using (var changed = new EditorGUI.ChangeCheckScope())
{
int newMask = EditorGUILayout.MaskField(mask, new string[] { "Arabic Fix", "Ignore Numbers in RTL", "Localize Parameters" }, GUILayout.Width(30));
if (newMask != mask)
{
value.mRTL_IgnoreArabicFix = (newMask & 1) == 0;
value.mRTL_ConvertNumbers = (newMask & 2) == 0;
value.m_DontLocalizeParameters = (newMask & 4) == 0;
}
}

if (GUILayout.Button($"Clear"))
{
/* this will also work
LocalizedString next = new LocalizedString("[NULL]");
*/
s_wtfKey = "[NULL]";
}

}

LocalizedString next = s_hasChanged ? new LocalizedString(s_wtfKey) : value;
s_hasChanged = false;
/*
draw preview
draw preview
draw preview
*/
return next;
}
}

string GetParamValue(string key)
{
for (int i = 0; i < ParmsArray.Length; i++)
{
if (ParmsArray[i].Name == key)
{
return string.IsNullOrEmpty(ParmsArray[i].Value) ? key : ParmsArray[i].Value;
}
}
return key;
}

#endif
}

又踩坑

因为这边使用了静态字段,所以如果需要同时绘制这个字段两次,那就会出问题。所以最后我还是换回OdinValueDrawer。
静态字段最好还是别在这种情况下使用吧。