RGP2D小游戏关联数据中心数据(第三天)

143 阅读7分钟

一、关联UI面板信息

由上篇文章我们已经搭建好UI界面了,这时候我们需要把最终的数据关联到我们的面板中,也就是说将ItemDataList_SO里面的数据转化在面板中。主要在demo脚本中实现

1.显示数据

demo.cs

using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
using System.Collections.Generic;
using System;
using System.Linq;
using Unity.VisualScripting;

public class demo : EditorWindow
{
    //先拿到数据
    ItemDataList_SO database;
    //最终要拿到我们的ItemDataList,存放我们所有数据的列表
    List<ItemDetails> itemDataList = new List<ItemDetails>();
    //定义一个左侧模板
    VisualTreeAsset leftListTemplate;
    //定义一个ListView
    ListView listView;
    //拿到右边的面板
    ScrollView itemDetailSelection;
    //获取点击的那一个item的数据
    ItemDetails activeItem;
    //设置一个默认的图片
    Sprite defaultIcon;

    [MenuItem("UI Toolkit/demo")] //和编辑器交互的,表示打开窗口的位置--这个路径跟菜单栏打开的路径一致
    public static void ShowExample()
    {
        demo wnd = GetWindow<demo>();
        wnd.titleContent = new GUIContent("demo");
    }

    public void CreateGUI()
    {
        VisualElement root = rootVisualElement;
        var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/UI Builder/ItemEditor.uxml");//
        VisualElement labelFromUXML = visualTree.Instantiate();//visualTree克隆出来的VisualElement节点
        root.Add(labelFromUXML);//在跟接待您的位置添加一个VisualElement节点
        leftListTemplate = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/UI Builder/ItemRowTemplate.uxml");
        //拿到左侧节点listView节点
        listView = root.Q<VisualElement>("ItemList").Q<ListView>(); //拿到ItemList下面的ItemList
        loadData();//加载数据
        generaLeftTemplate();
        //添加两个按钮的点击事件

        //添加按钮
        root.Q<Button>("AddButton").clicked += addItem;
        //删除按钮
        root.Q<Button>("DeleteButton").clicked += removeItem;
        //获取右侧面板
        itemDetailSelection = root.Q<ScrollView>("ItemDetails"); 
        //将右侧面板设置为隐藏
        itemDetailSelection.visible = false;
    }

    //删除方法
    private void removeItem()
    {
        itemDetailSelection.visible &= false;
        itemDataList.Remove(activeItem);
        listView.Rebuild();
    }

    //添加的方法
    private void addItem()
    {
        //加载默认图片
        defaultIcon = Resources.Load<Sprite>("/Icons/icon_M");//注意每次取消后面的后缀名
        //第二种方式获取默认图片
        //defaultIcon = AssetDatabase.LoadAssetAtPath<Sprite>("Assets/M Studio/Art/Items/Icons/icon_M.png");
        //添加要生成一个新的itemDetail,然后给他们附初始值--添加到itemList中
        ItemDetails newItem = new ItemDetails();
        newItem.ItemTcon = defaultIcon;
        newItem.ItemId = 1000+itemDataList.Capacity+1;
        
        //添加到列表里面去
        itemDataList.Add(newItem);
        activeItem = newItem;
        listView.Rebuild();//更新数据
    }

    private void generaLeftTemplate()
    {
        //bindItem绑定数据,makeItem显示数据来源。
        Func<VisualElement> makeItem = () => leftListTemplate.CloneTree(); //关联我们的动态模板
        Action<VisualElement, int> bindItem = (e, i) => //e表示我们显示数据的模板,i表示模板上列表的长度
        {
            if (i < itemDataList.Capacity)
            {
                //i要小于实际数据列表的长度,要不然直接获取可能会超出列表的长度
                if (itemDataList[i].ItemTcon != null)
                {
                    //到这里我们就可以给动态模板中的节点赋值了
                    e.Q<VisualElement>("Row").Q<VisualElement>("Icon").style.backgroundImage = itemDataList[i].ItemTcon.texture;
                    e.Q<VisualElement>("Row").Q<Label>("Name").text = (itemDataList[i].ItemName) == 
                    null?"NO ItemName": itemDataList[i].ItemName;//如果为空显示NO ItemName,不为空正常显示

                }
            }
            
        };
        //用代码控制列表中的每一项的高度统一,也可以手动在面板中控制Fixed Item Height
        //listView.fixedItemHeight = 50;
        listView.makeItem = makeItem;
        listView.bindItem= bindItem;
        listView.itemsSource = itemDataList;

        //左边选中中的监听,改变的时候会进行监听
        listView.onSelectionChange += selectionItemClick;
    }

