超级详细的游戏常见系统之背包系统制作以及优化
暂定
(一)基本知识点
拼面板:ugui
面板样子如图
数据来源:json配置表 ,file读取文件
(二)需求分析
1.背包中显示玩家拥有的物品信息
2.需要显示信息就要读取数据,数据来源(配置表:json,玩家信息:文件读取,json)
3.获取数据更新到面板上,处理面板逻辑:ugui,逻辑处理
(三)前期准备
(1).拼面板:注意ui命名最好唯一,主要是scrollow view和content下加grid layout组件+content size fitter:grid layout用来拍格子,content size fitter:用来动态控制content大小,让其随格子的多少而变化大小。注意,这里的道具,装备,宝石,是用toggle做的,
让显示多少内容是和viewpoint的mask有关:
去掉mask会变成这样
(2).数据准备:道具信息数据设计和读取,玩家数据信息设计存储和读取。具体数据准备步骤
*************************************准备道具信息
1.道具装备信息,用表格如图
2.excel转成json如图
可以利用在线转换工具:json在线转换工具
然后再放到在线工具中格式化校验一下,如图
3.让json可以在程序中使用,就要存到一个list中,而且把一些字符串格式的改成int型如id,type,price,如图
4.把格式化校验后的json,放到resources文件夹下的文本文件中,把itemsinfo.txt的格式改成utf-8的形式,不改txt格式,读取会出问题。
5.使用json数据,创建一个脚本gamedatamgr.cs,管理器是单例模式
代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameDataMgr
{
private static GameDataMgr _instance;
public static GameDataMgr Instance
{
get
{
if (_instance == null)
{
_instance = new GameDataMgr();
}
return _instance;
}
}
/// <summary>
/// 加载资源里的json文件
/// </summary>
public void Init()
{
string info = Resources.Load<TextAsset>("Json/itemInfo").text;
Debug.Log(info);
//根据json文件,解析成对应的数据结构,并存储
Items items = JsonUtility.FromJson<Items>(info);
Debug.Log(items.info.Count);
}
}
/// <summary>
/// 首先要有对应的数据结构去装这个json里的数据,这个是单个item的数据结构
/// </summary>
[System.Serializable]//序列化之后才能被unity自带的json解析出来
public class Item
{
//注意这里的命名要和json里对应的键一摸一样
public int id;
public string name;
public string icon;
public int type;
public int price;
public string tips;
}
/// <summary>
/// 用来装items的数据结构
/// </summary>
public class Items
{
//这里的info取名和json中的一样
public List<Item> info;
}
在项目入口处调试,能打印正确的json字符串和正确的道具信息个数,说明道具数据准备信息完成了。
6.再做一步优化,如果数据在list中,每次查找都要遍历,不方便,所以要把list里的数据放到字典中<int,item>这样可以通过id查找。然后再写一个通过id,从字典获取具体道具的方法,代码如下
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameDataMgr
{
//最终用字典存储items的信息
private Dictionary<int, Item> itemInfos = new Dictionary<int, Item>();
private static GameDataMgr _instance;
public static GameDataMgr Instance
{
get
{
if (_instance == null)
{
_instance = new GameDataMgr();
}
return _instance;
}
}
/// <summary>
/// 加载资源里的json文件
/// </summary>
public void Init()
{
string info = Resources.Load<TextAsset>("Json/itemInfo").text;
Debug.Log(info);
//根据json文件,解析成对应的数据结构,并存储
Items items = JsonUtility.FromJson<Items>(info);
Debug.Log(items.info.Count);
//将解析好的list添加进字典中
for(int i = 0; i < items.info.Count; i++)
{
itemInfos.Add(items.info[i].id, items.info[i]);
}
}
/// <summary>
/// 通过字典里的id获取具体道具信息
/// </summary>
/// <param name="id">道具id</param>
/// <returns></returns>
public Item GetItemInfo(int id)
{
if (itemInfos.ContainsKey(id))
{
return itemInfos[id];
}
return null;
}
}
/// <summary>
/// 首先要有对应的数据结构去装这个json里的数据,这个是单个item的数据结构
/// </summary>
[System.Serializable]//序列化之后才能被unity自带的json解析出来
public class Item
{
//注意这里的命名要和json里对应的键一摸一样
public int id;
public string name;
public string icon;
public int type;
public int price;
public string tips;
}
/// <summary>
/// 用来装items的数据结构
/// </summary>
public class Items
{
//这里的info取名和json中的一样
public List<Item> info;
}
准备数据的核心思想:数据和程序逻辑分离开,只要换json文件就行了。而且这样玩家那里的数据,不用存储具体的道具信息,只需存道具的id,和个数就可以了。这样玩家存储的数据就会减少。
*************************************准备玩家信息(暂时都写在GameDataMgr文件中)
1.初步设定玩家的数据结构包含哪些,简单的方法,根据面板上的显示,决定玩家数据需要准备什么,初步设定玩家的数据结构player是,名字,等级,金币,宝石,能量,道具,装备,碎片(宝石)如代码所示:
/// <summary>
/// 玩家信息数据结构
/// </summary>
public class player
{
public string name;
public int lev;
public int money;
public int gem;
public int pro;
public List<ItemInfo> items;
public List<ItemInfo> equips;
public List<ItemInfo> gems;
}
2.设计这些玩家数据的数据结构,玩家的道具只需包含id和个数即可,所以玩家的道具信息数据结构以及后面的装备,碎片的数据结构是,如下代码:
/// <summary>
/// 玩家持有的道具信息的数据结构
/// </summary>
[System.Serializable]//为的是unity自带json读取时能够识别,要序列化的是json里的数据,而不是容器list
public class ItemInfo
{
int id;
int num;
}
3.用本地文件,玩家信息的保存和读取
具体步骤:
初始化时:
(1)如果有这个文件,就从里面读取玩家信息
1.当有这个文件了,就读取这个文件的所有字节数组
2.转成json字符串
3.通过fromjson还原player对象
(2)如果没有这个文件,就创建这个文件,将初始化的玩家信息写入该文件中
1.player类的构造函数先赋值一些基本信息和道具信息
2.init()中然后把player对象的信息转成json字符串
3.然后json字符串转成字节数组,存入文件中
(3)把保存玩家信息的代码提取成一个方法SavePlayerInfo(),方便更新玩家数据
整段代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
public class GameDataMgr
{
//用来存储玩家信息文件的路径
private static string playerInfo_url = Application.persistentDataPath + "/PlayerInfo.txt";
//玩家对象
public static player playerInfo;
//最终用字典存储items的信息
private Dictionary<int, Item> itemInfos = new Dictionary<int, Item>();
private static GameDataMgr _instance;
public static GameDataMgr Instance
{
get
{
if (_instance == null)
{
_instance = new GameDataMgr();
}
return _instance;
}
}
/// <summary>
/// 初始化道具信息和玩家信息
/// </summary>
public void Init()
{
string info = Resources.Load<TextAsset>("Json/itemInfo").text;
Debug.Log(info);
//根据json文件,解析成对应的数据结构,并存储
Items items = JsonUtility.FromJson<Items>(info);
Debug.Log(items.info.Count);
//将解析好的list添加进字典中
for(int i = 0; i < items.info.Count; i++)
{
itemInfos.Add(items.info[i].id, items.info[i]);
}
//从本地文件读取玩家信息,如果没有就创建
if (File.Exists(playerInfo_url))
{
//读取文件的所有字节数组,转换成字符串json,然后还原成player对象
byte[] bytes = File.ReadAllBytes(playerInfo_url);
string json = System.Text.Encoding.UTF8.GetString(bytes);
playerInfo = JsonUtility.FromJson<player>(json);
//测试有没有读对,打印下玩家名字
Debug.Log(playerInfo.name);
}
else
{
//没有这个文件,就先创建player对象(对象的构造函数会给玩家信息赋值一个初始值,然后转成json字符串,通过字节数组存入文件中
playerInfo = new player();
SavePlayerInfo();
//测试打印下这个路径,查看下文件有没有写入成功
Debug.Log(playerInfo_url);
}
}
/// <summary>
/// 可以把存储json文件的部分提取成一个公共方法,方便更新玩家信息
/// </summary>
public static void SavePlayerInfo()
{
string json = JsonUtility.ToJson(playerInfo);
File.WriteAllBytes(playerInfo_url, System.Text.Encoding.UTF8.GetBytes(json));
}
/// <summary>
/// 通过字典里的id获取具体道具信息
/// </summary>
/// <param name="id">道具id</param>
/// <returns></returns>
public Item GetItemInfo(int id)
{
if (itemInfos.ContainsKey(id))
{
return itemInfos[id];
}
return null;
}
}
/// <summary>
/// 玩家信息数据结构
/// </summary>
public class player
{
public string name;
public int lev;
public int money;
public int gem;
public int pro;
public List<ItemInfo> items;
public List<ItemInfo> equips;
public List<ItemInfo> gems;
//给玩家构造函数初始化一个玩家信息
public player(){
name = "root";
lev = 99;
money = 999;
gem = 0;
pro = 99;
items = new List<ItemInfo>() { new ItemInfo() {id= 2,num=1} };
equips = new List<ItemInfo>() { new ItemInfo() { id = 1, num = 3 } };
gems = new List<ItemInfo>() { };
}
}
/// <summary>
/// 玩家持有的道具信息的数据结构
/// </summary>
[System.Serializable]//为的是unity自带json读取时能够识别,要序列化的是json里的数据,而不是容器list
public class ItemInfo
{
public int id;
public int num;
}
/// <summary>
/// 首先要有对应的数据结构去装这个json里的数据,这个是单个item的数据结构
/// </summary>
[System.Serializable]//序列化之后才能被unity自带的json解析出来
public class Item
{
//注意这里的命名要和json里对应的键一摸一样
public int id;
public string name;
public string icon;
public int type;
public int price;
public string tips;
}
/// <summary>
/// 用来装items的数据结构
/// </summary>
public class Items
{
//这里的info取名和json中的一样
public List<Item> info;
}
(四)基本逻辑实现
实现效果:
代码;
格子脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// 格子脚本
/// </summary>
public class ItemCell :BasePanel
{
private ItemInfo itemInfo;
public void InitItem(ItemInfo info)
{
this.itemInfo = info;
Item itemData = GameDataMgr.GetInstance().GetItemInfo(info.id);//通过id找到该物品的详细信息
Sprite icon= ResMgr.GetInstance().Load<Sprite>("Icon/" + itemData.icon);//找到该物品对应的图片
GetUI<Image>("imgIcon").sprite = icon;//替换当前的图片
GetUI<Text>("txtNum").text = info.num.ToString();//给物品数量赋值
}
}
背包脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public enum E_Bag_Type
{
Item,
equip,
Gem,
}
/// <summary>
/// 背包脚本
/// </summary>
public class BagPanel : BasePanel
{
public Transform content;
List<ItemCell> list = new List<ItemCell>();
// Start is called before the first frame update
void Start()
{
GetUI<Button>("Close_Img").onClick.AddListener(() =>{
UIManager.GetInstance().ClosePanel("BagPanel");
});
GetUI<Toggle>("dg_img").onValueChanged.AddListener(ToggleValueChange);
GetUI<Toggle>("zb_img").onValueChanged.AddListener(ToggleValueChange);
GetUI<Toggle>("bs_img").onValueChanged.AddListener(ToggleValueChange);
}
public override void Show()
{
base.Show();
ChangeType(E_Bag_Type.Item);
}
/// <summary>
/// 根据点击不同的toggle,且换不同的数据
/// </summary>
/// <param name="value"></param>
public void ToggleValueChange(bool value)
{
if (GetUI<Toggle>("dg_img").isOn)
{
ChangeType(E_Bag_Type.Item);
}else if (GetUI<Toggle>("zb_img").isOn)
{
ChangeType(E_Bag_Type.equip);
}
else if (GetUI<Toggle>("bs_img"))
{
ChangeType(E_Bag_Type.Gem);
}
}
private void ChangeType(E_Bag_Type type)
{
List<ItemInfo> tempInfo = GameDataMgr.playerInfo.items;
switch (type)
{
case E_Bag_Type.equip:
tempInfo = GameDataMgr.playerInfo.equips;
break;
case E_Bag_Type.Gem:
tempInfo = GameDataMgr.playerInfo.gems;
break;
}
//先销毁,再清空
for (int i = 0; i < list.Count; i++)
{
Destroy(list[i].gameObject);
}
list.Clear();
for (int i = 0; i < tempInfo.Count; i++)
{
GameObject itemCellObj = ResMgr.GetInstance().Load<GameObject>("UI/ItemCell");//获取实例化好的格子物体
ItemCell cell = itemCellObj.GetComponent<ItemCell>();//获取格子上的脚本
cell.transform.SetParent(content);//设置父对象
cell.transform.localScale = new Vector3(1, 1, 1);//设置大小
cell.InitItem(tempInfo[i]);//给格子数据初始化
list.Add(cell);//添加进list,方便下次显示
}
}
}
(五)进一步优化,当放到物品格子时,显示物品的详细信息
实现效果:
拼面板:
ps:为了让提示面板的其它部分不响应鼠标的点击事件,提示面板的所有射线检测关闭,不然鼠标放上去时,会有面板抖动现象
代码:
提示面板代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// 用来显示格子信息的提示面板
/// </summary>
public class TipsPanel : BasePanel
{
public void InitTipsInfo(ItemInfo info)
{
Item itemData = GameDataMgr.GetInstance().GetItemInfo(info.id);//通过id找到该物品的详细信息
Sprite icon = ResMgr.GetInstance().Load<Sprite>("Icon/" + itemData.icon);//找到该物品对应的图片
GetUI<Image>("imgIcon").sprite = icon;//替换当前的图片
GetUI<Text>("txtNum").text = "数量:"+info.num.ToString();//给物品数量赋值
//得到物品的详细描述
GetUI<Text>("txtTips").text = "描述:" + itemData.tips;
//得到物品名字
GetUI<Text>("txtName").text = "名字:" + itemData.name;
}
}
格子的代码:主要是添加格子里的图片的eventtrigger,然后设置好鼠标进入,和移出事件,摆放提示面板位置为图片中心位置,然后初始化提示面板数据
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
/// <summary>
/// 格子脚本
/// </summary>
public class ItemCell :BasePanel
{
private ItemInfo itemInfo;
private void Start()
{
//给图片添加事件监听
EventTrigger trigger=GetUI<Image>("imgIcon").gameObject.AddComponent<EventTrigger>();
//声明一个鼠标进入事件,鼠标移出事件
EventTrigger.Entry entry = new EventTrigger.Entry();
entry.eventID = EventTriggerType.PointerEnter;
entry.callback.AddListener(EnterItemCell);
trigger.triggers.Add(entry);//还要添加进事件
EventTrigger.Entry exit = new EventTrigger.Entry();
exit.eventID = EventTriggerType.PointerExit;
exit.callback.AddListener(ExitItemCell);
trigger.triggers.Add(exit);//还要添加进事件
}
//给鼠标点击和移除事件编写对应逻辑(显示面板,将面板的位置移到图片的中心点,然后设置提示面板的数据)
private void EnterItemCell(BaseEventData data)
{
UIManager.GetInstance().ShowPanel<TipsPanel>("TipsPanel", UI_Layer.top, (panel) =>
{
panel.transform.position = GetUI<Image>("imgIcon").gameObject.transform.position;
panel.InitTipsInfo(itemInfo);
});
}
private void ExitItemCell(BaseEventData data)
{
UIManager.GetInstance().ClosePanel("TipsPanel");
}
public void InitItem(ItemInfo info)
{
this.itemInfo = info;
Item itemData = GameDataMgr.GetInstance().GetItemInfo(info.id);//通过id找到该物品的详细信息
Sprite icon= ResMgr.GetInstance().Load<Sprite>("Icon/" + itemData.icon);//找到该物品对应的图片
GetUI<Image>("imgIcon").sprite = icon;//替换当前的图片
GetUI<Text>("txtNum").text = info.num.ToString();//给物品数量赋值
}
}
(六)如果背包要装非常多的物体,如何优化?
更多推荐
所有评论(0)