0


Unity 编辑器篇|(三)Attribute特性 (全面总结 | 建议收藏)

目录

前言

  • Unity 特性是一种用于装饰(标记)类、字段、方法和属性的 .NET 特性,用于控制 Unity 引擎的行为、检查代码的有效性以及提供更多的编辑器功能。Unity 特性可以帮助开发者更好地定制和控制游戏对象、组件、编辑器界面和其他 Unity 相关元素的行为。

1. System命名空间

1.1 Serializable

  • SerializableAttribute 是一个 .NET 特性,用于表示类或结构是可序列化的。 在 Unity 中,它常用于标记脚本的自定义类,使它们可以在 Inspector 中显示并且可以序列化。 当你在 Unity 中创建自定义类,例如一个用于保存游戏设置或角色属性的类,你通常会将 SerializableAttribute 特性添加到这些类上,以便你可以在 Inspector 中设置它们,或者将它们保存到 Unity 的资源文件中。 PlayerSettings 类被标记为 SerializableAttribute,这意味着你可以在 Unity 的 Inspector 窗口中创建一个 PlayerSettings 类型的字段,然后设置其属性。还可以将 PlayerSettings 对象保存到场景或资源文件中,以便在不同的场景之间或不同的游戏实例中使用。
usingSystem;usingUnityEngine;publicclassTest:MonoBehaviour{[Serializable]publicclassPlayerSettings{publicstring playerName;publicint playerScore;publicfloat playerHealth;}publicPlayerSettings playerSettings;}

1

1.2 NonSerialized

  • NonSerializedAttribute 属性是一个 C# 特性,通常用于标记字段,以指示这些字段不应该被序列化。 在 Unity 中,这通常用于在脚本中引用引用对象或在脚本中执行一些不希望在场景序列化时保存的操作。
  • myInt 字段上有 NonSerializedAttribute 特性,因此它不会出现在 Unity 的 Inspector 中并且不会被序列化。 myFloat 字段没有 NonSerializedAttribute 特性,因此它将被序列化并显示在 Unity 的 Inspector 中
