我正在构建一个纸牌游戏,我希望有一个干净的纸牌异能架构。我有一个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();
}
}
3条答案
按热度按时间bqf10yzr1#
好吧,我终于明白了。我读了上百个堆栈溢出和论坛帖子试图理解这一点,所以我把它转发,希望这能帮助其他人导航。这产生了一个编辑器,如下图所示,其中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的其他一些重要资源:
0md85ypi2#
不确定,因为编辑器脚本总是相当棘手,如果你没有一个完整的项目在你面前。
乍一看,我会认为您正在将资产添加到资产中,但没有使用
AssetDatabase.RemoveObjectFromAsset
删除它们你也许应该做些
也正如评论所说,我想你甚至不需要使用
如果没有它,资源将简单地标记为脏,并在下一次按CTRL+S时与场景沿着保存。
但是,如果您这样做,您应该始终将其与
AssetDatabase.Refresh();
结合使用,以实际查看在assets视图中反映的更改disbfnqx3#
确认已解决问题。
我没有足够的声望来评论,但是我想帮助你修复删除所有能力按钮中的一个错误
您需要添加**stateItemList.序列化属性.ClearArray();**见下文。