引擎项目开发第二天

418 阅读5分钟

一、什么是UI ToolKit

1.前言

我们之前使用过的UI解决方案有UGUI,这里学习的UIToolKit是unity新推出的UI解决方案,目的是为了让我们更高效得开发出可复性性高且复杂的UI界面

2.UI ToolKit设计原理

image.png

二、背包数据初始化

1.创建DataCollection存放所有数据集合

在数据集合中创建物品详情列表

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

[System.Serializable]//让下面生成的对象序列化,这样才可以显示到inspector窗口上  
//物品详情列表
public class ItemDetails
{
    //物品id
    public int itemID;
    //物品名字
    public string name;
    //物品类型
    public ItemType itemType;
    //物品图片
    public Sprite itemIcon;
    //物品在世界地图中显示的图片
    public Sprite itemOnWorldSprite;
    //物品中的描述信息
    public string itemDescription;
    //物品可显示的网格大小的范围
    public int itemUseRadius;
    //物品是否可以拾取
    public bool canPickedup;
    //物品是否可以被扔出
    public bool canDropped;
    //物品是否可以举着
    public bool canCarried;
    //物品的价格
    public int itemPrice;
    //物品的折扣范围 值是0-1
    [Range(0,1)]//在检查窗口中显示进度条0到1  
    public float sellPercentage;

}

2.创建Enum文件存储所有枚举类型的数据

编写存储物品类型的枚举

//Enums文件中存取了所有的枚举类型  

//物品的类型 
public enum ItemType
{
    //种子,商品,家具,
    Seed,Commodity,Furniture,
    //锄头,砍树的工具,砸石头的工具,割草的工具,浇水的工具,菜篮子,可以被割的杂草
    HoeTool,ChopTool,BeakTool,ReapTool,WaterTool,CollectTool,ReapableScenery
}

3.这里出现的中括号相关的代码参考其他博客文档

blog.csdn.net/qq_32065601…

4.在Script文件中创建一个inventory类库存放我们的SO数据类

image.png

5.数据列表类ItemDataList_SO

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

//作用是在Aseets文件夹下,鼠标右键,菜单栏中添加一个按钮项,菜单名为menuName,并执行生成名为fileName的脚本,order为按钮显示顺序,只有一个的时候可以不用填
[CreateAssetMenu(fileName = "ItemDataList_SO",menuName ="Inventory/ItemDataList")]
public class ItemDataList_SO : ScriptableObject
{
    public List<ItemDetails> itemDetailsList;
}
 

6.创建GameData来存放我们的数据库的信息

image.png

三、使用 UI Toolkit 和 UI Builder 制作物品编辑器

image.png

四、获取数据

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


public class ItemEditor : EditorWindow
{
    private ItemDataList_SO dataBase;//定义ItemDataList_SO类的变量
    private List<ItemDetails> itemList = new List<ItemDetails>();//获取数据列表
    private VisualTreeAsset itemRowTemplate;//左侧模板
    private ScrollView itemDetailsSection;
    private ItemDetails activeItem;

    //默认预览图片
    private Sprite defaultIcon;

    private VisualElement iconPreview;
    //获得VisualElement
    private ListView itemListView;

