在学习凉鞋老师的课程《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);
}
}
}
}