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

2024-04-05

我正在构建一款纸牌游戏,我希望有一个干净的纸牌能力架构。 我有一个带有卡片属性的 CardData ScriptableObject。我希望卡牌能力组合在一起来描述卡牌的作用,比如一张名为绘制和治疗卡抽 2 张牌并在玩时恢复 5 点生命值。

我立即意识到这意味着我需要为 CardAbility 的每个变体提供具体资产。因此 DrawAndHealCard 引用了两个资产:抽奖卡2 and 治愈玩家5。这太荒谬了,我希望所有数据都感觉像是在一个 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();
    }
}

好吧,我终于明白了。我阅读了一百个堆栈溢出和论坛帖子试图理解这一点,所以我将其转发,希望这可以帮助其他人解决这个问题。这会生成如下图所示的编辑器,其中 OnPlayed 是多态 ScriptableObject 的数组。这些 CardAbility SO 作为子资产存储在拥有的 ScriptableObject (CardData) 上。这里还有更多需要清理的地方,并且它可以变得更加通用,但对于其他尝试这样做的人来说应该是一个良好的开始。

[+] 按钮生成所有可添加的 CardAbility SO 的列表。 具体 CardAbility 的属性是动态呈现的。

关于这一切最奇怪的事情之一是你无法渲染objectReferenceValue using PropertyField,你必须构造一个SerializedObject首先是这样的:

SerializedObject nestedObject = new SerializedObject(element.objectReferenceValue);

谢谢Unity:检查器找不到 ScriptableObject 的字段 https://stackoverflow.com/questions/37278087/unity-inspector-cant-find-field-of-scriptableobject对于那个小费。