    [MenuItem("M STUDIO/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;

        // VisualElements objects can contain other VisualElement following a tree hierarchy.
        // VisualElement label = new Label("Hello World! From C#");
        // root.Add(label);

        // Import UXML
        var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/UI Builder/ItemEditor.uxml");
        VisualElement labelFromUXML = visualTree.Instantiate();
        root.Add(labelFromUXML);

        //拿到模版数据
        itemRowTemplate = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/UI Builder/ItemRowTemplate.uxml");

        //拿默认Icon图片
        defaultIcon = AssetDatabase.LoadAssetAtPath<Sprite>("Assets/M Studio/Art/Items/Icons/icon_M.png");

        //变量赋值
        itemListView = root.Q<VisualElement>("ItemList").Q<ListView>("ListView");//拿到左侧的列表的容器

        itemDetailsSection = root.Q<ScrollView>("ItemDetails");
        iconPreview = itemDetailsSection.Q<VisualElement>("Icon");


        //获得按键
        root.Q<Button>("AddButton").clicked += OnAddItemClicked;
        root.Q<Button>("DeleteButton").clicked += OnDeleteClicked;
        //加载数据
        LoadDataBase();  //加载数据

        //生成ListView
        GenerateListView();//生成左侧的列表  
    }

    #region 按键事件
    private void OnDeleteClicked()
    {
        itemList.Remove(activeItem);
        itemListView.Rebuild();
        itemDetailsSection.visible = false;
    }

    private void OnAddItemClicked()
    {
        ItemDetails newItem = new ItemDetails();
        newItem.name = "NEW ITEM";
        newItem.itemID = 1001 + itemList.Count;
        itemList.Add(newItem);
        itemListView.Rebuild();
    }
    #endregion

    private void LoadDataBase() //加载数据
    {
        var dataArray = AssetDatabase.FindAssets("ItemDataList_SO");

        if (dataArray.Length > 1)
        {
            var path = AssetDatabase.GUIDToAssetPath(dataArray[0]);
            dataBase = AssetDatabase.LoadAssetAtPath(path, typeof(ItemDataList_SO)) as ItemDataList_SO;
        }

        itemList = dataBase.itemDetailsList;
        //如果不标记则无法保存数据
        EditorUtility.SetDirty(dataBase);
        Debug.Log(itemList[0].itemID);
    }

    private void GenerateListView()
    {
        Func<VisualElement> makeItem = () => itemRowTemplate.CloneTree();   //克隆左侧模板  

        Action<VisualElement, int> bindItem = (e, i) =>
        {
            if (i < itemList.Count)
            {
                if (itemList[i].itemIcon != null)
                    e.Q<VisualElement>("Icon").style.backgroundImage = itemList[i].itemIcon.texture;
                e.Q<Label>("Name").text = itemList[i] == null ? "NO ITEM" : itemList[i].name;
            }
        };

        itemListView.fixedItemHeight = 50;  //根据需要高度调整数值
        itemListView.itemsSource = itemList;//将左侧模板添加到左侧的容器中  
        itemListView.makeItem = makeItem;
        itemListView.bindItem = bindItem;

        itemListView.onSelectionChange += OnListSelectionChange;

        //右侧信息面板不可见
        itemDetailsSection.visible = false;
    }

    private void OnListSelectionChange(IEnumerable<object> selectedItem)
    {
        activeItem = (ItemDetails)selectedItem.First();
        GetItemDetails();
        itemDetailsSection.visible = true;
    }

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

        itemDetailsSection.Q<IntegerField>("ItemID").value = activeItem.itemID;
        itemDetailsSection.Q<IntegerField>("ItemID").RegisterValueChangedCallback(evt =>
        {
            activeItem.itemID = evt.newValue;
        });

        itemDetailsSection.Q<TextField>("ItemName").value = activeItem.name;
        itemDetailsSection.Q<TextField>("ItemName").RegisterValueChangedCallback(evt =>
        {
            activeItem.name = evt.newValue;
            itemListView.Rebuild();
        });

        iconPreview.style.backgroundImage = activeItem.itemIcon == null ? defaultIcon.texture : activeItem.itemIcon.texture;
        itemDetailsSection.Q<ObjectField>("ItemIcon").value = activeItem.itemIcon;
        itemDetailsSection.Q<ObjectField>("ItemIcon").RegisterValueChangedCallback(evt =>
        {
            Sprite newIcon = evt.newValue as Sprite;
            activeItem.itemIcon = newIcon;

            iconPreview.style.backgroundImage = newIcon == null ? defaultIcon.texture : newIcon.texture;
            itemListView.Rebuild();
        });

        //其他所有变量的绑定
        itemDetailsSection.Q<ObjectField>("ItemSprite").value = activeItem.itemOnWorldSprite;
        itemDetailsSection.Q<ObjectField>("ItemSprite").RegisterValueChangedCallback(evt =>
        {
            activeItem.itemOnWorldSprite = (Sprite)evt.newValue;
        });

        itemDetailsSection.Q<EnumField>("ItemType").Init(activeItem.itemType);
        itemDetailsSection.Q<EnumField>("ItemType").value = activeItem.itemType;
        itemDetailsSection.Q<EnumField>("ItemType").RegisterValueChangedCallback(evt =>
        {
            activeItem.itemType = (ItemType)evt.newValue;
        });

        itemDetailsSection.Q<TextField>("Description").value = activeItem.itemDescription;
        itemDetailsSection.Q<TextField>("Description").RegisterValueChangedCallback(evt =>
        {
            activeItem.itemDescription = evt.newValue;
        });

        itemDetailsSection.Q<IntegerField>("ItemUseRadius").value = activeItem.itemUseRadius;
        itemDetailsSection.Q<IntegerField>("ItemUseRadius").RegisterValueChangedCallback(evt =>
        {
            activeItem.itemUseRadius = evt.newValue;
        });

        itemDetailsSection.Q<Toggle>("CanPickedup").value = activeItem.canPickedup;
        itemDetailsSection.Q<Toggle>("CanPickedup").RegisterValueChangedCallback(evt =>
        {
            activeItem.canPickedup = evt.newValue;
        });

        itemDetailsSection.Q<Toggle>("CanDropped").value = activeItem.canDropped;
        itemDetailsSection.Q<Toggle>("CanDropped").RegisterValueChangedCallback(evt =>
        {
            activeItem.canDropped = evt.newValue;
        });

        itemDetailsSection.Q<Toggle>("CanCarried").value = activeItem.canCarried;
        itemDetailsSection.Q<Toggle>("CanCarried").RegisterValueChangedCallback(evt =>
        {
            activeItem.canCarried = evt.newValue;
        });

        itemDetailsSection.Q<IntegerField>("Price").value = activeItem.itemPrice;
        itemDetailsSection.Q<IntegerField>("Price").RegisterValueChangedCallback(evt =>
        {
            activeItem.itemPrice = evt.newValue;
        });

        itemDetailsSection.Q<Slider>("SellPercentage").value = activeItem.sellPercentage;
        itemDetailsSection.Q<Slider>("SellPercentage").RegisterValueChangedCallback(evt =>
        {
            activeItem.sellPercentage = evt.newValue;
        });
    }
}

