Unity游戏开发:背包系统的实现

Posted float_freedom

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity游戏开发:背包系统的实现相关的知识,希望对你有一定的参考价值。

背包是游戏中经常使用的一个组件,它负责管理玩家在游戏中所获得的道具。一个完整的背包系统应当具有将物品放置进背包、对背包内物品进行管理和使用背包内物品等功能。而往往一个背包系统的逻辑关系较为复杂,如果把所有功能都放在一个脚本中实现会使代码显得十分冗杂且缺乏逻辑。所以在背包系统的设计过程中,我们常将其分解为数据、逻辑和UI三部分分别来进行完成。

一、UI设计


以Cotton Puzzle中的背包设计为例,我们需要有物品展示栏、物品切换按键和物品提示信息等部分。

在Canvas中创建ItemHolder,在ItemHolder中创建LeftButton和RightButton控制物品的左右切换、Slot来控制物品的显示以及ItemToolTip来显示物品的提示信息。

二、UI

创建InventoryUI脚本并挂在ItemHolder上以对整个背包UI进行管理。
首先,确定InventoryUI可以控制的组件并定义当前所显示的物品序号,由于ItemToolTip的显示与否和Slot直接相关联,所以可以通过控制Slot间接控制ItemToolTip,在InventoryUI中便不再单独对其进行控制。

//InventoryUI.cs
    public Button leftButton;
    public Button rightButton;
    public SlotUI slotUI;

    public int currentItemIndex;//当前物品序号

2.1 Slot中的物品显示

//InventoryUI.cs
    /// <summary>
    /// 更新Slot中物品图片
    /// </summary>
    /// <param name="itemDetails"></param>
    /// <param name="index"></param>
    private void OnUpdateUIEvent(ItemDetails itemDetails, int index)
    
        if (itemDetails == null)//当前物品为空
        
            slotUI.SetEmpty();
            currentItemIndex = -1;
            leftButton.interactable = false;
            rightButton.interactable=false;
        
        else
        
            currentItemIndex = index;
            slotUI.SetItem(itemDetails);

            if (index > 0)
                leftButton.interactable = true;

            if (currentItemIndex == -1)
            
                leftButton.interactable = false;
                rightButton.interactable = false;
            
        
    

定义UpdateUIEvent事件来实时管理Slot中显示的物品(通过调用SlotUI中实现的SetEmpty()和SetItem(itemDetails)方法实现)和左右按钮的可使用情况。

//InventoryUI.cs
    private void OnEnable()
    
        EventHandler.UpdateUIEvent += OnUpdateUIEvent;
    

    private void OnDisable()
    
        EventHandler.UpdateUIEvent -= OnUpdateUIEvent;
    

2.2 物品切换

//InventoryUI.cs
    /// <summary>
    /// 左右按钮使用事件
    /// </summary>
    /// <param name="amount"></param>
    public void SwitchItem(int amount)
    
        var index = currentItemIndex + amount;
        if(index< currentItemIndex)
        
            leftButton.interactable = false;
            rightButton.interactable = true;
        
        else if(index> currentItemIndex)
        
            leftButton.interactable = true;
            rightButton.interactable = false;
        
        else
        
            leftButton.interactable = true;
            rightButton.interactable = true;
        

        EventHandler.CallChangeItemEvent(index);
    

2.3 SlotUI的实现

在SlotUI中需要实现将Slot图片设置为指定物品(包含物品为空的情况)、鼠标点击反馈、鼠标移入移出反馈(这里主要要实现当鼠标移入显示物品文字描述信息,移出取消显示的功能)。

//SlotUI.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

