【Unity】背包系统 + 物品窗口管理系统(中)

发布于:2025-08-07 ⋅ 阅读:(16) ⋅ 点赞:(0)

物品窗口管理连接

简介:

在上一篇文章中我们已经创建好了一个UI界面,但是并没有和数据的链接,那么这篇文章就来连接一下我们的窗口中的各个属性,这篇文章的内容会比较简单,那么我们直接开始!

实现:

了解连接语法:

在Unity中我们连接UI和数据的属性的时候,Unity的官方文档中都有明确的解释以及使用这个时候我们就,可以了解一下我们Unity的文档,今天我将会带着大家一起写一下这个文章,那么我们直接开始,今天的内容都是一些代码内容也不会非常多那么我们先打开Editor文件夹下的cs脚本

1.连接 Item 表以此来获得Item数据

在我们第一章开始时我们创建了一个ScriptableObject,这个是用来存储我们的Item的所有信息的,根据我们的Item信息再对UI内容做相对应的修改,我们现在最主要的任务就是把数据先得到,无论是修改增加删除都需要先得到数据,大家看下面的代码,我在原来的基础上增加了加载我们ItemTable的函数,LoadDataBase(加载数据库),那么这个时候我们才可以进行下一步。大家看下面的代码:

using System.Collections.Generic;
using StockpileSystem;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;

public class ItemEditor : EditorWindow
{
    [SerializeField]
    private VisualTreeAsset m_VisualTreeAsset = default;

    private List<ItemBase> ItemList = new List<ItemBase>();
    private ItemTable DataBase;

    [MenuItem("Stockpile/ItemEditor")]
    public static void ShowExample()
    {
        ItemEditor wnd = GetWindow<ItemEditor>();
        wnd.titleContent = new GUIContent("ItemEditor");
    }

    public void CreateGUI()
    {
        // Each editor window contains a root VisualElement object
        VisualElement root = rootVisualElement;

        VisualElement labelFromUXML = m_VisualTreeAsset.Instantiate();
        root.Add(labelFromUXML);

        LoadDataBase();
    }
    /// <summary>
    /// 加载资源文件
    /// </summary> 
    private void LoadDataBase()
    {
        // 这个是Unity的一个加载资源的方法通过这个函数可以通过添加t:类型来找到这个项目里面这个类型的文件
        var DataArray = AssetDatabase.FindAssets("t:ItemTable");

        // 判断是否为空,也就是能否找到这个文件
        if (DataArray.Length > 0)
        {
            var path = AssetDatabase.GUIDToAssetPath(DataArray[0]);
            // 强制转换
            DataBase = AssetDatabase.LoadAssetAtPath(path, typeof(ItemTable)) as ItemTable;
            // 检查是否为空,并赋值
            if (DataBase != null && DataBase.ItemDetails != null)
            {
                ItemList = DataBase.ItemDetails;
                EditorUtility.SetDirty(DataBase);
            }
            else
            {
                // 报错
                Debug.LogError("Failed to load ItemDataTable or itemDetails is null");
            }
        }
        else
        {
            Debug.LogError("No ItemDataTable asset found in project");
        }
    }
}

2.增加Item部分逻辑

这个时候我们先不管如何修改Item的属性,我们先搞定一下如何把增加按钮连接一下,我们下一步就是先创建一个类似预制体的东西来在我们的ListView中添加,OK我们也不多废话,根据上一篇文章的内容相信大家也能做出一个优秀的预制体
我们在Editor文件夹下右键点击创建一个UI Document
在这里插入图片描述

此处我取名为ItemTemplate,当然了大家的取名只需要自己能够看得懂就行了不需要什么固定一模一样的内容,OK我们会发现他的图标和我们的ItemEditor的图标是相同的,我们开始吧!
双击打开并编辑如下内容:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
效果预览
在这里插入图片描述

OK、现在我们保存并退出,但别忘了我们得获得这个文件但是我们有不止一个这个文件,我们该怎么获取他呢,别急我们接下来会慢慢展示,当然了别急这个事情不会特别难。
那么我们再次回到代码部分。

效果展示
在这里插入图片描述
在这里插入图片描述

首先我们必须先理清楚逻辑,我们必须先拿到所有组件包括Icon列表等等,然后在实现相关逻辑,这部分的代码我直接给到大家,然后我会在代码中标注相关的注释中解释但是我强调一下
代码内容需要跟据自己情况修改,如果你是从头到尾跟着我走的复制走就行,如果没有那么请仔细看注释!!!
代码内容需要跟据自己情况修改,如果你是从头到尾跟着我走的复制走就行,如果没有那么请仔细看注释!!!
代码内容需要跟据自己情况修改,如果你是从头到尾跟着我走的复制走就行,如果没有那么请仔细看注释!!!
代码内容需要跟据自己情况修改,如果你是从头到尾跟着我走的复制走就行,如果没有那么请仔细看注释!!!

