@mflrevan/ucp 0.6.2 → 0.6.3

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.
Files changed (51) hide show
  1. package/README.md +1 -1
  2. package/bridge/com.ucp.bridge/CHANGELOG.md +48 -0
  3. package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs +55 -6
  4. package/bridge/com.ucp.bridge/Editor/Bridge/EditorStateSummary.cs +165 -0
  5. package/bridge/com.ucp.bridge/Editor/Bridge/EditorStateSummary.cs.meta +11 -0
  6. package/bridge/com.ucp.bridge/Editor/Compatibility/UnityObjectCompat.cs +22 -5
  7. package/bridge/com.ucp.bridge/Editor/Controllers/EditorController.cs +1 -0
  8. package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs +10 -10
  9. package/bridge/com.ucp.bridge/Editor/Controllers/LogsController.cs +71 -0
  10. package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs +2 -2
  11. package/bridge/com.ucp.bridge/Editor/Controllers/ObjectLocator.cs +4 -4
  12. package/bridge/com.ucp.bridge/Editor/Controllers/ObjectReferenceResolver.cs +3 -3
  13. package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs +6 -6
  14. package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs +7 -7
  15. package/bridge/com.ucp.bridge/Editor/Controllers/RecordingController.cs +8 -0
  16. package/bridge/com.ucp.bridge/Editor/Controllers/SceneChangeTracker.cs +3 -3
  17. package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs +3 -3
  18. package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs +5 -5
  19. package/bridge/com.ucp.bridge/Editor/Controllers/TestRunnerController.cs +1 -1
  20. package/bridge/com.ucp.bridge/Editor/Controllers/TransformController.cs +1 -1
  21. package/bridge/com.ucp.bridge/Editor/Controllers/UiController.cs +70 -0
  22. package/bridge/com.ucp.bridge/Editor/Controllers/UiController.cs.meta +11 -0
  23. package/bridge/com.ucp.bridge/Editor/Controllers/ViewController.cs +1 -1
  24. package/bridge/com.ucp.bridge/Editor/Protocol/MiniJson.cs +1 -1
  25. package/bridge/com.ucp.bridge/Editor/Ui/UiHost.cs +468 -0
  26. package/bridge/com.ucp.bridge/Editor/Ui/UiHost.cs.meta +11 -0
  27. package/bridge/com.ucp.bridge/Editor/Ui/UiLintService.cs +554 -0
  28. package/bridge/com.ucp.bridge/Editor/Ui/UiLintService.cs.meta +11 -0
  29. package/bridge/com.ucp.bridge/Editor/Ui/UiOperationManager.cs +870 -0
  30. package/bridge/com.ucp.bridge/Editor/Ui/UiOperationManager.cs.meta +11 -0
  31. package/bridge/com.ucp.bridge/Editor/Ui/UiScenarioApplier.cs +496 -0
  32. package/bridge/com.ucp.bridge/Editor/Ui/UiScenarioApplier.cs.meta +11 -0
  33. package/bridge/com.ucp.bridge/Editor/Ui/UiScenarioLoader.cs +854 -0
  34. package/bridge/com.ucp.bridge/Editor/Ui/UiScenarioLoader.cs.meta +11 -0
  35. package/bridge/com.ucp.bridge/Editor/Ui/UiScenarioModels.cs +374 -0
  36. package/bridge/com.ucp.bridge/Editor/Ui/UiScenarioModels.cs.meta +11 -0
  37. package/bridge/com.ucp.bridge/Editor/Ui/UiVisualTreeInspector.cs +690 -0
  38. package/bridge/com.ucp.bridge/Editor/Ui/UiVisualTreeInspector.cs.meta +11 -0
  39. package/bridge/com.ucp.bridge/{Runtime.meta → Editor/Ui.meta} +1 -1
  40. package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs +79 -4
  41. package/bridge/com.ucp.bridge/Tests/Editor/SpatialVisualControllerTests.cs +2 -2
  42. package/bridge/com.ucp.bridge/Tests/Editor/UiControllerTests.cs +547 -0
  43. package/bridge/com.ucp.bridge/Tests/Editor/UiControllerTests.cs.meta +11 -0
  44. package/bridge/com.ucp.bridge/Tests/Editor/UiLintServiceTests.cs +253 -0
  45. package/bridge/com.ucp.bridge/Tests/Editor/UiLintServiceTests.cs.meta +11 -0
  46. package/bridge/com.ucp.bridge/Tests/Editor/UiScenarioTests.cs +587 -0
  47. package/bridge/com.ucp.bridge/Tests/Editor/UiScenarioTests.cs.meta +11 -0
  48. package/bridge/com.ucp.bridge/package.json +1 -1
  49. package/package.json +1 -1
  50. package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef +0 -14
  51. package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef.meta +0 -7