public class SlotUI : MonoBehaviour, IPointerClickHandler, IPointerEnterHandler, IPointerExitHandler

    public Image itemImage;
    public ItemDetails currentItem;

    public ItemToolTip toolTip;

    private bool isClicked;

    /// <summary>
    /// 设置Slot图片为指定item
    /// </summary>
    /// <param name="itemDetails"></param>
    public void SetItem(ItemDetails itemDetails)
    
        currentItem = itemDetails;
        this.gameObject.SetActive(true);
        itemImage.sprite = itemDetails.itemSprite;
        itemImage.SetNativeSize();
    

    /// <summary>
    /// 设置Slot图片为空
    /// </summary>
    public void SetEmpty()
    
        this.gameObject.SetActive(false);
    

    public void OnPointerClick(PointerEventData eventData)
    
        isClicked = !isClicked;
        EventHandler.CallItemSelectedEvent(currentItem, isClicked);
    

    public void OnPointerEnter(PointerEventData eventData)
    
        if (this.gameObject.activeInHierarchy)
        
            toolTip.gameObject.SetActive(true);
            toolTip.UpdateItemName(currentItem.itemName);
        
    

    public void OnPointerExit(PointerEventData eventData)
    
        toolTip.gameObject.SetActive(false);

    


2.4 物品描述信息的展示

首先我们需要定义一个变量来记录物品的信息。这里我们在ItemToolTip下创建一个Text来记录文本信息。

//ItemToolTip.cs
    public Text itemNameText;

接下来定义方法根据目前Slot所展示的物品名字来修改Text的内容。

//ItemToolTip.cs
    public void UpdateItemName(ItemName itemName)
    
        itemNameText.text = itemName switch
        
            ItemName.Key => "信箱钥匙",
            ItemName.Ticket => "一张船票",
            _ => ""
        ;
    

为了方便使用我们将物品名写成了枚举变量,定义在Enums脚本中,方便在其他脚本中也可以使用。

//Enums.cs
/// <summary>
/// 物品名
/// </summary>
public enum ItemName

    None,
    Key,
    Ticket,

三、数据

首先为了我们能够在unity中更加直观地对数据进行修改,利用以下代码在unity的菜单下创建数据子菜单,并序列化对象。每个ItemDetails包含物品名(itemName)和物品贴图(itemSprite)两个属性。

//ItemDataList.cs
[CreateAssetMenu(fileName ="ItemDataList",menuName = "Inventory/ItemDataList")]

/// <summary>
/// 对象序列化
/// </summary>
[System.Serializable]
public class ItemDetails

    public ItemName itemName;
    public Sprite itemSprite;

在unity中可以直接新建一个ItemDataList的数据对象,创建方式为:右键 -> Create ->Inventory -> ItemDataList

定义GetItemDetails根据物品名获取ItemDetails 对象。

//ItemDataList.cs

    public List<ItemDetails> itemDetailsList = new List<ItemDetails>();
    public ItemDetails GetItemDetails(ItemName itemName)
    
        return itemDetailsList.Find(i => i.itemName.Equals(itemName));
    

四、逻辑

对于场景中的物体,在鼠标点击后需要能够被添加到背包内并在原有位置消失。可通过ItemClicked()函数来实现。

//Item.cs
    public ItemName itemName;

    public void ItemClicked()
    
        //隐藏鼠标点击物体
        this.gameObject.SetActive(false);
        //将物品添加到背包
        InventoryManager.Instance.AddItem(itemName);
    

最后,需要定义一个脚本InventoryManager对背包系统的UI、数据进行一个统一的管理

//InventoryManager.cs
    [SerializeField] private List<ItemName> itemList = new List<ItemName>();

    public ItemDataList itemData;//通过物品名在物品列表中查找相应物品

(1)在新加载游戏的时候需要清空物品列表

    private void OnStartNewGameEvent(int gameWeek)
    
        itemList.Clear();
    

(2)当物品被使用时需要将物品移除背包并更新UI

    private void OnItemUsedEvent(ItemName itemName)
    
        var index = GetItemIndex(itemName);
        itemList.RemoveAt(index);

        if (itemList.Count == 0)
            EventHandler.CallUpdateUIEvent(null, -1);
    