using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using System.Collections.Generic;
using System;
using System.Linq;
using UnityEditor.UIElements;
using StockpileSystem;

public class ItemEditor : EditorWindow
{
    [SerializeField]
    private VisualTreeAsset m_VisualTreeAsset = default;
    /// <summary>
    /// Item表数据
    /// </summary> 
    /// <typeparam name="ItemBase"></typeparam>
    /// <returns></returns>
    private List<ItemBase> ItemList = new List<ItemBase>();

    /// <summary>
    /// Item表数据文件
    /// </summary>
    private ItemTable DataBase;

    /// <summary>
    /// UI列表
    /// </summary>
    private ListView ItemListView;

    /// <summary>
    /// 物品详情
    /// </summary> 
    private VisualElement ItemDetailsSection;

    /// <summary>
    /// 物品详情中的图片信息
    /// </summary> 
    private VisualElement IconPreview;

    /// <summary>
    /// 当前选中的Item
    /// </summary>
    private ItemBase ActiveItem;

    /// <summary>
    /// 创建好的模板
    /// </summary>
    private VisualTreeAsset ItemRowTemplate;

    /// <summary>
    /// 默认图片
    /// </summary>
    private Sprite DefaultIcon;

    [MenuItem("Stockpile/ItemEditor")]
    public static void ShowExample()
    {
        ItemEditor wnd = GetWindow<ItemEditor>();
        wnd.titleContent = new GUIContent("ItemEditor");
    }

    public void CreateGUI()
    {
        // Each editor window contains a root VisualElement object
        VisualElement root = rootVisualElement;

        VisualElement labelFromUXML = m_VisualTreeAsset.Instantiate();
        root.Add(labelFromUXML);

        // 获取模板,这个模板获取的路径可以通过右键你想要获得的东西并且Copy Path就是这么简单
        /// <summary>
        /// 下面这些代码都是为了取得我们UI界面当中组件的代码,我们可以都会照着我们创建好的名字取获取组件
        /// 首先我强调一下啊就是括号后面的字符串是我们当时创建那个UI时取得名字一会儿我打开那个给你对照一下看一下我写的是不是和那个一一对应的
        /// </summary>
        ItemRowTemplate = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/ItemTemplate.uxml");
        ItemListView = root.Q<VisualElement>("ItemList").Q<ListView>("ListView");
        ItemDetailsSection = root.Q<VisualElement>("ItemDetails");
        IconPreview = ItemDetailsSection.Q<VisualElement>("Icon");
        DefaultIcon = AssetDatabase.LoadAssetAtPath<Sprite>("Assets/StockpileSystem/Image/QiututuLogo.png");

        /// <summary>
        /// 为我们的按钮添加点击时出发的事件
        /// </summary>
        root.Q<Button>("AddButton").clicked += OnAddItemClicked;
        root.Q<Button>("DeleteButton").clicked += OnDeleteClicked;

        // 加载资源函数
        LoadDataBase();
        // 渲染刷新的ListView
        GenerateListView();
    }


    /// <summary>
    /// 添加Item时触发的方法
    /// </summary>
    private void OnAddItemClicked()
    {
        // 声明一个新的Item并为其赋初始值
        ItemBase newItem = new ItemBase();
        newItem.ItemName = "New Item";
        newItem.ItemID = 1000 + ItemList.Count;
        // 在ItemList中添加
        ItemList.Add(newItem);
        // 刷新
        ItemListView.Rebuild();
    }

    /// <summary>
    /// 删除当前物品时触发的函数
    /// </summary> 
    private void OnDeleteClicked()
    {
        // 移除当前Item
        ItemList.Remove(ActiveItem);
        // 刷新
        ItemListView.Rebuild();
        // 关闭可视化
        ItemDetailsSection.visible = false;
    }

