gpt4 book ai didi

c# - 为嵌套的 ScriptableObjects 构建编辑器以在纸牌游戏中组合能力

转载 作者:行者123 更新时间:2023-12-04 16:40:04 26 4
gpt4 key购买 nike

我正在构建一个纸牌游戏,我希望有一个干净的纸牌能力架构。
我有一个带有卡片属性的 CardData ScriptableObject。我希望卡片的异能组合在一起来描述卡片的作用,比如一张名为 的卡片。 DrawAndHealCard 打出 2 张牌并治疗 5 点生命值。
我马上意识到这意味着我需要为 CardAbility 的每个变体提供一个具体的 Assets 。所以 DrawAndHealCard 引用了两个 Assets :抽奖卡2 HealPlayer5 .这太荒谬了,我希望所有数据都像在一个 DrawAndHealCard 上一样。
于是我了解到AssetDatabase.AddObjectToAsset() ,这似乎是正确的想法,我可以将能力作为 CardData Assets 的子 Assets ,而不必处理所有这些单独 Assets 的组织。所以现在我正在尝试构建一个 Editor管理这个,这是痛苦的。
我已经阅读了很多关于 Unity 序列化、SO、编辑器脚本等的内容……严重地撞到了这堵墙,并且即将降级到在架构上感觉不那么优雅的东西。如果有更好的方法来做到这一点,我也愿意接受关于完全不同路线的建议。
下面的代码被精简了,但它是我试图弄清楚的要点。
我现在所在的位置是 onAddCallback似乎正确添加了子 Assets ,但 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();
}
}

最佳答案

好吧,我终于想通了。我阅读了一百篇堆栈溢出和论坛帖子试图理解这一点,所以我正在支付它,希望这可以帮助其他人解决这个问题。这会生成一个如下图所示的编辑器,其中 OnPlayed 是一个多态 ScriptableObjects 数组。这些 CardAbility SO 作为子 Assets 存储在拥有的 ScriptableObject (CardData) 上。还有更多要在这里清理,它可以变得更通用,但对于尝试这样做的其他人来说应该是一个好的开始。
inspector
[+] 按钮生成可添加的所有 CardAbility SO 的列表。
并且具体 CardAbility 的属性是动态呈现的。
关于这一切最奇怪的事情之一是您无法渲染 objectReferenceValue 的内容。使用 PropertyField ,你必须构造一个 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();
    }
    }

    关于c# - 为嵌套的 ScriptableObjects 构建编辑器以在纸牌游戏中组合能力,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62768791/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com