(3)当场景加载之后,需要将目前背包中有的物品进行更新

    private void OnAfterSceneLoadedEvent()
    
        if (itemList.Count == 0)
            EventHandler.CallUpdateUIEvent(null, -1);
        else
        
            for (int i = 0; i < itemList.Count; i++)
            
                EventHandler.CallUpdateUIEvent(itemData.GetItemDetails(itemList[i]), i);
            
        
    

(4)当使用Button在背包中左右切换物品时,需要实时更改物品的图片并判断是否越界

    private void OnChangeItemEvent(int index)
    
        if (index >= 0 && index < itemList.Count)
        
            ItemDetails item = itemData.GetItemDetails(itemList[index]);
            EventHandler.CallUpdateUIEvent(item, index);
        
        else
        
            Debug.Log("越界!");
        
    

注册这些事件:

    private void OnEnable()
    
        EventHandler.ChangeItemEvent += OnChangeItemEvent;
        EventHandler.AfterSceneLoadedEvent += OnAfterSceneLoadedEvent;
        EventHandler.ItemUsedEvent += OnItemUsedEvent;
        EventHandler.StartNewGameEvent += OnStartNewGameEvent;
    


    private void OnDisable()
    
        EventHandler.ChangeItemEvent -= OnChangeItemEvent;
        EventHandler.AfterSceneLoadedEvent -= OnAfterSceneLoadedEvent;
        EventHandler.ItemUsedEvent -= OnItemUsedEvent;
        EventHandler.StartNewGameEvent -= OnStartNewGameEvent;
    

另外有一些工具函数需要实现:

    public void AddItem(ItemName itemName)
    
        if(!itemList.Contains(itemName))//列表中不包含物品则添加到列表中
         
            itemList.Add(itemName);
        
        //UI显示
        EventHandler.CallUpdateUIEvent(itemData.GetItemDetails(itemName), itemList.Count - 1);
    

    /// <summary>
    /// 获取物品在列表中的序号
    /// </summary>
    /// <param name="itemName"></param>
    /// <returns></returns>
    private int GetItemIndex(ItemName itemName)
    
        for(int i=0;i<itemList.Count;i++)
        
            if(itemName.Equals(itemList[i]))
                return i;
        
        return -1;
    

Unity UGUI有趣应用 -------------------- 背包系统(上)之简易单页背包系统及检索功能的实现

背包系统,无论是游戏还是应用,都是常常见到的功能,其作用及重要性不用我多说,玩过游戏的朋友都应该明白。

在Unity中实现一个简易的背包系统其实并不是太过复杂的事。本文要实现的是一个带检索功能的背包系统。先看一下我们要完成的效果 。由于上传的gif图不能大于5M,所以录制的质量比较一般。大家先将就看一下吧

 技术分享图片技术分享图片?

那现在,我们就开始动手了~~

一. 拼UI

由于本文着重讲述的是背包系统的运作,所以,背包里面的元素获取就简洁地做成这个样子

技术分享图片技术分享图片?

事实上,一般游戏内获取道具的途径是敌人掉落,完成任务,商城购买等,而这里简化了这一步,右边的加号代表向背包里添加一个该元素。好了,现在开始实际制作。

 

1.1 在 Resources 文件夹下创建两个子文件夹 Prefab 和 Sprite ,用于存放资源

, 技术分享图片技术分享图片?

 

1.2 创建一个Canvas;

    创建一个空物体,命名为Options;

    创建一个 Image,用来显示道具,命名为 X1-装备

    在上一步创建的 Image 下创建一个子 Image。

    然后给这两张 Image 赋值

技术分享图片技术分享图片?     技术分享图片技术分享图片?

技术分享图片技术分享图片

这里需要说明的是,命名为 X1-装备 是为了更方便的说明背包如何运作,事实上一般游戏的道具都是有着名字,种类等属性,而这些属性应该储存在 配置表 里面。本文不会涉及到配置表的操作,所以这里就简洁处理。还有这些UI资源都是随意的,读者自行选择自己的图片资源。

 

