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

一、UI规划

Unity游戏开发:背包系统的实现
以Cotton Puzzle中的背包规划为例,咱们需求有物品展现栏、物品切换按键和物品提示信息等部分。
Unity游戏开发:背包系统的实现

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

Unity游戏开发:背包系统的实现
界说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;
    }