Unity-背包系统
简介
背包是每个成功游戏中不可缺少的,玩家获取的装备与道具将会放入背包,需要时再拿出来使用。如果没有背包来储存玩家在游戏中获得的武器和道具,或许游戏将会变得十分单一枯燥,出招方式一成不变。
有了背包系统,玩家才可以使用不同的武器,搭配不同的道具,使出不同的攻击搭配,从而提高游戏的多样性。
简单构思
首先我们思考一下背包系统的简单逻辑,玩家拾取物品后,背包中出现该物品,点击该物品之后又可以使用。
上述步骤的实现,需要我们完成两个层面的工作,一个是 背包的数据库 ,一个是 背包的UI 。
背包的数据库
数据库逻辑
现在需要构思如何拾取物品后记录数据。这里可以使用 ScriptableObject 来记录各个物品以及背包的数据,当玩家拾取物品后,将物品的信息传入背包的数据库进行记录。
创建数据库的步骤及细节
根据上述简单逻辑,可以得到下列创建数据库的步骤:
- 需要给 每个物品 和 每个背包 都创建自己的 ScriptableObject 数据
- 物品数据中包含自身的各项参数;背包数据需要有将物品数据存进背包的函数
- 同时需要创建 MonoBehavior脚本,并将其挂载在 物品和背包 上用于 控制自身 或 读取数据
- 物品脚本需要获得物品数据,并拥有将该数据放入背包的函数;背包脚本只需要拥有背包数据即可
- Player在触碰到物体后,物体本身的脚本将会触发,并通过函数将自己的数据加入背包中
(还需要注意以下细节:
- 物品数据中只有自身的个数,无法记录相同物品在背包中的个数,需要 创建一个类来保存物品数据及个数
- 不同的物品会需要不同的物品数据,需根据需求获取不同物品独有的 ScriptableObject数据
- 背包数据中,存储数据的算法需要做到最简(?)
代码样例_物品数据
public enum ItemType{ Useable, Weapon, Armor }//控制物品类型
[CreateAssetMenu(fileName = "New Item", menuName = "Inventory/Item Data")]
public class ItemData_SO : ScriptableObject
{
public bool stackable; //判断物品是否可以堆叠
public ItemType itemType; //物品对应类型
public string itemName; //物品名字
public Sprite itemIcon; //物品图片
public int itemAmount; //物品个数
[TextArea]
public string description = ""; //用于描述物品信息
////////////////////////////////////////////////////////
/////////////////以下是各个物品的独有数据/////////////////
////////////////////////////////////////////////////////
[Header("Useable")] //使用品
public UseableItemData_SO useableItemData;
[Header("Weapon")] //武器
public GameObject weaponPrefabs;
public AttackData_SO attackData;
}
代码样例_背包数据
////////////////////////////////////////////////////////////
////////////////为记录背包中的物品个数创建该类/////////////////
///////////////////////////////////////////////////////////
[System.Serializable] //为使该类在Unity窗口中显示,需要序列化
public class InventroyItem
{
public ItemData_SO itemData; //物品信息
public int amount; //物品数量
}
////////////////////////////////////////////////////////////
////////////////为记录背包中的物品个数创建该类/////////////////
///////////////////////////////////////////////////////////
[CreateAssetMenu(fileName = "New Inventory", menuName = "Inventory/Inventory Data")]
public class InventoryData_So : ScriptableObject
{
public List<InventroyItem> items = new List<InventroyItem>();
//将物品数据保存进背包
public void AddItem(ItemData_SO itemData, int amount){
bool found = false; //判断物品是否被找到
//如果物品是可堆叠的,则先遍历列表
if(itemData.stackable){
foreach (var item in items){
if(item.itemData == itemData){
//如果背包中有同类型道具,则直接增加数量,并标明物品已经找到
item.amount += amount;
found = true;
break;
}
}
}
if(!found){ //如果物品没有被找到
for(int i = 0; i < items.Count; i++){
//则同样遍历整个列表,找到第一个空位并且添加
if(items[i].itemData == null){
items[i].itemData = itemData;
items[i].amount = amount;
break;
}
}
}
}
}
代码样例_背包脚本
public class InventroyManager : Singleton<InventroyManager> //继承单例模式
{
//TODO:最后复制模板保存数据
[Header("Inventory Data")] //背包数据
public InventoryData_So inventoryData;
//剩余不同背包........
void Start()
{
inventoryUI.RefreshUI();
//剩余不同背包.........
}
//UI部分代码
}
/*该类还有很多尚未完成的部分,未完成的部分会在下面讲述背包UI时完善*/
代码样例_物品脚本
public class ItemPickUp : MonoBehaviour
{
public ItemData_SO itemData;
void OnTriggerEnter(Collider other)
{
if(other.CompareTag("Player"))
{
//TODO:将物品添加到背包
InventroyManager.Instance.inventoryData.AddItem(itemData, itemData.itemAmount);
//........
//TODO:装备武器
// GameManager.Instance.playerStats.EquipWeapon(itemData);
Destroy(gameObject);
}
}
}
背包的UI
完成上述背包的数据库后,还需要根据数据库完成背包的UI部分,让玩家能够更直观的管理背包
背包UI逻辑
要完成背包UI,就需要 从背包的数据库中获取背包中各项物品的各项数据(图片、数量......),然后在背包UI上显示。
创建背包UI的步骤及细节
编写背包UI的 代码 需要三层,第一层是 物品UI层(ItemUI),第二层是 物品栏(SlotHolder),第三层是背包层(ContainerUI),最后需要将背包层挂再在 InventoryManager 上供其他类调用
根据背包UI代码所分的三层,背包UI的 GameObject 同样需要分成三层。第一层 挂载ItemUI 作为物品UI层,同时子物体需要有 图片(Image) 跟 文本(Text);第二层 挂载SlotHolder 作为物品栏,子物体需要 ItemUI;第三层 挂载Container 作为背包层,子物体需要 n个 SlotHolder
(下面将会通过代码实例更详细的讲述
代码样例_ItemUI
public class ItemUI : MonoBehaviour
{
public Image icon = null; //物品图片
public Text amount = null; //物品个数
public InventoryData_So Bag { get; set; } //物品所属背包,在SlotHolder中赋值
public int Index { get; set; } = -1; //物品代号
public void SetupItemUI(ItemData_SO item, int itemAmount) //更新图片数据
{
if(itemAmount == 0) //当数量为零时,清除背包中该位置的物品数据
{
Bag.items[Index].itemData = null;
icon.gameObject.SetActive(false);
return;
}
if(item != null) //如果Item不等于空, 则根据item数据显示物品
{
icon.sprite = item.itemIcon;
amount.text = itemAmount.ToString();
icon.gameObject.SetActive(true);
}
else
{
icon.gameObject.SetActive(false);
}
}
}
代码样例_SlotHolder
public enum SlotType { BAG, WEAPON, ARMOR, ACTION } //记录物品栏类型(背包、武器栏、防具栏、道具栏)
public class SlotHolder : MonoBehaviour, IPointerClickHandler
{
public SlotType slotType; //背包栏类型
public ItemUI itemUI; //外部赋值
//鼠标点击使用物品函数........
public void UpdateItem() //更新物品
{
switch(slotType)
{
//根据背包栏类型找对应数据库
case SlotType.BAG:
itemUI.Bag = InventroyManager.Instance.inventoryData;
break;
case SlotType.WEAPON:
itemUI.Bag = InventroyManager.Instance.equipmentData;
//需判断武器栏内是否为空,不为空则装备对应武器,2D不需要装备武器
break;
case SlotType.ARMOR:
itemUI.Bag = InventroyManager.Instance.equipmentData;
break;
case SlotType.ACTION:
itemUI.Bag = InventroyManager.Instance.actionData;
break;
}
//根据背包数据库中的信息更改背包UI
var item = itemUI.Bag.items[itemUI.Index];
itemUI.SetupItemUI(item.itemData, item.amount);
}
}
代码样例_ContainerUI
public class ContainerUI : MonoBehaviour
{
public SlotHolder[] slotHolders;
public void RefreshUI() //刷新UI
{
for(int i = 0; i < slotHolders.Length; i++)
{
//根据SlotHolder类的数组的序号,刷新各个背包栏的标号
slotHolders[i].itemUI.Index = i;
slotHolders[i].UpdateItem();
}
}
}
实现拖拽物品
实现物品拖拽,可以直接使用Unity自带的拖拽物品函数,只需要实现以下几个接口:
public class DragItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
public void OnBeginDrag(PointerEventData eventData){} //开始拖拽
public void OnDrag(PointerEventData eventData){} //拖拽中
public void OnEndDrag(PointerEventData eventData){} //拖拽结束
}
(这几个接口中传入的参数 eventData 包含了关于鼠标的各项参数,有需要的可以查阅Unity官方手册
需要知道的是:
- 需要拖拽的物品是挂载有 ItemUI 的 GameObject ,所以该类需要挂载在上述 GameObject 上
- 为了防止玩家将物品拖拽进错的位置,需要 记录物品原本的位置 并在其 位置错误之后,将物品复原
- 在拖动结束后,需要进行一系列判断,保证物品交换无误(直接交换、堆叠 或 回到原位)
(下面用代码样例进行详细说明
代码样例
[RequireComponent(typeof(ItemUI))]
public class DragItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
ItemUI currentItemUI;
SlotHolder currentHolder;
SlotHolder targetHolder;
void Awake()
{
currentItemUI = GetComponent<ItemUI>();
currentHolder = GetComponentInParent<SlotHolder>();
}
////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////// 以下各个PointerEventData类会有与鼠标相关的各个数据///////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////
public void OnBeginDrag(PointerEventData eventData) //开始拖拽
{
//记录原始数据
InventroyManager.Instance.currentDrag = new InventroyManager.DragData();
InventroyManager.Instance.currentDrag.originalHolder = GetComponentInParent<SlotHolder>();
InventroyManager.Instance.currentDrag.originalParent = (RectTransform)transform.parent;
//为了使物品不被物品栏遮挡,将其设置为一个层级更高的画布的子物体
transform.SetParent(InventroyManager.Instance.DragCanvas.transform, true);
}
public void OnDrag(PointerEventData eventData) //拖拽中
{
//跟随鼠标位置
transform.position = eventData.position;
}
public void OnEndDrag(PointerEventData eventData) //拖拽结束
{
//放下物品 交换数据
if(EventSystem.current.IsPointerOverGameObject()) //判断是否在UI上
{
/*判断是否在对应的物品栏中,因为InventoryManager中包含了各个背包UI的数据
所以将判断函数写在InventoryManager中,方便调用*/
if(InventroyManager.Instance.CheckInActionUI(eventData.position) || InventroyManager.Instance.CheckInEquipmentUI(eventData.position) ||
InventroyManager.Instance.CheckInInventoryUI(eventData.position))
{
//给targetHolder赋值,如果找到了SlotHolder组件,则直接赋值,如果没有找到,则在父级中找
if(eventData.pointerEnter.gameObject.GetComponent<SlotHolder>())
{
targetHolder = eventData.pointerEnter.gameObject.GetComponent<SlotHolder>();
}
else
{
targetHolder = eventData.pointerEnter.gameObject.GetComponentInParent<SlotHolder>();
}
switch(targetHolder.slotType) //判断targetHolder的物品栏类型
{
case SlotType.BAG:
SwapItem();
break;
case SlotType.WEAPON:
if(currentItemUI.Bag.items[currentItemUI.Index].itemData.itemType == ItemType.Weapon)
{
SwapItem();
}
break;
case SlotType.ARMOR:
if(currentItemUI.Bag.items[currentItemUI.Index].itemData.itemType == ItemType.Armor)
{
SwapItem();
}
break;
case SlotType.ACTION:
if(currentItemUI.Bag.items[currentItemUI.Index].itemData.itemType == ItemType.Useable)
{
SwapItem();
}
break;
}
currentHolder.UpdateItem();
targetHolder.UpdateItem();
}
}
//重置物品的层级关系
transform.SetParent(InventroyManager.Instance.currentDrag.originalParent);
RectTransform t = transform as RectTransform;
//为了防止图片出现堆放位置错误的问题
t.offsetMax = Vector2.one * 40;
t.offsetMin = -Vector2.one * 40;
}
//交换物品
public void SwapItem()
{
//先获得两个 slotHolder 对应的物品
var targetItem = targetHolder.itemUI.Bag.items[targetHolder.itemUI.Index];
var tempItem = currentHolder.itemUI.Bag.items[currentHolder.itemUI.Index];
//判断两个物品是否一样
bool isSameItem = tempItem.itemData == targetItem.itemData;
if(isSameItem && targetItem.itemData.stackable)
{
targetItem.amount += tempItem.amount;
tempItem.itemData = null;
tempItem.amount = 0;
}
else
{
currentHolder.itemUI.Bag.items[currentHolder.itemUI.Index] = targetItem;
targetHolder.itemUI.Bag.items[targetHolder.itemUI.Index] = tempItem;
}
}
}
标签:背包,itemData,系统,itemUI,Unity,UI,物品,public From: https://www.cnblogs.com/MMMMrD/p/17832317.html