1.3 进行第②中的操作,实现如下效果

技术分享图片技术分享图片?

技术分享图片技术分享图片

 

1.4 制作背包

    在Canvas下创建一个空物体,命名为BG,赋上一张背景图,调整尺寸大小

 技术分享图片技术分享图片?

技术分享图片技术分享图片

     在BG下创建一个Panel,调整其尺寸大小。并添加 Content Size FitterGrid Layout Group 组件,修改其中参数

  技术分享图片技术分享图片?

 技术分享图片技术分享图片?

 

1.5 检索UI

在BG下创建一个空物体,命名为 Toggles ,并在其下创建3个子Toggle,分别命名为 Med,Eqi,Goods。

再创建一个 InputField。如图

技术分享图片技术分享图片

技术分享图片技术分享图片?
 

1.6 单个道具Item

创建一张Image, 命名为Cell。在Cell下创建一个子Text。调整两者大小。做成prefab,放到prefab文件夹中

技术分享图片技术分享图片?

 

至此,UI 拼接就完成了。只缺逻辑了~~~

技术分享图片技术分享图片?

 

二. UI逻辑

 

2.1 物品类

首先创建一个C#脚本,命名为 CellItem,代表物品类。一个道具(物品),通常都会有 名字,所属种类,数量,价格等属性,而这里的话,只取前三种属性,完整代码:

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

public class CellItem  {

	private string name;
	private string tag;
	private int number;

	public CellItem(string name,string tag,int num)
	{
		Name = name;
		Tag = tag;
		Number = num;
	}

	public string Name
	{
		get { return name; }
		set { name = value; }
	}

	public string Tag
	{
		get { return tag; }
		set { tag = value; }
	}

	public int Number
	{
		get { return number; }
		set { number = value; }
	}
}
技术分享图片

 

2.2 数据管理类

当我们的角色获得了一个道具之后,理应有一个管理系统将新获得的道具信息添加进去。所以创建一个C#脚本,命名为DataManager

在本文中实现的背包系统中,只管理了三种道具:药品,装备,物品(消耗品)。对应着前文所描述的 UI拼接 。所以在 DataManager 中创建3个 Dictionary ,用来存储道具信息。

	/// <summary>
	/// 药品
	/// </summary>
	public Dictionary<string, CellItem> Medicine = new Dictionary<string, CellItem>();

	/// <summary>
	/// 装备
	/// </summary>
	public Dictionary<string, CellItem> Equipment = new Dictionary<string, CellItem>();

	/// <summary>
	/// 物品(消耗品)
	/// </summary>
	public Dictionary<string, CellItem> Goods = new Dictionary<string, CellItem>();
技术分享图片

  以每个道具的名字为 key。同时为了方便管理,我把DateManager做成了单例模式。不熟悉单例模式的朋友可以参考一下这篇博文    https://www.cnblogs.com/liaoguipeng/p/5130144.html

DataManager完整代码:

 

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

public class DataManager  {

	/// <summary>
	/// 药品
	/// </summary>
	public Dictionary<string, CellItem> Medicine = new Dictionary<string, CellItem>();

	/// <summary>
	/// 装备
	/// </summary>
	public Dictionary<string, CellItem> Equipment = new Dictionary<string, CellItem>();

	/// <summary>
	/// 物品(消耗品)
	/// </summary>
	public Dictionary<string, CellItem> Goods = new Dictionary<string, CellItem>();

	// 定义一个静态变量来保存类的实例
	private static DataManager _Instance;

	// 定义一个标识确保线程同步
	private static readonly object locker = new object();

	public static DataManager GetInstance()
	{
		// 当第一个线程运行到这里时,此时会对locker对象 "加锁",
		// 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁
		// lock语句运行完之后(即线程运行完之后)会对该对象"解锁"
		// 双重锁定只需要一句判断就可以了
		if (_Instance == null)
		{
			lock (locker)
			{
				// 如果类的实例不存在则创建,否则直接返回
				if (_Instance == null)
				{
					_Instance = new DataManager();
				}
			}
		}
		return _Instance;
	}
}
技术分享图片

 

