【Unity】QFramework通用背包系统优化:使用Odin优化编辑器

前言

在学习凉鞋老师的课程《QFramework系统设计:通用背包系统》第四章时,笔者使用了Odin插件,对Item和ItemDatabase的SO文件进行了一些优化,使物品页面更加紧凑、更易拓展。

核心逻辑和功能没有改动,整体代码量减少了,并且增加了一个复制ItemConfig的小功能。

需要注意:

  • 在ItemConfigGroup的列表中中删除ItemConfig时,应该点红色的X按钮,不要点最右侧的叉号,不然关联的ItemConfig SO文件不会被同时删除;
  • QFramework带有的自定义属性功能可能会和Odin冲突,建议只使用其中一种;

为了和原教程区分,下文将使用ItemConfig和ItemConfigGroup类来代替Item和ItemDatabase类。

修改前后对比:

代码

IItem接口:

csharp 复制代码
using UnityEngine;

namespace QFramework
{
    public interface IItem
    {
        string GetKey { get; }
        string GetName { get; }
        string GetDescription { get; }
        Sprite GetIcon { get; }
        bool GetStackable { get; }
        bool GetHasMaxStackableCount { get; }
        int GetMaxStackableCount { get; }
        ItemLanguagePackage.LocalItem LocalItem { get; set; }

        bool GetBoolean(string propertyName);
    }
}

ItemConfig类:

csharp 复制代码
using Sirenix.OdinInspector;
using UnityEditor;
using UnityEngine;

namespace QFramework
{
    [CreateAssetMenu(menuName = "@ItemKit/Create ItemConfig")]
    public class ItemConfig : ScriptableObject, IItem
    {
        public ItemConfigGroup ItemConfigGroup { get; set; }

        [HideLabel]
        [PreviewField(48, ObjectFieldAlignment.Left)]
        [HorizontalGroup("名称类型", 54), VerticalGroup("名称类型/left")]
        public Sprite Icon = null;

        private void OnValidate()
        {
            this.name = Key;
        }

        [VerticalGroup("名称类型/left")]
        [Button("X"), GUIColor(1, 0, 0)]
        private void RemoveThisConfig()
        {
            if (EditorUtility.DisplayDialog("删除物品", "确定要删除吗?\n(此操作不可恢复)", "删除", "取消"))
            {
                ItemConfigGroup.ItemConfigs.Remove(this);
                AssetDatabase.RemoveObjectFromAsset(this);
                AssetDatabase.SaveAssets();
                AssetDatabase.Refresh();
            }
        }

        [VerticalGroup("名称类型/left")]
        [Button("Dup"), GUIColor("yellow")]
        private void DuplicateThisConfig() // 增加复制/插入功能
        {
            if (ItemConfigGroup == null)
            {
                Debug.LogError("ItemConfigGroup is null!");
                return;
            }
            ItemConfigGroup.DuplicateItemConfig(ItemConfigGroup.ItemConfigs.IndexOf(this), this);
        }

        [VerticalGroup("名称类型/right"), LabelWidth(42)]
        [LabelText("名称")]
        public string Name = string.Empty;

        [VerticalGroup("名称类型/right"), LabelWidth(42)]
        [LabelText("描述")]
        [TextArea(minLines: 1, maxLines: 4)]
        public string Description = string.Empty;

        [VerticalGroup("名称类型/right"), LabelWidth(42)]
        [LabelText("关键字")]
        public string Key = string.Empty;

        [VerticalGroup("名称类型/right"), LabelWidth(42)]
        [LabelText("是武器")]
        public bool IsWeapon = false;

        [HorizontalGroup("属性")]
        [VerticalGroup("属性/stackable"), LabelWidth(66)]
        [LabelText("可堆叠")]
        public bool IsStackable = true;

        [ShowIf("IsStackable")]
        [VerticalGroup("属性/stackable"), LabelWidth(66)]
        [Indent]
        [LabelText("有最大值")]
        public bool HasMaxStackableCount = false;

        [ShowIf("IsStackable"), EnableIf("HasMaxStackableCount")]
        [DisplayIf(new string[] { "IsStackable", "HasMaxStackableCount" }, new[] { false, false })]
        [VerticalGroup("属性/stackable"), LabelWidth(66)]
        [Indent(2)]
        [LabelText("最大值")]
        public int MaxStackableCount = 99;

        public string GetName => ItemKit.CurrentLanguage == ItemKit.DefaultLanguage ? Name : LocalItem.Name;
        public string GetKey => Key;
        public string GetDescription => ItemKit.CurrentLanguage == ItemKit.DefaultLanguage ? Description : LocalItem.Description;
        public Sprite GetIcon => Icon;
        public bool GetStackable => IsStackable;
        public bool GetHasMaxStackableCount => HasMaxStackableCount;
        public int GetMaxStackableCount => MaxStackableCount;
        public ItemLanguagePackage.LocalItem LocalItem { get; set; }

        public bool GetBoolean(string propertyName)
        {
            if (propertyName == "IsWeapon")
            {
                return IsWeapon;
            }
            return false;
        }
    }
}

ItemConfigGroup类:

csharp 复制代码
using Sirenix.OdinInspector;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System;
using UnityEditor;

namespace QFramework
{
    [CreateAssetMenu(menuName = "@ItemKit/Create Item ConfigGroup")]
    public class ItemConfigGroup : ScriptableObject
    {
        public string NameSpace = "QFramework.Example";

        [Searchable]
        [TableList(ShowIndexLabels = true)]
        public List<ItemConfig> ItemConfigs = new List<ItemConfig>();

