Unity使用JSON存储实现背包功能

前言

在Unity有五种常用的存储数据的方法,可以用来存储我们游戏的数据。

一、PlayerPrefs

这是Unity自带的一种用于本地持久化保存与读取的一个类,采用以键值对的形式将数据保存在文件中。

PlayerPrefs

1
2
3
4
5
6
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

1
2
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 GUIFantasy Wooden GUI : Free

1、创建Canvas画布

创建一个Canvas(UI画布),然后在Canvas下创建一个Image,命名为Bag,放入图片,作为背包背景,将其缩放到合适大小,放置到合适位置。

在创建一个DragCanvas,并将sort order值设置为1,使其内的UI始终高于Canvas的UI显示,后面需要用到。

Bag

2、添加组件

然后添加Grid Layout Group组件,用来控制其子物体的排序格式。

Grid

CellSize:每个子物体的大小。

Spacing:每个子物体之间的间隔。

Start Corner:子物体的起始角落。

Start Axis:子物体起始轴线。

3、创建物品格

在Bag下创建一个Image,命名为Slot,如上添加图片,然后复制,达到想要的背包效果。

Slot

4、创建Item

在Slot下创建一个Image,命名为Item,用来显示物品的图片,然后在Item下创建一个Text,用来显示物品的数字。将Item制作为预设体,方便后期使用。

5、最终层级关系

层级关系

二、脚本编写

1、Inventory脚本

首先定义一个物品类,用来存放每个物品信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class InventoryInfo
{
public string Parent; //物品属于哪一个物品格
public string Name;
public int Num;
public Sprite icon; //物品图标

public enum Type //用于区分类别,本教程中未使用
{
Weapon,
Food
}
public Type myType;
}

在定义一个类来存储物品信息。

1
2
3
4
public class InvenInfoList
{
public List<InventoryInfo> inventoryInfo = new List<InventoryInfo>();
}

定义脚本所需要的变量。

1
2
3
4
5
6
7
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,让我们能往计算机硬盘中写入数据。

1
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函数,用于数据存储。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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函数,用于向硬盘内写入数据。

1
2
3
4
5
6
7
8
9
10
11
12
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函数中调用它。

1
2
3
4
5
6
7
8
9
10
11
12
private void LoadByJson()
{
string json;

StreamReader sr = new StreamReader(filePath); //获取硬盘中的Json
json = sr.ReadToEnd();
sr.Close();

list = JsonUtility.FromJson<InvenInfoList>(json); //将其重新写入list中

SetGame(); //设置游戏
}
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
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脚本

用于物品信息实时更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void OnBeginDrag(PointerEventData eventData)	//开始拖拽
{

}

public void OnDrag(PointerEventData eventData) //正在拖拽
{

}

public void OnEndDrag(PointerEventData eventData) //结束拖拽
{

}

需要实现物品拖移,首先我们需要记录下物品原始的位置,用于拖拽到非法位置之后可以回归到原来的位置,然后需要区分左右键,左键代表正常拖移,右键代表平分物体拖移

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

}
}

正在拖移至需要实时更新物品位置即可。

1
2
3
4
5
public void OnDrag(PointerEventData eventData)
{
//跟随鼠标移动
transform.position = eventData.position;
}

左后结束拖移需要判断物品当前位置是否合法。

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
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函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
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函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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脚本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

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
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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

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
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

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

Unity包资源 提取码: a53r