    private void selectionItemClick(IEnumerable<object> selectedItem) //强转为iitemDetails类型
    {
        //获取选中的哪一项给activeItem赋值
        activeItem = (ItemDetails)selectedItem.First();
        //Debug.Log(activeItem.ItemId);
        //点击的时候显示右侧的面板
        itemDetailSelection.visible=true; //点击左侧面板的时候设置为显示
        generalItemDetail(); //更新右侧面板的数据
    }
    //生成右侧的数据的方法
    private void generalItemDetail()
    {
        //MarkDirtyRepaint作用:当你进行删除和修改右侧数据的时候,会更新ItemDataList_SO的数据
        itemDetailSelection.MarkDirtyRepaint();
        //显示数据

        //拿到itemID
        itemDetailSelection.Q<IntegerField>("ItemID").value = activeItem.ItemId;
        //如果我改了数据要更新数据,更改当前选中的哪一项数据
        itemDetailSelection.Q<IntegerField>("ItemID").RegisterValueChangedCallback<int>(evt =>
        {
            activeItem.ItemId = evt.newValue;//将当前窗口输入新的值赋值给当前选中的那一项
        });

        //拿到itemName名字
        itemDetailSelection.Q<TextField>("ItemName").value = activeItem.ItemName;
        //如果我改了数据要更新数据,更改当前选中的哪一项数据
        itemDetailSelection.Q<TextField>("ItemName").RegisterValueChangedCallback<string>(evt =>
        {
            activeItem.ItemName = evt.newValue;//将当前窗口输入新的值赋值给当前选中的那一项
            //SO的数据更新了,但是左侧的列表没有更新
            listView.Rebuild();//更新左侧的名称
        });

        //拿到图片
        itemDetailSelection.Q<VisualElement>("Icon").style.backgroundImage = activeItem.ItemTcon.texture;
        itemDetailSelection.Q<ObjectField>("ItemIcon").value = activeItem.ItemTcon;//数据回显
        //如果我改了数据要更新数据,更改当前选中的哪一项数据
        itemDetailSelection.Q<ObjectField>("ItemIcon").RegisterValueChangedCallback(evt => //不知道什么类型的时候就不写
        {
            Sprite image = evt.newValue as Sprite;
            //更新右侧background的值
            itemDetailSelection.Q<VisualElement>("Icon").style.backgroundImage = image.texture;
            activeItem.ItemTcon = image; //转成Sprite类型
            //SO的数据更新了,但是左侧的列表没有更新
            listView.Rebuild();//更新左侧的名称
        });

        //Type枚举
        itemDetailSelection.Q<EnumField>("ItemType").Init(activeItem.Type);
        itemDetailSelection.Q<EnumField>("ItemType").value=activeItem.Type;
        itemDetailSelection.Q<EnumField>("ItemType").RegisterValueChangedCallback(evt => //不知道什么类型的时候就不写
        {
            activeItem.Type = (ItemType)evt.newValue;
        });

        //世界图片
        if (activeItem.ItemOnWorldIcon!=null)
        {
            itemDetailSelection.Q<ObjectField>("ItemSprite").value = activeItem.ItemOnWorldIcon;//数据回显
        }
        //如果我改了数据要更新数据,更改当前选中的哪一项数据
        itemDetailSelection.Q<ObjectField>("ItemSprite").RegisterValueChangedCallback(evt => //不知道什么类型的时候就不写
        {
            Sprite image =(Sprite) (evt.newValue);
            activeItem.ItemOnWorldIcon = image; //转成Sprite类型
        });

        //描述
        itemDetailSelection.Q<TextField>("Description").value = activeItem.Description;
        itemDetailSelection.Q<TextField>("ItemType").RegisterValueChangedCallback<string>(evt => //不知道什么类型的时候就不写
        {
            activeItem.Description = evt.newValue;
        });

        //use radius
        itemDetailSelection.Q<IntegerField>("ItemUseRadius").value = activeItem.ItemUseRadius;
        itemDetailSelection.Q<IntegerField>("ItemUseRadius").RegisterValueChangedCallback<int>(evt => //不知道什么类型的时候就不写
        {
            activeItem.ItemUseRadius = evt.newValue;
        });

        // 三个bool值
        itemDetailSelection.Q<Toggle>("CanPickedup").value = activeItem.CanPickUp;
        //如果我改了数据要更新数据,更改当前选中的哪一项数据
        itemDetailSelection.Q<Toggle>("CanPickedup").RegisterValueChangedCallback<bool>(evt =>
        {
            activeItem.CanPickUp = evt.newValue;//将当前窗口输入新的值赋值给当前选中的那一项
        });

        itemDetailSelection.Q<Toggle>("CanDropped").value = activeItem.CanDropDown;
        //如果我改了数据要更新数据,更改当前选中的哪一项数据
        itemDetailSelection.Q<Toggle>("CanDropped").RegisterValueChangedCallback<bool>(evt =>
        {
            activeItem.CanDropDown = evt.newValue;//将当前窗口输入新的值赋值给当前选中的那一项
        });

        itemDetailSelection.Q<Toggle>("CanCarried").value = activeItem.CanCarried;
        //如果我改了数据要更新数据,更改当前选中的哪一项数据
        itemDetailSelection.Q<Toggle>("CanCarried").RegisterValueChangedCallback<bool>(evt =>
        {
            activeItem.CanCarried = evt.newValue;//将当前窗口输入新的值赋值给当前选中的那一项
        });

        //价格
        itemDetailSelection.Q<IntegerField>("Price").value = activeItem.Ttemprice;
        //如果我改了数据要更新数据,更改当前选中的哪一项数据
        itemDetailSelection.Q< IntegerField>("Price").RegisterValueChangedCallback<int>(evt =>
        {
            activeItem.Ttemprice = evt.newValue;//将当前窗口输入新的值赋值给当前选中的那一项
        });

        //折扣
        itemDetailSelection.Q<Slider>("SellPercentage").value = activeItem.PricePercentage;
        //如果我改了数据要更新数据,更改当前选中的哪一项数据
        itemDetailSelection.Q<Slider>("SellPercentage").RegisterValueChangedCallback<float>(evt =>
        {
            activeItem.PricePercentage = evt.newValue;//将当前窗口输入新的值赋值给当前选中的那一项
        });
    }