ReorderableList 的其他一些很棒的资源:

  • https://va.lent.in/unity-make-your-lists-function-with-reorderablelist/ https://va.lent.in/unity-make-your-lists-functional-with-reorderablelist/
  • https://sites.google.com/site/tuxnots/gamming/unity3d/unitymakeyourlistsfunctionwithreorderablelist https://sites.google.com/site/tuxnots/gamming/unity3d/unitymakeyourlistsfunctionalwithreorderablelist
  • https://sandordaemen.nl/blog/unity-3d-extending-the-editor-part-3/ 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();
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为嵌套的 ScriptableObjects 构建编辑器以在纸牌游戏中组合能力 的相关文章

  • 如何检查图像对象与资源中的图像对象是否相同?

    所以我试图创建一个简单的程序 只需在单击图片框中更改图片即可 我目前只使用两张图片 所以我的图片框单击事件函数的代码 看起来像这样 private void pictureBox1 Click object sender EventArgs
  • 获取按下的按钮的返回值

    我有一个在特定事件中弹出的表单 它从数组中提取按钮并将标签值设置为特定值 因此 如果您要按下或单击此按钮 该函数应返回标签值 我怎样才能做到这一点 我如何知道点击了哪个按钮 此时代码返回 DialogResult 但我想从函数返回 Tag
  • 从父类调用子类方法

    a doStuff 方法是否可以在不编辑 A 类的情况下打印 B did stuff 如果是这样 我该怎么做 class Program static void Main string args A a new A B b new B a
  • 如何避免情绪低落?

    我有一个实现状态模式每个状态处理从事件队列获取的事件 根据State因此类有一个纯虚方法void handleEvent const Event 事件继承基础Event类 但每个事件都包含其可以是不同类型的数据 例如 int string
  • 当 contains() 工作正常时,xpath 函数ends-with() 工作时出现问题

    我正在尝试获取具有以特定 id 结尾的属性的标签 like span 我想获取 id 以 国家 地区 结尾的跨度我尝试以下xpath span ends with id Country 但我得到以下异常 需要命名空间管理器或 XsltCon
  • C 预处理器库

    我的任务是开发源分析工具C程序 并且我需要在分析本身之前预处理代码 我想知道什么是最好的图书馆 我需要一些重量轻 便于携带的东西 与其推出自己的 为什么不使用cpp这是的一部分gcc suite http gcc gnu org onlin
  • Qt moc 在头文件中实现?

    是否可以告诉 Qt MOC 我想声明该类并在单个文件中实现它 而不是将它们拆分为 h 和 cpp 文件 如果要在 cpp 文件中声明并实现 QObject 子类 则必须手动包含 moc 文件 例如 文件main cpp struct Sub
  • 使用 System.Text.Json 即时格式化 JSON 流

    我有一个未缩进的 Json 字符串 例如 hash 123 id 456 我想缩进字符串并将其序列化为 JSON 文件 天真地 我可以使用缩进字符串Newtonsoft如下 using Newtonsoft Json Linq JToken
  • 如何返回 json 结果并将 unicode 字符转义为 \u1234

    我正在实现一个返回 json 结果的方法 例如 public JsonResult MethodName Guid key var result ApiHelper GetData key Data is stored in db as v
  • 在 ASP.NET Core 3.1 中使用包含“System.Web.HttpContext”的旧项目

    我们有一些用 Net Framework编写的遗留项目 应该由由ASP NET Core3 1编写的API项目使用 问题是这些遗留项目正在使用 System Web HttpContext 您知道它不再存在于 net core 中 现在我们
  • 如何将单个 char 转换为 int [重复]

    这个问题在这里已经有答案了 我有一串数字 例如 123456789 我需要提取它们中的每一个以在计算中使用它们 我当然可以通过索引访问每个字符 但是如何将其转换为 int 我研究过 atoi 但它需要一个字符串作为参数 因此 我必须将每个字
  • Discord.net 无法在 Linux 上运行

    我正在尝试让在 Linux VPS 上运行的 Discord net 中编码的不和谐机器人 我通过单声道运行 但我不断收到此错误 Unhandled Exception System Exception Connection lost at
  • 插入记录后如何从SQL Server获取Identity值

    我在数据库中添加一条记录identity价值 我想在插入后获取身份值 我不想通过存储过程来做到这一点 这是我的代码 SQLString INSERT INTO myTable SQLString Cal1 Cal2 Cal3 Cal4 SQ
  • 需要哪个版本的 Visual C++ 运行时库?

    microsoft 的最新 vcredist 2010 版 是否包含以前的版本 2008 SP1 和 2005 SP1 还是我需要安装全部 3 个版本 谢谢 你需要所有这些
  • WCF:将随机数添加到 UsernameToken

    我正在尝试连接到用 Java 编写的 Web 服务 但有些东西我无法弄清楚 使用 WCF 和 customBinding 几乎一切似乎都很好 除了 SOAP 消息的一部分 因为它缺少 Nonce 和 Created 部分节点 显然我错过了一
  • 32 位到 64 位内联汇编移植

    我有一段 C 代码 在 GNU Linux 环境下用 g 编译 它加载一个函数指针 它如何执行并不重要 使用一些内联汇编将一些参数推送到堆栈上 然后调用该函数 代码如下 unsigned long stack 1 23 33 43 save
  • Validation.ErrorTemplate 的 Wpf 动态资源查找

    在我的 App xaml 中 我定义了一个资源Validation ErrorTemplate 这取决于动态BorderBrush资源 我打算定义独特的BorderBrush在我拥有的每个窗口以及窗口内的不同块内
  • x86 上未对齐的指针

    有人可以提供一个示例 将指针从一种类型转换为另一种类型由于未对齐而失败吗 在评论中这个答案 https stackoverflow com questions 544928 reading integer size bytes from a
  • 防止索引超出范围错误

    我想编写对某些条件的检查 而不必使用 try catch 并且我想避免出现 Index Out of Range 错误的可能性 if array Element 0 Object Length gt 0 array Element 1 Ob
  • 使用按位运算符相乘

    我想知道如何使用按位运算符将一系列二进制位相乘 但是 我有兴趣这样做来查找二进制值的十进制小数值 这是我正在尝试做的一个例子 假设 1010010 我想使用每个单独的位 以便将其计算为 1 2 1 0 2 2 1 2 3 0 2 4 虽然我

