浏览代码

Cleaned up code with correctness tests

/main
Steven Borkman 4 年前
当前提交
50825a13
共有 11 个文件被更改,包括 609 次插入65 次删除
  1. 142
      com.unity.perception/Runtime/GroundTruth/Labelers/KeyPointLabeler.cs
  2. 109
      com.unity.perception/Runtime/GroundTruth/Labelers/Visualization/VisualizationHelper.cs
  3. 3
      com.unity.perception/Runtime/GroundTruth/Labelers/Visualization/VisualizationHelper.cs.meta
  4. 10
      com.unity.perception/Runtime/Randomization/Parameters/ParameterTypes/CategorialParameters/AnimationClipParameter.cs
  5. 3
      com.unity.perception/Runtime/Randomization/Parameters/ParameterTypes/CategorialParameters/AnimationClipParameter.cs.meta
  6. 67
      com.unity.perception/Runtime/Randomization/Randomizers/RandomizerExamples/Randomizers/AnimationRandomizer.cs
  7. 3
      com.unity.perception/Runtime/Randomization/Randomizers/RandomizerExamples/Randomizers/AnimationRandomizer.cs.meta
  8. 16
      com.unity.perception/Runtime/Randomization/Randomizers/RandomizerExamples/Tags/AnimationRandomizerTag.cs
  9. 3
      com.unity.perception/Runtime/Randomization/Randomizers/RandomizerExamples/Tags/AnimationRandomizerTag.cs.meta
  10. 307
      com.unity.perception/Tests/Runtime/GroundTruthTests/KeyPointGroundTruthTests.cs
  11. 11
      com.unity.perception/Tests/Runtime/GroundTruthTests/KeyPointGroundTruthTests.cs.meta

142
com.unity.perception/Runtime/GroundTruth/Labelers/KeyPointLabeler.cs


EntityQuery m_EntityQuery;
Texture2D m_MissingTexture;
/// <summary>
/// Action that gets triggered when a new frame of key points are computed.
/// </summary>
public event Action<List<KeyPointEntry>> KeyPointsComputed;
/// <summary>
/// Creates a new key point labeler. This constructor creates a labeler that
/// is not valid until a <see cref="IdLabelConfig"/> and <see cref="KeyPointTemplate"/>
/// are assigned.
/// </summary>
public KeyPointLabeler() { }
/// <summary>
/// Creates a new key point labeler.
/// </summary>
/// <param name="config">The Id label config for the labeler</param>
/// <param name="template">The active keypoint template</param>
public KeyPointLabeler(IdLabelConfig config, KeyPointTemplate template)
{
this.idLabelConfig = config;
this.activeTemplate = template;
}
/// <inheritdoc/>
protected override void Setup()
{

entities.Dispose();
KeyPointsComputed?.Invoke(m_KeyPointEntries);
reporter.ReportValues(m_KeyPointEntries);
}

/// <summary>
/// Record storing all of the keypoint data of a labeled gameobject.
/// </summary>
struct KeyPointEntry
public class KeyPointEntry
/// The id of the labeled entity
/// The label id of the entity
/// <summary>
/// The instance id of the entity
/// </summary>
/// <summary>
/// The template that the points are based on
/// </summary>
/// <summary>
/// Array of all of the keypoints
/// </summary>
/// <summary>
/// The values of a specific keypoint
/// </summary>
struct KeyPoint
public class KeyPoint
/// <summary>
/// The index of the keypoint in the template file
/// </summary>
/// <summary>
/// The keypoint's x-coordinate pixel location
/// </summary>
/// <summary>
/// The keypoint's y-coordinate pixel location
/// </summary>
/// <summary>
/// The state of the point, 0 = not present, 1 = keypoint is present
/// </summary>
public int state;
}
// ReSharper restore InconsistentNaming

