【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);
            }
        }
    }
}
相关推荐
VidDown14 天前
VidDown 工具站:免费、本地优先的开发者工具箱
javascript·编辑器·音视频·视频编解码·视频
VidDown14 天前
显卡处理视频技术详解:从硬解码到 NVENC,GPU 如何让视频处理起飞?
javascript·编辑器·音视频·视频编解码·视频
夜猫逐梦14 天前
【UE基础】03.蓝图与编辑器工作流
编辑器·ue·蓝图·ue编辑器
VidDown14 天前
视频帧率技术详解:从 24fps 到 120fps,帧率如何影响你的观看体验?
网络·网络协议·编辑器·音视频·视频编解码·视频
叶帆14 天前
【YFIOs】用C#开发硬件之设备上云
开发语言·unity·c#
爱就是恒久忍耐14 天前
VSCode里如何比较2个branch
ide·vscode·编辑器
久数君14 天前
AI三维建模工具“造形家”:地理场景三维化的高效解决方案
unity·glb·ai算法·ai三维建模工具·地图框选·造形家·城市建筑模型
bloglin9999914 天前
vscode中可视化的合并分支,在“合并编辑器中解析”中“与基线进行比较”是什么意思
ide·vscode·编辑器
会思考的猴子15 天前
Unity VFX 属性 Postion 和 TargetPostion
unity
zyplayer-doc15 天前
企业知识库安全与权限管理完全指南:从加密到审计的六层防护
人工智能·安全·pdf·编辑器·创业创新