2.3 UI管理类

这个类是整个背包的重点,用于管理第一步中创建的所以UI。

创建一个C#脚本,命名为UIManager。在场景中创建一个空物体,命名为GameManager,并把UIManager挂载上去。

 为了获取第一步中所创建的UI,先定义与之相匹配的变量

	private Toggle selectMed;
	private Toggle selectEqi;
	private Toggle selectGoods;
	private InputField SearchBox;

	private bool isSelectMed;
	private bool isSelectEqi;
	private bool isSelectGoods;

	private Button[] buttons;
	private GameObject lattice;
	private GameObject content;
	private Dictionary<string, CellItem> whole = new Dictionary<string, CellItem>();


?
技术分享图片

其中,toggle 和 InputField 对应之前所制作的 toggle 和输入框;

          中间三个 bool 变量表示检索时是否选择的toggle代表的种类;

          buttons 代表左边6个添加按钮;  lattice 用来得到之前制作的 Cell 的prefab;

          content 代表前文的Panel;

          whole则会记录所以的道具信息;

 

先给这些变量赋值 。我会先给出每个UI所对应的响应方法,方法体中会牵涉其它核心的方法,这个稍后会讲。

private void Awake()
	{
		SearchBox = GameObject.Find("Canvas/BG/InputField").GetComponent<InputField>();
		SearchBox.onValueChanged.AddListener(SearchItem);

		lattice = Resources.Load<GameObject>("Prefab/Cell");

		content = GameObject.Find("Canvas/BG/Panel");
		selectMed = GameObject.Find("Canvas/BG/Toggles/Med").GetComponent<Toggle>();
		selectEqi = GameObject.Find("Canvas/BG/Toggles/Eqi").GetComponent<Toggle>();
		selectGoods = GameObject.Find("Canvas/BG/Toggles/Goods").GetComponent<Toggle>();

		selectMed.onValueChanged.AddListener(OnMedicineToggleClick);
		selectEqi.onValueChanged.AddListener(OnEquipmentToggleClick);
		selectGoods.onValueChanged.AddListener(OnGoodsToggleClick);

		GameObject buts = GameObject.Find("Canvas/Options");
		for (int count = 0; count < buts.transform.childCount; count++)
		{
			GameObject kid = buts.transform.GetChild(count).gameObject;
			kid.transform.GetChild(0).GetComponent<Button>().onClick.AddListener(() => OnAddButtonClick(kid.name));
		}

		isSelectMed = true;
		isSelectEqi = true;
		isSelectGoods = true;
	}
技术分享图片

 而3个bool值变量对应着三个方法。这三个方法作用于检索

	private string IsSelectMed()
	{
		if (isSelectMed) return "药品";
		return " ";
	}

	private string IsSelectEqi()
	{
		if (isSelectEqi) return "装备";
		return " ";
	}

	private string IsSelectGoods()
	{
		if (isSelectGoods) return "物品";
		return " ";
	}
技术分享图片

 3个Toggle也对应了3个响应方法

	public void OnMedicineToggleClick(bool isOn)
	{
		if (isOn)
			isSelectMed = true;
		else
			isSelectMed = false;

		ReflashBackup();
	}

	public void OnEquipmentToggleClick(bool isOn)
	{
		if (isOn)
			isSelectEqi = true;
		else
			isSelectEqi = false;

		ReflashBackup();
	}

	public void OnGoodsToggleClick(bool isOn)
	{
		if (isOn)
			isSelectGoods = true;
		else
			isSelectGoods = false;

		ReflashBackup();
	}
技术分享图片