{
status = false,
animator = null,
keyPoints = new KeyPointEntry()
keyPoints = new KeyPointEntry(),
overrides = new List<(JointLabel, int)>()
cached.keyPoints.instance_id = labeledEntity.instanceId;
cached.keyPoints.label_id = labelEntry.id;
cached.keyPoints.template_guid = activeTemplate.templateID.ToString();
cached.keyPoints.keypoints = new KeyPoint[activeTemplate.keyPoints.Length];
for (var i = 0; i < cached.keyPoints.keypoints.Length; i++)
{
cached.keyPoints.keypoints[i] = new KeyPoint { index = i, state = 0 };
}
var animator = entityGameObject.transform.GetComponentInChildren<Animator>();
if (animator != null)
{

cached.animator = animator;
cached.keyPoints.instance_id = labeledEntity.instanceId;
cached.keyPoints.label_id = labelEntry.id;
cached.keyPoints.template_guid = activeTemplate.templateID.ToString();
cached.keyPoints.keypoints = new KeyPoint[activeTemplate.keyPoints.Length];
cached.status = true;
}
}
for (var i = 0; i < cached.keyPoints.keypoints.Length; i++)
{
cached.keyPoints.keypoints[i].index = i;
cached.keyPoints.keypoints[i].state = 0;
}
cached.overrides = new List<(JointLabel, int)>();
foreach (var joint in entityGameObject.transform.GetComponentsInChildren<JointLabel>())
{
if (TryToGetTemplateIndexForJoint(activeTemplate, joint, out var idx))
{
cached.overrides.Add((joint, idx));
foreach (var joint in entityGameObject.transform.GetComponentsInChildren<JointLabel>())
{
if (TryToGetTemplateIndexForJoint(activeTemplate, joint, out var idx))
{
cached.overrides.Add((joint, idx));
}
}
}
}
}

}
}
Rect ToBoxRect(float x, float y, float halfSize = 3.0f)
{
return new Rect(x - halfSize, y - halfSize, halfSize * 2, halfSize * 2);
}
void DrawPoint(float x, float y, Color color, Texture2D texture)
{
var oldColor = GUI.color;
GUI.color = color;
GUI.DrawTexture(ToBoxRect(x, y, 4), texture);
GUI.color = oldColor;
}
float Magnitude(float p1X, float p1Y, float p2X, float p2Y)
{
var x = p2X - p1X;
var y = p2Y - p1Y;
return Mathf.Sqrt(x * x + y * y);
}
void DrawLine (float p1X, float p1Y, float p2X, float p2Y, Color color, Texture texture)
{
var oldColor = GUI.color;
GUI.color = color;
var matrixBackup = GUI.matrix;
const float width = 8.0f;
var angle = Mathf.Atan2 (p2Y - p1Y, p2X - p1X) * 180f / Mathf.PI;
var length = Magnitude(p1X, p1Y, p2X, p2Y);
GUIUtility.RotateAroundPivot (angle, new Vector2(p1X, p1Y));
const float halfWidth = width * 0.5f;
GUI.DrawTexture (new Rect (p1X - halfWidth, p1Y - halfWidth, length, width), texture);
GUI.matrix = matrixBackup;
GUI.color = oldColor;
}
/// <inheritdoc/>
protected override void OnVisualize()
{

if (joint1.state != 0 && joint2.state != 0)
{
DrawLine(joint1.x, joint1.y, joint2.x, joint2.y, bone.color, skeletonTexture);
VisualizationHelper.DrawLine(joint1.x, joint1.y, joint2.x, joint2.y, bone.color, 8, skeletonTexture);
}
}

DrawPoint(keypoint.x, keypoint.y, activeTemplate.keyPoints[keypoint.index].color, jointTexture);
VisualizationHelper.DrawPoint(keypoint.x, keypoint.y, activeTemplate.keyPoints[keypoint.index].color, 8, jointTexture);
}
}
}

109
com.unity.perception/Runtime/GroundTruth/Labelers/Visualization/VisualizationHelper.cs


