当前位置: 首页 > news >正文

【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;}elseItemConfigs.Remove(itemConfig);}}}
}

相关文章:

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

前言 在学习凉鞋老师的课程《QFramework系统设计&#xff1a;通用背包系统》第四章时&#xff0c;笔者使用了Odin插件&#xff0c;对Item和ItemDatabase的SO文件进行了一些优化&#xff0c;使物品页面更加紧凑、更易拓展。 核心逻辑和功能没有改动&#xff0c;整体代码量减少…...

基本算法--贪心

1.简述 贪心法的效率非常高&#xff0c;复杂度常常为O&#xff08;1&#xff09;&#xff0c;是一种局部最优的解题方法&#xff0c;而很多问题都需要求全局最优&#xff0c;&#xff0c;所以在使用贪心法之前需要评估是否能从局部最优推广到全局最优。 2.思路 作为算法的贪…...

13. 串口接收模块的项目应用案例

1. 使用串口来控制LED灯工作状态 使用串口发送指令到FPGA开发板&#xff0c;来控制第7课中第4个实验的开发板上的LED灯的工作状态。 LED灯的工作状态&#xff1a;让LED灯按指定的亮灭模式亮灭&#xff0c;亮灭模式未知&#xff0c;由用户指定&#xff0c;8个变化状态为一个循…...

Python re找到特定pattern并将此pattern重复n次

要找到字符串s中的数字&#xff0c;并将这些数字重复3次&#xff1a; import re s "abc123def456ghi789" # 找到所有的数字 numbers re.findall(r\d, s) # 重复每个数字3次 repeated_numbers [num * 3 for num in numbers] # 将重复的数字放回原位置 #…...

ChatGpt报错:We ran into an issue while authenticating you解决办法

在登录ChatGpt时报错&#xff1a;Oops&#xff01;,We ran into an issue while authenticating you.(我们在验证您时遇到问题)&#xff0c;记录一下解决过程。 完整报错&#xff1a; We ran into an issue while authenticating you. If this issue persists, please contact…...

如何从 iPhone 恢复已删除的视频:简单有效方法

无论您是在尝试释放空间时不小心删除了 iPhone 上的视频&#xff0c;还是在出厂时清空了手机&#xff0c;现在所有数据都消失了&#xff0c;都不要放弃。有一些方法可以恢复这些视频。 在本文中&#xff0c;我们将向您展示六种最有效的数据恢复方法&#xff0c;可以帮助您从 i…...

【python量化交易】qteasy使用教程02 - 获取和管理金融数据

qteasy教程2 - 获取并管理金融数据 qteasy教程2 - 获取并管理金融数据开始前的准备工作获取基础数据以及价格数据下载交易日历和基础数据查看股票和指数的基础数据下载沪市股票数据从本地获取股价数据生成K线图 数据类型的查找定期下载数据到本地回顾总结 qteasy教程2 - 获取并…...

数据库学习案例20240206-ORACLE NEW RAC agent and resource关系汇总。

1 集群架构图 整体集群架构图如下&#xff1a; 1 数据库启动顺序OHASD层面 操作系统进程init.ohasd run启动ohasd.bin init.ohasd run 集群自动启动是否被禁用 crsctl enable has/crsGIHOME所在文件系统是否被正常挂载。管道文件npohasd是否能够被访问&#xff0c; cd /var/t…...

TypeScript 入门

课程地址 ts 开发环境搭建 npm i -g typescript查看安装位置&#xff1a; $ npm root -g C:\Users\Daniel\AppData\Roaming\npm\node_modules创建 hello.ts&#xff1a; console.log("hello, ts");编译 ts 文件&#xff0c;得到 js 文件&#xff1a; $ tsc foo.…...

linux 磁盘相关操作

1.U盘接入虚拟机 &#xff08;1&#xff09;在插入u盘时&#xff0c;虚拟机会检测usb设备&#xff0c;在弹出窗口选择连接到虚拟机即可。 &#xff08;2&#xff09;或 直接在虚拟机--->可移动设备--->找到U盘---->连接 2.检测U盘是否被虚拟机识别 ls /dev/sd* 查…...

PyTorch: torch.max()函数详解

torch.max函数详解&#xff1a;基于PyTorch的深入探索 &#x1f335;文章目录&#x1f335; &#x1f333;引言&#x1f333;&#x1f333;torch.max()函数简介&#x1f333;&#x1f333;torch.max()的返回值&#x1f333;&#x1f333;torch.max()的应用示例&#x1f333;&am…...

Rust基础拾遗--核心功能

Rust基础拾遗 前言1.所有权与移动1.1 所有权 2.引用3.特型与泛型简介3.1 使用特型3.2 特型对象3.3 泛型函数与类型参数 4.实用工具特型5.闭包 前言 通过Rust程序设计-第二版笔记的形式对Rust相关重点知识进行汇总&#xff0c;读者通读此系列文章就可以轻松的把该语言基础捡起来…...

MySQL:常用指令