6个button对应着同一个方法,不过参数不同,参数为每个button所对应的名字,如“X1-装备”,“X2-药品”等

	public void OnAddButtonClick(string Name)
	{
		string[] parts = Name.Split(‘-‘);
		string name = parts[0];
		string tag = parts[1];
		switch(tag)
		{
			case "药品":
				ModifyKey(DataManager.GetInstance().Medicine, name,"药品");
				break;
			case "装备":
				ModifyKey(DataManager.GetInstance().Equipment, name,"装备");
				break;
			case "物品":
				ModifyKey(DataManager.GetInstance().Goods, name,"物品");
				break;
		}
		ModifyKey(whole, name,tag);
		ReflashBackup();
	}
技术分享图片

 

接下来就是比较重要的方法:

一. 修改键对值

当添加一个道具进来时,对其进行管理。应该先检索当前的 Dictionary 中是否有这个道具(key),如果有,则这个道具的数量属性 + 1;如果没有,则添加一个键对值。

	public void ModifyKey(Dictionary<string, CellItem> cells,string name,string tag)
	{
		//先判断是否存在这个Key,没有则add
		if (cells.ContainsKey(name))
		{
			int count = cells[name].Number;
			cells[name].Number = count + 1;
		}
		else
		{
			CellItem cellItem = new CellItem(name,tag,1);
			cells.Add(name, cellItem);
		}
	}
技术分享图片

二. 创建道具单元

在默认显示所有物品的情况下,只要遍历 Dictionary whole就可以创建出所有的道具。而在加入了检索功能后,则需要多进行一个判断,提取出符合检索标准的键对值,这一步我们用 Linq 可以快速获取。然后创建所有道具。

	public void InstantiateCell(Dictionary<string, CellItem> dictionary)
	{
		Dictionary<string, CellItem> SelectObjs = new Dictionary<string, CellItem>();

		var selects = from cell in dictionary
					  where (cell.Value.Tag == IsSelectMed() || cell.Value.Tag == IsSelectEqi() || cell.Value.Tag == IsSelectEqi())
					  select cell;

		foreach(var select in selects)
		{
			SelectObjs.Add(select.Key, select.Value);
		}

		foreach (KeyValuePair<string, CellItem> value in SelectObjs)
		{
			GameObject cell = GameObject.Instantiate(lattice, content.transform);
			SetPicture(value.Key, cell);
			cell.transform.GetChild(0).GetComponent<Text>().text =  value.Value.Number.ToString();
		}
	}
技术分享图片

 创建单元时需要把道具相应的图片显示出来,需要注意的是,我把我的资源放在了Resources/Sprite下,读者的资源路径与我不一致时,仅修改一行代码即可

 

	public void SetPicture(string name,GameObject cell)
	{
		//图片资源路径
		string path = "Sprite/" + name;
		Texture2D tex = Resources.Load<Texture2D>(path);
		Image img = cell.transform.GetComponent<Image>();

		Sprite sp = Sprite.Create(tex, new Rect(new Vector2(0, 0), new Vector2(tex.width, tex.height)), new Vector2(0, 0));
		img.rectTransform.sizeDelta = new Vector2(sp.rect.width, sp.rect.height);
		img.sprite = sp;
	}
技术分享图片

三. 刷新背包

当我们添加一个道具或者按种类检索时,都应该刷新背包。而刷新的思路也很简单:先把 Panel 下的所以 CellItem 删除,然后根据新的 Dictionary 创建新的一批道具单元。

删除 方法

	public void ClearContent()
	{
		Transform[] lattices = content.GetComponentsInChildren<Transform>();
		foreach (Transform lattice in lattices)
		{
			if (lattice.name != "Panel")
			{
				lattice.DetachChildren();
				GameObject.Destroy(lattice.gameObject);
			}
		}
	}
技术分享图片

所以刷新函数如下

	public void ReflashBackup()
	{
		ClearContent();
		InstantiateCell(whole);
	}
技术分享图片

 

四. 按名字搜索

  按名字搜索对应着前文创建的 InputField。思路也是比较简单的,如果字典中有这个道具key,就创建出来

	public void SearchItem(string itemName)
	{
		ClearContent();

		if (whole.ContainsKey(itemName))
		{
			GameObject cell = GameObject.Instantiate(lattice, content.transform);
			SetPicture(itemName, cell);
			cell.transform.GetChild(0).GetComponent<Text>().text = whole[itemName].Number.ToString();
		}

	}
技术分享图片

 

完整代码:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using System.Linq;

public class UIManager : MonoBehaviour {

	private Toggle selectMed;
	private Toggle selectEqi;
	private Toggle selectGoods;
	private InputField SearchBox;

	private bool isSelectMed;
	private bool isSelectEqi;
	private bool isSelectGoods;

	private Button[] buttons;
	private GameObject lattice;
	private GameObject content;
	private Dictionary<string, CellItem> whole = new Dictionary<string, CellItem>();

	private void Awake()
	{
		SearchBox = GameObject.Find("Canvas/BG/InputField").GetComponent<InputField>();
		SearchBox.onValueChanged.AddListener(SearchItem);

		lattice = Resources.Load<GameObject>("Prefab/Cell");

		content = GameObject.Find("Canvas/BG/Panel");
		selectMed = GameObject.Find("Canvas/BG/Toggles/Med").GetComponent<Toggle>();
		selectEqi = GameObject.Find("Canvas/BG/Toggles/Eqi").GetComponent<Toggle>();
		selectGoods = GameObject.Find("Canvas/BG/Toggles/Goods").GetComponent<Toggle>();

		selectMed.onValueChanged.AddListener(OnMedicineToggleClick);
		selectEqi.onValueChanged.AddListener(OnEquipmentToggleClick);
		selectGoods.onValueChanged.AddListener(OnGoodsToggleClick);

		GameObject buts = GameObject.Find("Canvas/Options");
		for (int count = 0; count < buts.transform.childCount; count++)
		{
			GameObject kid = buts.transform.GetChild(count).gameObject;
			kid.transform.GetChild(0).GetComponent<Button>().onClick.AddListener(() => OnAddButtonClick(kid.name));
		}

		isSelectMed = true;
		isSelectEqi = true;
		isSelectGoods = true;
	}

	private string IsSelectMed()
	{
		if (isSelectMed) return "药品";
		return " ";
	}

	private string IsSelectEqi()
	{
		if (isSelectEqi) return "装备";
		return " ";
	}

	private string IsSelectGoods()
	{
		if (isSelectGoods) return "物品";
		return " ";
	}

	public void OnAddButtonClick(string Name)
	{
		string[] parts = Name.Split(‘-‘);
		string name = parts[0];
		string tag = parts[1];
		switch(tag)
		{
			case "药品":
				ModifyKey(DataManager.GetInstance().Medicine, name,"药品");
				break;
			case "装备":
				ModifyKey(DataManager.GetInstance().Equipment, name,"装备");
				break;
			case "物品":
				ModifyKey(DataManager.GetInstance().Goods, name,"物品");
				break;
		}
		ModifyKey(whole, name,tag);
		ReflashBackup();
	}

	public void OnMedicineToggleClick(bool isOn)
	{
		if (isOn)
			isSelectMed = true;
		else
			isSelectMed = false;

		ReflashBackup();
	}

	public void OnEquipmentToggleClick(bool isOn)
	{
		if (isOn)
			isSelectEqi = true;
		else
			isSelectEqi = false;

		ReflashBackup();
	}

	public void OnGoodsToggleClick(bool isOn)
	{
		if (isOn)
			isSelectGoods = true;
		else
			isSelectGoods = false;

		ReflashBackup();
	}

	public void ModifyKey(Dictionary<string, CellItem> cells,string name,string tag)
	{
		//先判断是否存在这个Key,没有则add
		if (cells.ContainsKey(name))
		{
			int count = cells[name].Number;
			cells[name].Number = count + 1;
		}
		else
		{
			CellItem cellItem = new CellItem(name,tag,1);
			cells.Add(name, cellItem);
		}
	}