namespace UnityEngine.Perception.GroundTruth
{
/// <summary>
/// Helper class that contains common visualization methods useful to ground truth labelers.
/// </summary>
public static class VisualizationHelper
{
static Texture2D s_OnePixel = new Texture2D(1, 1);
/// <summary>
/// Converts a 3D world space coordinate to image pixel space.
/// </summary>
/// <param name="camera">The rendering camera</param>
/// <param name="worldLocation">The 3D world location to convert</param>
/// <returns>The coordinate in pixel space</returns>
public static Vector3 ConvertToScreenSpace(Camera camera, Vector3 worldLocation)
{
var pt = camera.WorldToScreenPoint(worldLocation);
pt.y = Screen.height - pt.y;
return pt;
}
static Rect ToBoxRect(float x, float y, float halfSize = 3.0f)
{
return new Rect(x - halfSize, y - halfSize, halfSize * 2, halfSize * 2);
}
/// <summary>
/// Draw a point (in pixel space) on the screen
/// </summary>
/// <param name="pt">The point location, in pixel space</param>
/// <param name="color">The color of the point</param>
/// <param name="width">The width of the point</param>
/// <param name="texture">The texture to use for the point, defaults to a solid pixel</param>
public static void DrawPoint(Vector3 pt, Color color, float width = 4.0f, Texture texture = null)
{
DrawPoint(pt.x, pt.y, color, width, texture);
}
/// <summary>
/// Draw a point (in pixel space) on the screen
/// </summary>
/// <param name="x">The point's x value, in pixel space</param>
/// <param name="y">The point's y value, in pixel space</param>
/// <param name="color">The color of the point</param>
/// <param name="width">The width of the point</param>
/// <param name="texture">The texture to use for the point, defaults to a solid pixel</param>
public static void DrawPoint(float x, float y, Color color, float width = 4, Texture texture = null)
{
if (texture == null) texture = s_OnePixel;
var oldColor = GUI.color;
GUI.color = color;
GUI.DrawTexture(ToBoxRect(x, y, width * 0.5f), texture);
GUI.color = oldColor;
}
static float Magnitude(float p1X, float p1Y, float p2X, float p2Y)
{
var x = p2X - p1X;
var y = p2Y - p1Y;
return Mathf.Sqrt(x * x + y * y);
}
/// <summary>
/// Draw's a texture between two locations of a passed in width.
/// </summary>
/// <param name="p1">The start point in pixel space</param>
/// <param name="p2">The end point in pixel space</param>
/// <param name="color">The color of the line</param>
/// <param name="width">The width of the line</param>
/// <param name="texture">The texture to use, if null, will draw a solid line of passed in color</param>
public static void DrawLine(Vector2 p1, Vector2 p2, Color color, float width = 3.0f, Texture texture = null)
{
DrawLine(p1.x, p1.y, p2.x, p2.y, color, width, texture);
}
/// <summary>
/// Draw's a texture between two locations of a passed in width.
/// </summary>
/// <param name="p1X">The start point's x coordinate in pixel space</param>
/// <param name="p1Y">The start point's y coordinate in pixel space</param>
/// <param name="p2X">The end point's x coordinate in pixel space</param>
/// <param name="p2Y">The end point's y coordinate in pixel space</param>
/// <param name="color">The color of the line</param>
/// <param name="width">The width of the line</param>
/// <param name="texture">The texture to use, if null, will draw a solid line of passed in color</param>
public static void DrawLine (float p1X, float p1Y, float p2X, float p2Y, Color color, float width = 3.0f, Texture texture = null)
{
if (texture == null) texture = s_OnePixel;
var oldColor = GUI.color;
GUI.color = color;
var matrixBackup = GUI.matrix;
var angle = Mathf.Atan2 (p2Y - p1Y, p2X - p1X) * 180f / Mathf.PI;
var length = Magnitude(p1X, p1Y, p2X, p2Y);
GUIUtility.RotateAroundPivot (angle, new Vector2(p1X, p1Y));
var halfWidth = width * 0.5f;
GUI.DrawTexture (new Rect (p1X - halfWidth, p1Y - halfWidth, length, width), texture);
GUI.matrix = matrixBackup;
GUI.color = oldColor;
}
}
}

3
com.unity.perception/Runtime/GroundTruth/Labelers/Visualization/VisualizationHelper.cs.meta


fileFormatVersion: 2
guid: b4ef50bfa62549848a6d12c049397eba
timeCreated: 1611019489

10
com.unity.perception/Runtime/Randomization/Parameters/ParameterTypes/CategorialParameters/AnimationClipParameter.cs


using System;
namespace UnityEngine.Experimental.Perception.Randomization.Parameters
{
/// <summary>
/// A categorical parameter for animation clips
/// </summary>
[Serializable]
public class AnimationClipParameter : CategoricalParameter<AnimationClip> { }
}

3
com.unity.perception/Runtime/Randomization/Parameters/ParameterTypes/CategorialParameters/AnimationClipParameter.cs.meta


