本人能力有限,如有不足还请斧正,理论分析链接如下:
目录
[InventoryManager 仓库的管理](#InventoryManager 仓库的管理)
[SlotData 物品的增删查改*(逻辑层)](#SlotData 物品的增删查改*(逻辑层))
[1.SlotUI 将物品绘制到格子上](#1.SlotUI 将物品绘制到格子上)
[BagUI 将格子数据绘制到界面上](#BagUI 将格子数据绘制到界面上)
[ItemMoveHandler 物品的增删查改*(表现层)](#ItemMoveHandler 物品的增删查改*(表现层))
[Plyaer 拾取与丢弃](#Plyaer 拾取与丢弃)
1.M层:数据存储
物品
一个2D的物品必不可少的就是类型,图标,物体(这个指Gameobjcet和其名字)堆叠数量
拓展思路:稀有度,价值,耐久,描述,重量,
cs
using UnityEngine;
/// <summary>
/// 不同的物品可以有不同的类型 以用作其他逻辑的判断
/// 举例:食物可以吃但通常不能打人
/// 武器通常不能吃但可以打人
/// </summary>
public enum ItemType{
a,
b,
c,
d
}
/// <summary>
///
/// </summary>
[CreateAssetMenu()]
public class ItemData:ScriptableObject
{
public ItemType itmeType;
public Sprite itemSprite;
public GameObject itemPrefab;
public int maxCount =1;//最大堆叠数量:默认:1
}
另外可能需要一个外部类去定义其属性,比如可拾取的,不可拾取的 这个的作用体现在玩家拾取方面,需要提前知道一下
cs
public class Pickable : MonoBehaviour
{
public ItemType thisItemType;
}
so对象举例
仓库容器
先认识一个单词:Inventory,仓库,库存 在本文中特指背包类
之所以要对Inventory写为So,是因为游戏中可能存在许多的仓库,在玩家手中叫做背包,物品栏,在NPC手中就可能叫做商店,锻造了,因此:
cs
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu()]
public class InventoryData :ScriptableObject{
//TODO :拓展 仓库名称,最大容量,自动扩容
public List<SlotData> slotList;
}
创建两个
可以看到其容量是我们自定义的,也就是写死的没有自动扩容功能
因此在今后的拓展之中可以优化
加载方式
2.M层:逻辑撰写
InventoryManager 仓库的管理
我们需要一个仓库管理器去管理所有的背包所以用到了InventoryData 对象
另外,管理所有物品的时候也应需要一个容器去预加载,所以用到了字典
可以先不看这一行:我们需要为外部提供提供向背包添加物品的方法,所以用到了单例模式
cs
using System.Collections.Generic;
using UnityEngine;
public class InventoryManager : MonoBehaviour {
private static InventoryManager instance;
public static InventoryManager Instance => instance;
public InventoryData BagInventory;
public InventoryData ToolBarInventory;
public Dictionary<ItemType, ItemData> itemDataDict = new Dictionary<ItemType, ItemData>();
private void Awake() {
if (instance == null) {
instance = this;
}
else {
Destroy(this);
}
InitInventoryData();
}
private void Start() {
}
/// <summary>
/// 加载本地物品和已有的背包数据
/// </summary>
private void InitInventoryData() {
ItemData[] itemDatas = Resources.LoadAll<ItemData>("Itmes");
foreach (var singleItem in itemDatas) {
itemDataDict.Add(singleItem.itmeType, singleItem);
}
BagInventory = Resources.Load<InventoryData>("Inventorys/MyInventory");
ToolBarInventory= Resources.Load<InventoryData>("Inventorys/ToolBarInventory");
}
/// <summary>
/// 通过指定的物品类型从字典中获取物品数据
/// </summary>
/// <param name="type">物品类型</param>
/// <returns>如果找到则返回物品数据,否则返回null</returns>
private ItemData GetItem(ItemType type) {
if (itemDataDict.TryGetValue(type, out var item))
return item;
else {
Debug.LogError("未找到指定物品");
return null;
}
}
/// <summary>
/// 向背包中添加物品
/// </summary>
/// <param name="itemType">要添加的物品类型</param>
public void AddItemToInventory(ItemType itemType) {
ItemData aItem = GetItem(itemType);
// 情况1:字典中有该物品且格子未满 如果格子满了将会走情况2
foreach (var slot in BagInventory.slotList) {
if (slot.item == aItem && slot.CanAddItem()) {
slot.Add();
return;
}
}
// 情况2:格子为空,添加物品
foreach (var slot in BagInventory.slotList) {
if (slot.count == 0) {
slot.AddItem(aItem);
return;
}
}
// 情况3:背包已满
Debug.LogWarning($"未能成功添加物品 {aItem.name} 因为 {BagInventory.name} 已经满了");
}
}
SlotData 物品的增删查改*(逻辑层)
这个类将目光聚焦于背包中的格子,因为它才是背包的最小单位
格子需要持有物品类,并且有一个当前数量的变量
每次修改格子物品信息的时候需要给到其通知(也就是M层----->V层这一步),所以需要做发布者发布一个委托,让V层去监听
cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public class SlotData {
public ItemData item;
public int currentCount = 0; // 物品数量
private Action OnChange;
#region 增(Add)
// 添加物品到槽位
public void Add(int numToAdd = 1) {
this.currentCount += numToAdd;
OnChange?.Invoke();
}
// 设置槽位的物品和数量
public void AddItem(ItemData item, int count = 1) {
this.item = item;
this.currentCount = count;
OnChange?.Invoke();
}
#endregion
#region 删(Remove)
// 减少槽位中的物品数量
public void Reduce(int numToReduce = 1) {
currentCount -= numToReduce;
if (currentCount == 0) {
Clear();
}
else {
OnChange?.Invoke();
}
}
// 清空槽位
public void Clear() {
item = null;
currentCount = 0;
OnChange?.Invoke();
}
#endregion
#region 查(Check)
// 检查槽位是否为空
public bool IsEmpty() {
return currentCount == 0;
}
// 检查槽位是否可以添加物品
public bool CanAddItem() {
return currentCount < item.maxCount;
}
// 获取槽位的空余空间
public int GetFreeSpace() {
return item.maxCount - currentCount;
}
#endregion
#region 改(Update)
// 移动槽位数据
public void MoveSlot(SlotData data) {
this.item = data.item;
this.currentCount = data.currentCount;
OnChange?.Invoke();
}
// 添加监听器
public void AddListener(Action OnChange) {
this.OnChange = OnChange;
}
#endregion
}
就这么点东西,至此M层就算是写完辣
3.V层:搭建UI
1.SlotUI 将物品绘制到格子上
自然其应持有SlotData类,并订阅其发布的委托,回调函数就是ChangeUI方法
SetData实现的就是C---->M层,GetData是V---->C层
cs
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class SlotUI : MonoBehaviour,IPointerClickHandler
{
protected SlotData slotData;
protected Image icon;
protected TextMeshProUGUI num;
private void Start() {
icon = transform.Find("icon").GetComponent<Image>();
num = transform.Find("num").GetComponent<TextMeshProUGUI>();
}
public SlotData GetData(){
return slotData;
}
/// <summary>
/// 为该脚本上的对象赋值一个SlotData
/// </summary>
public void SetData(SlotData slotData) {
this.slotData = slotData;
//事件监听 - 订阅者
slotData.AddListener(UpdateUI2Slot);
UpdateUI2Slot();
}
/ <summary>
/ 监听对象
/ </summary>
//public void ChangeUI(){
// UpdateUI2Slot();
//}
private void UpdateUI2Slot(){
if (slotData==null || slotData.item == null || slotData.currentCount <= 0) {
icon.enabled = false;
num.enabled = false;
}
else {
icon.enabled = true;
num.enabled = true;
icon.sprite = slotData.item.itemSprite;
num.text = slotData.currentCount.ToString();
}
}
public void OnPointerClick(PointerEventData eventData) {
Debug.Log("发生了点击");
ItemMoveHandler.Instance.OnSlotClick(this);
}
}
选中后将物品依附到鼠标上面
2.三个类
BarUI工具栏的选中与数字切换
cs
using System.Collections.Generic;
using UnityEngine;
public class BarUI : MonoBehaviour
{
//工具栏ui列表 先给这个列表对应好ui格子,之后将数据列表粘贴到ui列表中 并更新UI即完成可视化
[SerializeField]private List<BarSlotUI> barSlotUIList;
[SerializeField] private GameObject ContentList;
[SerializeField] private BarSlotUI curSelectBarSlotUI;//当前选中的工具栏的格子
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
barSlotUIList = new List<BarSlotUI>();
ContentList =transform.Find("Bar/ContentList").gameObject;
InitSlotUI();
}
private void Update() {
SelectBarSlot();
}
/// <summary>
/// 默认curSelectBarSlotUI为空,所以首次不会进入第二个if
/// 当第二次选中格子时,curSelectBarSlotUI指向第一个格子,所以不为空就高亮第一个格子
/// </summary>
public void SelectBarSlot(){
for (int i = (int)KeyCode.Alpha1; i < (int)KeyCode.Alpha9 + 1; i++) {
if (Input.GetKeyDown((KeyCode)i)) {
if (curSelectBarSlotUI != null) {
curSelectBarSlotUI.BarSlotLight();
}
int index = i - (int)KeyCode.Alpha1;
curSelectBarSlotUI = barSlotUIList[index];
curSelectBarSlotUI.BarSlotDark();
}
}
}
public void InitSlotUI() {
if (ContentList != null) {
foreach (BarSlotUI barSlotUI in ContentList.GetComponentsInChildren<SlotUI>()) {
barSlotUIList.Add(barSlotUI);
}
}
UpdataUI();
}
public void UpdataUI() {
for (int i = 0; i < InventoryManager.Instance.ToolBarInventory.slotList.Count; i++) {
barSlotUIList[i].SetData(InventoryManager.Instance.ToolBarInventory.slotList[i]);
}
}
}
BarSoltUI格子高亮
cs
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class BarSlotUI : SlotUI
{
[SerializeField] private Sprite slotLight;
[SerializeField] private Sprite slotDark;
[SerializeField]private Image thisImage;
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
InitBar_Slot();
}
public void InitBar_Slot(){
this.icon = transform.Find("Bar_icon").GetComponent<Image>();
this.num = transform.Find("Bar_num").GetComponent<TextMeshProUGUI>();
thisImage =GetComponent<Image>();
slotLight = Resources.Load<Sprite>("SlotUI/slotLight");
slotDark = Resources.Load<Sprite>("SlotUI/slotDark");
}
public void BarSlotLight(){
thisImage.sprite = slotLight;
}
public void BarSlotDark() {
thisImage.sprite = slotDark;
}
}
BagUI 将格子数据绘制到界面上
cs
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class BagUI : MonoBehaviour {
[SerializeField] private Button close;
[SerializeField] private GameObject BG;
[SerializeField] private GameObject slotGrid;
[SerializeField] private List<SlotUI> soltuiList = new List<SlotUI>();
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start() {
InitElement();
InitSlotUI();
}
// Update is called once per frame
void Update() {
ColseBag();
}
public void InitElement() {
BG = transform.Find("BG").gameObject;
close = transform.Find("BG/BgElement/Close").GetComponent<Button>();
slotGrid = transform.Find("BG/SlotGrid").gameObject;
if (close != null) {
close.onClick.AddListener(() => {
if (BG != null)
BG.SetActive(!BG.activeSelf);
else {
Debug.LogWarning("没找到BG对象");
return;
}
});
}
else
Debug.LogWarning("没有加载到close按钮");
}
public void UpdataUI() {
for (int i = 0; i < InventoryManager.Instance.BagInventory.slotList.Count; i++) {
soltuiList[i].SetData(InventoryManager.Instance.BagInventory.slotList[i]);
}
}
public void InitSlotUI() {
if (slotGrid != null) {
foreach (SlotUI slotUi in slotGrid.GetComponentsInChildren<SlotUI>()) {
soltuiList.Add(slotUi);
}
}
UpdataUI();
}
public void ColseBag() {
if (Input.GetKeyDown(KeyCode.Tab))
BG.SetActive(!BG.activeSelf);
}
}
4.C层:玩家控制类
ItemMoveHandler 物品的增删查改*(表现层)
这个类其实应该好好讲一讲,但是笔者写了一个多小时雀氏有点累了,反正都是一些常见的逻辑 所以用ai给到注释,如果前面的代码都理解了,那么这里一点也不难
cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class ItemMoveHandler : MonoBehaviour {
// 单例模式的实例
public static ItemMoveHandler Instance {
get; private set;
}
// 图标
private Image icon;
// 选中的槽数据
private SlotData selectedSlotData;
// 玩家对象
private Player player;
// 控制键是否按下
private bool isCtrlDown = false;
// Awake方法在脚本实例化时调用
private void Awake() {
Instance = this;
icon = GetComponentInChildren<Image>();
HideIcon();
player = GameObject.FindAnyObjectByType<Player>();
}
// Update方法在每帧调用
private void Update() {
// 如果图标启用,更新图标位置
if (icon.enabled) {
Vector2 position;
RectTransformUtility.ScreenPointToLocalPointInRectangle(
GetComponent<RectTransform>(), Input.mousePosition,
null,
out position);
icon.GetComponent<RectTransform>().anchoredPosition = position;
}
// 左键点击时,如果没有点击UI元素,丢弃物品
if (Input.GetMouseButtonDown(0)) {
if (EventSystem.current.IsPointerOverGameObject() == false) {
ThrowItem();
}
}
// 检测Ctrl键按下和松开
if (Input.GetKeyDown(KeyCode.LeftControl)) {
isCtrlDown = true;
}
if (Input.GetKeyUp(KeyCode.LeftControl)) {
isCtrlDown = false;
}
// 右键点击时,强制清空手上的物品
if (Input.GetMouseButtonDown(1)) {
ClearHandForced();
}
}
// 槽点击事件处理
public void OnSlotClick(SlotUI slotui) {
// 判断手上是否有物品
if (selectedSlotData != null) {
// 手上有物品
if (slotui.GetData().IsEmpty()) {
// 当前点击了一个空格子
MoveToEmptySlot(selectedSlotData, slotui.GetData());
}
else {
// 当前点击了一个非空格子
if (selectedSlotData == slotui.GetData())
return;
else {
// 点击了别的格子 且 两个格子的物品相同
if (selectedSlotData.item == slotui.GetData().item) {
MoveToNotEmptySlot(selectedSlotData, slotui.GetData());
}
else {
SwitchData(selectedSlotData, slotui.GetData());
}
}
}
}
else {
// 手上没有物品
if (slotui.GetData().IsEmpty())
return;
selectedSlotData = slotui.GetData();
ShowIcon(selectedSlotData.item.itemSprite);
}
}
// 隐藏图标
void HideIcon() {
icon.enabled = false;
}
// 显示图标
void ShowIcon(Sprite sprite) {
icon.sprite = sprite;
icon.enabled = true;
}
// 清空手上的物品
void ClearHand() {
if (selectedSlotData.IsEmpty()) {
HideIcon();
selectedSlotData = null;
}
}
// 强制清空手上的物品
void ClearHandForced() {
HideIcon();
selectedSlotData = null;
}
// 丢弃物品
private void ThrowItem() {
if (selectedSlotData != null) {
GameObject prefab = selectedSlotData.item.itemPrefab;
int count = selectedSlotData.currentCount;
if (isCtrlDown) {
player.ThrowItem2Creat(prefab, 1);
selectedSlotData.Reduce();
}
else {
player.ThrowItem2Creat(prefab, count);
selectedSlotData.Clear();
}
ClearHand();
}
}
// 移动物品到空槽
private void MoveToEmptySlot(SlotData fromData, SlotData toData) {
if (isCtrlDown) {
toData.AddItem(fromData.item);
fromData.Reduce();
}
else {
toData.MoveSlot(fromData);
fromData.Clear();
}
ClearHand();
}
// 移动物品到非空槽
private void MoveToNotEmptySlot(SlotData fromData, SlotData toData) {
if (isCtrlDown) {
if (toData.CanAddItem()) {
toData.Add();
fromData.Reduce();
}
}
else {
int freespace = toData.GetFreeSpace();
if (fromData.currentCount > freespace) {
toData.Add(freespace);
fromData.Reduce(freespace);
}
else {
toData.Add(fromData.currentCount);
fromData.Clear();
}
}
ClearHand();
}
// 交换槽数据
private void SwitchData(SlotData data1, SlotData data2) {
ItemData item = data1.item;
int count = data1.currentCount;
data1.MoveSlot(data2);
data2.AddItem(item, count);
ClearHandForced();
}
}
Plyaer 拾取与丢弃
cs
using UnityEngine;
public class Player : MonoBehaviour
{
private void OnTriggerEnter2D(Collider2D collision) {
Debug.Log("进入了触发检测");
if (collision.CompareTag("Item")){
Debug.Log("检测到了物品");
InventoryManager.Instance.AddItemToInventory(collision.GetComponent<Pickable>().thisItemType);
Destroy(collision.gameObject);
}
}
/// <summary>
/// 丢弃背包的物品 并在场景中实例化出来
/// </summary>
/// <param name="itemPrefab">丢弃对象</param>
/// <param name="count">丢弃数量</param>
public void ThrowItem2Creat(GameObject itemPrefab,int count){
for(int i = 0; i < count; i++){
GameObject go =Instantiate(itemPrefab);
Vector2 dir = Random.insideUnitCircle.normalized * 0.8f;
go.transform.position = new Vector3(dir.x,dir.y,0);
}
}
}