        [Button("添加 ItemConfig", ButtonSizes.Large), GUIColor("yellow")]
        private void AddItemConfig()
        {
            // 创建一个新的 ItemConfig 实例
            ItemConfig itemConfig = CreateInstance<ItemConfig>();
            itemConfig.ItemConfigGroup = this;
            itemConfig.name = nameof(ItemConfig);
            itemConfig.Name = "新物品";
            itemConfig.Key = "item_new";

            // 将新创建的 itemConfig 添加到 ItemConfigGroup 的资源中
            AssetDatabase.AddObjectToAsset(itemConfig, this);
            // 在 ItemConfigs 列表中添加一个新的元素
            ItemConfigs.Add(itemConfig);

            // 保存所有更改到资源
            AssetDatabase.SaveAssets();
            // 刷新资源
            AssetDatabase.Refresh();
        }

        public void DuplicateItemConfig(int index, ItemConfig itemConfig)
        {
            // 创建一个新的 ItemConfig 实例
            ItemConfig itemConfigSO = CreateInstance<ItemConfig>();
            itemConfigSO.ItemConfigGroup = this;
            itemConfigSO.name = itemConfig.Key;
            itemConfigSO.Name = string.Empty;
            itemConfigSO.Key = "item_new";
            itemConfigSO.IsWeapon = itemConfig.IsWeapon;
            itemConfigSO.IsStackable = itemConfig.IsStackable;
            itemConfigSO.HasMaxStackableCount = itemConfig.HasMaxStackableCount;
            itemConfigSO.MaxStackableCount = itemConfig.MaxStackableCount;

            // 将新创建的 itemConfig 添加到 ItemConfigGroup 的资源文件中
            AssetDatabase.AddObjectToAsset(itemConfigSO, this);
            // 在 ItemConfigs 列表中添加一个新的元素
            ItemConfigs.Insert(index + 1, itemConfigSO);

            // 保存所有更改到资源
            AssetDatabase.SaveAssets();
            // 刷新资源
            AssetDatabase.Refresh();
        }

        [Button("生成 Items 代码", ButtonSizes.Large), GUIColor("green")]
        private void GenerateCode()
        {
            var itemDatabase = this;
            // 获取当前 ItemDatabase 脚本的文件路径,并确定生成代码的保存位置
            string filePath = AssetDatabase.GetAssetPath(itemDatabase).GetFolderPath() + "/Items.cs";

            // 使用 QFramework 中的代码生成功能
            // 创建一个代码作用域树,用于生成代码结构
            ICodeScope rootCode = new RootCode()
                // 添加命名空间
                .Using("UnityEngine")
                .Using("QFramework")
                // 空一行
                .EmptyLine()
                // 定义命名空间
                .Namespace(itemDatabase.NameSpace, ns =>
                {
                    // 在命名空间中定义一个类
                    ns.Class("Items", String.Empty, false, false, c =>
                    {
                        // 为每个 itemDB.ItemConfigs 生成一个静态字符串字段
                        foreach (ItemConfig itemConfig in itemDatabase.ItemConfigs)
                        {
                            c.Custom($"public static string {itemConfig.Key} = \"{itemConfig.Key}\";");
                            Debug.Log(itemConfig.Key);
                        }
                    });
                });

            // 创建或覆盖文件,并准备写入生成的代码
            // 使用 using 语句自动管理 StreamWriter 的生命周期。
            // 当离开 using 代码块的作用域时,fileWriter 的 Dispose 方法会被自动调用,确保文件资源被正确关闭。
            using StreamWriter fileWriter = File.CreateText(filePath);
            // 创建一个代码写入器,将代码作用域树转换为字符串
            FileCodeWriter codeWriter = new FileCodeWriter(fileWriter);
            // 生成代码并写入文件
            rootCode.Gen(codeWriter);

            // 保存所有未保存的资源更改
            AssetDatabase.SaveAssets();
            // 刷新 Unity 编辑器的资源数据库
            AssetDatabase.Refresh();
        }

        private void OnValidate()
        {
            foreach (ItemConfig itemConfig in ItemConfigs)
            {
                if (itemConfig != null)
                {
                    itemConfig.name = itemConfig.Key;
                }
                else
                    ItemConfigs.Remove(itemConfig);
            }
        }
    }
}
相关推荐
向宇it6 小时前
【unity小技巧】unity 什么是反射?反射的作用?反射的使用场景?反射的缺点?常用的反射操作?反射常见示例
开发语言·游戏·unity·c#·游戏引擎
为什么每天的风都这么大8 小时前
Vscode/Code-server无网环境安装通义灵码
ide·vscode·阿里云·编辑器·ai编程·code-server
Heaphaestus,RC8 小时前
【Unity3D】获取 GameObject 的完整层级结构
unity·c#
芋芋qwq8 小时前
Unity UI射线检测 道具拖拽
ui·unity·游戏引擎
tealcwu8 小时前
【Unity服务】关于Unity LevelPlay的基本情况
unity·游戏引擎
大眼睛姑娘11 小时前
Unity3d场景童话梦幻卡通Q版城镇建筑植物山石3D模型游戏美术素材
unity·游戏美术
鹿野素材屋15 小时前
Unity Dots下的动画合批工具:GPU ECS Animation Baker
unity·游戏引擎
陌上阳光16 小时前
vscode连接远程开发机报错
ide·vscode·编辑器
羊子雄起17 小时前
CKEditor前端样式和编辑器的样式不一致的问题
前端·编辑器
界面开发小八哥20 小时前
「Java EE开发指南」如何使用Visual JSF编辑器设计JSP?(一)
java·ide·java-ee·编辑器·myeclipse