Unity 编辑器扩展:解决 enum 元素追加导致的 Inspector 值错乱问题
enumの途中追加に対応しようとした話
「在Unity中使用enum来管理状态很方便,试试看吧」
「增加了新的状态,那就添加进去吧」
「也要整理一下脚本内的外观」
「……咦?之前设置的元素怎么不见了……」
有过这样的经历吗?
我多次为此烦恼,于是决定通过扩展编辑器来尝试解决问题
本文将总结这一过程及其成果
问题的起因
正如开头所述,在Unity中使用enum可以方便地进行状态管理等操作,但在无法应对元素的添加时,我感到不便,这便是契机。
具体例
以下是作为测试创建的ScriptableObject类
定义了名为TestState的enum,并通过SerializeField属性使其可以在Inspector中进行编辑。
using UnityEngine;
[CreateAssetMenu(fileName = "Test", menuName = "Scriptable Objects/Test")]
public class Test : ScriptableObject
{
[SerializeField] TestState _test;
}
enum TestState
{
Idle,
Move,
Attack,
Jump
}
实际上在Inspector中查看的话…
这样,可以选择在TestState中定义的内容
这里让我们添加一个名为Die的元素吧
enum TestState
{
Idle,
Move,
Attack,
Jump,
Die // 追加
}
已经成功添加了
那么 如果将Die元素移动到TestState的最上方 会怎样呢?
enum TestState
{
Die, // 一番上に移動
Idle,
Move,
Attack,
Jump
}
刚才在Inspector上选择了"Idle",但保存此更改后…
要素的顺序被反映出来,之前选择的元素也变成了"Die"…
虽然说是理所当然的,但我对此感到不便。
关于问题
如上例所示,当添加了导致 enum 定义顺序发生变化的元素时,Inspector 中显示的顺序以及已设置的元素都会发生变化,我认为这是一个问题。
造成这种行为的原因是
- 在 enum 中定义的项会被自动分配 连续的值
- Unity 在序列化 enum 时,会 保存所分配的值
ということがあげられます
先の例で言えば、
enum TestState
{
Idle, // 0
Move, // 1
Attack, // 2
Jump // 3
}
というように値が割り振られていたということであり、Inspectorで "Idle" を設定した場合にUnity内部では "Idle" ではなく、そこに割り振られていた "0" を保存していたというわけです
あとから "Die" を追加しても問題が起きなかったのは割り振られた値に変更がなかったためです
enum TestState
{
Idle, // 0 <= 番号に変更なし
Move, // 1
Attack, // 2
Jump, // 3
Die // 4
}
しかしその後、要素の順番を入れ替えた時に問題が起きたのは、 順番を入れ替えたことによって割り振られる番号が変わってしまったから というわけですね
enum TestState
{
Die, // 0
Idle, // 1 <= 順番が変わって番号も変わった
Move, // 2
Attack, // 3
Jump // 4
}
ここまでの流れをまとめると、
- 在Inspector中设置了Idle = 0
- Unity保存了"0"而不是"Idle"
- 添加了Die = 4(此处无变化)
- 更改为Die = 0
- 变为Idel = 1
- Unity显示了当前枚举元素"Die",它对应于已保存的"0"
ということになります
ちょっとの修正で済むのであれば気にすることもないのですが、
- あらゆる場所でenumを使っている
- 要素を順番の初めの方に追加する
この条件下では、ほとんどの場所で さっきまで設定していた要素とは一個以上ずれている わけですから、それらをすべて修正しなければいけないなんて考えたくもありません…
全くあり得ない条件ではないということもわかると思います
問題解消について
到目前为止,
有人可能会想:"难道不能自己分配编号吗?"
但"完全正确"
enum 也可以由用户自行指定编号,只要能够自主管理编号,问题就能得到解决
这与 Unity 以编号管理 enum 的方式也非常契合
实际上,如果像下面这样操作,在大多数情况下都不会遇到麻烦
enum TestState
{
Idle = 0,
Move = 10,
Attack = 20,
Jump = 30
// 番号に幅を持たせることで要素を追加してもある程度は対応できる
}
…不过,如果必须手动分配编号,或者即使预留了范围,当需要添加的元素超出该范围时,最终还是不得不重新分配编号,麻烦事依然存在(如果有人说"这也没多费事,干脆放弃吧",那也就只能如此了……)
最重要的是,即便最初是这样定义的,
enum TestState
{
Idle = 0,
Move = 1,
Attack = 2,
Jump = 3
}
如果之后觉得这样的定义更清晰的话,
enum TestState
{
Move = 1,
Jump = 3,
Attack = 2,
Idle = 0
// 番号に変更なし
}
虽然可能是局限的情况,但这,不觉得有点恶心吗…?
挑战解决问题
总之,铺垫了这么久,接下来我将说明是如何尝试解决问题的
虽然听起来像是想到了什么了不起的东西,但实际上为了解决问题所考虑的逻辑极其简单
- 保存更改前的enum(分配的编号和名称)
- 在 Inspector 中设置所需的元素
- 更改 enum
- 获取与当前保存在 Unity 中的值相对应的“更改前的 enum 元素名称”
- 获取已分配给更改后的 enum 的值,并用该值覆盖当前保存在 Unity 中的值
- 更新 Inspector 的显示
由于用文字说明可能难以理解,我们通过具体示例来查看
1. 首先保存 enum 的定义
修改前的 enum
enum TestState
{
Idle,
Move,
Attack,
Jump
}
対応表
Idle : 0
Move : 1
Attack : 2
Jump : 3
💾 保存一下吧
2.在 Inspector 中设置喜欢的元素
这次假设暂时设置为 "Idle"
也就是说,在 Unity 中保存的是 "0"
3.更改 enum
"Idle" を一番下に移動しました
変更後enum
enum TestState
{
Move,
Attack,
Jump,
Idle // 一番下に移動
}
💾 保存しましょう
4.現在Unityに保存されている値に対応する"変更前のenumの要素の名前"を取得
現在Unityには "0" が保存されていたため、変更前のenumに照らし合わせると "Idle" を取得できます
保存形式
Idle : 0 // <= ここを参照
Move : 1
Attack : 2
Jump : 3
5.取得した名前が"変更後のenumで割り当てられている値"を取得し、Unityに現在保存されている値をこの値で上書きする
由于在修改后的枚举中,Idle被分配了值"3",因此需要将当前保存在Unity中的值"0"改写为"3"
修改后的枚举
enum TestState
{
Move,
Attack,
Jump,
Idle // <= ここを参照
}
6. 更新Inspector的显示
最后,如果更新了Inspector的显示,Unity将会显示与当前保存的值相对应的枚举元素,因此在修改后的枚举中,仍然会显示选择了"Idle"。
关于成果物
以下是项目的GitHub链接,按照README.md中描述的步骤操作,即可使用我所创建的功能。
这只是一个初步尝试制作的东西,完成度仍处于原型阶段,但如果您能试用一下,我将不胜感激。
目前仅支持ScriptableObject,但未来(如果有动力和时间的话),我希望能够扩展到所有使用枚举的地方。
如果发现任何错误,请务必告知我。
查看点赞用户列表
0
注册新用户并更便捷地使用 Qiita
- 获取符合您需求的文章
- 高效回顾有用信息
- 支持深色主题
来源:Qiita · Unity 开发社区 · qiita.com