五、背包管理中心相关代码

1.背包数据管理器的代码

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

namespace MFarm.Inventory{//添加命名空间  
public class InventoryManager : Singleton<InventoryManager>
{
        //背包数据管理中心
        public ItemDataList_SO itemDataList_SO;
        //创建获取对应id的item的方法
        public ItemDetails GetItemDetails(int ID)
        {
            return itemDataList_SO.itemDetailsList.Find(i=>i.itemID==ID);//找出对应itemID等于ID的那一项

        }
 /// <summary>
 /// 添加物品到背包中的方法
 /// </summary>
 /// <param name="item"></param>
 /// <param name="toDestroy">是否要删除</param>
        public void addItem(Item item,bool toDestroy)
        {
            if (toDestroy)
            {
                Destroy(item.gameObject);
            }
        }


}

}


2.单例模式基类

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Purchasing.Extension;
//使用SingleTon实现泛型单例  
public class Singleton<T> : MonoBehaviour where T : Singleton<T>
{
    private static T instance;

    public static T Instance
    {
        get => instance;//静态属性  
    }
    protected virtual void Awake()
    {
        if (instance != null) Destroy(gameObject);
        else instance = (T)this;
    }

    protected virtual void Ondestroy()
    {
        if (instance == this) instance = null;
    }
}

3.显示在世界中的物品管理器

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

namespace MFarm.Inventory {
    public class Item : MonoBehaviour
    {
        //显示指定物体到页面上
        //要显示的物品id
        public int itemID;
        //显示的图片组件
        private SpriteRenderer spriteRenderer;
        private ItemDetails itemDetails;
        //拿到碰撞体
        private BoxCollider2D coll;
        //获取渲染组件
        private void Awake()
        {
            spriteRenderer = GetComponentInChildren<SpriteRenderer>();
            coll = GetComponent<BoxCollider2D>();
        }
        //只要id不为0我们就初始化数据,显示对应的图片
        private void Start()
        {
            if (itemID!=0)
            {
                Init(itemID);
            }
        }

        private void Init(int ID)
        {
            itemDetails = InventoryManager.Instance.GetItemDetails(itemID);//获取对应id的数据
            if (itemDetails!=null)
            {
                //显示图片,当没有世界中的图片,就显示itemIcon
                spriteRenderer.sprite = itemDetails.itemOnWorldSprite != null ? itemDetails.itemOnWorldSprite : itemDetails.itemIcon;
                //修改碰撞器尺寸 让碰撞器大小和当前显示图片的大小一致 
                //获取新的图片的尺寸
                Vector2 newSize = new Vector2(spriteRenderer.sprite.bounds.size.x,spriteRenderer.sprite.bounds.size.y);
                //将碰撞体的尺寸设置为新的尺寸
                coll.size = newSize;
                //更改位置,有可能图片根据轴心或者中心来显示,所以还要调整碰撞器实际的位置
                coll.offset = new Vector2(0,spriteRenderer.sprite.bounds.center.y);//其他轴不用移动就y轴往上移动图片一半的高度就可以了



            }
        }
    }
}

六、拾取物品逻辑

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

namespace MFarm.Inventory
{
public class ItemPickUp : MonoBehaviour
{
        private void OnTriggerEnter2D(Collider2D other)
        {
            //获取other身上的item组件,如果有item组件说明我们碰到的是工具物品
            Item item = other.GetComponent<Item>();
            if (item!=null)
            {
                if (item.itemDetails.canPickedup)//物品能够被拾取,它身上的canPickUp属性必须为true
                {
                    //拾取物品添加包背包中
                    InventoryManager.Instance.addItem(item,true);
                }
            }
        }
    }

}