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

前言

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

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

需要注意:

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

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

修改前后对比:
请添加图片描述

代码

IItem接口:

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类:

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类:

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);
            }
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/377793.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AI-数学-高中-23-三角函数的平移与伸缩

原作者视频&#xff1a;三角函数】11三角函数的平移伸缩&#xff08;易&#xff09;_哔哩哔哩_bilibili 左加右减&#xff1a;针对函数中的x变化&#xff0c;上加下减&#xff1a;针对函数f(x)变化。 示例1&#xff1a; 示例2&#xff1a; 示例3

实现远程开机(电脑)的各种方法总结

一.为什么要远程开机 因为工作需要&#xff0c;总是需要打开某台不在身边的电脑&#xff0c;相信很多值友也遇到过相同的问题&#xff0c;出门在外&#xff0c;或者在公司&#xff0c;突然需要的一个文件存在家里的电脑上&#xff0c;如果家里有人可以打个电话回家&#xff0c…

响应式设计的基本原理和实现方法(超级详细)

目录 一、是什么二、实现方式媒体查询百分比vw/vhrem小结 三、总结参考文献 一、是什么 响应式网站设计&#xff08;Responsive Web design&#xff09;是一种网络页面设计布局&#xff0c;页面的设计与开发应当根据用户行为以及设备环境(系统平台、屏幕尺寸、屏幕定向等)进行…

【状态管理一】概览:状态使用、状态分类、状态具体使用

文章目录 一. 状态使用概览二. 状态的数据类型1. 算子层面2. 接口层面2.1. UML与所有状态类型介绍2.2. 内部状态&#xff1a;InternalKvState 将知识与实际的应用场景、设计背景关联起来&#xff0c;这是学以致用、刨根问底知识的一种直接方式。 本文介绍 状态数据管理&#x…

【Linux系统学习】3.Linux用户和权限

Linux用户和权限 1.认知root用户 1.1 root用户&#xff08;超级管理员&#xff09; 无论是Windows、MacOS、Linux均采用多用户的管理模式进行权限管理。 在Linux系统中&#xff0c;拥有最大权限的账户名为&#xff1a;root&#xff08;超级管理员&#xff09; 而在前期&#…

海外云手机的核心优势

随着5G时代的到来&#xff0c;云计算产业正处于高速发展的时期&#xff0c;为海外云手机的问世创造了一个可信任的背景。在资源有限且需求不断增加的时代&#xff0c;将硬件设备集中在云端&#xff0c;降低个人用户的硬件消耗&#xff0c;同时提升性能&#xff0c;这一点单单就…

Vue3中路由配置Catch all routes (“*“) must .....问题

Vue3中路由配置Catch all routes (“*”) must …问题 文章目录 Vue3中路由配置Catch all routes ("*") must .....问题1. 业务场景描述1. 加载并添加异步路由场景2. vue2中加载并添加异步路由(OK)3. 转vue3后不好使(Error)1. 代码2. 错误 2. 处理方式1. 修改前2. 修…

分布式存储中常见的容错机制:多副本、纠删码(RS、LRC、SHEC)

文章目录 分布式存储中常见的容错机制浴缸原理多副本纠删码RSLRCSHEC 总结 分布式存储中常见的容错机制 浴缸原理 在存储领域中&#xff0c;通常我们会使用浴缸曲线来描述硬盘的故障率&#xff0c;如下图。 浴缸曲线 故障率随着时间变化&#xff0c;主要分为三个阶段&#x…

设计模式2-对象池模式

对象池模式&#xff0c;Object Pool Pattern&#xff0c;当你的应用程序需要频繁创建和销毁某种资源&#xff08;比如数据库连接、线程、socket连接等&#xff09;时&#xff0c;Object Pool 设计模式就变得很有用。它通过预先创建一组对象并将它们保存在池中&#xff0c;以便在…

数据结构——单链表详解

目录 前言 一.什么是链表 1.概念 ​编辑 2.分类 二.单链表的实现(不带头单向不循环链表) 2.1初始化 2.2打印 2.3创建新节点 2.4头插、尾插 2.5头删、尾删 2.6查找 2.7在指定位置之前插入 2.8在指定位置之后插入 2.9删除pos位置 2.10删除pos之后的 2.11销毁链表…