usingSystem;usingUnityEngine;publicclassTest:MonoBehaviour{// 这个字段将不被序列化[NonSerialized]publicint myInt;// 这个字段将被序列化publicfloat myFloat;}

2

2. UnityEngine命名空间

2.1 AddComponentMenu

  • AddComponentMenu 用于自定义在 Unity 编辑器的 “Component” 菜单中显示的脚本的位置。 这个特性允许你将脚本添加到特定的菜单路径下,以便更好地组织你的自定义组件。 在 Unity 中,通常情况下,你创建的所有脚本将显示在 “Component/Scripts” 菜单中。这可能会导致菜单变得混乱不堪,尤其是在复杂的项目中。为了解决这个问题,你可以使用 AddComponentMenu特性来将脚本分类和组织。
usingUnityEngine;[AddComponentMenu("Menu/CustomMenu")]publicclassTest:MonoBehaviour{}

3

2.2 AddComponentMenu

  • AddComponentMenu 用于自定义在 Unity 编辑器的 “Component” 菜单中显示的脚本的位置。 这个特性允许你将脚本添加到特定的菜单路径下,以便更好地组织你的自定义组件。
  • 在 Unity 中,通常情况下,你创建的所有脚本将显示在 “Component/Scripts” 菜单中。这可能会导致菜单变得混乱不堪,尤其是在复杂的项目中。为了解决这个问题,你可以使用 AddComponentMenu特性来将脚本分类和组织。
usingUnityEngine;[AddComponentMenu("test/CustomScript")]publicclasstest:MonoBehaviour{// 这是你的脚本的内容}

2.3 ColorUsage

  • ColorUsageAttribute 用于指定颜色字段的属性,以便在 Inspector 窗口中显示颜色拾取器,允许用户选择颜色。
  • ColorUsageAttribute 的参数有两个: - Show Alpha: 这是一个布尔值,用于指定是否显示颜色拾取器中的 Alpha 透明度通道。如果设置为 true,则颜色拾取器将包括 Alpha 透明度通道,允许用户选择带有透明度的颜色。- HDR: 这也是一个布尔值,用于指定颜色是否使用高动态范围 (HDR)。如果设置为 true,颜色值将被视为 HDR 颜色,可以具有更高的亮度和对比度范围。
usingUnityEngine;publicclassTest:MonoBehaviour{[ColorUsage(true,true)]publicColor myColor;}

4

2.4 ContextMenu

  • ContextMenu 用于在 Inspector 窗口中为组件或脚本添加上下文菜单。
  • 使用这个特性,你可以在 Inspector 中的组件或脚本上创建自定义菜单项,以便在编辑时执行这些方法。
  • 这个特性通常用于自定义编辑器扩展,以便提供额外的工具或便捷的功能。例如,你可以在脚本中的方法上添加一个上下文菜单项,当点击该菜单项时,会触发方法执行,用于执行特定任务。
usingUnityEngine;publicclassTest:MonoBehaviour{[ContextMenu("Do Something")]privatevoidCustomMethod(){// 当在 Inspector 中的上下文菜单中选择 "Do Something" 时,将触发这个方法。
        Debug.Log("Doing something...");}}

5

2.5 ContextMenuItem

  • ContextMenuItemAttribute 用于在 Inspector 窗口中为字段或属性添加自定义上下文菜单项。
  • 你可以在自定义的字段上应用这个特性,以便在 Unity 编辑器的 Inspector 窗口中为它们添加自定义的上下文菜单项。这些菜单项可以在编辑时执行特定的操作。
usingUnityEngine;publicclassTest:MonoBehaviour{[ContextMenuItem("Reset Score","ResetScore")]publicint playerScore;privatevoidResetScore(){
        playerScore =0;}}
  • 在这个示例中,[ContextMenuItem(“Reset Score”, “ResetScore”)] 特性被应用于 playerScore 字段。 这将为 Inspector 中的 playerScore 字段创建一个上下文菜单项,当在该字段上右击时显示为 “Reset Score”。 当用户在 Inspector 中选择这个菜单项时,将触发 ResetScore 方法,这个方法将 playerScore 重置为 0。6

2.6 CreateAssetMenu

  • CreateAssetMenuAttribute 用于在 Unity 编辑器中为脚本创建自定义资源菜单项。 这个特性允许你在 “Assets” 菜单下创建新的自定义资源,例如脚本的实例或配置文件。 你可以将 CreateAssetMenuAttribute 特性应用于你的自定义脚本类,以便在 Unity 编辑器中方便地创建新的资源实例。这对于生成自定义配置文件、材质、纹理等非常有用。
usingUnityEngine;usingUnityEditor;[CreateAssetMenu(fileName ="NewTest", menuName ="MyGame/Test")]publicclassTest:ScriptableObject{}

6

  • 这个特性指定了新资源的文件名(fileName)和在 “Assets” 菜单下的位置(menuName)。 在 Unity 编辑器中,你现在可以点击 “Assets” 菜单,然后选择 “Create” > “MyGame” > “Test” 来创建一个新的 Test资源实例。

2.7 Delayed

  • DelayedAttribute 用于标记字段,告诉 Unity 在编辑器中延迟应用字段的值。
  • 这个特性通常用于需要稍后或在特定时机应用值的情况。
  • 当你在 Inspector 中调整带有 Delayed 特性的字段时,Unity 不会立即应用更改,而是等到编辑器的 “Update” 帧之后才应用。这允许你在多次更改字段值之间进行优化,以避免频繁的重计算或资源加载。
usingUnityEngine;publicclassTest:MonoBehaviour{[Delayed]publicfloat delayedValue =0.0f;privatevoidUpdate(){// 这里的 delayedValue 可能在多次更改后才会应用
        Debug.Log("Delayed Value: "+ delayedValue);}}
  • 在上面的示例中,delayedValue 字段被标记为 DelayedAttribute。当你在 Inspector 中更改它的值时,Unity 不会立即应用更改,而是等到下一帧的 Update 方法调用时才应用。这有助于在编辑器中进行值调整而不触发过多的重计算。
  • DelayedAttribute特性对于需要调整和测试数值字段的行为非常有用,因为它允许你在进行多次微调时获得更好的反馈,而不会在每次更改后触发昂贵的操作。

2.8 DisallowMultipleComponent

  • DisallowMultipleComponent 用于防止将多个相同类型的组件添加到同一 GameObject 上。
  • 当你将此特性添加到一个脚本类上时,Unity 将确保在同一 GameObject 上只能添加一个该脚本类型的组件。
  • 这个特性的主要目的是帮助开发人员避免在同一 GameObject 上意外添加多个相同类型的组件,因为这可能会导致不可预测的行为和错误。通过使用 DisallowMultipleComponent 特性,你可以确保每个 GameObject 上只有一个特定类型的组件。
usingUnityEngine;[DisallowMultipleComponent]publicclassTest:MonoBehaviour{}

6

  • 在上面的示例中,MyComponent 脚本被标记为 DisallowMultipleComponent ,这意味着你无法将多个 Test组件添加到同一 GameObject 上。

2.9 ExecuteInEditMode

  • ExecuteInEditMode 特性用于标记在 Unity 中的脚本类,以确保它们在编辑模式下也会执行,而不仅仅在播放模式下执行。这对于那些需要在编辑器中进行实时交互和编辑的脚本非常有用。
usingUnityEngine;[ExecuteInEditMode]// 使用 ExecuteInEditMode 特性publicclassTest:MonoBehaviour{publicVector3 somePosition;privatevoidUpdate(){// 这里的代码将在编辑器模式下执行
        transform.position = somePosition;}}
  • 在上面的示例中,EditModeScript 类被标记为 ExecuteInEditMode 特性。这表示其中的 Update 方法将在编辑器模式下也执行。这允许开发者在编辑场景时实时观察和交互对象的属性,而不必进入播放模式。