fileFormatVersion: 2
guid: 6af6ee532f5e4e4b83353f2f32105665
timeCreated: 1610935882

67
com.unity.perception/Runtime/Randomization/Randomizers/RandomizerExamples/Randomizers/AnimationRandomizer.cs


using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Experimental.Perception.Randomization.Parameters;
using UnityEngine.Experimental.Perception.Randomization.Randomizers.SampleRandomizers.Tags;
using UnityEngine.Experimental.Perception.Randomization.Samplers;
using UnityEngine.Playables;
namespace UnityEngine.Experimental.Perception.Randomization.Randomizers.SampleRandomizers
{
/// <summary>
/// Chooses a random of frame of a random clip for a gameobject
/// </summary>
[Serializable]
[AddRandomizerMenu("Perception/Animation Randomizer")]
public class AnimationRandomizer : Randomizer
{
FloatParameter m_FloatParameter = new FloatParameter{ value = new UniformSampler(0, 1) };
Dictionary<GameObject, (PlayableGraph, Animator)> m_GraphMap;
/// <inheritdoc/>
protected override void OnCreate()
{
m_GraphMap = new Dictionary<GameObject, (PlayableGraph, Animator)>();
}
(PlayableGraph, Animator) GetGraph(GameObject gameObject)
{
if (!m_GraphMap.ContainsKey(gameObject))
{
var graph = PlayableGraph.Create();
graph.SetTimeUpdateMode(DirectorUpdateMode.GameTime);
var animator = gameObject.GetComponent<Animator>();
m_GraphMap[gameObject] = (graph, animator);
}
return m_GraphMap[gameObject];
}
/// <inheritdoc/>
protected override void OnIterationStart()
{
var taggedObjects = tagManager.Query<AnimationRandomizerTag>();
foreach (var taggedObject in taggedObjects)
{
var (graph, animator) = GetGraph(taggedObject.gameObject);
var tag = taggedObject.GetComponent<AnimationRandomizerTag>();
var clips = tag.animationClips;
CategoricalParameter<AnimationClip> param = new AnimationClipParameter();
param.SetOptions(clips);
var clip = param.Sample();
var output = AnimationPlayableOutput.Create(graph, "Animation", animator);
var playable = AnimationClipPlayable.Create(graph, clip);
output.SetSourcePlayable(playable);
var l = clip.length;
var t = m_FloatParameter.Sample();
playable.SetTime(t * l);
playable.SetSpeed(0);
graph.Play();
}
}
}
}

3
com.unity.perception/Runtime/Randomization/Randomizers/RandomizerExamples/Randomizers/AnimationRandomizer.cs.meta


fileFormatVersion: 2
guid: 8b57910cfd4a4dec90d6aa4a8ef824da
timeCreated: 1610802187

16
com.unity.perception/Runtime/Randomization/Randomizers/RandomizerExamples/Tags/AnimationRandomizerTag.cs


namespace UnityEngine.Experimental.Perception.Randomization.Randomizers.SampleRandomizers.Tags
{
/// <summary>
/// Used in conjunction with a <see cref="AnimationRandomizer"/> to select a random animation frame for
/// the tagged game object
/// </summary>
[RequireComponent(typeof(Animator))]
[AddComponentMenu("Perception/RandomizerTags/Animation Randomizer Tag")]
public class AnimationRandomizerTag : RandomizerTag
{
/// <summary>
/// A list of animation clips from which to choose
/// </summary>
public AnimationClip[] animationClips;
}
}

3
com.unity.perception/Runtime/Randomization/Randomizers/RandomizerExamples/Tags/AnimationRandomizerTag.cs.meta


fileFormatVersion: 2
guid: f8943e41c7c34facb177a5decc1b2aef
timeCreated: 1610802367

307
com.unity.perception/Tests/Runtime/GroundTruthTests/KeyPointGroundTruthTests.cs


using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.Perception.GroundTruth;
using UnityEngine.TestTools;
namespace GroundTruthTests
{
[TestFixture]
public class KeyPointGroundTruthTests : GroundTruthTestBase
{
static GameObject SetupCamera(IdLabelConfig config, KeyPointTemplate template, Action<List<KeyPointLabeler.KeyPointEntry>> computeListener)
{
var cameraObject = new GameObject();
cameraObject.SetActive(false);
var camera = cameraObject.AddComponent<Camera>();
camera.orthographic = false;
camera.fieldOfView = 60;
camera.nearClipPlane = 0.3f;
camera.farClipPlane = 1000;
camera.transform.position = new Vector3(0, 0, -10);
var perceptionCamera = cameraObject.AddComponent<PerceptionCamera>();
perceptionCamera.captureRgbImages = false;
var keyPointLabeler = new KeyPointLabeler(config, template);
if (computeListener != null)
keyPointLabeler.KeyPointsComputed += computeListener;
perceptionCamera.AddLabeler(keyPointLabeler);
return cameraObject;
}
static KeyPointTemplate CreateTestTemplate(Guid guid, string label)
{
var keyPoints = new[]
{
new KeyPointDefinition
{
label = "FrontLowerLeft",
associateToRig = false,
color = Color.black
},
new KeyPointDefinition
{
label = "FrontUpperLeft",
associateToRig = false,
color = Color.black
},
new KeyPointDefinition
{
label = "FrontUpperRight",
associateToRig = false,
color = Color.black
},
new KeyPointDefinition
{
label = "FrontLowerRight",
associateToRig = false,
color = Color.black
},
new KeyPointDefinition
{
label = "BackLowerLeft",
associateToRig = false,
color = Color.black
},
new KeyPointDefinition
{
label = "BackUpperLeft",
associateToRig = false,
color = Color.black
},
new KeyPointDefinition
{
label = "BackUpperRight",
associateToRig = false,
color = Color.black
},
new KeyPointDefinition
{
label = "BackLowerRight",
associateToRig = false,
color = Color.black
},
new KeyPointDefinition
{
label = "Center",
associateToRig = false,
color = Color.black
}
};
var skeleton = new[]
{
new SkeletonDefinition
{
joint1 = 0,
joint2 = 1,
color = Color.magenta
},
new SkeletonDefinition
{
joint1 = 1,
joint2 = 2,
color = Color.magenta
},
new SkeletonDefinition
{
joint1 = 2,
joint2 = 3,
color = Color.magenta
},
new SkeletonDefinition
{
joint1 = 3,
joint2 = 0,
color = Color.magenta
},
new SkeletonDefinition
{
joint1 = 4,
joint2 = 5,
color = Color.blue
},
new SkeletonDefinition
{
joint1 = 5,
joint2 = 6,
color = Color.blue
},
new SkeletonDefinition
{
joint1 = 6,
joint2 = 7,
color = Color.blue
},
new SkeletonDefinition
{
joint1 = 7,
joint2 = 4,
color = Color.blue
},
new SkeletonDefinition
{
joint1 = 0,
joint2 = 4,
color = Color.green
},
new SkeletonDefinition
{
joint1 = 1,
joint2 = 5,
color = Color.green
},
new SkeletonDefinition
{
joint1 = 2,
joint2 = 6,
color = Color.green
},
new SkeletonDefinition
{
joint1 = 3,
joint2 = 7,
color = Color.green
},
};
var template = ScriptableObject.CreateInstance<KeyPointTemplate>();
template.templateID = guid;
template.templateName = label;
template.jointTexture = null;
template.skeletonTexture = null;
template.keyPoints = keyPoints;
template.skeleton = skeleton;
return template;
}
[Test]
public void KeypointTemplate_CreateTemplateTest()
{
var guid = Guid.NewGuid();
const string label = "TestTemplate";
var template = CreateTestTemplate(guid, label);
Assert.AreEqual(template.templateID, guid);
Assert.AreEqual(template.templateName, label);
Assert.IsNull(template.jointTexture);
Assert.IsNull(template.skeletonTexture);
Assert.IsNotNull(template.keyPoints);
Assert.IsNotNull(template.skeleton);
Assert.AreEqual(template.keyPoints.Length, 9);
Assert.AreEqual(template.skeleton.Length, 12);
var k0 = template.keyPoints[0];
Assert.NotNull(k0);
Assert.AreEqual(k0.label, "FrontLowerLeft");
Assert.False(k0.associateToRig);
Assert.AreEqual(k0.color, Color.black);
var s0 = template.skeleton[0];
Assert.NotNull(s0);
Assert.AreEqual(s0.joint1, 0);
Assert.AreEqual(s0.joint2, 1);
Assert.AreEqual(s0.color, Color.magenta);
}
static IdLabelConfig SetUpLabelConfig()
{
var cfg = ScriptableObject.CreateInstance<IdLabelConfig>();
cfg.Init(new List<IdLabelEntry>()
{
new IdLabelEntry
{
id = 1,
label = "label"
}
});
return cfg;
}
static void SetupCubeJoint(GameObject cube, KeyPointTemplate template, string label, float x, float y, float z)
{
var joint = new GameObject();
joint.transform.parent = cube.transform;
joint.transform.localPosition = new Vector3(x, y, z);
var jointLabel = joint.AddComponent<JointLabel>();
jointLabel.templateInformation = new List<JointLabel.TemplateData>();
var templateData = new JointLabel.TemplateData
{
template = template,
label = label
};
jointLabel.templateInformation.Add(templateData);
}
static void SetupCubeJoints(GameObject cube, KeyPointTemplate template)
{
SetupCubeJoint(cube, template, "FrontLowerLeft", -0.5f, -0.5f, -0.5f);
SetupCubeJoint(cube, template, "FrontUpperLeft", -0.5f, 0.5f, -0.5f);
SetupCubeJoint(cube, template, "FrontUpperRight", 0.5f, 0.5f, -0.5f);
SetupCubeJoint(cube, template, "FrontLowerRight", 0.5f, -0.5f, -0.5f);
SetupCubeJoint(cube, template, "BackLowerLeft", -0.5f, -0.5f, 0.5f);
SetupCubeJoint(cube, template, "BackUpperLeft", -0.5f, 0.5f, 0.5f);
SetupCubeJoint(cube, template, "BackUpperRight", 0.5f, 0.5f, 0.5f);
SetupCubeJoint(cube, template, "BackLowerRight", 0.5f, -0.5f, 0.5f);
}
[UnityTest]
public IEnumerator Keypoint_TestStaticLabeledCube()
{
var incoming = new List<List<KeyPointLabeler.KeyPointEntry>>();
var template = CreateTestTemplate(Guid.NewGuid(), "TestTemplate");
var cam = SetupCamera(SetUpLabelConfig(), template, (data) =>
{
incoming.Add(data);
});
var cube = TestHelper.CreateLabeledCube(scale: 6, z: 8);
SetupCubeJoints(cube, template);
cube.SetActive(true);
cam.SetActive(true);
AddTestObjectForCleanup(cam);
AddTestObjectForCleanup(cube);
yield return null;
yield return null;
var testCase = incoming.Last();
Assert.AreEqual(1, testCase.Count);
var t = testCase.First();
Assert.NotNull(t);
Assert.AreEqual(1, t.instance_id);
Assert.AreEqual(1, t.label_id);
Assert.AreEqual(template.templateID.ToString(), t.template_guid);
Assert.AreEqual(9, t.keypoints.Length);
Assert.AreEqual(t.keypoints[0].x, t.keypoints[1].x);
Assert.AreEqual(t.keypoints[2].x, t.keypoints[3].x);
Assert.AreEqual(t.keypoints[4].x, t.keypoints[5].x);
Assert.AreEqual(t.keypoints[6].x, t.keypoints[7].x);
Assert.AreEqual(t.keypoints[0].y, t.keypoints[3].y);
Assert.AreEqual(t.keypoints[1].y, t.keypoints[2].y);
Assert.AreEqual(t.keypoints[4].y, t.keypoints[7].y);
Assert.AreEqual(t.keypoints[5].y, t.keypoints[6].y);
for (var i = 0; i < 9; i++) Assert.AreEqual(i, t.keypoints[i].index);
for (var i = 0; i < 8; i++) Assert.AreEqual(1, t.keypoints[i].state);
Assert.Zero(t.keypoints[8].state);
Assert.Zero(t.keypoints[8].x);
Assert.Zero(t.keypoints[8].y);
}
}
}

11
com.unity.perception/Tests/Runtime/GroundTruthTests/KeyPointGroundTruthTests.cs.meta


fileFormatVersion: 2
guid: c62092ba10e4e4a80a0ec03e6e92593a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
正在加载...
取消
保存