@@ -0,0 +1,11 @@
1
+ fileFormatVersion: 2
2
+ guid: 3212acd0da8b4de4acec109d5282a505
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
@@ -0,0 +1,496 @@
1
+ #if UNITY_6000_0_OR_NEWER
2
+ using System;
3
+ using System.Collections;
4
+ using System.Collections.Generic;
5
+ using System.Globalization;
6
+ using System.Linq;
7
+ using System.Runtime.CompilerServices;
8
+ using Unity.Properties;
9
+ using UnityEngine;
10
+ using UnityEngine.UIElements;
11
+
12
+ namespace UCP.Bridge
13
+ {
14
+ internal static class UiScenarioApplier
15
+ {
16
+ private static readonly ConditionalWeakTable<VisualElement, AppliedState> AppliedStates =
17
+ new ConditionalWeakTable<VisualElement, AppliedState>();
18
+
19
+ private static bool _propertyBagsRegistered;
20
+
21
+ public static UiApplyReport Apply(VisualElement root, UiResolvedScenario scenario)
22
+ {
23
+ if (root == null)
24
+ throw new ArgumentNullException(nameof(root));
25
+ if (scenario == null)
26
+ throw new ArgumentNullException(nameof(scenario));
27
+
28
+ EnsurePropertyBags();
29
+ AppliedStates.Remove(root);
30
+
31
+ var report = new UiApplyReport();
32
+ report.BindingRewriteCount = RewriteBindingsToDictionaryKeys(root);
33
+ root.dataSource = scenario.Data;
34
+ AddLargeIntegerDiagnostics(scenario.Data, report);
35
+
36
+ foreach (var operation in scenario.SetOperations)
37
+ {
38
+ var target = ResolveSingle(root, operation.Selector, operation.Location);
39
+ ApplySet(target, operation);
40
+ report.SetCount++;
41
+ }
42
+
43
+ var state = new AppliedState();
44
+ foreach (var collection in scenario.Collections)
45
+ {
46
+ var target = ResolveSingle(root, collection.Selector, collection.Location);
47
+ var items = ResolveCollectionSource(scenario.Data, collection.Source, collection.Location);
48
+ var metadata = new UiCollectionMetadata(
49
+ collection.Selector,
50
+ collection.Mode,
51
+ collection.Source,
52
+ items.Count,
53
+ target,
54
+ root);
55
+
56
+ if (collection.Mode == UiCollectionMode.Repeat)
57
+ ApplyRepeat(target, items, collection, metadata, report);
58
+ else
59
+ ApplyListView(target, items, collection, metadata, report);
60
+
61
+ state.Collections.Add(metadata);
62
+ report.AddCollection(metadata);
63
+ }
64
+
65
+ AppliedStates.Add(root, state);
66
+ return report;
67
+ }
68
+
69
+ public static IReadOnlyList<UiCollectionMetadata> GetCollectionMetadata(VisualElement root)
70
+ {
71
+ if (root != null && AppliedStates.TryGetValue(root, out var state))
72
+ return state.Collections;
73
+ return Array.Empty<UiCollectionMetadata>();
74
+ }
75
+
76
+ private static void ApplyRepeat(
77
+ VisualElement target,
78
+ IList items,
79
+ UiCollectionDefinition definition,
80
+ UiCollectionMetadata metadata,
81
+ UiApplyReport report)
82
+ {
83
+ if (target is ListView)
84
+ throw Error("ui.collection-target-type", "repeat collections cannot target a ListView.", definition.Location + ".target");
85
+
86
+ target.Clear();
87
+ for (var i = 0; i < items.Count; i++)
88
+ {
89
+ var row = definition.Template.CloneTree();
90
+ report.BindingRewriteCount += RewriteBindingsToDictionaryKeys(row);
91
+ row.dataSource = items[i];
92
+ target.Add(row);
93
+ metadata.Bind(row, i);
94
+ report.RepeatItemCount++;
95
+ }
96
+ }
97
+
98
+ private static void ApplyListView(
99
+ VisualElement target,
100
+ IList items,
101
+ UiCollectionDefinition definition,
102
+ UiCollectionMetadata metadata,
103
+ UiApplyReport report)
104
+ {
105
+ if (!(target is ListView listView))
106
+ throw Error("ui.collection-target-type", "list-view collections must target a ListView element.", definition.Location + ".target");
107
+
108
+ listView.makeItem = () =>
109
+ {
110
+ var row = definition.Template.CloneTree();
111
+ report.BindingRewriteCount += RewriteBindingsToDictionaryKeys(row);
112
+ metadata.Realize(row);
113
+ return row;
114
+ };
115
+ listView.bindItem = (row, index) =>
116
+ {
117
+ if (index < 0 || index >= items.Count)
118
+ throw new ArgumentOutOfRangeException(nameof(index));
119
+ row.dataSource = items[index];
120
+ metadata.Bind(row, index);
121
+ };
122
+ listView.unbindItem = (row, _) =>
123
+ {
124
+ row.dataSource = null;
125
+ metadata.Unbind(row);
126
+ };
127
+ listView.destroyItem = row =>
128
+ {
129
+ row.dataSource = null;
130
+ metadata.Destroy(row);
131
+ };
132
+
133
+ if (definition.ItemHeight.HasValue)
134
+ {
135
+ listView.virtualizationMethod = CollectionVirtualizationMethod.FixedHeight;
136
+ listView.fixedItemHeight = definition.ItemHeight.Value;
137
+ }
138
+ else
139
+ {
140
+ listView.virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight;
141
+ }
142
+
143
+ listView.itemsSource = items;
144
+ listView.Rebuild();
145
+ }
146
+
147
+ private static IList ResolveCollectionSource(
148
+ Dictionary<string, object> data,
149
+ string pointer,
150
+ string location)
151
+ {
152
+ object current = data;
153
+ if (pointer.Length > 0)
154
+ {
155
+ var segments = pointer.Substring(1).Split('/');
156
+ foreach (var encodedSegment in segments)
157
+ {
158
+ var segment = encodedSegment.Replace("~1", "/").Replace("~0", "~");
159
+ if (current is Dictionary<string, object> dictionary)
160
+ {
161
+ if (!dictionary.TryGetValue(segment, out current))
162
+ throw Error("ui.collection-source-missing", $"Collection source segment '{segment}' was not found.", location + ".source");
163
+ }
164
+ else if (current is IList list)
165
+ {
166
+ if (!int.TryParse(segment, NumberStyles.None, CultureInfo.InvariantCulture, out var index) ||
167
+ index < 0 || index >= list.Count)
168
+ {
169
+ throw Error("ui.collection-source-index", $"Collection source index '{segment}' is invalid.", location + ".source");
170
+ }
171
+ current = list[index];
172
+ }
173
+ else
174
+ {
175
+ throw Error("ui.collection-source-traversal", $"Cannot traverse collection source through '{segment}'.", location + ".source");
176
+ }
177
+ }
178
+ }
179
+
180
+ if (!(current is IList result))
181
+ throw Error("ui.collection-source-type", "Collection source must resolve to a JSON array.", location + ".source");
182
+ return result;
183
+ }
184
+
185
+ private static VisualElement ResolveSingle(VisualElement root, string selector, string location)
186
+ {
187
+ if (selector == ":root")
188
+ return root;
189
+
190
+ var matches = new List<VisualElement>();
191
+ CollectMatches(root, selector, matches);
192
+ if (matches.Count == 0)
193
+ throw Error("ui.selector-not-found", $"Selector '{selector}' did not match any element.", location + ".target");
194
+ if (matches.Count > 1)
195
+ throw Error("ui.selector-ambiguous", $"Selector '{selector}' matched {matches.Count} elements; targets must be unique.", location + ".target");
196
+ return matches[0];
197
+ }
198
+
199
+ private static void CollectMatches(VisualElement element, string selector, List<VisualElement> output)
200
+ {
201
+ var matches = (selector[0] == '#' && string.Equals(element.name, selector.Substring(1), StringComparison.Ordinal)) ||
202
+ (selector[0] == '.' && element.ClassListContains(selector.Substring(1)));
203
+ if (matches)
204
+ output.Add(element);
205
+
206
+ foreach (var child in element.hierarchy.Children())
207
+ CollectMatches(child, selector, output);
208
+ }
209
+
210
+ private static void ApplySet(VisualElement target, UiSetOperation operation)
211
+ {
212
+ switch (operation.Property)
213
+ {
214
+ case "text":
215
+ if (!(target is TextElement textElement))
216
+ throw SetTypeError(operation, target, "TextElement");
217
+ textElement.text = RequireStringOrNull(operation.Value, operation);
218
+ return;
219
+ case "value":
220
+ ApplyValue(target, operation);
221
+ return;
222
+ case "enabled":
223
+ target.SetEnabled(RequireBool(operation.Value, operation));
224
+ return;
225
+ case "display":
226
+ target.style.display = ParseDisplay(operation);
227
+ return;
228
+ case "visibility":
229
+ target.style.visibility = ParseVisibility(operation);
230
+ return;
231
+ case "tooltip":
232
+ target.tooltip = RequireStringOrNull(operation.Value, operation);
233
+ return;
234
+ }
235
+
236
+ if (operation.Property.StartsWith("class:", StringComparison.Ordinal))
237
+ {
238
+ target.EnableInClassList(
239
+ operation.Property.Substring("class:".Length),
240
+ RequireBool(operation.Value, operation));
241
+ return;
242
+ }
243
+
244
+ throw Error("ui.set-property", $"Property '{operation.Property}' is not allowed.", operation.Location + ".property");
245
+ }
246
+
247
+ private static void ApplyValue(VisualElement target, UiSetOperation operation)
248
+ {
249
+ switch (target)
250
+ {
251
+ case TextField field:
252
+ field.SetValueWithoutNotify(RequireStringOrNull(operation.Value, operation));
253
+ return;
254
+ case IntegerField field:
255
+ field.SetValueWithoutNotify(RequireInt(operation.Value, operation));
256
+ return;
257
+ case LongField field:
258
+ field.SetValueWithoutNotify(RequireLong(operation.Value, operation));
259
+ return;
260
+ case FloatField field:
261
+ field.SetValueWithoutNotify(RequireFloat(operation.Value, operation));
262
+ return;
263
+ case DoubleField field:
264
+ field.SetValueWithoutNotify(RequireDouble(operation.Value, operation));
265
+ return;
266
+ case Toggle field:
267
+ field.SetValueWithoutNotify(RequireBool(operation.Value, operation));
268
+ return;
269
+ case RadioButton field:
270
+ field.SetValueWithoutNotify(RequireBool(operation.Value, operation));
271
+ return;
272
+ case Foldout field:
273
+ field.SetValueWithoutNotify(RequireBool(operation.Value, operation));
274
+ return;
275
+ case Slider field:
276
+ field.SetValueWithoutNotify(RequireFloat(operation.Value, operation));
277
+ return;
278
+ case SliderInt field:
279
+ field.SetValueWithoutNotify(RequireInt(operation.Value, operation));
280
+ return;
281
+ case DropdownField field:
282
+ field.SetValueWithoutNotify(RequireStringOrNull(operation.Value, operation));
283
+ return;
284
+ case ProgressBar field:
285
+ field.SetValueWithoutNotify(RequireFloat(operation.Value, operation));
286
+ return;
287
+ default:
288
+ throw SetTypeError(operation, target, "a supported UI Toolkit value control");
289
+ }
290
+ }
291
+
292
+ private static DisplayStyle ParseDisplay(UiSetOperation operation)
293
+ {
294
+ var value = RequireStringOrNull(operation.Value, operation);
295
+ if (value == "flex")
296
+ return DisplayStyle.Flex;
297
+ if (value == "none")
298
+ return DisplayStyle.None;
299
+ throw Error("ui.set-value", "display must be 'flex' or 'none'.", operation.Location + ".value");
300
+ }
301
+
302
+ private static Visibility ParseVisibility(UiSetOperation operation)
303
+ {
304
+ var value = RequireStringOrNull(operation.Value, operation);
305
+ if (value == "visible")
306
+ return Visibility.Visible;
307
+ if (value == "hidden")
308
+ return Visibility.Hidden;
309
+ throw Error("ui.set-value", "visibility must be 'visible' or 'hidden'.", operation.Location + ".value");
310
+ }
311
+
312
+ private static string RequireStringOrNull(object value, UiSetOperation operation)
313
+ {
314
+ if (value == null || value is string)
315
+ return (string)value;
316
+ throw Error("ui.set-value-type", $"'{operation.Property}' requires a string or null.", operation.Location + ".value");
317
+ }
318
+
319
+ private static bool RequireBool(object value, UiSetOperation operation)
320
+ {
321
+ if (value is bool result)
322
+ return result;
323
+ throw Error("ui.set-value-type", $"'{operation.Property}' requires a boolean.", operation.Location + ".value");
324
+ }
325
+
326
+ private static int RequireInt(object value, UiSetOperation operation)
327
+ {
328
+ if (value is int integer)
329
+ return integer;
330
+ if (value is long longInteger && longInteger >= int.MinValue && longInteger <= int.MaxValue)
331
+ return (int)longInteger;
332
+ throw Error("ui.set-value-type", "value requires a 32-bit integer for this control.", operation.Location + ".value");
333
+ }
334
+
335
+ private static long RequireLong(object value, UiSetOperation operation)
336
+ {
337
+ if (value is int integer)
338
+ return integer;
339
+ if (value is long longInteger)
340
+ return longInteger;
341
+ throw Error("ui.set-value-type", "value requires an integer for this control.", operation.Location + ".value");
342
+ }
343
+
344
+ private static float RequireFloat(object value, UiSetOperation operation)
345
+ {
346
+ var number = RequireDouble(value, operation);
347
+ if (number < -float.MaxValue || number > float.MaxValue)
348
+ throw Error("ui.set-value-range", "value is outside the supported float range.", operation.Location + ".value");
349
+ return (float)number;
350
+ }
351
+
352
+ private static double RequireDouble(object value, UiSetOperation operation)
353
+ {
354
+ double number;
355
+ if (value is int integer)
356
+ number = integer;
357
+ else if (value is long longInteger)
358
+ number = longInteger;
359
+ else if (value is float single)
360
+ number = single;
361
+ else if (value is double doubleValue)
362
+ number = doubleValue;
363
+ else
364
+ throw Error("ui.set-value-type", "value requires a number for this control.", operation.Location + ".value");
365
+
366
+ if (double.IsNaN(number) || double.IsInfinity(number))
367
+ throw Error("ui.set-value-range", "value must be finite.", operation.Location + ".value");
368
+ return number;
369
+ }
370
+
371
+ private static UiScenarioException SetTypeError(
372
+ UiSetOperation operation,
373
+ VisualElement target,
374
+ string expected)
375
+ {
376
+ return Error(
377
+ "ui.set-target-type",
378
+ $"Cannot set '{operation.Property}' on {target.GetType().Name}; expected {expected}.",
379
+ operation.Location + ".target");
380
+ }
381
+
382
+ private static int RewriteBindingsToDictionaryKeys(VisualElement root)
383
+ {
384
+ var changed = 0;
385
+ RewriteBindingsRecursive(root, ref changed);
386
+ return changed;
387
+ }
388
+
389
+ private static void RewriteBindingsRecursive(VisualElement element, ref int changed)
390
+ {
391
+ var bindings = element.GetBindingInfos().ToList();
392
+ foreach (var bindingInfo in bindings)
393
+ {
394
+ if (!(bindingInfo.binding is DataBinding dataBinding) || dataBinding.dataSourcePath.IsEmpty)
395
+ continue;
396
+
397
+ dataBinding.dataSourcePath = ConvertNamesToDictionaryKeys(dataBinding.dataSourcePath);
398
+ element.ClearBinding(bindingInfo.bindingId);
399
+ element.SetBinding(bindingInfo.bindingId, dataBinding);
400
+ changed++;
401
+ }
402
+
403
+ foreach (var child in element.hierarchy.Children())
404
+ RewriteBindingsRecursive(child, ref changed);
405
+ }
406
+
407
+ private static PropertyPath ConvertNamesToDictionaryKeys(PropertyPath original)
408
+ {
409
+ var converted = new PropertyPath();
410
+ for (var i = 0; i < original.Length; i++)
411
+ {
412
+ var part = original[i];
413
+ if (part.IsName)
414
+ {
415
+ if (part.Name == "Value" && i > 0 && original[i - 1].IsKey)
416
+ {
417
+ converted = PropertyPath.AppendName(converted, part.Name);
418
+ continue;
419
+ }
420
+
421
+ converted = PropertyPath.AppendKey(converted, part.Name);
422
+ converted = PropertyPath.AppendName(converted, "Value");
423
+ }
424
+ else if (part.IsIndex)
425
+ {
426
+ converted = PropertyPath.AppendIndex(converted, part.Index);
427
+ }
428
+ else if (part.IsKey)
429
+ {
430
+ converted = PropertyPath.AppendKey(converted, part.Key);
431
+ }
432
+ }
433
+ return converted;
434
+ }
435
+
436
+ private static void EnsurePropertyBags()
437
+ {
438
+ if (_propertyBagsRegistered)
439
+ return;
440
+
441
+ PropertyBag.RegisterDictionary<string, object>();
442
+ PropertyBag.RegisterList<object>();
443
+ _propertyBagsRegistered = true;
444
+ }
445
+
446
+ private static void AddLargeIntegerDiagnostics(
447
+ Dictionary<string, object> data,
448
+ UiApplyReport report)
449
+ {
450
+ var locations = new List<string>();
451
+ FindLargeIntegers(data, "$data", locations, 20);
452
+ foreach (var location in locations)
453
+ {
454
+ report.AddDiagnostic(
455
+ "warning",
456
+ "ui.integer-64-bit",
457
+ "Integer is outside the 32-bit range and was preserved as a 64-bit value; some UI controls cannot bind it.",
458
+ location);
459
+ }
460
+ }
461
+
462
+ private static void FindLargeIntegers(object value, string location, List<string> output, int limit)
463
+ {
464
+ if (output.Count >= limit)
465
+ return;
466
+ if (value is long integer)
467
+ {
468
+ if (integer < int.MinValue || integer > int.MaxValue)
469
+ output.Add(location);
470
+ return;
471
+ }
472
+ if (value is Dictionary<string, object> dictionary)
473
+ {
474
+ foreach (var pair in dictionary)
475
+ FindLargeIntegers(pair.Value, location + "." + pair.Key, output, limit);
476
+ return;
477
+ }
478
+ if (value is IList list)
479
+ {
480
+ for (var i = 0; i < list.Count && output.Count < limit; i++)
481
+ FindLargeIntegers(list[i], $"{location}[{i}]", output, limit);
482
+ }
483
+ }
484
+
485
+ private static UiScenarioException Error(string code, string message, string location)
486
+ {
487
+ return new UiScenarioException(code, message, location);
488
+ }
489
+
490
+ private sealed class AppliedState
491
+ {
492
+ public readonly List<UiCollectionMetadata> Collections = new List<UiCollectionMetadata>();
493
+ }
494
+ }
495
+ }
496
+ #endif
@@ -0,0 +1,11 @@
1
+ fileFormatVersion: 2
2
+ guid: 7983a726840b46d1972718808fc28a8b
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant: