当前位置: 首页 > news >正文

Unity2D初级背包设计中篇 MVC分层撰写(万字详解)

         本人能力有限,如有不足还请斧正,理论分析链接如下:

        Unity2D初级背包设计前篇 理论分析-CSDN博客

目录

1.M层:数据存储  

物品

仓库容器

加载方式

2.M层:逻辑撰写

InventoryManager 仓库的管理

SlotData 物品的增删查改*(逻辑层)

3.V层:搭建UI

        1.SlotUI 将物品绘制到格子上

        2.三个类

BarUI工具栏的选中与数字切换

    BarSoltUI格子高亮 

        BagUI 将格子数据绘制到界面上

4.C层:玩家控制类

ItemMoveHandler 物品的增删查改*(表现层)

         Plyaer 拾取与丢弃


1.M层:数据存储  

物品

        一个2D的物品必不可少的就是类型,图标,物体(这个指Gameobjcet和其名字)堆叠数量

        拓展思路:稀有度,价值,耐久,描述,重量,

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
}

        另外可能需要一个外部类去定义其属性,比如可拾取的,不可拾取的 这个的作用体现在玩家拾取方面,需要提前知道一下

public class Pickable : MonoBehaviour
{public ItemType thisItemType;
}

         so对象举例

        

        

仓库容器

        先认识一个单词:Inventory,仓库,库存 在本文中特指背包类

        之所以要对Inventory写为So,是因为游戏中可能存在许多的仓库,在玩家手中叫做背包,物品栏,在NPC手中就可能叫做商店,锻造了,因此:

using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu()]
public class InventoryData :ScriptableObject{//TODO :拓展 仓库名称,最大容量,自动扩容public List<SlotData> slotList;
}

        创建两个

        可以看到其容量是我们自定义的,也就是写死的没有自动扩容功能

        因此在今后的拓展之中可以优化

加载方式

2.M层:逻辑撰写

InventoryManager 仓库的管理

       我们需要一个仓库管理器去管理所有的背包所以用到了InventoryData 对象

        另外,管理所有物品的时候也应需要一个容器去预加载,所以用到了字典

  可以先不看这一行:我们需要为外部提供提供向背包添加物品的方法,所以用到了单例模式

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:字典中有该物品且格子未满 如果格子满了将会走情况2foreach (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层去监听  

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层

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工具栏的选中与数字切换

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 createdvoid 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格子高亮 

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 createdvoid 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 将格子数据绘制到界面上

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 createdvoid Start() {InitElement();InitSlotUI();}// Update is called once per framevoid 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;}});}elseDebug.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给到注释,如果前面的代码都理解了,那么这里一点也不难

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 拾取与丢弃

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);} }
}


http://www.mrgr.cn/news/82730.html

相关文章:

  • 安卓NDK视觉开发——手机拍照文档边缘检测实现方法与库封装
  • 管理加密SQLite数据库的软件工具研究
  • 计算机网络 (20)高速以太网
  • Python爬虫基础——认识网页结构(各种标签的使用)
  • 构建现代化的AI流水线:从数据处理到模型部署
  • C#编写的盘符图标修改器 - 开源研究系列文章
  • 音视频-----RTSP协议 音视频编解码
  • VisualRules规则引擎语法介绍
  • Linux(Ubuntu24.04)源码编译安装VTK7.1.1记录
  • 源代码编译安装X11及相关库、vim,配置vim(2)
  • Qt 5.14.2 学习记录 —— 사 信号与槽机制(1)
  • 部署API式机器学习模型并接入AI agent
  • BurpSuite工具安装
  • 在 macOS 中,设置自动将文件夹排在最前
  • 初学stm32 --- RTC实时时钟
  • 时序优化方法
  • AlphaPi相关硬件驱动提取
  • 拥抱时代--AI(3)
  • List-顺序表--2
  • vue3 vite 动态加载路由遇到的问题
  • [python3]Excel解析库-openpyxl
  • 开源存储详解-分布式存储与ceph
  • [python3]Excel解析库-XlsxWriter
  • Redis 5设计与源码分析读书笔记
  • 33.时间函数相关 C#例子
  • 下载excel