MySQL官网 一、在Windows 系统 cmd窗口里执行的命令 启动:net start MySQL停止:net stop MySQL卸载:sc delete MySQL 二、在macOS系统终端里执行的命令 启动&#xff1a;mysql.server start停止&#xff1a;mysql.server stop重启&#xff1a;mysql.server restart 三、执行帮…...

Scrapy:Python中强大的网络爬虫框架

Scrapy&#xff1a;Python中强大的网络爬虫框架 在当今信息爆炸的时代&#xff0c;从互联网上获取数据已经成为许多应用程序的核心需求。Scrapy是一款基于Python的强大网络爬虫框架&#xff0c;它提供了一种灵活且高效的方式来提取、处理和存储互联网上的数据。本文将介绍Scrap…...

linux系统非关系型数据库redis的配置文件

redis配置文件 Redis的配置文件位于Redis安装目录下&#xff0c;文件名为redis.conf&#xff0c;配置项说明如下 Redis默认不是以守护进程的方式运行&#xff0c;可以通过该配置项修改&#xff0c;使用yes启用守护进程 daemonize no当Redis以守护进程方式运行时&#xff0c;Red…...

电力负荷预测 | 基于LSTM、TCN的电力负荷预测(Python)

文章目录 效果一览文章概述源码设计参考资料效果一览 文章概述 电力负荷预测 | 基于LSTM、TCN的电力负荷预测(Python) 源码设计 #------------------...

Java+SpringBoot实习管理系统探秘

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…...

c入门第十六篇——学生成绩管理系统

师弟&#xff1a;“师兄&#xff0c;我最近构建了一个学生成绩管理系统&#xff0c;有空试用一下么&#xff1f;” 我&#xff1a;“好啊&#xff01;” 一个简单的学生成绩管理系统&#xff0c;基本功能包括&#xff1a;添加学生信息、显示所有学生信息、按学号查找学生信息、…...

大文件上传如何做断点续传?

文章目录 一、是什么分片上传断点续传 二、实现思路三、使用场景小结 参考文献 一、是什么 不管怎样简单的需求&#xff0c;在量级达到一定层次时&#xff0c;都会变得异常复杂 文件上传简单&#xff0c;文件变大就复杂 上传大文件时&#xff0c;以下几个变量会影响我们的用…...

SpringCloud-Eureka原理分析

Eureka是Netflix开源的一款用于实现服务注册与发现的工具。在微服务架构中&#xff0c;服务的动态注册和发现是必不可少的组成部分&#xff0c;而Eureka正是为了解决这一问题而诞生的。 一、为何需要Eureka 在微服务架构中&#xff0c;服务之间的协同合作和高效通信是至关重要…...

使用VSCode开发Django指南

使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架&#xff0c;专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用&#xff0c;其中包含三个使用通用基本模板的页面。在此…...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题&#xff1a; 下面创建一个简单的Flask RESTful API示例。首先&#xff0c;我们需要创建环境&#xff0c;安装必要的依赖&#xff0c;然后…...

23-Oracle 23 ai 区块链表(Blockchain Table)

小伙伴有没有在金融强合规的领域中遇见&#xff0c;必须要保持数据不可变&#xff0c;管理员都无法修改和留痕的要求。比如医疗的电子病历中&#xff0c;影像检查检验结果不可篡改行的&#xff0c;药品追溯过程中数据只可插入无法删除的特性需求&#xff1b;登录日志、修改日志…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路

进入2025年以来&#xff0c;尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断&#xff0c;但全球市场热度依然高涨&#xff0c;入局者持续增加。 以国内市场为例&#xff0c;天眼查专业版数据显示&#xff0c;截至5月底&#xff0c;我国现存在业、存续状态的机器人相关企…...

全球首个30米分辨率湿地数据集(2000—2022)

数据简介 今天我们分享的数据是全球30米分辨率湿地数据集&#xff0c;包含8种湿地亚类&#xff0c;该数据以0.5X0.5的瓦片存储&#xff0c;我们整理了所有属于中国的瓦片名称与其对应省份&#xff0c;方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

微服务商城-商品微服务

数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...

uniapp微信小程序视频实时流+pc端预览方案

方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度​WebSocket图片帧​定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐​RTMP推流​TRTC/即构SDK推流❌ 付费方案 &#xff08;部分有免费额度&#x…...

JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案

JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停​​ 1. ​​安全点(Safepoint)阻塞​​ ​​现象​​:JVM暂停但无GC日志,日志显示No GCs detected。​​原因​​:JVM等待所有线程进入安全点(如…...

代码规范和架构【立芯理论一】(2025.06.08)

1、代码规范的目标 代码简洁精炼、美观&#xff0c;可持续性好高效率高复用&#xff0c;可移植性好高内聚&#xff0c;低耦合没有冗余规范性&#xff0c;代码有规可循&#xff0c;可以看出自己当时的思考过程特殊排版&#xff0c;特殊语法&#xff0c;特殊指令&#xff0c;必须…...

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...