相机图像质量研究(8)常见问题总结:光学结构对成像的影响--工厂调焦

系列文章目录 相机图像质量研究(1)Camera成像流程介绍 相机图像质量研究(2)ISP专用平台调优介绍 相机图像质量研究(3)图像质量测试介绍 相机图像质量研究(4)常见问题总结&#xff1a;光学结构对成像的影响--焦距 相机图像质量研究(5)常见问题总结&#xff1a;光学结构对成…

js中事件代理的解析和应用场景

文章目录 一、是什么二、应用场景三、总结 一、是什么 事件代理&#xff0c;俗地来讲&#xff0c;就是把一个元素响应事件&#xff08;click、keydown…&#xff09;的函数委托到另一个元素 前面讲到&#xff0c;事件流的都会经过三个阶段&#xff1a; 捕获阶段 -> 目标阶…

canvas绘制横竖坐标轴(带有箭头和刻度)

查看专栏目录 canvas实例应用100专栏&#xff0c;提供canvas的基础知识&#xff0c;高级动画&#xff0c;相关应用扩展等信息。canvas作为html的一部分&#xff0c;是图像图标地图可视化的一个重要的基础&#xff0c;学好了canvas&#xff0c;在其他的一些应用上将会起到非常重…

NLP_“预训练+微调大模型”模式和Prompt/Instruct模式的异同

文章目录 “预训练微调大模型”的模式以提示/指令模式直接使用大模型“预训练微调大模型”模式和Prompt/Instruct模式的异同小结 “预训练微调大模型”的模式 经过预训练的大模型所习得的语义信息和所蕴含的语言知识&#xff0c;很容易向下游任务迁移。NLP应用人员可以根据自己…

04-Java建造者模式 ( Builder Pattern )

建造者模式 摘要实现范例 建造者模式&#xff08;Builder Pattern&#xff09;使用多个简单的对象一步一步构建成一个复杂的对象 一个Builder 类会一步一步构造最终的对象&#xff0c;该 Builder 类是独立于其他对象的 建造者模式属于创建型模式&#xff0c;它提供了一种创建对…

如何从 iPhone 上恢复永久删除的照片

您的 iPhone 上缺少照片吗&#xff1f;讽刺的是&#xff0c;iPhone 的许多高级功能可能正是这个问题如此普遍的原因。幸运的是&#xff0c;还有很多方法可以从 iPhone 恢复已删除的照片&#xff0c;具体取决于您设备的设置方式。 本文涵盖了所有这些内容。该过程根据您的具体情…

MongoDB从入门到实战之MongoDB工作常用操作命令

前言&#xff1a; 上一章节我们快速的在Docker容器中安装了MongoDB&#xff0c;并且通过Navicat MongoDB可视化管理工具快速的连接、创建数据库、集合以及添加了文档数据源。这一章节我们主要是了解一下在日常工作中MongoDB一些常用的操作命令。 MongoDB从入门到实战的相关教程…

并发编程 java锁机制

1、什么是锁&#xff0c;为什么需要锁&#xff1f; 并发环境下&#xff0c;会存在多个线程对同一个资源进行争抢的情况&#xff0c;假设线程A对资源正在进行修改&#xff0c;此时线程B又对同一资源进行了修改&#xff0c;就会导致数据不一致的问题。为了解决这个问题&#xff…

通过nginx学习linux进程名的修改

目录 1. 缘起2. 背景知识3. 源码分析3.1 准备工作3.2 设置进程名字 1. 缘起 在运行nginx的时候&#xff0c;用ps查看nginx的进程信息&#xff0c;可能的输出如下&#xff1a; root 42169 3105 0 16:51 ? 00:00:00 nginx: master process ./objs/nginx root …

计算两个数相除后的余数返回值为浮点型math.fmod(x, y)

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 计算两个数相除后的余数 返回值为浮点型 math.fmod(x, y) [太阳]选择题 请问以下代码执行math.fmod()后输出的结果是&#xff1f; import math print("【执行】math.fmod(10, 4)"…
最新文章