    /// <summary>
    /// 加载资源文件
    /// </summary> 
    private void LoadDataBase()
    {
        // 这个是Unity的一个加载资源的方法通过这个函数可以通过添加t:类型来找到这个项目里面这个类型的文件
        var DataArray = AssetDatabase.FindAssets("t:ItemTable");

        // 判断是否为空,也就是能否找到这个文件
        if (DataArray.Length > 0)
        {
            var path = AssetDatabase.GUIDToAssetPath(DataArray[0]);
            // 强制转换
            DataBase = AssetDatabase.LoadAssetAtPath(path, typeof(ItemTable)) as ItemTable;
            // 检查是否为空,并赋值
            if (DataBase != null && DataBase.ItemDetails != null)
            {
                ItemList = DataBase.ItemDetails;
                EditorUtility.SetDirty(DataBase);
            }
            else
            {
                // 报错
                Debug.LogError("Failed to load ItemDataTable or itemDetails is null");
            }
        }
        else
        {
            Debug.LogError("No ItemDataTable asset found in project");
        }
    }
    private void GenerateListView()
    {
        // 将我们使用的模板放在ItemList的子物体
        Func<VisualElement> MakeItem = () => ItemRowTemplate.CloneTree();
        Action<VisualElement, int> BindItem = (e, i) =>
        {
            if (i < ItemList.Count)
            {
                e.Q<Label>("ItemName").text = ItemList[i].ItemName == null ? "No Item" : ItemList[i].ItemName;
            }
        };
        // 设置子物体的属性之后再进行下一步
        ItemListView.fixedItemHeight = 30;
        ItemListView.itemsSource = ItemList;
        ItemListView.makeItem = MakeItem;
        ItemListView.bindItem = BindItem;
        ItemListView.selectionChanged += OnListSelectionChange;
        ItemDetailsSection.visible = false;
    }

    private void OnListSelectionChange(IEnumerable<object> selectedItem)
    {
        // 切换当前激活的Item
        ActiveItem = (ItemBase)selectedItem.First();
        GetItemDetails();
        ItemDetailsSection.visible = true;
    }

    private void GetItemDetails()
    {
        ItemDetailsSection.MarkDirtyRepaint();


        /// <summary>
        /// 这个时取得我们的ItemID因为我们使用的是整形输入,然后名字是ItemID那么我们就通过这样的方式来寻找组件并为其赋值
        /// 首先我们先找到该组件并为其赋值激活的Item的内容
        /// 接着我们再和Item连接使得我们的修改能够反映到文件中
        /// 那么其他都是同理的
        /// </summary>
        ItemDetailsSection.Q<IntegerField>("ItemID").value = ActiveItem.ItemID;
        ItemDetailsSection.Q<IntegerField>("ItemID").RegisterValueChangedCallback(evt =>
        {
            ActiveItem.ItemID = evt.newValue;
        });

        ItemDetailsSection.Q<TextField>("ItemName").value = ActiveItem.ItemName;
        ItemDetailsSection.Q<TextField>("ItemName").RegisterValueChangedCallback(evt =>
        {
            ActiveItem.ItemName = evt.newValue;
            ItemListView.Rebuild();
        });

        
        ItemDetailsSection.Q<ObjectField>("Icon").value = ActiveItem.ItemIcon;
        ItemDetailsSection.Q<ObjectField>("Icon").RegisterValueChangedCallback(evt =>
        {
            /// <summary>
            /// 此处是为了报错我们在没有赋值之前Icon是空的那么没办法我们得先为他设置一个默认值然后再进行下一步
            /// </summary>
            Sprite newIcon = evt.newValue as Sprite;
            ActiveItem.ItemIcon = newIcon;
            IconPreview.style.backgroundImage = newIcon == null ? DefaultIcon.texture : newIcon.texture;
            ItemListView.Rebuild();
        });

        ItemDetailsSection.Q<EnumField>("ItemType").Init(ActiveItem.Type);
        ItemDetailsSection.Q<EnumField>("ItemType").value = ActiveItem.Type;
        ItemDetailsSection.Q<EnumField>("ItemType").RegisterValueChangedCallback(evt =>
        {
            ActiveItem.Type = (ItemType)evt.newValue;
        });

        ItemDetailsSection.Q<ObjectField>("WorldSprite").value = ActiveItem.ItemOnWorld;
        ItemDetailsSection.Q<ObjectField>("WorldSprite").RegisterValueChangedCallback(evt =>
        {
            ActiveItem.ItemOnWorld = evt.newValue as Sprite;
        });
    }
}

OK 搞定了,其实今天的部分很简单但其实这个只是一部分我们需要开发更完善的内容就需要我们学以致用,然后开发出更完善的ItemEditor这个窗口部分就完成了,那么明天就会是我们的最后部分实现逻辑的关键部分

结语:

这两天打星露谷物语玩傻了,我要正式投入开发了,不能再颓废下去了,嘻嘻嘻,那么我先把bilibili给删掉吧!好了不开玩笑了,孩子们我要去考科目二了咋办我现在老担心我半坡起步熄火。哎算了明天再去练两天上刑场了。


网站公告

今日签到

点亮在社区的每一天
去签到