2.10 GUITarget

  • 在选择哪些显示器调用OnGUI函数。
usingUnityEngine;publicclassTest:MonoBehaviour{[GUITarget(0,1)]privatevoidOnGUI(){
        GUI.Label(newRect(10,10,300,100),"Visible on TV and Wii U GamePad only");}}

2.11 Header

  • HeaderAttribute 用于在 Inspector 窗口中的字段或变量之前添加标题头。
  • 通常情况下,HeaderAttribute 用于在 Inspector 窗口中创建字段的逻辑分组。
usingUnityEngine;publicclassTest:MonoBehaviour{[Header("objectName")]publicstring objectName;[Header("objectScale")]publicfloat objectScale;}

7

2.12 HelpURL

  • HelpURLAttribute 用于为Inspector 窗口中的组件或字段提供帮助链接。 可以很轻松地访问有关该组件或字段的额外信息、文档或在线资源。
usingUnityEngine;[HelpURL("https://docs.unity3d.com/ScriptReference/Transform.html")]publicclassTest:MonoBehaviour{}

8

2.13 HideInInspector

  • HideInInspector用于将字段隐藏在 Inspector 窗口中,即使它们是公共字段 这对于隐藏一些内部或不希望用户更改的字段很有用。
usingUnityEngine;publicclassTest:MonoBehaviour{publicint publicField;// 这个字段将显示在 Inspector 窗口中[HideInInspector]publicint hiddenField;// 这个字段将被隐藏,不会在 Inspector 中显示}

9

2.14 ImageEffectAllowedInSceneView

  • 使用了这个特性的图像特效可以渲染在 SceneView的摄像机上。

2.15 ImageEffectOpaque

  • 可以在不透明通道直接执行图像特效。

2.16 ImageEffectTransformsToLDR

  • 在HDR渲染模式下,使用图像特效用LDR渲染模式。

2.17 Multiline

  • 在MultilineAttribute 用于标记一个字符串字段,告诉 Inspector 窗口该字段可以显示多行文本输入框。
  • 这通常用于长文本字段或需要多行输入的字段。
usingUnityEngine;publicclassTest:MonoBehaviour{[Multiline(4)]// 将该字段标记为多行文本输入,显示 4 行文本输入框publicstring description;}

11

2.18 PreferBinarySerialization

  • 只能用于ScriptableObject 子类,用二进制序列化,有利于处理大量数据的资源文件,提升读写性能。主要缺点是二进制的文件我们看不懂,还有不能用版本控制软件合并它。

2.19 Property

  • 只这个是监视板里面修改样式的抽象基类,例如显示小标题、显示多行编辑文本等等都是以它为基类。

2.20 Range

  • RangeAttribute 用于指定字段或属性的有效值范围。
  • RangeAttribute 可以用来限制 Inspector 窗口中字段的值,确保它们在指定的范围内。
usingUnityEngine;publicclassTest:MonoBehaviour{[Range(0.0f,1.0f)]publicfloat rangeValue;[Range(1,10)]publicint rangeCount;}

12

2.21 RequireComponent

  • RequireComponent 用于确保特定类型的组件在同一 GameObject 上一起存在。
  • 这个特性通常用于协助开发人员避免在 GameObject 上忘记添加必需的组件。
  • 当你将 RequireComponent 特性添加到一个脚本类上时,你可以指定一个或多个必需的组件类型。如果某个 GameObject 上附加了该脚本类,但缺少指定的组件,Unity 将自动添加这些必需的组件,以确保 GameObject 具有正确的组件配置。
usingUnityEngine;[RequireComponent(typeof(Rigidbody))]publicclassTest:MonoBehaviour{// 这个脚本需要一个 Rigidbody 组件// 如果 GameObject 上没有 Rigidbody 组件,Unity 会自动添加一个}

13

2.22 RPC

  • 用于Networking,但废弃了。

2.23 RuntimeInitializeOnLoadMethod

  • RuntimeInitializeOnLoadMethodAttribute 用于标记静态方法,在 Unity 运行时初始化时自动调用。
  • 通常,这个特性用于在游戏启动时执行某些初始化逻辑或注册回调函数。
  • 要使用 RuntimeInitializeOnLoadMethodAttribute 特性,你需要编写一个静态方法,并将该特性应用于该方法。当你的游戏启动时,Unity 将自动调用带有此特性的方法。
usingUnityEngine;publicclassTest{[RuntimeInitializeOnLoadMethod]staticvoidtestInitializeOnLoad(){// 在游戏启动时执行的初始化逻辑
        Debug.Log("Game is starting, performing initialization...");}}
  • 在上面的示例中,InitializeOnLoad 方法被 RuntimeInitializeOnLoadMethod 特性标记,因此它将在游戏启动时执行。你可以在该方法内执行任何你需要在游戏启动时进行的初始化工作。
  • 这个特性通常用于在项目启动时执行某些全局初始化操作,例如设置游戏的初始状态、加载配置、注册全局事件等。这样可以确保这些操作在游戏启动时自动执行,而不需要手动触发。

2.24 SelectionBase

  • SelectionBaseAttribute 用于将其 GameObject 标记为 Scene View 选取的选择基础对象。
  • 在 Unity Scene View 中,当单击以选择对象时,Unity 将尝试找出为您选择的最佳对象。
  • 如果单击作为预制件一部分的某个对象,则会选择该预制件的根,因为预制件根被视为选择基础。也可以使其他对象被视为选择基础。
usingUnityEngine;[SelectionBase]// 使用 SelectionBase 特性publicclassTest:MonoBehaviour{}

14

2.25 SerializeField

  • SerializeField 用于指定字段或属性应在 Inspector 窗口中可见和序列化。
  • 当将该特性应用于一个字段或属性时,它将允许访问并编辑它,而不需要将字段标记为 public。
usingUnityEngine;publicclassTest:MonoBehaviour{[SerializeField]privateint myField;}
  • myField字段被标记为 SerializeField,因此它可以在 Unity 的 Inspector 窗口中可见和编辑。
  • 如果不想将字段标记为 public,但仍希望能够在 Inspector 窗口中编辑它们。通过添加 SerializeField,可以序列化非公开字段,使其在编辑器中可见。15

2.26 SharedBetweenAnimators

  • 用于StateMachineBehaviour,类似prefab,Animator之间公用这个实例,减少内存消耗。

2.27 Space

  • 用SpaceAttribute 用于在 Inspector 窗口中创建字段之间的间距,以改善界面布局。
  • 通过在字段之前或之后添加空白间距,可以提高字段的可读性和可访问性。
usingUnityEngine;publicclassTest:MonoBehaviour{publicint field1;[Space(10)]// 在此处添加一个 10 像素的间距publicfloat field2;[Space(20)]// 在此处添加一个 20 像素的间距publicstring field3;}
  • [Space(10)] 和 [Space(20)] 分别在 field2 和 field3 字段之前添加了 10 像素和 20 像素的间距。15

2.28 TextArea

  • TextAreaAttribute 类似于 MultilineAttribute,但允许您更灵活地控制多行文本输入框的外观和行为。
  • TextAreaAttribute 可以用于标记字符串字段,以在 Unity 的 Inspector 窗口中创建多行文本输入框。
usingUnityEngine;publicclassTest:MonoBehaviour{[TextArea(3,6)]// 文本框显示 3-6 行文本publicstring description;}
  • 与 MultilineAttribute 不同,TextAreaAttribute 允许设置以下属性: - minLines:文本框的最小行数。- maxLines:文本框的最大行数。- description 字段被标记为多行文本输入框,并指定了行数的范围。

16

2.29 Tooltip

  • TooltipAttribute 用于在 Inspector 窗口中为字段或属性提供鼠标悬停提示信息。
  • 对于描述字段或属性的用途、范围、特定设置或其他有关信息非常有用。
usingUnityEngine;publicclassTest:MonoBehaviour{[Tooltip("This is the object's name.")]publicstring objectName;}

18

2.30 UnityAPICompatibilityVersion

  • 用来声明程序集的API版本,避免处理时是否可以用旧版本的Unity API。

3. UnityEditor命名空间

3.1 CallbackOrder

  • 所有带order(顺序)回调属性的特性基类。

3.2 CanEditMultipleObjects

  • CanEditMultipleObjects 用于标记自定义编辑器脚本类,允许你同时编辑多个相同类型的对象。
  • 这对于在 Unity 编辑器中处理多个对象的编辑非常有用。
  • 当你创建一个自定义编辑器脚本(通常是继承自 Editor 类),你可以使用 CanEditMultipleObjects 特性来指示你的编辑器可以同时编辑多个对象。
  • 这意味着当你选择多个相同类型的对象时,Unity 会使用你的自定义编辑器来显示它们的属性,并让你可以一次性修改它们的值。
usingUnityEditor;[CustomEditor(typeof(MyComponent))][CanEditMultipleObjects]publicclassTestComponentEditor:Editor{privateTestComponent testComponent {get{return(TestComponent)target;}}publicoverridevoidOnInspectorGUI(){base.OnInspectorGUI();

        testComponent.age= EditorGUILayout.FloatField("年龄值", testComponent.age);
        testComponent.myName = EditorGUILayout.TextField("名字", testComponent.myName);}}
  • 在上面的示例中,我们创建了一个名为 MyComponentEditor 的自定义编辑器,并为它的目标对象类型添加了 CustomEditor特性,以及 CanEditMultipleObjects特性。这表示当你选择多个 MyComponent 类型的对象时,Unity 会使用 MyComponentEditor 来编辑它们。
  • 这个特性非常适用于处理一组具有相同脚本类型的对象,允许你一次性修改它们的属性,提高了编辑效率。

3.3 CustomEditor

  • CustomEditor 用于关联自定义编辑器脚本类与特定的游戏对象脚本类。
  • 这使得你可以为游戏对象脚本类创建自定义检视器面板,以便更灵活地编辑游戏对象的属性。
  • 通常情况下,Unity会根据游戏对象的脚本自动生成一个默认的检视器面板。但有时,你可能需要更多的控制权,以按照自己的方式自定义检视器面板。这就是 CustomEditor 特性派上用场的地方。
usingUnityEngine;publicclassTest:MonoBehaviour{publicfloat age;publicstring myName;}
usingUnityEditor;[CustomEditor(typeof(Test))]publicclassTestEditor:Editor{privateTest myTest {get{return(Test)target;}}publicoverridevoidOnInspectorGUI(){
        myTest.age = EditorGUILayout.FloatField("年龄", myTest.age);
        myTest.myName = EditorGUILayout.TextField("名字", myTest.myName);}}

19

3.4 CustomPreview

  • CustomPreviewAttribute 允许你为特定类型的资源或组件提供自定义预览功能。资源文件包括纹理、声音、模型等。
  • 这样,你可以在 Inspector 中查看资源或组件时,显示自定义的预览图像,而不仅仅是默认的图标。
  • 通过使用 CustomPreviewAttribute特性,你可以为特定类型的资源文件创建自定义的预览面板,使其在资源窗口中显示自定义信息和视觉元素。
[CustomPreview(typeof(Texture))]// 将预览特性与 Texture 关联publicclassTexturePreview:ObjectPreview{publicoverrideboolHasPreviewGUI(){returntrue;}publicoverrideGUIContentGetPreviewTitle(){returnnewGUIContent("Custom Texture Preview");}publicoverridevoidOnPreviewGUI(Rect r,GUIStyle background){if(target isTexture){Texture texture =(Texture)target;// 绘制纹理预览
             GUI.DrawTexture(newRect(r.width /4, r.height /4, r.width/2,r.height/2), texture);// 在预览面板上绘制内容
             GUI.Label(newRect(r.width /4, r.height /2, r.width /2, r.height /2),"Custom Preview Example",newGUIStyle{
                 fontSize =40,
                 normal ={ textColor=Color.white}});}}

21

3.5 CustomPropertyDrawer

  • 自定义属性渲染,如果要自定义PropertyDrawer或 DecoratorDrawer,要加上这个特性。

3.6 DrawGizmo

  • DrawGizmo 特性用于在 Scene 视图中绘制 Gizmos,这是 Unity 编辑器中用于可视化调试和编辑的图形表示。
  • 开发者可以使用 DrawGizmo 特性自定义如何在 Scene 视图中绘制这些 Gizmos。
  • 该特性可以用于自定义编辑器类,以便在编辑场景中的对象时,你可以显示自定义的 Gizmos,以帮助可视化场景中的各种信息。
usingUnityEngine;publicclasstest:MonoBehaviour{}
usingUnityEngine;usingUnityEditor;publicclasstestGizmoDrawer{[DrawGizmo(GizmoType.Selected | GizmoType.Active)]staticvoidDrawGizmoForMyScript(test scr,GizmoType gizmoType){// 在选中或激活状态下绘制 Gizmo
        Gizmos.color = Color.red;
        Gizmos.DrawWireSphere(scr.transform.position,1.0f);}}

20

3.7 InitializeOnLoad

  • 当InitializeOnLoadAttribute 用于在 Unity 加载时和重新编译脚本时初始化 Editor 类。 重新编译项目中的脚本时,会调用带有此属性的静态构造函数(也称为域重新加载)。 在 Unity 首次加载项目时、在 Unity 检测到脚本修改时(取决于自动刷新首选项),以及当进入运行模式时(取决于运行模式配置),会进行此调用。
usingUnityEditor;usingUnityEngine;[InitializeOnLoad]publicclasstestInitialization{statictestInitialization(){// 在指定的时刻执行初始化代码
        Debug.Log("Initialization has occurred.");}}
  • 这个构造函数将在 Unity 编辑器启动时执行,以进行自定义的初始化操作。
  • 使用 InitializeOnLoadAttribute 的典型情况包括注册自定义编辑器工具、设置编辑器菜单项、加载资源或执行其他需要在编辑器启动时运行的任务。

3.8 InitializeOnLoadMethod

  • 同InitializeOnLoadMethodAttribute 用于在 Unity 加载时和重新编译脚本时初始化方法。 重新编译项目中的脚本时,会调用带有此属性的静态方法。 在 Unity 首次加载项目时、在 Unity 检测到脚本修改时,以及当进入运行模式时,会进行此调用.
usingUnityEditor;publicclasstestInitialization{[InitializeOnLoadMethod]staticvoidInitialize(){// 在指定的时刻执行初始化代码
        Debug.Log("Initialization method has been called.");}}

3.9 MenuItem

  • MenuItem 用于在 Unity 编辑器的菜单栏中添加自定义菜单项。 这允许开发人员创建自定义的编辑器工具、快捷方式或功能,并将它们集成到 Unity 编辑器中,以提高工作效率。 MenuItem 特性有两个常用的用途: - 打开脚本或场景: 开发人员可以使用 MenuItem创建一个菜单项,使他们可以轻松地打开脚本文件或场景文件,而无需手动导航到资源面板或文件资源。- 执行自定义功能: 通过MenuItem,开发人员可以创建自定义功能,例如自动化工作流、清理操作或自定义编辑器工具。这些功能可以通过菜单项触发,使 Unity编辑器更加强大和易于使用。

3.10 PreferenceItem

  • 添给Preference窗口添加菜单项,调用的也是静态方法。例子是官方的。

本文转载自: https://blog.csdn.net/backlighting2015/article/details/135488782
版权归原作者 游戏开发小Y 所有, 如有侵权,请联系我们删除。

“Unity 编辑器篇|(三)Attribute特性 (全面总结 | 建议收藏)”的评论:

还没有评论