我可以使用抽象基础 class 作为 Unity 编辑器元素吗?
Can I use an abstract base class as a Unity Editor element?
我正在尝试为 Unity GameObject
创建一个组件,我们称它为 MediaController
。我希望它能够管理不同媒体 (audio/video) 的时间 (play/pause/etc)。我用基本的 properties/fields/methods 创建了一个抽象 class PlayableMedia
并创建了 2 个 classes,PlayableVideo
和 PlayableAudio
,它们根据我们的继承和实现需要。
目的是拥有一个 PlayableMedia
的单一列表,它可以 audio/video 不可知,允许在特定应用程序时间轻松(即)media.Play()
调用,而不管类型如何。 .但是我的字段 public List<PlayableMedia> MediaList;
没有出现在编辑器中并且没有错误。
所以最终我的问题如标题所述:是否可以使用 PlayableMedia
class 作为字段类型?
根据我的经验,我怀疑 "no",但我发现 "yes" 或 "yes, sort of" 的链接似乎指向 to custom editors/inspectors/drawers,但我在这些方面的经验为 0,无法实施(见下文)。
[System.Serializable]
public class RegisteredMedia
{
public float StartTime;
public PlayableMedia Media;
}
[CustomPropertyDrawer(typeof(RegisteredMedia))]
class RegisteredMediaDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), new GUIContent("Playable Media"));
var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
Rect rectStartTime = new Rect(position.x, position.y, 30, position.height);
Rect rectMedia = new Rect(position.x + 35, position.y, 50, position.height);
EditorGUI.PropertyField(rectStartTime, property.FindPropertyRelative("StartTime"), GUIContent.none);
EditorGUI.PropertyField(rectMedia, property.FindPropertyRelative("Media"), GUIContent.none);
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
}
}
public class MediaController : MonoBehaviour
{
public List<RegisteredMedia> MediaList = new List<RegisteredMedia>();
\[...] rest of implementation
}
谁能帮帮我?要么确认这是不可能的,要么帮助我实现它?
此外,如果可以使用自定义 editors/inspectors/drawers 来完成,有人可以帮我在 List<RegisteredMedia>
中获取单个项目以显示为 Start Time ____ Playable Media [=====]
(其中 PlayableMedia
将是 GameObject
并附加了适当的组件)?
请小心使用 "property" 这个词。 In C# it means something very specific。
is it possible to use the PlayableMedia class as the type of a property?
我认为你在这里问错了问题。与其想出一个新的实施方案,不如考虑一下为什么您当前的实施方案可能无法正常工作?
首先,我给大家举个例子:
public abstract class Car : MonoBehaviour { }
public class Jeep : Car { }
public class Ferrari : Car { }
public class CarHolder : MonoBehaviour
{
public List<Car> Cars;
}
在此示例中,我可以使用 CarHolder 组件创建一个游戏对象,并且能够附加 Jeep 和 Ferrari 对象。请务必注意,我定义的每个 monoBehavior class 都位于其自己的文件中,并且文件名与 class 名称匹配。这就是 Unity 的工作原理。
所以要回答我想你问的问题(假设我们用 "field" 替换 "property"),确实可以使用抽象 class 类型并让它们显示出来在检查器中。我怀疑您需要将 classes 分成单独的文件。
从 2019.3 版本开始,通过 [SerializeReference]
属性 https://docs.unity3d.com/ScriptReference/SerializeReference.html
例如
using System.Collections.Generic;
using UnityEngine;
using System;
[Serializable]
public abstract class AbstractExample {
public int foo;
}
// [Serializable] not needed here
public class ConcreteExample : AbstractExample {
}
public class Consumer : MonoBehaviour {
[SerializeReference]
public List<AbstractExample> examples = new() { new ConcreteExample() };
// both the list and the concrete instance visible in the editor
// and editable without any additional editor extensions
// note that you can't effectively add new items to the list via editor
// since that way you create a faulty abstract-ish instances instead
// (no actual way for the editor to know what subtype do you want)
// if you're OK with having the base class being not explicitly abstract
// and can provide a sensible constructor for it, just drop the abstract
// you'll still have full polymorphism support etc. with SerializeReference
}
我正在尝试为 Unity GameObject
创建一个组件,我们称它为 MediaController
。我希望它能够管理不同媒体 (audio/video) 的时间 (play/pause/etc)。我用基本的 properties/fields/methods 创建了一个抽象 class PlayableMedia
并创建了 2 个 classes,PlayableVideo
和 PlayableAudio
,它们根据我们的继承和实现需要。
目的是拥有一个 PlayableMedia
的单一列表,它可以 audio/video 不可知,允许在特定应用程序时间轻松(即)media.Play()
调用,而不管类型如何。 .但是我的字段 public List<PlayableMedia> MediaList;
没有出现在编辑器中并且没有错误。
所以最终我的问题如标题所述:是否可以使用 PlayableMedia
class 作为字段类型?
根据我的经验,我怀疑 "no",但我发现 "yes" 或 "yes, sort of" 的链接似乎指向 to custom editors/inspectors/drawers,但我在这些方面的经验为 0,无法实施(见下文)。
[System.Serializable]
public class RegisteredMedia
{
public float StartTime;
public PlayableMedia Media;
}
[CustomPropertyDrawer(typeof(RegisteredMedia))]
class RegisteredMediaDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), new GUIContent("Playable Media"));
var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
Rect rectStartTime = new Rect(position.x, position.y, 30, position.height);
Rect rectMedia = new Rect(position.x + 35, position.y, 50, position.height);
EditorGUI.PropertyField(rectStartTime, property.FindPropertyRelative("StartTime"), GUIContent.none);
EditorGUI.PropertyField(rectMedia, property.FindPropertyRelative("Media"), GUIContent.none);
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
}
}
public class MediaController : MonoBehaviour
{
public List<RegisteredMedia> MediaList = new List<RegisteredMedia>();
\[...] rest of implementation
}
谁能帮帮我?要么确认这是不可能的,要么帮助我实现它?
此外,如果可以使用自定义 editors/inspectors/drawers 来完成,有人可以帮我在 List<RegisteredMedia>
中获取单个项目以显示为 Start Time ____ Playable Media [=====]
(其中 PlayableMedia
将是 GameObject
并附加了适当的组件)?
请小心使用 "property" 这个词。 In C# it means something very specific。
is it possible to use the PlayableMedia class as the type of a property?
我认为你在这里问错了问题。与其想出一个新的实施方案,不如考虑一下为什么您当前的实施方案可能无法正常工作?
首先,我给大家举个例子:
public abstract class Car : MonoBehaviour { }
public class Jeep : Car { }
public class Ferrari : Car { }
public class CarHolder : MonoBehaviour
{
public List<Car> Cars;
}
在此示例中,我可以使用 CarHolder 组件创建一个游戏对象,并且能够附加 Jeep 和 Ferrari 对象。请务必注意,我定义的每个 monoBehavior class 都位于其自己的文件中,并且文件名与 class 名称匹配。这就是 Unity 的工作原理。
所以要回答我想你问的问题(假设我们用 "field" 替换 "property"),确实可以使用抽象 class 类型并让它们显示出来在检查器中。我怀疑您需要将 classes 分成单独的文件。
从 2019.3 版本开始,通过 [SerializeReference]
属性 https://docs.unity3d.com/ScriptReference/SerializeReference.html
例如
using System.Collections.Generic;
using UnityEngine;
using System;
[Serializable]
public abstract class AbstractExample {
public int foo;
}
// [Serializable] not needed here
public class ConcreteExample : AbstractExample {
}
public class Consumer : MonoBehaviour {
[SerializeReference]
public List<AbstractExample> examples = new() { new ConcreteExample() };
// both the list and the concrete instance visible in the editor
// and editable without any additional editor extensions
// note that you can't effectively add new items to the list via editor
// since that way you create a faulty abstract-ish instances instead
// (no actual way for the editor to know what subtype do you want)
// if you're OK with having the base class being not explicitly abstract
// and can provide a sensible constructor for it, just drop the abstract
// you'll still have full polymorphism support etc. with SerializeReference
}