前言 花了几天时间去磨这个背包系统,发现有很多可学习的点
在背包系统有以下知识点,包括但不限于
对于UI的综合应用,对于设计模式的综合应用,对于数据处理的综合应用,对于存储方式的应用,编辑器扩展的应用。
建立背包UI 这一部分大量使用了UGUI的组件,是对于组件的灵活应用
UGUI框架 
在Canvas下
Canvas Scaler组件 -> UI Scale Mode -> Scale Whith Sceeen Size 
Reference Resolution -> X 1920 Y 1080
确保界面在任意分辨率下有着正确的表现方式
创建空对象PackagePanel 
在Rect Transform组件下的锚点点击Alt选中右下角的跟随父物体伸展宽高
顶部菜单
在顶部创建一个空对象Menus挂载
Horizontal Layout Group:
Content Size Fitter: 将Horizontal Fit 设置为Preferred Size
滚动容器
创建一个Scroll View 
在Scroll Rect中可以选择Horizontal和Vertical来选择是否水平或者垂直滚动
取消Horizontal选项
在Scroll View ->Viewport下创建一个子空对象Content
添加组件
Grid Layout Group :调整Padding
Content Size Fitter:将Verrical Fit 设置为Preferred Size
小结
寥寥几行,花了一天时间,在这个背包下对于UI的整理以及UGUI的配置是很大耐心挑战
 
存储数据 对于unity的存储有PlayerPrefs,ScriptableObject,json,xml,csv(excle)等方式
静态数据与动态数据 对于静态数据和动态数据 来说
类别 
描述 
存储时长 
示例 
 
 
静态数据 
一些永久性的数据,一般存储在硬盘中。硬盘的存储空间一般都比较大,现在普通计算机的硬盘都有500G左右,因此硬盘中可以存放一些比较大的文件。 
计算机关闭之后再开启,这些数据依旧还在,只要你不主动删除或者硬盘没坏,这些数据永远都在。 
静态数据一般是以文件的形式存储在硬盘上,比如:文档,照片,视频。 
 
动态数据 
动态数据指在程序运行过程中,动态产生的临时数据,一般存储在内存中,内存的存储空间一般都比较小,现在普通计算机的内存只有8G左右,因此要谨慎使用内存,不要占用太多的内存空间。 
计算机关闭之后,这些临时数据就会被清除。 
当运行某个程序(软件)时,整个程序就会被加载到内存中,在程序运行过程中,会产生各种各样的临时数据。当程序停止运行或者计算机被强制关闭时,这个程序产生的多有的临时数据都会被清除。 
 
