com.artromskiy.deltaecs.unity 0.0.38 → 0.0.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/CHANGELOG.md.meta +7 -0
- package/Editor/BoundEntityRootEditor.cs +23 -6
- package/Editor/ComponentPropertyBagFields.cs +280 -0
- package/Editor/ComponentPropertyBagFields.cs.meta +2 -0
- package/Editor/Resources/Inspector/SceneAuthoringEditor.uss +15 -16
- package/Editor/Resources/Inspector/SceneAuthoringEditor.uxml +1 -1
- package/Editor/SceneEntityInspectorView.cs +251 -120
- package/Editor.meta +8 -0
- package/README.md +1 -1
- package/Runtime/ComponentData.cs +16 -25
- package/Runtime/PropertyBagValueAccess.cs +258 -0
- package/Runtime/PropertyBagValueAccess.cs.meta +2 -0
- package/Runtime/SceneWorld.cs +62 -5
- package/Runtime.meta +8 -0
- package/package.json +8 -2
- package/package.json.meta +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -20,7 +20,9 @@ namespace Delta.ECS.Unity.Editor
|
|
|
20
20
|
return null;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
if (objects == null || objects.Length != 1
|
|
23
|
+
if (objects == null || objects.Length != 1
|
|
24
|
+
|| objects[0] is not GameObject gameObject
|
|
25
|
+
|| gameObject == null)
|
|
24
26
|
{
|
|
25
27
|
return null;
|
|
26
28
|
}
|
|
@@ -43,6 +45,11 @@ namespace Delta.ECS.Unity.Editor
|
|
|
43
45
|
|
|
44
46
|
internal static UnityEditor.Editor CreateDefaultGameObjectEditor(GameObject gameObject)
|
|
45
47
|
{
|
|
48
|
+
if (gameObject == null)
|
|
49
|
+
{
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
46
53
|
_creatingDefaultGameObjectEditor = true;
|
|
47
54
|
try
|
|
48
55
|
{
|
|
@@ -68,7 +75,7 @@ namespace Delta.ECS.Unity.Editor
|
|
|
68
75
|
|
|
69
76
|
private void OnEnable()
|
|
70
77
|
{
|
|
71
|
-
if (target is GameObject gameObject)
|
|
78
|
+
if (target is GameObject gameObject && gameObject != null)
|
|
72
79
|
{
|
|
73
80
|
_gameObjectEditor = BoundEntityRootEditorSelector.CreateDefaultGameObjectEditor(gameObject);
|
|
74
81
|
}
|
|
@@ -143,10 +150,7 @@ namespace Delta.ECS.Unity.Editor
|
|
|
143
150
|
{
|
|
144
151
|
root.Add(new IMGUIContainer(() =>
|
|
145
152
|
{
|
|
146
|
-
|
|
147
|
-
{
|
|
148
|
-
_gameObjectEditor.DrawHeader();
|
|
149
|
-
}
|
|
153
|
+
DrawGameObjectHeader(_gameObjectEditor);
|
|
150
154
|
}));
|
|
151
155
|
}
|
|
152
156
|
|
|
@@ -182,6 +186,19 @@ namespace Delta.ECS.Unity.Editor
|
|
|
182
186
|
return root;
|
|
183
187
|
}
|
|
184
188
|
|
|
189
|
+
private void DrawGameObjectHeader(UnityEditor.Editor gameObjectEditor)
|
|
190
|
+
{
|
|
191
|
+
if (gameObjectEditor == null
|
|
192
|
+
|| target is not GameObject gameObject
|
|
193
|
+
|| gameObject == null
|
|
194
|
+
|| gameObjectEditor.target == null)
|
|
195
|
+
{
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
gameObjectEditor.DrawHeader();
|
|
200
|
+
}
|
|
201
|
+
|
|
185
202
|
private void RefreshComponentInspectors()
|
|
186
203
|
{
|
|
187
204
|
if (_componentInspectorRoot != null && !HaveSameComponents())
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections.Generic;
|
|
3
|
+
using Delta;
|
|
4
|
+
using Delta.ECS;
|
|
5
|
+
using Delta.ECS.Integration;
|
|
6
|
+
using Delta.ECS.Unity;
|
|
7
|
+
using Unity.Properties;
|
|
8
|
+
using UnityEditor;
|
|
9
|
+
using UnityEditor.UIElements;
|
|
10
|
+
using UnityEngine;
|
|
11
|
+
using UnityEngine.UIElements;
|
|
12
|
+
|
|
13
|
+
namespace Delta.ECS.Unity.Editor
|
|
14
|
+
{
|
|
15
|
+
internal readonly struct ComponentPropertyBinding
|
|
16
|
+
{
|
|
17
|
+
internal readonly string[] Path;
|
|
18
|
+
internal readonly Type ValueType;
|
|
19
|
+
internal readonly VisualElement Control;
|
|
20
|
+
|
|
21
|
+
internal ComponentPropertyBinding(string[] path, Type valueType, VisualElement control)
|
|
22
|
+
{
|
|
23
|
+
Path = path;
|
|
24
|
+
ValueType = valueType;
|
|
25
|
+
Control = control;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
internal static class ComponentPropertyBagFields
|
|
30
|
+
{
|
|
31
|
+
internal static ComponentPropertyBinding[] Build(VisualElement parent, ComponentDescriptor descriptor,
|
|
32
|
+
object value, bool canWrite, Action<string[], object> onChanged,
|
|
33
|
+
Func<string, Entity, Action<object>, VisualElement> createEntityField)
|
|
34
|
+
{
|
|
35
|
+
if (value == null)
|
|
36
|
+
{
|
|
37
|
+
return Array.Empty<ComponentPropertyBinding>();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
IPropertyBag propertyBag = PropertyBag.GetPropertyBag(descriptor.ValueType);
|
|
41
|
+
if (propertyBag == null)
|
|
42
|
+
{
|
|
43
|
+
return Array.Empty<ComponentPropertyBinding>();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
var bindings = new List<ComponentPropertyBinding>();
|
|
47
|
+
var visitor = new PropertyFieldVisitor(parent, canWrite, Array.Empty<string>(), 0,
|
|
48
|
+
bindings, onChanged, createEntityField);
|
|
49
|
+
propertyBag.Accept(visitor, ref value);
|
|
50
|
+
return bindings.ToArray();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private sealed class PropertyFieldVisitor : IPropertyBagVisitor, IPropertyVisitor
|
|
54
|
+
{
|
|
55
|
+
private readonly VisualElement _parent;
|
|
56
|
+
private readonly bool _canWrite;
|
|
57
|
+
private readonly string[] _path;
|
|
58
|
+
private readonly int _depth;
|
|
59
|
+
private readonly List<ComponentPropertyBinding> _bindings;
|
|
60
|
+
private readonly Action<string[], object> _onChanged;
|
|
61
|
+
private readonly Func<string, Entity, Action<object>, VisualElement> _createEntityField;
|
|
62
|
+
|
|
63
|
+
internal PropertyFieldVisitor(VisualElement parent, bool canWrite, string[] path, int depth,
|
|
64
|
+
List<ComponentPropertyBinding> bindings, Action<string[], object> onChanged,
|
|
65
|
+
Func<string, Entity, Action<object>, VisualElement> createEntityField)
|
|
66
|
+
{
|
|
67
|
+
_parent = parent;
|
|
68
|
+
_canWrite = canWrite;
|
|
69
|
+
_path = path;
|
|
70
|
+
_depth = depth;
|
|
71
|
+
_bindings = bindings;
|
|
72
|
+
_onChanged = onChanged;
|
|
73
|
+
_createEntityField = createEntityField;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public void Visit<TContainer>(IPropertyBag<TContainer> properties, ref TContainer container)
|
|
77
|
+
{
|
|
78
|
+
foreach (var property in properties.GetProperties(ref container))
|
|
79
|
+
{
|
|
80
|
+
((IPropertyAccept<TContainer>)property).Accept(this, ref container);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public void Visit<TContainer, TValue>(Property<TContainer, TValue> property, ref TContainer container)
|
|
85
|
+
{
|
|
86
|
+
TValue value = property.GetValue(ref container);
|
|
87
|
+
Type valueType = typeof(TValue);
|
|
88
|
+
string[] path = Append(_path, property.Name);
|
|
89
|
+
string label = GetLabel(property.Name);
|
|
90
|
+
bool canWrite = _canWrite && !property.IsReadOnly;
|
|
91
|
+
|
|
92
|
+
VisualElement control = CreateFieldControl(label, valueType, value,
|
|
93
|
+
next => _onChanged(path, next), canWrite, _createEntityField);
|
|
94
|
+
if (control != null)
|
|
95
|
+
{
|
|
96
|
+
_parent.Add(control);
|
|
97
|
+
_bindings.Add(new ComponentPropertyBinding(path, valueType, control));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (value != null && _depth < 24)
|
|
102
|
+
{
|
|
103
|
+
object nestedValue = value;
|
|
104
|
+
IPropertyBag nestedBag = PropertyBag.GetPropertyBag(nestedValue.GetType());
|
|
105
|
+
if (nestedBag != null)
|
|
106
|
+
{
|
|
107
|
+
var foldout = new Foldout { text = label, value = true };
|
|
108
|
+
foldout.AddToClassList("ecs-property-foldout");
|
|
109
|
+
_parent.Add(foldout);
|
|
110
|
+
int previousCount = _bindings.Count;
|
|
111
|
+
bool canWriteNested = canWrite && valueType.IsValueType;
|
|
112
|
+
var nestedVisitor = new PropertyFieldVisitor(foldout, canWriteNested, path,
|
|
113
|
+
_depth + 1, _bindings, _onChanged, _createEntityField);
|
|
114
|
+
nestedBag.Accept(nestedVisitor, ref nestedValue);
|
|
115
|
+
if (_bindings.Count == previousCount)
|
|
116
|
+
{
|
|
117
|
+
var empty = new Label("No exposed properties.");
|
|
118
|
+
empty.AddToClassList("ecs-empty-component");
|
|
119
|
+
foldout.Add(empty);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
string detail = value == null ? "Null" : $"Unsupported: {valueType.Name}";
|
|
127
|
+
var unsupported = new Label($"{label} · {detail}");
|
|
128
|
+
unsupported.AddToClassList("ecs-unsupported-field");
|
|
129
|
+
_parent.Add(unsupported);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private static VisualElement CreateFieldControl(string label, Type type, object value,
|
|
134
|
+
Action<object> onChanged, bool canWrite,
|
|
135
|
+
Func<string, Entity, Action<object>, VisualElement> createEntityField)
|
|
136
|
+
{
|
|
137
|
+
VisualElement control;
|
|
138
|
+
if (type == typeof(float))
|
|
139
|
+
{
|
|
140
|
+
var field = new FloatField(label) { value = value == null ? 0f : (float)value, isDelayed = true };
|
|
141
|
+
field.RegisterValueChangedCallback(evt => onChanged(evt.newValue));
|
|
142
|
+
control = field;
|
|
143
|
+
}
|
|
144
|
+
else if (type == typeof(double))
|
|
145
|
+
{
|
|
146
|
+
var field = new DoubleField(label) { value = value == null ? 0d : (double)value, isDelayed = true };
|
|
147
|
+
field.RegisterValueChangedCallback(evt => onChanged(evt.newValue));
|
|
148
|
+
control = field;
|
|
149
|
+
}
|
|
150
|
+
else if (type == typeof(string))
|
|
151
|
+
{
|
|
152
|
+
var field = new TextField(label) { value = (string)value ?? string.Empty, isDelayed = true };
|
|
153
|
+
field.RegisterValueChangedCallback(evt => onChanged(evt.newValue));
|
|
154
|
+
control = field;
|
|
155
|
+
}
|
|
156
|
+
else if (type == typeof(char))
|
|
157
|
+
{
|
|
158
|
+
var field = new TextField(label) { value = value == null ? string.Empty : value.ToString(), isDelayed = true };
|
|
159
|
+
field.RegisterValueChangedCallback(evt => onChanged(string.IsNullOrEmpty(evt.newValue) ? '\0' : evt.newValue[0]));
|
|
160
|
+
control = field;
|
|
161
|
+
}
|
|
162
|
+
else if (type == typeof(byte) || type == typeof(sbyte) || type == typeof(short)
|
|
163
|
+
|| type == typeof(ushort) || type == typeof(int))
|
|
164
|
+
{
|
|
165
|
+
var field = new IntegerField(label) { value = value == null ? 0 : Convert.ToInt32(value), isDelayed = true };
|
|
166
|
+
field.RegisterValueChangedCallback(evt => onChanged(ConvertInteger(type, evt.newValue)));
|
|
167
|
+
control = field;
|
|
168
|
+
}
|
|
169
|
+
else if (type == typeof(uint) || type == typeof(long))
|
|
170
|
+
{
|
|
171
|
+
var field = new LongField(label) { value = value == null ? 0L : Convert.ToInt64(value), isDelayed = true };
|
|
172
|
+
field.RegisterValueChangedCallback(evt => onChanged(type == typeof(uint)
|
|
173
|
+
? (object)(uint)Math.Max(0L, Math.Min(uint.MaxValue, evt.newValue))
|
|
174
|
+
: evt.newValue));
|
|
175
|
+
control = field;
|
|
176
|
+
}
|
|
177
|
+
else if (type == typeof(ulong))
|
|
178
|
+
{
|
|
179
|
+
var field = new TextField(label) { value = value == null ? "0" : ((ulong)value).ToString(), isDelayed = true };
|
|
180
|
+
field.RegisterValueChangedCallback(evt =>
|
|
181
|
+
{
|
|
182
|
+
if (ulong.TryParse(evt.newValue, out ulong parsed)) onChanged(parsed);
|
|
183
|
+
});
|
|
184
|
+
control = field;
|
|
185
|
+
}
|
|
186
|
+
else if (type == typeof(bool))
|
|
187
|
+
{
|
|
188
|
+
var field = new Toggle(label) { value = value != null && (bool)value };
|
|
189
|
+
field.RegisterValueChangedCallback(evt => onChanged(evt.newValue));
|
|
190
|
+
control = field;
|
|
191
|
+
}
|
|
192
|
+
else if (type == typeof(float3))
|
|
193
|
+
{
|
|
194
|
+
float3 vector = value == null ? default : (float3)value;
|
|
195
|
+
var field = new Vector3Field(label) { value = new Vector3(vector.x, vector.y, vector.z) };
|
|
196
|
+
field.RegisterValueChangedCallback(evt => onChanged(new float3(evt.newValue.x, evt.newValue.y, evt.newValue.z)));
|
|
197
|
+
control = field;
|
|
198
|
+
}
|
|
199
|
+
else if (type == typeof(Vector2))
|
|
200
|
+
{
|
|
201
|
+
var field = new Vector2Field(label) { value = value == null ? default : (Vector2)value };
|
|
202
|
+
field.RegisterValueChangedCallback(evt => onChanged(evt.newValue));
|
|
203
|
+
control = field;
|
|
204
|
+
}
|
|
205
|
+
else if (type == typeof(Vector3))
|
|
206
|
+
{
|
|
207
|
+
var field = new Vector3Field(label) { value = value == null ? default : (Vector3)value };
|
|
208
|
+
field.RegisterValueChangedCallback(evt => onChanged(evt.newValue));
|
|
209
|
+
control = field;
|
|
210
|
+
}
|
|
211
|
+
else if (type == typeof(Vector4))
|
|
212
|
+
{
|
|
213
|
+
var field = new Vector4Field(label) { value = value == null ? default : (Vector4)value };
|
|
214
|
+
field.RegisterValueChangedCallback(evt => onChanged(evt.newValue));
|
|
215
|
+
control = field;
|
|
216
|
+
}
|
|
217
|
+
else if (type == typeof(Color))
|
|
218
|
+
{
|
|
219
|
+
var field = new ColorField(label) { value = value == null ? default : (Color)value };
|
|
220
|
+
field.RegisterValueChangedCallback(evt => onChanged(evt.newValue));
|
|
221
|
+
control = field;
|
|
222
|
+
}
|
|
223
|
+
else if (type == typeof(Entity))
|
|
224
|
+
{
|
|
225
|
+
Entity current = value == null ? default : (Entity)value;
|
|
226
|
+
control = createEntityField(label, current, onChanged);
|
|
227
|
+
}
|
|
228
|
+
else if (type.IsEnum)
|
|
229
|
+
{
|
|
230
|
+
var field = new EnumField(label, (Enum)value);
|
|
231
|
+
field.RegisterValueChangedCallback(evt => onChanged(evt.newValue));
|
|
232
|
+
control = field;
|
|
233
|
+
}
|
|
234
|
+
else if (typeof(UnityEngine.Object).IsAssignableFrom(type))
|
|
235
|
+
{
|
|
236
|
+
var field = new ObjectField(label)
|
|
237
|
+
{
|
|
238
|
+
objectType = type,
|
|
239
|
+
allowSceneObjects = true,
|
|
240
|
+
value = value as UnityEngine.Object
|
|
241
|
+
};
|
|
242
|
+
field.RegisterValueChangedCallback(evt => onChanged(evt.newValue));
|
|
243
|
+
control = field;
|
|
244
|
+
}
|
|
245
|
+
else
|
|
246
|
+
{
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
control.AddToClassList(BaseField<float>.alignedFieldUssClassName);
|
|
251
|
+
control.AddToClassList("ecs-value-field");
|
|
252
|
+
control.SetEnabled(canWrite);
|
|
253
|
+
return control;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private static object ConvertInteger(Type type, int value)
|
|
257
|
+
{
|
|
258
|
+
if (type == typeof(byte)) return (byte)Mathf.Clamp(value, byte.MinValue, byte.MaxValue);
|
|
259
|
+
if (type == typeof(sbyte)) return (sbyte)Mathf.Clamp(value, sbyte.MinValue, sbyte.MaxValue);
|
|
260
|
+
if (type == typeof(short)) return (short)Mathf.Clamp(value, short.MinValue, short.MaxValue);
|
|
261
|
+
if (type == typeof(ushort)) return (ushort)Mathf.Clamp(value, ushort.MinValue, ushort.MaxValue);
|
|
262
|
+
return value;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private static string[] Append(string[] path, string name)
|
|
266
|
+
{
|
|
267
|
+
var result = new string[path.Length + 1];
|
|
268
|
+
Array.Copy(path, result, path.Length);
|
|
269
|
+
result[path.Length] = name;
|
|
270
|
+
return result;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private static string GetLabel(string name)
|
|
274
|
+
{
|
|
275
|
+
return int.TryParse(name, out int index)
|
|
276
|
+
? $"Element {index}"
|
|
277
|
+
: ObjectNames.NicifyVariableName(name);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
@@ -81,12 +81,6 @@
|
|
|
81
81
|
border-bottom-color: var(--unity-colors-inspector_titlebar-border_accent);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
.ecs-component-list .ecs-add-component-row,
|
|
85
|
-
.ecs-component-list .ecs-add-component-row:hover,
|
|
86
|
-
.ecs-component-list .ecs-add-component-row:selected {
|
|
87
|
-
border-bottom-width: 0;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
84
|
.ecs-component-list .ecs-component-last-bound-row,
|
|
91
85
|
.ecs-component-list .ecs-component-last-bound-row:hover,
|
|
92
86
|
.ecs-component-list .ecs-component-last-bound-row:selected {
|
|
@@ -97,10 +91,6 @@
|
|
|
97
91
|
background-color: var(--unity-colors-inspector_titlebar-background-hover);
|
|
98
92
|
}
|
|
99
93
|
|
|
100
|
-
.ecs-add-component-row > .unity-list-view__reorderable-handle {
|
|
101
|
-
display: none;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
94
|
.ecs-entity-header {
|
|
105
95
|
margin-bottom: 4px;
|
|
106
96
|
}
|
|
@@ -132,8 +122,8 @@
|
|
|
132
122
|
}
|
|
133
123
|
|
|
134
124
|
.ecs-view-binding {
|
|
135
|
-
flex-direction:
|
|
136
|
-
align-items:
|
|
125
|
+
flex-direction: column;
|
|
126
|
+
align-items: stretch;
|
|
137
127
|
margin-top: 6px;
|
|
138
128
|
}
|
|
139
129
|
|
|
@@ -143,7 +133,8 @@
|
|
|
143
133
|
}
|
|
144
134
|
|
|
145
135
|
.ecs-view-binding > .unity-button {
|
|
146
|
-
margin-
|
|
136
|
+
margin-top: 4px;
|
|
137
|
+
margin-left: 0;
|
|
147
138
|
}
|
|
148
139
|
|
|
149
140
|
.ecs-entity-metadata {
|
|
@@ -269,10 +260,18 @@
|
|
|
269
260
|
font-size: 14px;
|
|
270
261
|
}
|
|
271
262
|
|
|
272
|
-
.ecs-
|
|
273
|
-
width:
|
|
263
|
+
.ecs-action-button {
|
|
264
|
+
width: 100%;
|
|
265
|
+
min-width: 0;
|
|
274
266
|
height: 24px;
|
|
275
|
-
|
|
267
|
+
min-height: 24px;
|
|
268
|
+
max-height: 24px;
|
|
269
|
+
flex-grow: 1;
|
|
270
|
+
flex-shrink: 0;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.ecs-add-component {
|
|
274
|
+
align-self: stretch;
|
|
276
275
|
margin-top: 5px;
|
|
277
276
|
margin-bottom: 4px;
|
|
278
277
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<ui:UXML xmlns:ui="UnityEngine.UIElements">
|
|
2
|
-
<ui:Style src="
|
|
2
|
+
<ui:Style src="SceneAuthoringEditor.uss" />
|
|
3
3
|
<ui:VisualElement name="inspectorRoot" class="ecs-inspector-root">
|
|
4
4
|
<ui:VisualElement name="inspectorHeader" class="ecs-inspector-header" />
|
|
5
5
|
<ui:ListView name="componentList" class="ecs-component-list" virtualization-method="DynamicHeight" reorderable="true" reorder-mode="Animated" selection-type="None" />
|