原本以为这东西会如我以往那般代码量并不会过多,但事实是,代码量远超出了我的想象
首先,这个系统参考了codeMonkey大佬的教程,以下是他视频B站的搬运视频的链接,搬运视频的简介里会有油管的原视频链接,我个人建议去看大佬的教程,人家讲的确实好
【搬运】Unity简单背包系统实现 Simple Inventory System in Unity_哔哩哔哩_bilibili
在原来大佬教程的理解上(虽然我现在感觉我是理解了个寂寞)我认为背包系统应该要有方便保存数据的特性,所以保存数据的对象应当尽量的小,并且应尽可能的方便在开发时能尽可能方便的加入新的物品,所以我尝试实现了自己的想法。
接下来是我个人对于背包系统的理解,一种是刻板印象里的背包,如《像素地牢》里面的人物背包主要强调用于保存玩家获得的物品,而另一种并不像刻板印象的背包,但它又如背包那样可以保存,获取,调用,但相比于刻板印象中的背包强调 “保存” 而它是强调 “调用”(类似于《元气骑士》里的武器吧) 与其叫她为背包或许叫它装备更为合适,可能这种的 “背包”真的被称为装备吧,学的不多欢迎指教,总之,不同的 “背包” 侧重点不同,因此背包系统不应像保存数据的系统和对象池一样,有着固定通用的设计思路,应该根据项目的需要定制。
说回正题,因为这个系统涉及的类太多了所以我制作了一个关系图,这样或许能直观点,但是相比于以往少了大量的注释,可能日后会补上吧(终于我也要变成自己讨厌的人了吗?)
以下是各类的脚本代码
首先是ItenData和ItemDataType类(我把它们写一起了)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(menuName = "ZiZhiLei/物品类", fileName = "ItemData")]
public class ItemData : ScriptableObject //为什么要创建一个ScriptableObject容器而不是写一个脚本的呢
//因为创建脚本要把他挂载到游戏对象上,甚至还要放到场景中(当然,有预制体可能不用)
//而ScriptableObject不用
{
[Header("元素号就是物品对象在list中的位置")]
public List<ItemDataType> list=new List<ItemDataType>();
}
[System.Serializable]
public class ItemDataType
{
//这里的这些数据感觉可以放在一个结构体里
[Tooltip("物品的名字")]
public string name;
[Tooltip("物品的图标")]
public Sprite ItemSprite;
[Tooltip("物品能否堆叠")]
public bool IsStacking;
[Tooltip("物品的数值")]
public float ItemValue;
}
criptableObject容器就跳过了(详情可以参考我以前的帖子)
ItemDataManager类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ItemDataManager : MonoBehaviour //这个类似于中介将背包类
{
//说一下我对于C#中对委托这一概念的理解,
//委托可以在一个类中调用事先存入的一个另一个类的方法,
//而调用时这个方法还是在拥有哪个方法的类中运行的,
//因此委托只是一个类去调用令一个类的“权限”,
//但是这种调用相比实例化后的直接调用可以更加灵活
public ItemData _itemData;//这里未来可能要改成主动获取而不是挂载
private void Start()
{
}
public ItemDataType GatItemData(int Number)//获取变量
{
if (Number <= _itemData.list.Count - 1)
{
return _itemData.list[Number];
}
else
{
return new ItemDataType { name = "itemData内没有数据或者索引超出list的长度了" };
}
}
public void GetItemClass(int ItemNumber) //获取变量的方法
{
switch (ItemNumber) //未来用的时候要传入调用他的物品类
{
case 2:
Debug.Log("使用了医疗箱");
break;
case 0:
Debug.Log("使用了回复药水");
break;
case 1:
Debug.Log("使用了魔法药水");
break;
default:
Debug.Log("没有这个物品");
break;
}
}
}
ItemBag类和BagItemAmount类(这两也写一起了)
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ItemBag : MonoBehaviour
{
[Tooltip("背包内的数据")]
private List<BagItemAmount> BagItems = new List<BagItemAmount>() ;
[Tooltip("物品管理类的对象")]
public ItemDataManager _ItemDataManager ;//这个未来可能也要改成主动获取
[Tooltip("背包的单例")]
public static ItemBag Instance ;
private void Awake()
{
Instance = this;
}
private void Start()//以后可能要在这里调用JSON系统(或者其他的读取硬盘数据系统)
{
//Instance = this;
}
public void UseingItemClassFromBag(int ItemNumber)
{
if (BagItems[ItemNumber].amount != 0)
{
_ItemDataManager.GetItemClass(ItemNumber);
GetItemDataFromBag(ItemNumber, 1 );
}
else
{
//这里调用物品已经用完了的方法
Debug.Log("物品已用完");
}
}
public ItemDataType GetItemDataFromBag(int ItemNumber, int GetAmount) //从背包获取数据(仅引用的话数量就填零)
{
for (int i = 0; i < BagItems.Count; i++)
{
if (this.BagItems[i].ItemNumber == ItemNumber) //查找包里还有没有一样的对象
{
this.BagItems[i] = new BagItemAmount
{
amount = this.BagItems[i].amount - GetAmount,
ItemNumber = ItemNumber,
};
return _ItemDataManager.GatItemData(ItemNumber);
}
}
return null;
}
//用的时候记得搞个东西判定里面到底有没有东西
public ItemDataType GetItemDataFromBag(int ItemNumber)//加载背包表格时用 因此没有计数功能
{
if (ItemNumber < BagItems.Count || ItemNumber == 0)
{
return _ItemDataManager.GatItemData(BagItems[ItemNumber].ItemNumber );
}
Debug.Log("引用失败");
return null;
}
public void AddItemToBag(int amount,int ItemNumber,bool IsStacking) //往背包内加入数据
{
if (IsStacking)
{
for (int i = 0; i < BagItems.Count; i++)
{
if (this.BagItems[i].ItemNumber == ItemNumber) //查找包里还有没有一样的对象
{
this.BagItems[i]= new BagItemAmount
{
amount = this.BagItems[i].amount + amount,
ItemNumber = ItemNumber,
};
return;
}
}
BagItems.Add(new BagItemAmount
{
amount = amount,
ItemNumber = ItemNumber,
});
}
else
{
BagItems.Add(new BagItemAmount
{
amount=amount,
ItemNumber=ItemNumber,
} );
}
}
public int GetAmountByItemNumber(int a) //获取某物品的数量如果里面的物体有堆叠数量限制的话,
{ //这个方法就要改,因为会存在多个可堆叠物品
for (int i = 0; i < BagItems.Count; i++)
{
if (this.BagItems[i].ItemNumber == a) //查找包里还有没有一样的对象
{
return this.BagItems[i].amount;
}
}
return 0;
}
public void CleanBagList() //清理List里面的amount元素为0的元素
{
for (int i = 0; i < this.BagItems.Count; i++)
{
if (this.BagItems[i].amount == 0) //查找包里还有没有一样的对象
{
this.BagItems.RemoveAt(i);
}
}
}
public int GetBagListLength() //获取list的长度
{
return BagItems.Count;
}
public int GetBagItemAmountByLength(int A) //使用list的索引查询某物品的数量
{
return BagItems[A].amount;
}
public int GetBagItemNumberByLength(int A) //使用list的索引查询某物品的编号
{
return BagItems[A].ItemNumber;
}
public struct BagItemAmount //背包里的数据对象
{
public int amount;//物品数量
public int ItemNumber;//物品的编号 元素号就是物品对象在ScriptableObject容器中的位置
}
}
下面的就都是一些用于测试背包系统的类了它们不属于储存部分
BagUIManager类
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;
public class BagUIManager : MonoBehaviour
{
[Tooltip("背包的背景")]
private GameObject _ItemBotton;
[Tooltip("计数变量现在的作用是保证他只运行一次")]
private int JiShu;
public static BagUIManager Instance;
void Start()
{
JiShu = 0;
Instance = this;
}
// Update is called once per frame
void Update()
{
//因为PLayAAA类会在start时加载往背包里加入数据所以第一次加载时不能在
//start时加载,这样会什么都加载不出来
if (JiShu == 0)
{
//Debug.Log(JiShu == 0);
LoadBagUI();
JiShu=87;
//Debug.Log(JiShu);
}
}
public void LoadBagUI()
{
_ItemBotton = this.transform.Find("ItemButton").gameObject;
//_ItemBotton.SetActive(true);
int ListLength = ItemBag.Instance.GetBagListLength();//获取背包的长度
int BagUIChildNumber = this.transform.childCount;//背包里面的子物体数量
for (int i=1; i<BagUIChildNumber && BagUIChildNumber>0 ; i++) //删除所有的东西(除了模板)
{
GameObject A = this.transform.GetChild(i).gameObject;
GameObject.Destroy(A);
}
for (int i=0;i<ListLength && ListLength!=0 ;i++ ) //重新加载所有的物品
{
//复制物体
GameObject A = GameObject.Instantiate(_ItemBotton, this.transform);
A.SetActive(true);
ItemUIButton BBoutton = A.GetComponent<ItemUIButton>();//获得按钮脚本的对象
BBoutton.ItemNumber = ItemBag.Instance.GetBagItemNumberByLength(i);
BBoutton.OnCli += LoadBagUI;//这里处于练习的目的使用了一下委托,实际上我个人觉得应该是没必要用的
GameObject A1 = A.transform.Find("ItemNumberText").gameObject;
A1.GetComponent<Text>().text = ItemBag.Instance.GetItemDataFromBag(i).name;
GameObject A2 = A.transform.Find("Image").gameObject;
A2.GetComponent<SpriteRenderer>().sprite = ItemBag.Instance.GetItemDataFromBag(i).ItemSprite;
GameObject A3 = A.transform.Find("ItemAmount").gameObject;
A3.GetComponent<Text>().text = ItemBag.Instance.GetBagItemAmountByLength(i).ToString();
}
}
}
ItemUIButton类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class ItemUIButton : MonoBehaviour
{
public int ItemNumber;
public Action OnCli;
void Start()
{
Debug.Log("测试");
}
// Update is called once per frame
void Update()
{
}
public void AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA()
{
ItemBag.Instance.UseingItemClassFromBag(ItemNumber);
Debug.Log("测试点击"+ItemNumber);
OnCli?.Invoke();
}
}
HeanathPotionWorld类,ManaPotionInWorld类,MedkitInworld类(它们长得基本都一样不过有些地方的参数不一样用于区分不同的药水)这里只写了HeanathPotionWorld类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HeanathPotionWorld : MonoBehaviour
{
private BoxCollider2D _BoxCollider;
void Start()
{
//Debug.Log("dfg");
_BoxCollider = GetComponent<BoxCollider2D>();
}
// Update is called once per frame
void Update()
{
}
void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.tag == "Player")
{
ItemBag.Instance.AddItemToBag(4, 0, true);
BagUIManager.Instance.LoadBagUI();
Debug.Log("测试添加回复药水");
}
}
}
PlayAAA类,用于模拟玩家的类(这个命名方式很抽象吧属实是反面教材了)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class PLayAAA : MonoBehaviour
{ //用来测试的类
//public event EventHandler AAA;
public float Speed;
public ItemBag _bag;
// Start is called before the first frame update
void Start()
{
_bag.AddItemToBag(2,0,true);
_bag.AddItemToBag(3,1,true);
_bag.AddItemToBag(1, 2, false);
//Debug.Log(_bag.GetItemDataFromBag(0, 0).name+"的数量是" + _bag.GetAmountByItemNumber(0));
//Debug.Log(_bag.GetItemDataFromBag(1, 0).name+"的数量是" + _bag.GetAmountByItemNumber(1));
//Debug.Log(_bag.GetItemDataFromBag(2, 0).name + "的数量是" + _bag.GetAmountByItemNumber(2));
}
// Update is called once per frame
void Update()
{
Move();
}
private void Move()
{
float x = Input.GetAxis("Horizontal");
float y = Input.GetAxis("Vertical");
Vector3 vector3 = new Vector3(x,y,0);
this.transform.Translate(vector3 * Speed * Time.deltaTime );
}
}
以上的就是全部的代码了,因为里面很多地方都没有注释,而且存在许多的冗余代码,因此我还是建议大家去看大佬的教程,但是如果还是想知道,那欢迎私信,我会尽我所能的为你解释具体含义
第一次写背包系统,欢迎大佬们的拷打,毫无疑问,这对我非常重要,谢谢