为什么不把所有的应用程序加载到硬盘中执行?因为内存的访问速度比硬盘快N倍。
静态数据的配置 PackageTable.cs
PackageTableItem为每个物品的属性,PackageTable为背包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 using  System;using  System.Collections;using  System.Collections.Generic;using  UnityEngine;[CreateAssetMenu(menuName = "XiaoQi/PackageTable" , fileName = "PackageTable" ) ] public  class  PackageTable  : ScriptableObject {     public  List<PackageTableItem> DataList = new  List<PackageTableItem>(); } [System.Serializable ] public  class  PackageTableItem {     public  int  id;                       public  int  type;                     public  int  star;                     public  string  name;                  public  string  description;           public  string  skillDescription;      public  string  imagePath;         } 
调试 在Editor文件夹下
GMCmd.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 using  System.Collections;using  System.Collections.Generic;using  UnityEngine;using  UnityEditor;using  Unity.VisualScripting;public  class  GMCmd  : EditorWindow {     [MenuItem("GMCmd/ReadItem" ) ]     public  static  void  ReadItem ()     {         PackageTable packageTable = Resources.Load<PackageTable>("Table/Inventory" );         foreach  (PackageTableItem packageTableItem in  packageTable.DataList)         {             Debug.Log(string .Format("[id]:{0},[name]:{1}" , packageTableItem.id, packageTableItem.name));         }     }          [MenuItem("GMCmd/OpenBag" ) ]     public  static  void  OpenBag ()     {         UIManager.Instance.OpenPanel(UIConst.PackagePanel);     } } 
存储本地数据 PackageLocalData.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 using  UnityEngine;using  System.Collections.Generic;public  class  PackageLocalData {     private  static  PackageLocalData _instance;          public  static  PackageLocalData Instance     {         get          {             if  (_instance == null )             {                 _instance = new  PackageLocalData();             }             return  _instance;         }     }     public  List<PackageLocalItem> items;                    public  void  SavePackage ()     {                  string  inventoryJson = JsonUtility.ToJson(this );                  PlayerPrefs.SetString("PackageLocalData" , inventoryJson);         PlayerPrefs.Save();     }                    public  List<PackageLocalItem> LoadPackage ()     {         if  (items != null )         {             return  items;         }                  if  (PlayerPrefs.HasKey("PackageLocalData" ))         {             string  inventoryJson = PlayerPrefs.GetString("PackageLocalData" );                          PackageLocalData packageLocalData = JsonUtility.FromJson<PackageLocalData>(inventoryJson);             items = packageLocalData.items;             return  items;         }         else          {             items = new  List<PackageLocalItem>();             return  items;         }     } } [System.Serializable ] public  class  PackageLocalItem {     public  string  uid;     public  int  id;     public  int  num;     public  int  level;     public  bool  isNew;  	                    public  override  string  ToString ()     {         return  string .Format("[id]:{0} [num]:{1}" , id, num);     } } 
这一部分是MVC框架中Model模型部分。
通过数据处理和数据存储来进行逻辑书写
界面逻辑 对于UI开关的框架 BasePanel.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 using  System.Collections;using  System.Collections.Generic;using  UnityEngine;public  class  BasePanel  : MonoBehaviour {     protected  bool  isRemove = false ;     protected  new  string  name;     protected  virtual  void  Awake ()     {     } 	                    public  virtual  void  SetActive (bool  active     {         gameObject.SetActive(active);     }     public  virtual  void  OpenPanel (string  name     {         this .name = name;         SetActive(true );     }     public  virtual  void  ClosePanel ()     {         isRemove = true ;         SetActive(false );         Destroy(gameObject);                  if  (UIManager.Instance.panelDict.ContainsKey(name))         {             UIManager.Instance.panelDict.Remove(name);         }     } }  
这边使用了this函数是个this函数的经典应用
[C++中this指针的理解与作用详解 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/95735331#:~:text=其作用就是指向成员函数所作用的对象,  所以非静态成员函数中可以直接使用,this 来代表指向该函数作用的对象的指针。)
UIManager.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 using  System.Collections;using  System.Collections.Generic;using  System;using  UnityEngine;public  class  UIManager {     private  static  UIManager _instance;     private  Transform _uiRoot;          private  Dictionary<string , string > pathDict;               private  Dictionary<string , GameObject> prefabDict;          public  Dictionary<string , BasePanel> panelDict;                         public  static  UIManager Instance     {         get          {             if  (_instance == null )             {                 _instance = new  UIManager();             }             return  _instance;         }     }     public  Transform UIRoot     {         get          {             if  (_uiRoot == null )             {                 if  (GameObject.Find(FindCanvasPath()))                 {                     _uiRoot = GameObject.Find(FindCanvasPath()).transform;                 }                 else                  {                     _uiRoot = new  GameObject(FindCanvasPath()).transform;                 }             };             return  _uiRoot;         }     }     private  UIManager ()     {         InitDicts();     }                    private  void  InitDicts ()     {         prefabDict = new  Dictionary<string , GameObject>();         panelDict = new  Dictionary<string , BasePanel>();                  pathDict = new  Dictionary<string , string >()         {             {UIConst.PackagePanel, "Package/PackagePanel" },         };     }     public  BasePanel GetPanel (string  name     {         BasePanel panel = null ;                  if  (panelDict.TryGetValue(name, out  panel))         {             return  panel;         }         return  null ;     }     public  BasePanel OpenPanel (string  name     {         BasePanel panel = null ;                  if  (panelDict.TryGetValue(name, out  panel))         {             Debug.Log("界面已打开: "  + name);             return  null ;         }                  string  path = "" ;         if  (!pathDict.TryGetValue(name, out  path))         {             Debug.Log("界面名称错误,或未配置路径: "  + name);             return  null ;         }                  GameObject panelPrefab = null ;         if  (!prefabDict.TryGetValue(name, out  panelPrefab))         {             string  realPath = FindPrefabsPanelPath(path);             panelPrefab = Resources.Load<GameObject>(realPath) as  GameObject;             prefabDict.Add(name, panelPrefab);         }                           if  (panelPrefab == null )         {             Debug.Log("未找到相应路径的prefabs"  + FindPrefabsPanelPath(path));             return  panel;         }         GameObject panelObject = GameObject.Instantiate(panelPrefab, UIRoot, false );                  panel = panelObject.GetComponent<BasePanel>();         panelDict.Add(name, panel);         panel.OpenPanel(name);         return  panel;     }     public  bool  ClosePanel (string  name     {         BasePanel panel = null ;         if  (!panelDict.TryGetValue(name, out  panel))         {             Debug.Log("界面未打开: "  + name);             return  false ;         }         panel.ClosePanel();                  return  true ;     }     string  FindCanvasPath ()     {         return  "Canvas" ;     }                              string  FindPrefabsPanelPath (string  path     {         return  "Prefabs/Panel/"  + path;     } } public  class  UIConst {               public  const  string  PackagePanel = "PackagePanel" ; } 
UIManager.cs 才用了单例模式和观察者模式(字典)
对于Open和Close操作进行了封装,
在封装类中加了多重判断来保证对于页面的单一开启
对于页面的实例 PackagePanel.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 using  System.Collections;using  System.Collections.Generic;using  UnityEngine;using  UnityEngine.UI;public  class  PackagePanel  : BasePanel {     private  Transform UIMenu;     private  Transform UIMenuWeapon;     private  Transform UIMenuFood;     private  Transform UITabName;     private  Transform UICloseBtn;     private  Transform UICenter;     private  Transform UIScrollView;     private  Transform UIDetailPanel;     private  Transform UILeftBtn;     private  Transform UIRightBtn;     private  Transform UIDeletePanel;     private  Transform UIDeleteBackBtn;     private  Transform UIDeleteInfoText;     private  Transform UIDeleteConfirmBtn;     private  Transform UIBottomMenus;     private  Transform UIDeleteBtn;     private  Transform UIDetailBtn;     public  GameObject PackageUIItemPrefab;     override  protected  void  Awake ()     {         base .Awake();         InitUI();     }     private  void  Start ()     {         RefreshUI();     }     private  void  InitUI ()     {         InitUIName();         InitClick();     }     private  void  RefreshUI ()     {         RefreshScroll();     }     private  void  RefreshScroll ()     {                           RectTransform scrollContent = UIScrollView.GetComponent<ScrollRect>().content;         for  (int  i = 0 ; i < scrollContent.childCount; i++)         {             Destroy(scrollContent.GetChild(i).gameObject);         }         foreach  (PackageLocalItem localData in  GameManager.Instance.GetSortPackageLocalData())         {             Transform PackageUIItem = Instantiate(PackageUIItemPrefab.transform, scrollContent) as  Transform;             PackageCell packageCell = PackageUIItem.GetComponent<PackageCell>();             packageCell.Refresh(localData, this );         }     }         private  void  InitUIName ()     {         UIMenu = transform.Find("TopCenter/Menu" );         UIMenuWeapon = transform.Find("TopCenter/Menus/Weapon" );         UIMenuFood = transform.Find("TopCenter/Menus/Food" );         UITabName = transform.Find("LeftTop/TabName" );         UICloseBtn = transform.Find("RightTop/Close" );         UICenter = transform.Find("Center" );         UIScrollView = transform.Find("Center/Scroll View" );         UIDetailPanel = transform.Find("Center/DetailPanel" );         UILeftBtn = transform.Find("Left/Button" );         UIRightBtn = transform.Find("Right/Button" );         UIDeletePanel = transform.Find("Bottom/DeletePanel" );         UIDeleteBackBtn = transform.Find("Bottom/DeletePanel/Back" );         UIDeleteInfoText = transform.Find("Bottom/DeletePanel/InfoText" );         UIDeleteConfirmBtn = transform.Find("Bottom/DeletePanel/ConfirmBtn" );         UIBottomMenus = transform.Find("Bottom/BottomMenus" );         UIDeleteBtn = transform.Find("Bottom/BottomMenus/DeleteBtn" );         UIDetailBtn = transform.Find("Bottom/BottomMenus/DetailBtn" );         UIDeletePanel.gameObject.SetActive(false );         UIBottomMenus.gameObject.SetActive(true );     } 	                private  void  InitClick ()     {         UIMenuWeapon.GetComponent<Button>().onClick.AddListener(OnClickWeapon);         UIMenuFood.GetComponent<Button>().onClick.AddListener(OnClickFood);         UICloseBtn.GetComponent<Button>().onClick.AddListener(OnClickClose);         UILeftBtn.GetComponent<Button>().onClick.AddListener(OnClickLeft);         UIRightBtn.GetComponent<Button>().onClick.AddListener(OnClickRight);         UIDeleteBackBtn.GetComponent<Button>().onClick.AddListener(OnDeleteBack);         UIDeleteConfirmBtn.GetComponent<Button>().onClick.AddListener(OnDeleteConfirm);         UIDeleteBtn.GetComponent<Button>().onClick.AddListener(OnDelete);         UIDetailBtn.GetComponent<Button>().onClick.AddListener(OnDetail);     }     private  void  OnClickWeapon ()     {         print(">>>>> OnClickWeapon" );     }     private  void  OnClickFood ()     {         print(">>>>> OnClickFood" );     }     private  void  OnClickClose ()     {         print(">>>>> OnClickClose" );         ClosePanel();              }     private  void  OnClickLeft ()     {         print(">>>>> OnClickLeft" );     }     private  void  OnClickRight ()     {         print(">>>>> OnClickRight" );     }     private  void  OnDeleteBack ()     {         print(">>>>> onDeleteBack" );     }     private  void  OnDeleteConfirm ()     {         print(">>>>> OnDeleteConfirm" );     }     private  void  OnDelete ()     {         print(">>>>> OnDelete" );     }     private  void  OnDetail ()     {         print(">>>>> OnDetail" );     } } 
PackagePanel.cs中 
继承与BasePanel .cs使用了 原型模式
初始化了滚动容器,在滚动容器中清理并添加新的数据
命名并初始化了UI的Transform
在有Btn的下来注册点击事件(目前是空)
PackageCell.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 using  System.Collections;using  System.Collections.Generic;using  UnityEngine;using  UnityEngine.UI;public  class  PackageCell  : MonoBehaviour {     private  Transform UIIcon;     private  Transform UIHead;     private  Transform UINew;     private  Transform UISelect;     private  Transform UILevel;     private  Transform UIStars;     private  Transform UIDeleteSelect;     private  PackageLocalItem packageLocalData;     private  PackageTableItem packageTableItem;     private  PackagePanel uiParent;     private  void  Awake ()     {         InitUIName();     }     private  void  InitUIName ()     {         UIIcon = transform.Find("Top/Icon" );         UIHead = transform.Find("Top/Head" );         UINew = transform.Find("Top/New" );         UILevel = transform.Find("Bottom/LevelText" );         UIStars = transform.Find("Bottom/Stars" );         UISelect = transform.Find("Select" );         UIDeleteSelect = transform.Find("DeleteSelect" );         UIDeleteSelect.gameObject.SetActive(false );     }     public  void  Refresh (PackageLocalItem packageLocalData, PackagePanel uiParent )     {                  this .packageLocalData = packageLocalData;         this .packageTableItem = GameManager.Instance.GetPackageItemById(packageLocalData.id);         this .uiParent = uiParent;                  UILevel.GetComponent<Text>().text = "Lv."  + this .packageLocalData.level.ToString();                  UINew.gameObject.SetActive(this .packageLocalData.isNew);                  Texture2D t = (Texture2D)Resources.Load(this .packageTableItem.imagePath);         Sprite temp = Sprite.Create(t, new  Rect(0 , 0 , t.width, t.height), new  Vector2(0 , 0 ));         UIIcon.GetComponent<Image>().sprite = temp;                  RefreshStars();     }     public  void  RefreshStars ()     {         for  (int  i = 0 ; i < UIStars.childCount; i++)         {             Transform star = UIStars.GetChild(i);             if  (this .packageTableItem.star > i)             {                 star.gameObject.SetActive(true );             }             else              {                 star.gameObject.SetActive(false );             }         }     } }  
PackageCell.cs 中
和PackagePanel.cs相同,命名并初始化了UI的Transform
并且对于特殊的星级判断独立写了一个显示函数
GameManager.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 using  System.Collections;using  System.Collections.Generic;using  UnityEngine;public  class  GameManager  : MonoBehaviour {     private  static  GameManager _instance;     private  PackageTable packageTable;          private  void  Awake ()     {         _instance = this ;         DontDestroyOnLoad(gameObject);     }          public  static  GameManager Instance     {         get          {             return  _instance;         }     }          void  Start ()     {         UIManager.Instance.OpenPanel(UIConst.PackagePanel);                       }               public  PackageTable GetPackageTable ()     {         if  (packageTable == null )         {             packageTable = Resources.Load<PackageTable>("TableData/PackageTable" );         }         return  packageTable;     }     public  List<PackageLocalItem> GetPackageLocalData ()     {         return  PackageLocalData.Instance.LoadPackage();     }     public  PackageTableItem GetPackageItemById (int  id     {         List<PackageTableItem> packageDataList = GetPackageTable().DataList;         foreach  (PackageTableItem item in  packageDataList)         {             if  (item.id == id)             {                 return  item;             }         }         return  null ;     }     public  PackageLocalItem GetPackageLocalItemByUId (string  uid     {         List<PackageLocalItem> packageDataList = GetPackageLocalData();         foreach  (PackageLocalItem item in  packageDataList)         {             if  (item.uid == uid)             {                 return  item;             }         }         return  null ;     }     public  List<PackageLocalItem> GetSortPackageLocalData ()     {         List<PackageLocalItem> localItems = PackageLocalData.Instance.LoadPackage();         localItems.Sort(new  PackageItemComparer());         return  localItems;     } } public  class  PackageItemComparer  : IComparer <PackageLocalItem >{     public  int  Compare (PackageLocalItem a, PackageLocalItem b )     {         PackageTableItem x = GameManager.Instance.GetPackageItemById(a.id);         PackageTableItem y = GameManager.Instance.GetPackageItemById(b.id);                  int  starComparison = y.star.CompareTo(x.star);                  if  (starComparison == 0 )         {             int  idComparison = y.id.CompareTo(x.id);             if  (idComparison == 0 )             {                 return  b.level.CompareTo(a.level);             }             return  idComparison;         }         return  starComparison;     } } 
GameManager.cs 使用了命令模式在Manager下统一对于数据的管理
结尾 本篇作为背包系统的落地实现
有着多个设计模式:观察者模式(Dictionary),命令模式(Manager),单例模式 ,原型模式(简直就是一个设计模式的大集合)
而且对于UI的综合应用有着很大提升
对于存储方式也有很大理解(具体可能会在写一篇新的文章)
最全面的游戏背包系统讲解 | 技术原理分析 | 框架逻辑设计| 背包+抽卡+整理一网打尽 | 小棋出品,必属精品_哔哩哔哩_bilibili 
Unity 简单背包系统(ScriptableObject)_unity官方背包系统代码-CSDN博客 
UI框架bug修复版 - 哔哩哔哩 (bilibili.com) 
背包系统源码及工程(截止第三节课) - 哔哩哔哩 (bilibili.com)