随机推荐

  • Docker compose 指定镜像与 Dockerfile

    我是新来的docker compose阅读完文档后 我仍然有一些不清楚的事情浮现在我的脑海中 到目前为止 当我使用 docker 时 我将构建保存在以下目录树中 builds Service A Dockerfile ServiceA ja
  • 如何使用OpenGL模拟OpenCV的warpPerspective功能(透视变换)

    我在 Python 和 C 中使用 OpenCV 完成了图像变形 看到可口可乐徽标在我选择的角落变形 使用以下图像 和这个 完整专辑 包含过渡图片和说明 https i stack imgur com 40pdD jpg 我确实需要这样做
  • ASP.NET Identity EF 中的动态用户声明

    我正在开发一个使用 ASP NET Identity 和 Entity Framework 的身份验证系统 并且我希望有一些声明是计算值 而不是硬编码到声明表中 当用户登录时 如何将动态声明添加到该登录会话而不实际将其添加到声明表中 例如
  • Hadoop put 命令抛出 - 只能复制到 0 个节点,而不是 1 个

    我是 Hadoop 新手 我正在尝试在我的 ubuntu 机器上进行伪分布式模式设置 并面临以下问题hadoop put命令 我的配置详细信息可以在这篇文章中找到 gt 命令 hadoop namenode format 会做什么 http
  • 使用 Gulp Notify 和 Plumber 的全局错误消息

    是否可以创建一个全局 OnError 函数 我可以将标题和错误消息传递给该函数 我希望对与管道工一起运行的所有任务执行类似的操作 onError function error notify onError title Error subti
  • PhoneGap 应用程序被拒绝 10.6

    我们的 PhoneGap 应用程序刚刚被拒绝 二进制被拒绝 10 6 Apple 和我们的客户高度重视简单 精致 有创意 经过深思熟虑的界面 他们需要更多的工作 但 值得 苹果设定了很高的标准 如果您的用户界面很复杂或者 不太好 可能会被拒
  • 如何在 Eclipse 中为托管 ARM C 项目添加单元测试?

    我有一个托管 Eclipse 项目 它使用 GNU ARM 嵌入式工具链为 ST 微控制器进行编译 我现在想做的是 对该项目中的代码进行单元测试 我想用于单元测试的框架是Google Test 我尝试为单元测试创 建第二个 Eclipse
  • C++ for-each 语句触发“向量迭代器不兼容”断言失败:this->_Getcont() == 0

    这是使用 Visual Studio 2012 static void func const std vector
  • Oracle 数据更改通知超时和工作流程

    美好时光 我们在 Java 应用程序中配置了 Oracle DCN 功能 一切工作正常 但应用程序关闭时出现一些问题 如果应用程序意外关闭 例如 tomcat 进程通过kill 9命令 DCN 订阅者被挂在数据库中 select from
  • SQL服务器重复连接问题

    任何人都可以帮忙 我尝试加入重复的值 但它没有按我想要的方式出现 CREATE TABLE TestTable1 No varchar 50 Value1 float Desc varchar 50 insert into TestTabl
  • 使用 p/invoke 和 win-api 监控音频线路输入 (C#)

    在过去的几天里 我试图编写一个小程序来使用 win api winmm dll 来监视音频线路输入 我可以 pinvoke api 函数 例如 waveInOpen 但我不知道如何使用这些函数来实现我的目标 问题是 有人可以简单地告诉我如何
  • 动画 UITextView 时打字

    我试图拥有一个可变高度的 UITextView 它可以更改大小以适应其内容 但是当尺寸更改动画中的框架发生变化时 UITextView 中不会捕获一两次击键 动画持续时间为 0 1 秒 通常当您打字速度相当快时 它只会漏掉一个字母 然而 当
  • Python:相对导入是否意味着您无法单独执行子包?

    我最近将我的 Python 项目移植到 Python 3 1 上运行 为此 我必须在项目的子模块和子包中采用相对导入的策略 我没有这样做 现在项目本身可以工作 但我注意到我无法执行其中的任何子包或子模块 如果我尝试 我会收到 builtin
  • Highcharts 有“趋势线”功能吗?

    基本上 我试图让 Highcharts 自动绘制从第一个数据点到最后一个数据点的直线 以便我可以更轻松地看到 总体趋势 我相信这被称为 趋势线 或其他东西 但我在文档中没有找到与之相关的任何内容 不过 它很可能仍然具有此功能 可以 据我所知
  • iPhone - 保存 URL,无需 setURL:forKey: 和 NSURL

    无论如何 是否可以使用 NSUserDefaults 保存没有 setURL forKey 的 URL 这仅适用于 iOS 4 0 及更高版本 我使用 fileURLWithPath 在本地加载 HTML 文件 它从介绍页面开始 用户可以点
  • 如何从 onPress on Alert 函数调用方法 [React-Native]

    如何从 onPress on Alert 函数调用方法 React Native
  • java中特定货币的货币符号的位置

    我知道如何使用 locale 和 NumberFormat 类获取 java 中货币的货币对象和其他详细信息 但我无法在 API 中找到任何内容来了解 货币符号是在开始还是结束时显示 例如 在美国 10 表示 10 美元 其中 位于数字开头
  • 如何在 Trigger.io 上禁用“ipad 兼容性”?

    我需要我的应用程序只能在 iPhone 上运行 而不能在 iPAD 上运行 这个要怎么设置呢 我们在平台 v1 4 16 中提供了对此的支持 http docs trigger io en v1 4 release notes html v
  • 我可以检测 Android 设备上是否存在“LED 通知”吗?

    背景 我有一个带有通知的应用程序 我想支持 LED 通知 并且它运行良好 在我的偏好中 我允许用户自定义 LED 通知 问题 如果设备不支持 LED 自定义选项 我不想显示这些选项 因为这可能会让用户感到困惑 如果您拥有的只是廉价的 And
  • 为嵌套的 ScriptableObjects 构建编辑器以在纸牌游戏中组合能力

    我正在构建一款纸牌游戏 我希望有一个干净的纸牌能力架构 我有一个带有卡片属性的 CardData ScriptableObject 我希望卡牌能力组合在一起来描述卡牌的作用 比如一张名为绘制和治疗卡抽 2 张牌并在玩时恢复 5 点生命值 我