    private void loadData()
    {
        //本身数据文件存放在Assets文件夹里面,在该文件夹里面找到ItemDataListSO类文件
        var GuidArray = AssetDatabase.FindAssets("ItemDataList_SO");
        if(GuidArray.Length>0) { 
            //获取database之前先拿到路径
            var path = AssetDatabase.GUIDToAssetPath(GuidArray[0]);//找到ItemDataListSO可以返回ItemDataListSO的本身路径
            database = AssetDatabase.LoadAssetAtPath(path,typeof(ItemDataList_SO)) as ItemDataList_SO;
            itemDataList = database.ItemDataList;
            //现在阶段加载到数据了,现在要保存数据。
            EditorUtility.SetDirty(database);
        }
    }
}

2.2D场景01Field

将01Field场景存放在Scene文件夹中,并且跟当前的场景一起使用在同一个窗口中。

image.png

3.在地图中显示图片

新建ItemBase空对象,里面包含Sprite空对象

给ItemBase挂载2D盒状碰撞器,该功能主要是实现人物和物品进行的碰撞检测必须要的。

image.png 挂载Item脚本文件,主要实现对图片的显示和初始化

勾选是否为触发器选项

image.png

Item.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace MFarm.Inventory
{
    public class Item : MonoBehaviour
    {
        //关联ID
        public int ItemID;
        //SpriteRenderer组件
        private SpriteRenderer spriteRender;
        //碰撞器
        private BoxCollider2D coll;
        //当前物品的详情
        public ItemDetails itemDetails;
        void Awake()
        {
            spriteRender = GetComponentInChildren<SpriteRenderer>();//找的应该是孩子里面的SpriteRenderer组件
            coll = GetComponent<BoxCollider2D>();
        }

        private void Start()
        {
            if (ItemID != 0)
            {
                Init();
            }
        }

        //初始化图片的方法
        private void Init()
        {
            itemDetails = Inventory.inventoryManager.Instance.getItemDetails(ItemID);
            //更改精灵图
            spriteRender.sprite = itemDetails.ItemOnWorldIcon == null ? itemDetails.ItemTcon : itemDetails.ItemOnWorldIcon;
            //设置碰撞体的尺寸 --图片大小不一样,图片的中心点有可能不一样
            Vector2 newSize = new Vector2(spriteRender.sprite.bounds.size.x, spriteRender.sprite.bounds.size.y);
            coll.size = newSize; //将图片的大小设置到和碰撞器的大小一致
                                 //首先我们不知道是否需要往上移动,所以为了确保精灵图能够正常的显示出来,这里要进行判断处理。
                                 //将不是中心点的图片进行合适的位置显示
                                 //如果是中心点的话,spriteRender.sprite.bounds.size.y为0,如果是底部的话,spriteRender.sprite.bounds.size.y为0.5
            coll.offset = new Vector2(0, spriteRender.sprite.bounds.size.y);


        }
    }
}