	public void ReflashBackup()
	{
		ClearContent();
		InstantiateCell(whole);
	}

	public void InstantiateCell(Dictionary<string, CellItem> dictionary)
	{
		Dictionary<string, CellItem> SelectObjs = new Dictionary<string, CellItem>();

		var selects = from cell in dictionary
					  where (cell.Value.Tag == IsSelectMed() || cell.Value.Tag == IsSelectEqi() || cell.Value.Tag == IsSelectEqi())
					  select cell;

		foreach(var select in selects)
		{
			SelectObjs.Add(select.Key, select.Value);
		}

		foreach (KeyValuePair<string, CellItem> value in SelectObjs)
		{
			GameObject cell = GameObject.Instantiate(lattice, content.transform);
			SetPicture(value.Key, cell);
			cell.transform.GetChild(0).GetComponent<Text>().text =  value.Value.Number.ToString();
		}
	}

	public void SetPicture(string name,GameObject cell)
	{
		//图片资源路径
		string path = "Sprite/" + name;
		Texture2D tex = Resources.Load<Texture2D>(path);
		Image img = cell.transform.GetComponent<Image>();

		Sprite sp = Sprite.Create(tex, new Rect(new Vector2(0, 0), new Vector2(tex.width, tex.height)), new Vector2(0, 0));
		img.rectTransform.sizeDelta = new Vector2(sp.rect.width, sp.rect.height);
		img.sprite = sp;
	}

	public void ClearContent()
	{
		Transform[] lattices = content.GetComponentsInChildren<Transform>();
		foreach (Transform lattice in lattices)
		{
			if (lattice.name != "Panel")
			{
				lattice.DetachChildren();
				GameObject.Destroy(lattice.gameObject);
			}
		}
	}

	public void SearchItem(string itemName)
	{
		ClearContent();

		if (whole.ContainsKey(itemName))
		{
			GameObject cell = GameObject.Instantiate(lattice, content.transform);
			SetPicture(itemName, cell);
			cell.transform.GetChild(0).GetComponent<Text>().text = whole[itemName].Number.ToString();
		}

	}
}
技术分享图片

 

有个问题是,当按名字搜索时,添加道具会执行相应的代码。事实上,当游戏中处于检索时一般是不会触发添加物体这种事件的。

三. 总结

总体来说,本文实现的这个背包系统不算复杂,也有人会问,本文一直在使用 Dictionary whole。而在 DataManager 中定义的三个字典除了存储了信息之外,并没有用到别的地方去,似乎是有点多余。

事实上,本文只是着重介绍整个背包系统的原理才使用了whole,可以更为简洁地说明。读者可以思考一下,如果我有着 10个种类的道具,每种道具都需要一页表来显示。如果我使用whole,那么对whole筛选10遍,重复操作多,浪费资源,而如果我有10个于种类对应的字典则可以直接使用,无需筛选。所以,分类保存是很有必要的。

在下一篇中,我会对这个背包系统进行优化和升级

  • 采用读取配置表来录入道具信息
  • 从单页背包升级分页背包
  • 更为完善的检索系统

码字不易,希望这篇文章能对各位读者有所帮助O(∩_∩)O哈哈~

技术分享图片技术分享图片?

 



















以上是关于Unity游戏开发:背包系统的实现的主要内容,如果未能解决你的问题,请参考以下文章

Unity 游戏黑暗之光笔记第五章 背包系统的实现

Unity UGUI有趣应用 -------------------- 背包系统(上)之简易单页背包系统及检索功能的实现

unity游戏里背包的整理功能怎么实现

Unity 3d游戏开发者该掌握哪些内容?

Siki_Unity_2-4_UGUI_Unity5.1 UI 案例学习

unity3d背包怎么扩容