前言 花了几天时间去磨这个背包系统,发现有很多可学习的点
在背包系统有以下知识点,包括但不限于
对于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)