Sprite空对象挂载Sprite Renderer组件,该组件主要是实现对精灵图的展示

image.png

4.拾取物品和查找物品

创建一个单例模式的基类,该功能实现的是可以简化代码的使用单例模式,以后只要使用单例的时候就可以使用它

Singleton.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Singleton<T> : MonoBehaviour where T : Singleton<T> //实现一个单例模式的基类
{
    public static T Instance;
    public virtual void Awake()
    {
        Instance = (T)this;
    }
}

新建一个空对象inventoryManager,挂载inventoryManager脚本文件,同时将ItemDataList_SO关联到脚本文件中

inventoryManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//背包的数据管理中心
//给inventory添加一个命名空间
namespace MFarm.Inventory
{
    public class inventoryManager : Singleton<inventoryManager>
    {
        //获取itemDataList_SO
        public ItemDataList_SO ItemDataList_SO; //需要关联

        //查找数据
        /// <summary>
        /// 这是一个查找数据的方法
        /// </summary>
        /// <param name="ID">物品的id</param>
        /// <returns>返回的是ID对应的物品信息</returns>
        public ItemDetails getItemDetails(int ID)
        {
            return ItemDataList_SO.ItemDataList.Find(i=>i.ItemId==ID); //判断传进来的ID和数据中心的ID是否一致,一致返回对应ID的物品信息
        }

        /// <summary>
        /// 表示添加物品到背包中
        /// </summary>
        /// <param name="item"></param>
        /// <param name="toDestory">是否要删除物品</param>
        public void addItem(Item item,bool toDestory=true) //是否要移除,不传的话默认是TRUE
        {
            if(toDestory) { 
            
                Destroy(item.gameObject); //删除当前的物品信息
            }
        }
    }
}


新建脚本ItemPickUp,用于碰撞检测方法

ItemPickUp.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MFarm.Inventory;

namespace MFarm.Inventory
{
    public class ItemPickUp : MonoBehaviour
    {
        //碰撞检测
        private void OnTriggerEnter2D(Collider2D other)
        {
            Item item = other.GetComponent<Item>();
            if (item != null)
            {
                //物品必须是可以拾取--如果不消失的话要查看一下对应的物品是否可以勾选
                if (item.itemDetails.CanPickUp)
                {
                    //调用inventoryManager中添加背包的方法
                    inventoryManager.Instance.addItem(item,true);
                }
            }
        }
    }
}


5.最终效果

拾取.gif