一、关联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文件夹中,并且跟当前的场景一起使用在同一个窗口中。
3.在地图中显示图片
新建ItemBase空对象,里面包含Sprite空对象
给ItemBase挂载2D盒状碰撞器,该功能主要是实现人物和物品进行的碰撞检测必须要的。
挂载Item脚本文件,主要实现对图片的显示和初始化
勾选是否为触发器选项
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组件,该组件主要是实现对精灵图的展示
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);
}
}
}
}
}