Unity使用JSON存储实现背包功能
前言
在Unity有五种常用的存储数据的方法,可以用来存储我们游戏的数据。
一、PlayerPrefs
这是Unity自带的一种用于本地持久化保存与读取的一个类,采用以键值对的形式将数据保存在文件中。
int IntValue;
float FloatValue;
String StringValue;
PlayerPrefs.SetFloat("FloatKey",FloatValue); //存储float类型的值,对应的键为FloatKey
PlayerPrefs.SetInt("IntKey",IntValue); //存储int类型的值,对应的键为IntKey
PlayerPrefs.SetString("StringKey",StringValue); ////存储string类型的值,对应的键为StringKey
二、读取普通文本:TextAsset
TextAsset text=(TextAsset)Resources.Load("Text");
Debug.Log(text.text);
在Project窗口的根目录创建Resources文件夹,然后把名字为Text.txt的文件夹的文件放在Resources文件夹下就可以读取到。
三、JSON
本篇使用的方法,后续详细讲解。
四、XML储存
本篇不讲解。
五、Splite
本篇不讲解。
具体实现方法
一、背包UI界面创建
本教程使用的UI资源来自Unity Assets Store中的免费资源SIMPLE FANTASY GUI和Fantasy Wooden GUI : Free。
1、创建Canvas画布
创建一个Canvas(UI画布),然后在Canvas下创建一个Image,命名为Bag,放入图片,作为背包背景,将其缩放到合适大小,放置到合适位置。
在创建一个DragCanvas,并将sort order值设置为1,使其内的UI始终高于Canvas的UI显示,后面需要用到。
2、添加组件
然后添加Grid Layout Group组件,用来控制其子物体的排序格式。
CellSize:每个子物体的大小。
Spacing:每个子物体之间的间隔。
Start Corner:子物体的起始角落。
Start Axis:子物体起始轴线。
3、创建物品格
在Bag下创建一个Image,命名为Slot,如上添加图片,然后复制,达到想要的背包效果。
4、创建Item
在Slot下创建一个Image,命名为Item,用来显示物品的图片,然后在Item下创建一个Text,用来显示物品的数字。将Item制作为预设体,方便后期使用。
5、最终层级关系
二、脚本编写
1、Inventory脚本
首先定义一个物品类,用来存放每个物品信息。
public class InventoryInfo
{
public string Parent; //物品属于哪一个物品格
public string Name;
public int Num;
public Sprite icon; //物品图标
public enum Type //用于区分类别,本教程中未使用
{
Weapon,
Food
}
public Type myType;
}
在定义一个类来存储物品信息。
public class InvenInfoList
{
public List<InventoryInfo> inventoryInfo = new List<InventoryInfo>();
}
定义脚本所需要的变量。
public static Inventory instance; // 将该脚本定义为静态变量。
private string filePath = Application.streamingAssetsPath +"/GameData/saveData.json";//存储位置
private InvenInfoList list = new InvenInfoList();
public GameObject itemPrefab; //获取Item预设体,用于后面生成。
public GameObject dragCanvas; //拖拽画布
我们需要引入命名空间using System.IO
,让我们能往计算机硬盘中写入数据。
using System.IO;
接下来需要就需要用到Unity关于Json文件数据的存储。
JsonUtility支持的数据类型。
·支持数字数据类型:int、float、double、decimal、long,包括 uint、float2x4、double2 等数据类型
·支持字符数据类型:char、string
·【特别】支持 Vector 数据类型,包括 Vetor2、Vector3、Vector2x2 等数据类型
·【特别】支持 Quateration 四元数数据类型
·【特别】支持 public 访问类型的类、字段
·【特别】支持 SerializeField 特性指引的类、字段
·JsonUtility.toJson(object target, bool prettyPrint)
object
:对象转化为Json文本。
prettyPrint
:决定最终的 Json 数据文本是否是一个格式化后的数据文本,即是否使用 Json 文本的 Format 化。
·FromJson(string text)
1、将 Json 数据文本转存至类中 public 或 附有 SerializeField 特性的字段上赋值。
2、使用时无需管理值具体分配。其将基于字段命名自行匹配并赋值。
·FromJsonOverwrite(string text, object objectToOverwrite)
编写CreateSave
函数,用于数据存储。
private void CreateSave() //向list中添加需要序列化存储的数据信息
{
for (int i = 0; i < transform.childCount; i++)
{
InventoryInfo a = new InventoryInfo();
GameObject slot = transform.GetChild(i).gameObject; //获取物品的父物体即物品格信息
if (slot.transform.childCount != 0) //存储物品信息
{
Item tmp =slot.transform.GetChild(0).GetComponent<Item>();
a.Parent = slot.name;
a.Name = tmp.Info.Name;
a.Num = tmp.Info.Num;
a.icon = tmp.Info.icon;
a.myType = tmp.Info.myType;
list.inventoryInfo.Add(a);
}
}
}
接下来编写SaveByJson
函数,用于向硬盘内写入数据。
private void SaveByJson()
{
list.inventoryInfo.Clear(); //清空list内的内容
CreateSave();
string json = JsonUtility.ToJson(list,true); //转化为Json文本
StreamWriter sw = new StreamWriter(filePath); //写入硬盘
sw.Write(json);
sw.Close();
}
存储数据的内容就写完了,接下来需要编写读取Json数据的函数。编写LoadByJson
函数,并且在Awake
函数中调用它。
private void LoadByJson()
{
string json;
StreamReader sr = new StreamReader(filePath); //获取硬盘中的Json
json = sr.ReadToEnd();
sr.Close();
list = JsonUtility.FromJson<InvenInfoList>(json); //将其重新写入list中
SetGame(); //设置游戏
}
private void SetGame()
{
for (int i = 0; i < list.inventoryInfo.Count; i++)
{
InventoryInfo a = new InventoryInfo();
for (int j = 0; j < transform.childCount; j++)
{
GameObject slot = transform.GetChild(j).gameObject;
//读取到当前物品格信息,写入
if (slot.name == list.inventoryInfo[i].Parent)
{
GameObject it = Instantiate(itemPrefab, slot.transform, true);
Item tmp =it.GetComponent<Item>();
RectTransform rt = it.GetComponent<RectTransform>();
rt.offsetMax = new Vector2(-5f, -5f);
rt.offsetMin = new Vector2(5f, 5f);
tmp.Info.Name = list.inventoryInfo[i].Name;
tmp.Info.Num = list.inventoryInfo[i].Num;
tmp.Info.icon = list.inventoryInfo[i].icon;
Image icon = tmp.GetComponent<Image>();
icon.sprite = tmp.Info.icon;
tmp.Info.myType = list.inventoryInfo[i].myType;
}
}
}
}
2、Item脚本
用于物品信息实时更新。
public class Item : MonoBehaviour
{
public InventoryInfo Info;
private TMP_Text a;
private void Start()
{
a = transform.GetChild(0).GetComponent<TMP_Text>();
}
private void Update()
{
a.text = Info.Num.ToString();
}
}
3、DragItem脚本
首先要实现拖移UI功能,需要先引入三个接口,IBeginDragHandler,IDragHandler,IEndDragHandler
。
然后先创建3个函数。
public void OnBeginDrag(PointerEventData eventData) //开始拖拽
{
}
public void OnDrag(PointerEventData eventData) //正在拖拽
{
}
public void OnEndDrag(PointerEventData eventData) //结束拖拽
{
}
需要实现物品拖移,首先我们需要记录下物品原始的位置,用于拖拽到非法位置之后可以回归到原来的位置,然后需要区分左右键,左键代表正常拖移,右键代表平分物体拖移。
public Transform originalParent; //初始位置
public void OnBeginDrag(PointerEventData eventData)
{
//记录初始位置
originalParent = transform.parent;
Item iItem = gameObject.GetComponent<Item>();
if (Average.instance.isLeft || iItem.Info.Num==1)
{
//将拖动的物品放到DragCanvas下
transform.SetParent(Inventory.instance.dragCanvas.transform,true);
}
else//右键平分物品
{
GameObject a = Instantiate(gameObject, transform.parent, true);
int num = iItem.Info.Num / 2;
iItem.Info.Num -= num;
Item aItem = a.GetComponent<Item>();
aItem.Info.Num = num;
transform.SetParent(Inventory.instance.dragCanvas.transform,true);
}
}
正在拖移至需要实时更新物品位置即可。
public void OnDrag(PointerEventData eventData)
{
//跟随鼠标移动
transform.position = eventData.position;
}
左后结束拖移需要判断物品当前位置是否合法。
public void OnEndDrag(PointerEventData eventData)
{
//放下物品 交换数据
if (EventSystem.current.IsPointerOverGameObject())//是否指向UI组件
{
if(Inventory.instance.CheckInInventoryUI(eventData.position))
{
Vector2 a = eventData.pointerEnter.transform.position;
//寻找最近的物品格
GameObject item = null;
double minDistance=1000000000f;
for (int i = 0; i < Inventory.instance.transform.childCount; i++)
{
Vector2 b = Inventory.instance.transform.GetChild(i).transform.position;
double dis = Vector2.Distance(a, b);
if (dis < minDistance)
{
minDistance = dis;
item = Inventory.instance.transform.GetChild(i).gameObject;
}
}
if (item != null)
{
//物品交换
if (Swap(item))
{
Item it1 = item.transform.GetChild(0).GetComponent<Item>();
Item it2 = transform.GetComponent<Item>();
//合并同类物品
if (it1.Info.Name == it2.Info.Name)
{
it1.Info.Num += it2.Info.Num;
Destroy(gameObject);
}
else
{
item.transform.GetChild(0).SetParent(originalParent,true);
transform.SetParent(item.transform, true);
SetRectTransform(gameObject);
SetRectTransform(originalParent.GetChild(0).gameObject);
}
}
else
{
//空物品格
transform.SetParent(item.transform, true);
SetRectTransform(gameObject);
}
}
}
else
{
//不是物品格,返回原位
transform.SetParent(originalParent,true);
SetRectTransform(gameObject);
}
}
}
这边需要在Inventory脚本中添加一个CheckInInventoryUI
函数。
public bool CheckInInventoryUI(Vector3 position)//此处这个位置是要传输进来的位置
{
for(int i = 0; i < transform.childCount; i++)
{
RectTransform t = transform.GetChild(i).transform as RectTransform;//强制类型转换
if (RectTransformUtility.RectangleContainsScreenPoint(t, position))//判断当前位置是否物品栏里
{
return true;
}
}
return false;
}
最后编写swap
函数和SetRectTransform
函数。
private bool Swap(GameObject tmp)
{
if (tmp.transform.childCount == 0)
{
return false;
}
else
{
return true;
}
}
private void SetRectTransform(GameObject tmp)
{
RectTransform rt = tmp.transform.GetComponent<RectTransform>();
rt.offsetMax = new Vector2(-5f, -5f);
rt.offsetMin = new Vector2(5f, 5f);
}
4、Average脚本
最后是在DragItem中用到的Average脚本。
public static Average instance;
public bool isLeft;
private void Awake()
{
instance = gameObject.GetComponent<Average>();
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Mouse0))
{
isLeft = true;
}
else if (Input.GetKeyDown(KeyCode.Mouse1))
{
isLeft = false;
}
}
效果
完整代码
Inventory
using System;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using UnityEngine.Serialization;
using UnityEngine.UI;
[System.Serializable]
public class InventoryInfo
{
public string Parent;
public string Name;
public int Num;
public Sprite icon;
public enum Type
{
Weapon,
Food
}
public Type myType;
}
public class InvenInfoList
{
public List<InventoryInfo> inventoryInfo = new List<InventoryInfo>();
}
public class Inventory : MonoBehaviour
{
public static Inventory instance;
private string filePath = Application.streamingAssetsPath +"/GameData/saveData.json";
private InvenInfoList list = new InvenInfoList();
public GameObject itemPrefab;
public GameObject dragCanvas;
private void Awake()
{
dragCanvas = GameObject.Find("DragCanvas");
instance = GetComponent<Inventory>();
//如果存在存档,读取
if (File.Exists(filePath))
{
LoadByJson();
}
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.M))
{
SaveByJson();
}
}
private void CreateSave()
{
for (int i = 0; i < transform.childCount; i++)
{
InventoryInfo a = new InventoryInfo();
GameObject slot = transform.GetChild(i).gameObject;
if (slot.transform.childCount != 0)
{
Item tmp =slot.transform.GetChild(0).GetComponent<Item>();
a.Parent = slot.name;
a.Name = tmp.Info.Name;
a.Num = tmp.Info.Num;
a.icon = tmp.Info.icon;
a.myType = tmp.Info.myType;
list.inventoryInfo.Add(a);
}
}
}
private void SaveByJson()
{
list.inventoryInfo.Clear();
CreateSave();
string json = JsonUtility.ToJson(list,true);
StreamWriter sw = new StreamWriter(filePath);
sw.Write(json);
sw.Close();
}
private void LoadByJson()
{
string json;
StreamReader sr = new StreamReader(filePath);
json = sr.ReadToEnd();
sr.Close();
list = JsonUtility.FromJson<InvenInfoList>(json);
SetGame();
}
private void SetGame()
{
for (int i = 0; i < list.inventoryInfo.Count; i++)
{
InventoryInfo a = new InventoryInfo();
for (int j = 0; j < transform.childCount; j++)
{
GameObject slot = transform.GetChild(j).gameObject;
//读取到当前物品格信息,写入
if (slot.name == list.inventoryInfo[i].Parent)
{
GameObject it = Instantiate(itemPrefab, slot.transform, true);
Item tmp =it.GetComponent<Item>();
RectTransform rt = it.GetComponent<RectTransform>();
rt.offsetMax = new Vector2(-5f, -5f);
rt.offsetMin = new Vector2(5f, 5f);
tmp.Info.Name = list.inventoryInfo[i].Name;
tmp.Info.Num = list.inventoryInfo[i].Num;
tmp.Info.icon = list.inventoryInfo[i].icon;
Image icon = tmp.GetComponent<Image>();
icon.sprite = tmp.Info.icon;
tmp.Info.myType = list.inventoryInfo[i].myType;
}
}
}
}
public bool CheckInInventoryUI(Vector3 position)//此处这个位置是要传输进来的位置
{
for(int i = 0; i < transform.childCount; i++)
{
RectTransform t = transform.GetChild(i).transform as RectTransform;//强制类型转换
if (RectTransformUtility.RectangleContainsScreenPoint(t, position))//判断当前位置是否物品栏里
{
return true;
}
}
return false;
}
}
Item
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class Item : MonoBehaviour
{
public InventoryInfo Info;
private TMP_Text a;
private void Start()
{
a = transform.GetChild(0).GetComponent<TMP_Text>();
}
private void Update()
{
a.text = Info.Num.ToString();
}
}
DragItem
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class DragItem : MonoBehaviour,IBeginDragHandler,IDragHandler,IEndDragHandler
{
public Transform originalParent; //初始位置
public void OnBeginDrag(PointerEventData eventData)
{
//记录初始位置
originalParent = transform.parent;
Item iItem = gameObject.GetComponent<Item>();
if (Average.instance.isLeft || iItem.Info.Num==1)
{
//将拖动的物品放到DragCanvas下
transform.SetParent(Inventory.instance.dragCanvas.transform,true);
}
else//右键平分物品
{
GameObject a = Instantiate(gameObject, transform.parent, true);
int num = iItem.Info.Num / 2;
iItem.Info.Num -= num;
Item aItem = a.GetComponent<Item>();
aItem.Info.Num = num;
transform.SetParent(Inventory.instance.dragCanvas.transform,true);
}
}
public void OnDrag(PointerEventData eventData)
{
//跟随鼠标移动
transform.position = eventData.position;
}
public void OnEndDrag(PointerEventData eventData)
{
//放下物品 交换数据
if (EventSystem.current.IsPointerOverGameObject())//是否指向UI组件
{
if(Inventory.instance.CheckInInventoryUI(eventData.position))
{
Vector2 a = eventData.pointerEnter.transform.position;
//寻找最近的物品格
GameObject item = null;
double minDistance=1000000000f;
for (int i = 0; i < Inventory.instance.transform.childCount; i++)
{
Vector2 b = Inventory.instance.transform.GetChild(i).transform.position;
double dis = Vector2.Distance(a, b);
if (dis < minDistance)
{
minDistance = dis;
item = Inventory.instance.transform.GetChild(i).gameObject;
}
}
if (item != null)
{
//物品交换
if (Swap(item))
{
Item it1 = item.transform.GetChild(0).GetComponent<Item>();
Item it2 = transform.GetComponent<Item>();
//合并同类物品
if (it1.Info.Name == it2.Info.Name)
{
it1.Info.Num += it2.Info.Num;
Destroy(gameObject);
}
else
{
item.transform.GetChild(0).SetParent(originalParent,true);
transform.SetParent(item.transform, true);
SetRectTransform(gameObject);
SetRectTransform(originalParent.GetChild(0).gameObject);
}
}
else
{
//空物品格
transform.SetParent(item.transform, true);
SetRectTransform(gameObject);
}
}
}
else
{
//不是物品格,返回原位
transform.SetParent(originalParent,true);
SetRectTransform(gameObject);
}
}
}
private bool Swap(GameObject tmp)
{
if (tmp.transform.childCount == 0)
{
return false;
}
else
{
return true;
}
}
private void SetRectTransform(GameObject tmp)
{
RectTransform rt = tmp.transform.GetComponent<RectTransform>();
rt.offsetMax = new Vector2(-5f, -5f);
rt.offsetMin = new Vector2(5f, 5f);
}
}
Average
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Average : MonoBehaviour
{
public static Average instance;
public bool isLeft;
private void Awake()
{
instance = gameObject.GetComponent<Average>();
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Mouse0))
{
isLeft = true;
}
else if (Input.GetKeyDown(KeyCode.Mouse1))
{
isLeft = false;
}
}
}
资源下载可前往个人博客:Ackow