为嵌套的 ScriptableObjects 构建编辑器以在纸牌游戏中组合能力
Building an Editor for nested ScriptableObjects to compose abilities in a card game
我正在制作一款纸牌游戏,我想要一个干净的纸牌能力架构。
我有一个带有卡片属性的 CardData ScriptableObject。我希望将卡牌能力组合在一起来描述卡牌的作用,例如一张名为 DrawAndHealCard 的卡牌,它在使用时抽取 2 张卡牌并恢复 5 点生命值。
我马上意识到这意味着我需要为 CardAbility 的每个变体提供一个具体的资产。所以 DrawAndHealCard 有两个资产的引用:DrawCards2 和 HealPlayer5。这太荒谬了,我希望所有数据都感觉像是在一张 DrawAndHealCard 上。
所以我了解了 AssetDatabase.AddObjectToAsset()
,这似乎是个正确的想法,我可以拥有作为 CardData 资产的子资产的能力,而不必处理所有这些单独资产的组织。所以现在我正在尝试构建一个 Editor
来管理它,这很痛苦。
我已经阅读了很多关于 Unity 序列化、SO、编辑器脚本的内容,...严重撞墙,即将降级到架构上感觉不那么优雅的东西。如果有更好的方法,我也愿意接受关于完全不同路线的建议。
下面的代码被精简了,但这是我要弄清楚的要点。
我现在所在的位置 onAddCallback
似乎正确地添加了一个子资产,但 onRemoveCallback
并没有删除它。不过,我的 清除所有能力 按钮确实有效。我找不到关于这些东西的任何好的文档或指南,所以我目前很迷茫。
// CardData.cs
[CreateAssetMenu(fileName = "CardData", menuName = "Card Game/CardData", order = 1)]
public class CardData : ScriptableObject
{
public Sprite image;
public string description;
public CardAbility[] onPlayed;
}
// CardAbility.cs
public class CardAbility : ScriptableObject
{
public abstract void Resolve();
}
// DrawCards.cs
public class DrawCards : CardAbility
{
public int numCards = 1;
public override void Resolve()
{
Deck.instance.DrawCards(numCards);
}
}
// HealPlayer.cs
public class HealPlayer : CardAbility
{
public int healAmt = 10;
public override void Resolve()
{
Player.instance.Heal(healAmt);
}
}
// CardDataEditor.cs
[CustomEditor(typeof(CardData))]
public class CardDataEditor : Editor
{
private ReorderableList abilityList;
public void OnEnable()
{
abilityList = new ReorderableList(
serializedObject,
serializedObject.FindProperty("onPlayed"),
draggable: true,
displayHeader: true,
displayAddButton: true,
displayRemoveButton: true);
abilityList.onRemoveCallback = (ReorderableList l) => {
l.serializedProperty.serializedObject.Update();
var obj = l.serializedProperty.GetArrayElementAtIndex(l.index).objectReferenceValue;
DestroyImmediate(obj, true);
AssetDatabase.SaveAssets();
l.serializedProperty.DeleteArrayElementAtIndex(l.index);
l.serializedProperty.serializedObject.ApplyModifiedProperties();
};
abilityList.onAddCallback = (ReorderableList l) => {
var index = l.serializedProperty.arraySize;
l.serializedProperty.arraySize++;
l.index = index;
var element = l.serializedProperty.GetArrayElementAtIndex(index);
// Hard coding a specific ability for now
var cardData = (CardData)target;
var newAbility = ScriptableObject.CreateInstance<DrawCards>();
newAbility.name = "test";
newAbility.numCards = 22;
element.objectReferenceValue = newAbility;
AssetDatabase.AddObjectToAsset(newAbility, cardData);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
serializedObject.ApplyModifiedProperties();
};
// Will use this to provide a menu of abilities to choose from.
/*
abilityList.onAddDropdownCallback = (Rect buttonRect, ReorderableList l) => {
var menu = new GenericMenu();
var guids = AssetDatabase.FindAssets("", new[]{"Assets/CardAbility"});
foreach (var guid in guids) {
var path = AssetDatabase.GUIDToAssetPath(guid);
menu.AddItem(new GUIContent("Mobs/" + Path.GetFileNameWithoutExtension(path)), false, clickHandler, new WaveCreationParams() {Type = MobWave.WaveType.Mobs, Path = path});
}
menu.ShowAsContext();
};
*/
// Will use this to render CardAbility properties
/*
abilityList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => {
};
*/
}
public override void OnInspectorGUI()
{
serializedObject.Update();
DrawDefaultInspector();
abilityList.DoLayoutList();
// XXX: Ultimately don't expect to use these, experimenting with
// other ways of adding/deleting.
if (GUILayout.Button("Add Ability")) {
var cardData = (CardData)target;
var newAbility = ScriptableObject.CreateInstance<CardAbility>();
AssetDatabase.AddObjectToAsset(newAbility, cardData);
AssetDatabase.SaveAssets();
}
if (GUILayout.Button("Clear All Abilities")) {
var path = AssetDatabase.GetAssetPath(target);
Object[] assets = AssetDatabase.LoadAllAssetRepresentationsAtPath(path);
for (int i = 0; i < assets.Length; i++) {
if (assets[i] is CardAbility) {
Object.DestroyImmediate(assets[i], true);
}
}
AssetDatabase.SaveAssets();
}
serializedObject.ApplyModifiedProperties();
}
}
不确定,因为如果您面前没有完整的项目,编辑器脚本总是很棘手。
乍一看,我会说您正在将资产添加到资产中,但您没有使用 AssetDatabase.RemoveObjectFromAsset
删除它们
你可能应该做类似的事情
abilityList.onRemoveCallback = (ReorderableList l) =>
{
// Update should be redundant here since you already call it anyway on beginning of the draw loop
// Are you also sure the `l.index` is the correct value to use here?
var obj = l.serializedProperty.GetArrayElementAtIndex(l.index).objectReferenceValue;
AssetDatabase.RemoveObjectFromAsset(obj);
DestroyImmediate(obj, true);
l.serializedProperty.DeleteArrayElementAtIndex(l.index);
// If you do save assets also refresh
// Not sure if you should even do that though to be honest
AssetDatabase.SaveAssets();
// Also refresh here
AssetDatabase.Refresh();
// Also ApplyModifiedProperties should be redundant
};
也如评论所述,我认为您甚至不必使用
AssetDatabase.SaveAssets();
如果没有它,资产将被简单地标记为脏,并在下一次 CTRL+S 按下时与场景一起保存。
但是,afaik,如果你这样做,你应该始终将它与 AssetDatabase.Refresh();
结合起来,以实际看到所做的更改反映在资产视图中
好的,我终于明白了。我阅读了一百个堆栈溢出和论坛帖子试图理解这一点,所以我正在付出代价,希望这可以帮助其他人解决这个问题。这将生成如下图所示的编辑器,其中 OnPlayed 是一个多态 ScriptableObject 数组。这些 CardAbility SO 作为子资产存储在拥有的 ScriptableObject (CardData) 上。这里还有更多需要清理的地方,它可以变得更通用,但对于其他尝试这样做的人来说应该是一个好的开始。
[+] 按钮生成可添加的所有 CardAbility SO 的列表。
具体 CardAbility 的属性是动态呈现的。
关于这一切最奇怪的事情之一是你不能使用 PropertyField
呈现 objectReferenceValue
的内容,你必须首先像这样构造一个 SerializedObject
:
SerializedObject nestedObject = new SerializedObject(element.objectReferenceValue);
感谢 Unity: Inspector can't find field of ScriptableObject 的提示。
ReorderableList 的其他一些重要资源:
- https://va.lent.in/unity-make-your-lists-functional-with-reorderablelist/
- https://sites.google.com/site/tuxnots/gamming/unity3d/unitymakeyourlistsfunctionalwithreorderablelist
- https://sandordaemen.nl/blog/unity-3d-extending-the-editor-part-3/
// CardData.cs
[CreateAssetMenu(fileName = "CardData", menuName = "Card Game/CardData", order = 1)]
public class CardData : ScriptableObject
{
public enum CardType
{
Attack,
Skill
}
public CardType type;
public Sprite image;
public string description;
// XXX: Hidden in inspector because it will be drawn by custom Editor.
[HideInInspector]
public CardAbility[] onPlayed;
}
// CardAbility.cs
public abstract class CardAbility : ScriptableObject
{
public abstract void Resolve();
}
// DrawCards.cs
public class DrawCards : CardAbility
{
public int numCards = 1;
public override void Resolve()
{
Deck.instance.DrawCards(numCards);
}
}
// HealPlayer.cs
public class HealPlayer : CardAbility
{
public int healAmount = 10;
public override void Resolve()
{
Player.instance.Heal(healAmount);
}
}
// CardDataEditor.cs
[CustomEditor(typeof(CardData))]
[CanEditMultipleObjects]
public class CardDataEditor : Editor
{
private ReorderableList abilityList;
private SerializedProperty onPlayedProp;
private struct AbilityCreationParams {
public string Path;
}
public void OnEnable()
{
onPlayedProp = serializedObject.FindProperty("onPlayed");
abilityList = new ReorderableList(
serializedObject,
onPlayedProp,
draggable: true,
displayHeader: true,
displayAddButton: true,
displayRemoveButton: true);
abilityList.drawHeaderCallback = (Rect rect) => {
EditorGUI.LabelField(rect, "OnPlayed Abilities");
};
abilityList.onRemoveCallback = (ReorderableList l) => {
var element = l.serializedProperty.GetArrayElementAtIndex(l.index);
var obj = element.objectReferenceValue;
AssetDatabase.RemoveObjectFromAsset(obj);
DestroyImmediate(obj, true);
l.serializedProperty.DeleteArrayElementAtIndex(l.index);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
ReorderableList.defaultBehaviours.DoRemoveButton(l);
};
abilityList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => {
SerializedProperty element = onPlayedProp.GetArrayElementAtIndex(index);
rect.y += 2;
rect.width -= 10;
rect.height = EditorGUIUtility.singleLineHeight;
if (element.objectReferenceValue == null) {
return;
}
string label = element.objectReferenceValue.name;
EditorGUI.LabelField(rect, label, EditorStyles.boldLabel);
// Convert this element's data to a SerializedObject so we can iterate
// through each SerializedProperty and render a PropertyField.
SerializedObject nestedObject = new SerializedObject(element.objectReferenceValue);
// Loop over all properties and render them
SerializedProperty prop = nestedObject.GetIterator();
float y = rect.y;
while (prop.NextVisible(true)) {
if (prop.name == "m_Script") {
continue;
}
rect.y += EditorGUIUtility.singleLineHeight;
EditorGUI.PropertyField(rect, prop);
}
nestedObject.ApplyModifiedProperties();
// Mark edits for saving
if (GUI.changed) {
EditorUtility.SetDirty(target);
}
};
abilityList.elementHeightCallback = (int index) => {
float baseProp = EditorGUI.GetPropertyHeight(
abilityList.serializedProperty.GetArrayElementAtIndex(index), true);
float additionalProps = 0;
SerializedProperty element = onPlayedProp.GetArrayElementAtIndex(index);
if (element.objectReferenceValue != null) {
SerializedObject ability = new SerializedObject(element.objectReferenceValue);
SerializedProperty prop = ability.GetIterator();
while (prop.NextVisible(true)) {
// XXX: This logic stays in sync with loop in drawElementCallback.
if (prop.name == "m_Script") {
continue;
}
additionalProps += EditorGUIUtility.singleLineHeight;
}
}
float spacingBetweenElements = EditorGUIUtility.singleLineHeight / 2;
return baseProp + spacingBetweenElements + additionalProps;
};
abilityList.onAddDropdownCallback = (Rect buttonRect, ReorderableList l) => {
var menu = new GenericMenu();
var guids = AssetDatabase.FindAssets("", new[]{"Assets/CardAbility"});
foreach (var guid in guids) {
var path = AssetDatabase.GUIDToAssetPath(guid);
var type = AssetDatabase.LoadAssetAtPath(path, typeof(UnityEngine.Object));
if (type.name == "CardAbility") {
continue;
}
menu.AddItem(
new GUIContent(Path.GetFileNameWithoutExtension(path)),
false,
addClickHandler,
new AbilityCreationParams() {Path = path});
}
menu.ShowAsContext();
};
}
private void addClickHandler(object dataObj) {
// Make room in list
var data = (AbilityCreationParams)dataObj;
var index = abilityList.serializedProperty.arraySize;
abilityList.serializedProperty.arraySize++;
abilityList.index = index;
var element = abilityList.serializedProperty.GetArrayElementAtIndex(index);
// Create the new Ability
var type = AssetDatabase.LoadAssetAtPath(data.Path, typeof(UnityEngine.Object));
var newAbility = ScriptableObject.CreateInstance(type.name);
newAbility.name = type.name;
// Add it to CardData
var cardData = (CardData)target;
AssetDatabase.AddObjectToAsset(newAbility, cardData);
AssetDatabase.SaveAssets();
element.objectReferenceValue = newAbility;
serializedObject.ApplyModifiedProperties();
}
public override void OnInspectorGUI()
{
serializedObject.Update();
DrawDefaultInspector();
abilityList.DoLayoutList();
if (GUILayout.Button("Delete All Abilities")) {
var path = AssetDatabase.GetAssetPath(target);
Object[] assets = AssetDatabase.LoadAllAssetRepresentationsAtPath(path);
for (int i = 0; i < assets.Length; i++) {
if (assets[i] is CardAbility) {
Object.DestroyImmediate(assets[i], true);
}
}
AssetDatabase.SaveAssets();
}
serializedObject.ApplyModifiedProperties();
}
}
我正在制作一款纸牌游戏,我想要一个干净的纸牌能力架构。 我有一个带有卡片属性的 CardData ScriptableObject。我希望将卡牌能力组合在一起来描述卡牌的作用,例如一张名为 DrawAndHealCard 的卡牌,它在使用时抽取 2 张卡牌并恢复 5 点生命值。
我马上意识到这意味着我需要为 CardAbility 的每个变体提供一个具体的资产。所以 DrawAndHealCard 有两个资产的引用:DrawCards2 和 HealPlayer5。这太荒谬了,我希望所有数据都感觉像是在一张 DrawAndHealCard 上。
所以我了解了 AssetDatabase.AddObjectToAsset()
,这似乎是个正确的想法,我可以拥有作为 CardData 资产的子资产的能力,而不必处理所有这些单独资产的组织。所以现在我正在尝试构建一个 Editor
来管理它,这很痛苦。
我已经阅读了很多关于 Unity 序列化、SO、编辑器脚本的内容,...严重撞墙,即将降级到架构上感觉不那么优雅的东西。如果有更好的方法,我也愿意接受关于完全不同路线的建议。
下面的代码被精简了,但这是我要弄清楚的要点。
我现在所在的位置 onAddCallback
似乎正确地添加了一个子资产,但 onRemoveCallback
并没有删除它。不过,我的 清除所有能力 按钮确实有效。我找不到关于这些东西的任何好的文档或指南,所以我目前很迷茫。
// CardData.cs
[CreateAssetMenu(fileName = "CardData", menuName = "Card Game/CardData", order = 1)]
public class CardData : ScriptableObject
{
public Sprite image;
public string description;
public CardAbility[] onPlayed;
}
// CardAbility.cs
public class CardAbility : ScriptableObject
{
public abstract void Resolve();
}
// DrawCards.cs
public class DrawCards : CardAbility
{
public int numCards = 1;
public override void Resolve()
{
Deck.instance.DrawCards(numCards);
}
}
// HealPlayer.cs
public class HealPlayer : CardAbility
{
public int healAmt = 10;
public override void Resolve()
{
Player.instance.Heal(healAmt);
}
}
// CardDataEditor.cs
[CustomEditor(typeof(CardData))]
public class CardDataEditor : Editor
{
private ReorderableList abilityList;
public void OnEnable()
{
abilityList = new ReorderableList(
serializedObject,
serializedObject.FindProperty("onPlayed"),
draggable: true,
displayHeader: true,
displayAddButton: true,
displayRemoveButton: true);
abilityList.onRemoveCallback = (ReorderableList l) => {
l.serializedProperty.serializedObject.Update();
var obj = l.serializedProperty.GetArrayElementAtIndex(l.index).objectReferenceValue;
DestroyImmediate(obj, true);
AssetDatabase.SaveAssets();
l.serializedProperty.DeleteArrayElementAtIndex(l.index);
l.serializedProperty.serializedObject.ApplyModifiedProperties();
};
abilityList.onAddCallback = (ReorderableList l) => {
var index = l.serializedProperty.arraySize;
l.serializedProperty.arraySize++;
l.index = index;
var element = l.serializedProperty.GetArrayElementAtIndex(index);
// Hard coding a specific ability for now
var cardData = (CardData)target;
var newAbility = ScriptableObject.CreateInstance<DrawCards>();
newAbility.name = "test";
newAbility.numCards = 22;
element.objectReferenceValue = newAbility;
AssetDatabase.AddObjectToAsset(newAbility, cardData);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
serializedObject.ApplyModifiedProperties();
};
// Will use this to provide a menu of abilities to choose from.
/*
abilityList.onAddDropdownCallback = (Rect buttonRect, ReorderableList l) => {
var menu = new GenericMenu();
var guids = AssetDatabase.FindAssets("", new[]{"Assets/CardAbility"});
foreach (var guid in guids) {
var path = AssetDatabase.GUIDToAssetPath(guid);
menu.AddItem(new GUIContent("Mobs/" + Path.GetFileNameWithoutExtension(path)), false, clickHandler, new WaveCreationParams() {Type = MobWave.WaveType.Mobs, Path = path});
}
menu.ShowAsContext();
};
*/
// Will use this to render CardAbility properties
/*
abilityList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => {
};
*/
}
public override void OnInspectorGUI()
{
serializedObject.Update();
DrawDefaultInspector();
abilityList.DoLayoutList();
// XXX: Ultimately don't expect to use these, experimenting with
// other ways of adding/deleting.
if (GUILayout.Button("Add Ability")) {
var cardData = (CardData)target;
var newAbility = ScriptableObject.CreateInstance<CardAbility>();
AssetDatabase.AddObjectToAsset(newAbility, cardData);
AssetDatabase.SaveAssets();
}
if (GUILayout.Button("Clear All Abilities")) {
var path = AssetDatabase.GetAssetPath(target);
Object[] assets = AssetDatabase.LoadAllAssetRepresentationsAtPath(path);
for (int i = 0; i < assets.Length; i++) {
if (assets[i] is CardAbility) {
Object.DestroyImmediate(assets[i], true);
}
}
AssetDatabase.SaveAssets();
}
serializedObject.ApplyModifiedProperties();
}
}
不确定,因为如果您面前没有完整的项目,编辑器脚本总是很棘手。
乍一看,我会说您正在将资产添加到资产中,但您没有使用 AssetDatabase.RemoveObjectFromAsset
你可能应该做类似的事情
abilityList.onRemoveCallback = (ReorderableList l) =>
{
// Update should be redundant here since you already call it anyway on beginning of the draw loop
// Are you also sure the `l.index` is the correct value to use here?
var obj = l.serializedProperty.GetArrayElementAtIndex(l.index).objectReferenceValue;
AssetDatabase.RemoveObjectFromAsset(obj);
DestroyImmediate(obj, true);
l.serializedProperty.DeleteArrayElementAtIndex(l.index);
// If you do save assets also refresh
// Not sure if you should even do that though to be honest
AssetDatabase.SaveAssets();
// Also refresh here
AssetDatabase.Refresh();
// Also ApplyModifiedProperties should be redundant
};
也如评论所述,我认为您甚至不必使用
AssetDatabase.SaveAssets();
如果没有它,资产将被简单地标记为脏,并在下一次 CTRL+S 按下时与场景一起保存。
但是,afaik,如果你这样做,你应该始终将它与 AssetDatabase.Refresh();
结合起来,以实际看到所做的更改反映在资产视图中
好的,我终于明白了。我阅读了一百个堆栈溢出和论坛帖子试图理解这一点,所以我正在付出代价,希望这可以帮助其他人解决这个问题。这将生成如下图所示的编辑器,其中 OnPlayed 是一个多态 ScriptableObject 数组。这些 CardAbility SO 作为子资产存储在拥有的 ScriptableObject (CardData) 上。这里还有更多需要清理的地方,它可以变得更通用,但对于其他尝试这样做的人来说应该是一个好的开始。
[+] 按钮生成可添加的所有 CardAbility SO 的列表。 具体 CardAbility 的属性是动态呈现的。
关于这一切最奇怪的事情之一是你不能使用 PropertyField
呈现 objectReferenceValue
的内容,你必须首先像这样构造一个 SerializedObject
:
SerializedObject nestedObject = new SerializedObject(element.objectReferenceValue);
感谢 Unity: Inspector can't find field of ScriptableObject 的提示。
ReorderableList 的其他一些重要资源:
- https://va.lent.in/unity-make-your-lists-functional-with-reorderablelist/
- https://sites.google.com/site/tuxnots/gamming/unity3d/unitymakeyourlistsfunctionalwithreorderablelist
- https://sandordaemen.nl/blog/unity-3d-extending-the-editor-part-3/
// CardData.cs
[CreateAssetMenu(fileName = "CardData", menuName = "Card Game/CardData", order = 1)]
public class CardData : ScriptableObject
{
public enum CardType
{
Attack,
Skill
}
public CardType type;
public Sprite image;
public string description;
// XXX: Hidden in inspector because it will be drawn by custom Editor.
[HideInInspector]
public CardAbility[] onPlayed;
}
// CardAbility.cs
public abstract class CardAbility : ScriptableObject
{
public abstract void Resolve();
}
// DrawCards.cs
public class DrawCards : CardAbility
{
public int numCards = 1;
public override void Resolve()
{
Deck.instance.DrawCards(numCards);
}
}
// HealPlayer.cs
public class HealPlayer : CardAbility
{
public int healAmount = 10;
public override void Resolve()
{
Player.instance.Heal(healAmount);
}
}
// CardDataEditor.cs
[CustomEditor(typeof(CardData))]
[CanEditMultipleObjects]
public class CardDataEditor : Editor
{
private ReorderableList abilityList;
private SerializedProperty onPlayedProp;
private struct AbilityCreationParams {
public string Path;
}
public void OnEnable()
{
onPlayedProp = serializedObject.FindProperty("onPlayed");
abilityList = new ReorderableList(
serializedObject,
onPlayedProp,
draggable: true,
displayHeader: true,
displayAddButton: true,
displayRemoveButton: true);
abilityList.drawHeaderCallback = (Rect rect) => {
EditorGUI.LabelField(rect, "OnPlayed Abilities");
};
abilityList.onRemoveCallback = (ReorderableList l) => {
var element = l.serializedProperty.GetArrayElementAtIndex(l.index);
var obj = element.objectReferenceValue;
AssetDatabase.RemoveObjectFromAsset(obj);
DestroyImmediate(obj, true);
l.serializedProperty.DeleteArrayElementAtIndex(l.index);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
ReorderableList.defaultBehaviours.DoRemoveButton(l);
};
abilityList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => {
SerializedProperty element = onPlayedProp.GetArrayElementAtIndex(index);
rect.y += 2;
rect.width -= 10;
rect.height = EditorGUIUtility.singleLineHeight;
if (element.objectReferenceValue == null) {
return;
}
string label = element.objectReferenceValue.name;
EditorGUI.LabelField(rect, label, EditorStyles.boldLabel);
// Convert this element's data to a SerializedObject so we can iterate
// through each SerializedProperty and render a PropertyField.
SerializedObject nestedObject = new SerializedObject(element.objectReferenceValue);
// Loop over all properties and render them
SerializedProperty prop = nestedObject.GetIterator();
float y = rect.y;
while (prop.NextVisible(true)) {
if (prop.name == "m_Script") {
continue;
}
rect.y += EditorGUIUtility.singleLineHeight;
EditorGUI.PropertyField(rect, prop);
}
nestedObject.ApplyModifiedProperties();
// Mark edits for saving
if (GUI.changed) {
EditorUtility.SetDirty(target);
}
};
abilityList.elementHeightCallback = (int index) => {
float baseProp = EditorGUI.GetPropertyHeight(
abilityList.serializedProperty.GetArrayElementAtIndex(index), true);
float additionalProps = 0;
SerializedProperty element = onPlayedProp.GetArrayElementAtIndex(index);
if (element.objectReferenceValue != null) {
SerializedObject ability = new SerializedObject(element.objectReferenceValue);
SerializedProperty prop = ability.GetIterator();
while (prop.NextVisible(true)) {
// XXX: This logic stays in sync with loop in drawElementCallback.
if (prop.name == "m_Script") {
continue;
}
additionalProps += EditorGUIUtility.singleLineHeight;
}
}
float spacingBetweenElements = EditorGUIUtility.singleLineHeight / 2;
return baseProp + spacingBetweenElements + additionalProps;
};
abilityList.onAddDropdownCallback = (Rect buttonRect, ReorderableList l) => {
var menu = new GenericMenu();
var guids = AssetDatabase.FindAssets("", new[]{"Assets/CardAbility"});
foreach (var guid in guids) {
var path = AssetDatabase.GUIDToAssetPath(guid);
var type = AssetDatabase.LoadAssetAtPath(path, typeof(UnityEngine.Object));
if (type.name == "CardAbility") {
continue;
}
menu.AddItem(
new GUIContent(Path.GetFileNameWithoutExtension(path)),
false,
addClickHandler,
new AbilityCreationParams() {Path = path});
}
menu.ShowAsContext();
};
}
private void addClickHandler(object dataObj) {
// Make room in list
var data = (AbilityCreationParams)dataObj;
var index = abilityList.serializedProperty.arraySize;
abilityList.serializedProperty.arraySize++;
abilityList.index = index;
var element = abilityList.serializedProperty.GetArrayElementAtIndex(index);
// Create the new Ability
var type = AssetDatabase.LoadAssetAtPath(data.Path, typeof(UnityEngine.Object));
var newAbility = ScriptableObject.CreateInstance(type.name);
newAbility.name = type.name;
// Add it to CardData
var cardData = (CardData)target;
AssetDatabase.AddObjectToAsset(newAbility, cardData);
AssetDatabase.SaveAssets();
element.objectReferenceValue = newAbility;
serializedObject.ApplyModifiedProperties();
}
public override void OnInspectorGUI()
{
serializedObject.Update();
DrawDefaultInspector();
abilityList.DoLayoutList();
if (GUILayout.Button("Delete All Abilities")) {
var path = AssetDatabase.GetAssetPath(target);
Object[] assets = AssetDatabase.LoadAllAssetRepresentationsAtPath(path);
for (int i = 0; i < assets.Length; i++) {
if (assets[i] is CardAbility) {
Object.DestroyImmediate(assets[i], true);
}
}
AssetDatabase.SaveAssets();
}
serializedObject.ApplyModifiedProperties();
}
}