U3D背包系统
u3d背包系统功能,通过按键在格子中增加物品,通过鼠标拖动可以自由的拖拽物体,鼠标进入物体时候可以显示物品属性;
如图:
目录结构:
基本实现逻辑:
- 在Assets/StreamingAssets(必须使用这个文件名,放在Unity项目中名为StreamingAssets文件夹中的任何文件将会被一字不差地复制到目标机器上的特定文件夹里 MAC/Win: path = = Application.dataPath + “/StreamingAssets”; iOS: path = Application.dataPath + “/Raw”; Android: path = "jar:fil;)中存放着数据配置文件,该数据文件用xml(或者ini)编写记录着背包中的物品的字段和属性,如类型,名字,描述等,在代码中通过Configitem.cs类对该数据配置文件进行读取,并保存在一个字典容器中。之后通过代码加载,值得一提的是,Resources中的资源通过load加载,StreamAssets中的资源通过www加载。
- 通过itemInfo类控制物品属性的显示,即鼠标进入格子的时候出现一个面板显示物品信息,为了显示一个渐渐的效果,通过控制CanvasGroup的alpha值0~1来实现。
- 通过item类控制物品。item脚本加在物品上,item类继承自TriggerEventer事件触发器,这是因为当我们鼠标进入item时需要触发itemInfo的函数,所以需要实现OnPointerEnter方法。在item类中实现了对鼠标操作物品的逻辑,当鼠标拖动物品移动后再点击将物品放下时,需要进行判断:如果当下item中有物品时要进行交换操作;如果当下item为空时,将物品存储在当下item中。具体操作流程:item.cs(onPointerDown时判断鼠标上是否有物品)->如果没有,调用itemMouse.cs的Item属性加载图片,并把图片保存在item的icon中。(在我们平时玩的游戏中,其实通常是通过鼠标松开来实现物品的放下或者交换操作,此时就应该在onPointerUp时候进行判断),鼠标的位置信息是在ui的每一帧都在实时的获取。(还有一种实现方法:通过重写鼠标松开事件,在鼠标松开时,获得当前鼠标下的物品栏,将拖动的物品的父类设置为该物品栏,同时将该物品栏里的子物品的父类设置为拖动的物品之前的父类)
- 在bag.cs下有很多的items,通过PanelRoleBag.cs对所有的item进行操作。
//GameUI.cs,程序入口
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameUI : SingletonMono<GameUI> {
private RectTransform rect;
private PanelRoleBag bag;
public bool isPickItem;//鼠标上是否拾取了物品
private ItemMouse itemMouse;//鼠标上的物品
private Vector3 itemMouseOldPos;//保存鼠标上物品的初始位置
public ItemMouse ItemMouse
{
get { return itemMouse; }
set { itemMouse = value; }
}
//物品提示框
public bool isShowItemInfo;//是否显示物品信息提示框
private ItemInfo itemInfo;//物品的信息提示
/// <summary>
/// 隐藏鼠标拾取物品
/// </summary>
public void hideMousePickItem()
{
itemMouse.transform.localPosition = itemMouseOldPos;
isPickItem = false;
}
public override void Awake()
{
base.Awake();
//配置数据加载
//print("配置数据调用前");
ConfigItem.init();
//print("配置数据调用后");
bag = transform.Find("PanelRoleBag").GetComponent<PanelRoleBag>();
itemMouse = transform.Find("ItemMouse").GetComponent<ItemMouse>();
itemMouseOldPos = itemMouse.transform.localPosition;
rect = this.GetComponent<RectTransform>();
itemInfo = transform.Find("ItemInfo").GetComponent<ItemInfo>();
}
void Start () {
}
// Update is called once per frame
void Update () {
//更新物品提示框的位置
if (isShowItemInfo)
{
Vector2 point = Vector2.zero;
RectTransformUtility.ScreenPointToLocalPointInRectangle(rect, Input.mousePosition, null, out point);
itemInfo.transform.localPosition = point;
}
//鼠标拾取到物品之后
if (isPickItem)
{
Vector2 point = Vector2.zero;
//该函数是将屏幕坐标转化以第一个参数对象的子节点坐标
//参数一:需要转换的坐标以该对象作为父节点
//参数二:鼠标点
//参数三:参数一对象以哪个摄像机渲染(由于该参数一画布没有相机渲染,故为null)
//参数四:返回一个需要转换的目标点
RectTransformUtility.ScreenPointToLocalPointInRectangle(rect, Input.mousePosition, null, out point);
itemMouse.transform.localPosition = point;
}
//按空格键盘,模拟生成物品
if (Input.GetKeyUp(KeyCode.Space))
{
int randId = Random.Range(0, ConfigItem.ItemCount);//物品ID
ConfigItem data = ConfigItem.Get(randId);//通过物品ID得到物品的数据信息
bag.storeItemToBag(data);//通过数据添加物品
}
}
/// <summary>
/// 显示物品提示信息
/// </summary>
/// <param name="str"></param>
public void showItemInfo(string str = "")
{
isShowItemInfo = true;
itemInfo.show(str);
}
/// <summary>
/// 隐藏物品提示信息
/// </summary>
public void hideItemInfo()
{
isShowItemInfo = false;
itemInfo.hide();
}
}
//PanelRoleBag,对item进行操作的脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PanelRoleBag : MonoBehaviour {
public Item[] items;//面板上所有的背包格子
// Use this for initialization
void Start () {
GameObject goBag = GameObject.Find("Bag");
items = goBag.GetComponentsInChildren<Item>();
}
// Update is called once per frame
void Update () {
}
/// <summary>
/// 从已经存储的物品中找ID相同的物品
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
private Item findTheSameItem(int id)
{
foreach (Item s in items)
{
if (s.isStored && s.Data.id == id)
{
return s;
}
}
return null;
}
/// <summary>
/// 从所有格子中查找一个空格子
/// </summary>
/// <returns></returns>
private Item findEmptyItem()
{
foreach (Item s in items)
{
if (!s.isStored)//没有存放数据
{
return s;
}
}
//没有找到空的物品槽,说明背包以满
return null;
}
/// <summary>
/// 存储物品到背包中
/// </summary>
/// <param name="d"></param>
/// <returns></returns>
public bool storeItemToBag(ConfigItem d)
{
if (d == null) return false;
if (d.capacity == 1)//如果该物品在物品槽中只能存放一个
{
Item item = findEmptyItem();//直接找到一个还没有使用的物品格子
if (item != null)//如果找到了空的物品槽,将该物品放进去格子里
{
item.storeItem(d);
}
else//如果没有找到,则将说明背包已满
{
Debug.Log("没有找到空的物品槽,可能是背包以满");
return false;
}
}
else if (d.capacity > 1)//物品可以叠加
{
//从背包中查找一个相同ID的物品
Item item = findTheSameItem(d.id);
if (item != null)
{
item.addItem(1);//如果找到,直接叠加一个单位
}
else
{
item = findEmptyItem();//如果没有找到已存物体,继续查找空格子
if (item != null)
{
item.storeItem(d);//找到空格子,直接存放该物品
}
else
{
Debug.Log("没有找到空的物品槽,可能是背包以满,需要扩充背包格子!!!");
return false;
}
}
}
return true; ;
}
}
//item.cs,挂载在每个item上的脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class Item : EventTrigger
{
private Image icon;
private Image lockFlag;
private Text textNum;
//--------------物品数据
ConfigItem data;
private int count;//物品数量
public int Count
{
get { return count; }
}
public ConfigItem Data
{
get { return data; }
set
{
data = value;
//通过配置数据去资源目录下读取图片
//icon.sprite = Resources.Load<Sprite>(data.icon);
string iconPath = "file://" + Application.dataPath + "\\ItemIcon\\" + data.icon;//Application.dataPath:指的是Assets文件夹所在的目录
StartCoroutine(Tools.LoadImage(iconPath, icon));
}
}
//判断是否已经存储了物品
public bool isStored
{
get
{
return icon.gameObject.activeInHierarchy;
}
}
//判断格子是否被锁住
public bool isLocked
{
get
{
return lockFlag.gameObject.activeInHierarchy;
}
}
void Awake()
{
icon = transform.Find("Icon").GetComponent<Image>();
lockFlag = transform.Find("Lock").GetComponent<Image>();
textNum = transform.Find("Num").GetComponent<Text>();
}
public override void OnPointerEnter(PointerEventData eventData)
{
//鼠标进入格子,显示格子上物品的信息
if (isStored && !GameUI.Instance.isPickItem)
{
GameUI.Instance.showItemInfo(data.descrip);
}
}
public override void OnPointerDown(PointerEventData eventData)
{
print("OnPointerDown");
if (!isStored)//格子里还没有存物品
{
if (GameUI.Instance.isPickItem)//鼠标上已经拾取
{
//icon.gameObject.SetActive(true);
storeItem(GameUI.Instance.ItemMouse.Item.data, GameUI.Instance.ItemMouse.Item.count);
GameUI.Instance.hideMousePickItem();//隐藏鼠标上的物品
}
}
else//格子里有物品
{
if (GameUI.Instance.isPickItem)//鼠标上已经拾取
{
exchangeItem(GameUI.Instance.ItemMouse.Item);
GameUI.Instance.hideMousePickItem();
}
else//鼠标上没有拾取物品
{
GameUI.Instance.ItemMouse.Item = this;
icon.gameObject.SetActive(false);
textNum.gameObject.SetActive(false);
}
}
//GameUI.Instance.hideItemInfo();
}
public override void OnPointerExit(PointerEventData eventData)
{
//鼠标离开格子
if (isStored)
{
GameUI.Instance.hideItemInfo();
}
}
/// <summary>
/// 往已经存放了物品的格子中追加物品
/// </summary>
/// <param name="num"></param>
public void addItem(int num = 1)
{
this.count += num;
if (textNum)
{
if (data.capacity > 1)
{
textNum.text = this.count.ToString();
textNum.gameObject.SetActive(true);
}
else
{
textNum.text = "";
//textNum.gameObject.SetActive(false);
}
}
}
/// <summary>
/// 存储物品
/// </summary>
/// <param name="data"></param>
/// <param name="num"></param>
public void storeItem(ConfigItem data, int num = 1)
{
textNum.gameObject.SetActive(false);
//icon.gameObject.SetActive(true);
Data = data;
this.count = num;
if (count > 1)
{
textNum.text = count.ToString();
textNum.gameObject.SetActive(true);
}
}
/// <summary>
/// 交换两个格子中的物品
/// </summary>
/// <param name="other"></param>
public void exchangeItem(Item other)
{
ConfigItem otherData = other.data;
int otherCount = other.count;
other.storeItem(data, count);
this.storeItem(otherData, otherCount);
}
}
//itemInfo,提示物品属性的面板
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// 物品信息提示
/// </summary>
public class ItemInfo : MonoBehaviour {
private Text content;//需要显示文本(显示物品描述信息)
private CanvasGroup cg;
public float finalAlpha=0.0f;//最终显示的alpha
private float speedAlpha = 4.0f;
void Awake()
{
content = transform.Find("Content").GetComponent<Text>();
cg = transform.GetComponent<CanvasGroup>();
}
void Update ()
{
//控制提示信息框的淡入淡出效果
if(finalAlpha!=cg.alpha)
{
cg.alpha = Mathf.Lerp(cg.alpha,finalAlpha,speedAlpha*Time.deltaTime);
if (Mathf.Abs(finalAlpha - cg.alpha) <= 0.01)
{
cg.alpha = finalAlpha;
}
}
}
/// <summary>
/// 物品提示信息显示
/// </summary>
/// <param name="str"></param>
public void show(string str)
{
content.text = str;
finalAlpha = 1.0f;
}
/// <summary>
/// 物品提示信息隐藏
/// </summary>
public void hide()
{
finalAlpha = 0;
}
}
//itemMouse,拖拽时在鼠标上的物品,鼠标点击物品栏的时候,通过this指针可以获得该上的物品
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// 鼠标指针上的物品
/// </summary>
public class ItemMouse : MonoBehaviour {
private Image icon;
private Text num;
private Item itemFirstPick;//对第一次鼠标拾取物品的引用
public Item Item
{
get { return itemFirstPick; }
set {
itemFirstPick = value;
GameUI.Instance.isPickItem = true;
num.gameObject.SetActive(false);
if(itemFirstPick.Data.capacity>1)
{
num.text = itemFirstPick.Count.ToString();
num.gameObject.SetActive(true);
}
string iconPath = "file://" + Application.dataPath + "\\ItemIcon\\" + itemFirstPick.Data.icon;
StartCoroutine(Tools.LoadImage(iconPath, icon));
}
}
void Awake () {
icon = transform.GetComponent<Image>();
num = transform.Find("Num").GetComponent<Text>();
}
}
//ConfigItem.cs,配置文件,获取数据配置
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using UnityEngine;
public class ConfigItem
{
public readonly int id;//物品ID
public readonly int type;//物品类型
public readonly string name;//物品名字
public readonly string descrip;//物品描述
public readonly string icon;//物品图标
public readonly int quality;//物品品质
public readonly int capacity;//物品容量(决定是否可以叠加)
public ConfigItem() { }
public ConfigItem(int id, int type, string name, string descrip, string icon, int quality, int capacity)
{
this.id = id;
this.type = type;
this.name = name;
this.descrip = descrip;
this.icon = icon;
this.quality = quality;
this.capacity = capacity;
}
public static int ItemCount
{
get { return dic.Count; }
}
//字典容器
private static Dictionary<int, ConfigItem> dic = new Dictionary<int, ConfigItem>();
public static ConfigItem Get(int id)
{
ConfigItem config;
if (dic.TryGetValue(id, out config))
{
return config;
}
else
{
return null;
}
}
/// <summary>
/// 用来数据的初始化
/// </summary>
public static void init()
{
loadItem(Application.streamingAssetsPath+@"/item.xml");
}
public static void loadItem(string path)
{
StreamReader sr = new StreamReader(path, Encoding.UTF8);//数据流
XmlDocument doc = new XmlDocument();//XML文档
doc.Load(sr);
XmlNodeList nodeItems;//节点列表
nodeItems = doc.SelectNodes("/Items/Item");//使用xpath表达式选择文档中所有的stage子节点
for (int i = 0; i < nodeItems.Count; i++)
{
XmlNode nodeItem = nodeItems[i];
int id = int.Parse(nodeItem.Attributes["id"].Value);
if (dic.ContainsKey(id))
{
Debug.LogWarning("配置了同样的id:" + id);
return;
}
int type = int.Parse(nodeItem.Attributes["type"].Value);
string name = nodeItem.Attributes["name"].Value;
string descrip = nodeItem.Attributes["descrip"].Value;
string icon = nodeItem.Attributes["icon"].Value;
int quality = int.Parse(nodeItem.Attributes["quality"].Value);
int capacity = int.Parse(nodeItem.Attributes["capacity"].Value);
ConfigItem data = new ConfigItem(id, type, name, descrip, icon, quality, capacity);//生成一个物品对象
dic.Add(id, data);//把关卡对象放入到字典中
}
sr.Close();
sr.Dispose();
}
}
总结:
在GameUI中进行obect的初始化等操作,在按下空格键后在物品栏中添加物品,调用PanelRoleBags类的storeItemToBag函数添加,物品通过ConfigItem对象表示,该对象通过调用xml配置文件对物品的属性进行了描述,其中包括了物品icon名,在item类中有storeitem函数,该函数负责将storeItemToBag函数传递的ConfigItem对象添加到物品栏中(通过了对象中的icon名,使用load.image将文件夹中的图片文件加载到item的icon文件中),配置文件中其实还描述了物品是否可以叠加存储,代码里如果可以叠加存储,那么添加item的时候会find一个同类型的item,将该item的num+1,否则找一个空的物品栏进行存储。最核心的其实是鼠标操作,首先应该将鼠标在屏幕上的坐标转换为物品栏里的坐标,这样有一个坐标位置,就可以通过坐标位置存放交换物品。当点击的时候鼠标上没有物品的时候,通过item的this指针将item实例化的物品赋值给itemMouse.item即鼠标上的物品。