【图文详解】Unity存储游戏数据的几种方法_unity存储数据的方法-CSDN博客

前言

PlayerPrefs 用于存入磁盘本地的数据

ScriptableObject用于缓存固定的数值,打包用不了
Json 可以写入磁盘
如果数据需要在Web应用程序之间传输,则 JSON格式通常是首选,
如果数据需要与其他系统进行交互,则XML格式通常是首选,因为它是一种通用的数据交换格式,被广泛使用。

PlayerPrefs

https://blog.csdn.net/qq_32175379/article/details/121199932

概念

  • PlayerPrefs是Unity自带的一种简单的键值(键即用于查找的关键字,值即存储的数据)存储系统,用于存储少量的游戏数据。
  • PlayerPrefs是基于本地文件存储数据的,数据以键值对的形式保存在本地磁盘上。

作用

  • PlayerPrefs主要适用于一些简单的游戏数据的存储,例如玩家的音效、音乐、难度等级等。使用PlayerPrefs可以方便地在游戏中读写这些数据。

  • 可用于存储一些非关键性的数据,尤其在没有服务器的单机游戏中,游戏存档、分数排名等都需要用到数据存储,可以使用PlayerPrefs轻松实现数据存储。

数据存储

  • 存储数据直接调用三种Set方法
1
2
3
PlayerPrefs.SetInt("myAge",18);
PlayerPrefs.SetFloat("myHeight",178.1f);
PlayerPrefs.SetString("myName","Frag";)
  • 存储在硬盘

现在是数据存储在内存中,正常游戏结束时unity会自动把数据存在硬盘中

如果游戏是崩溃结束的话,数据是不会存在硬盘中的

调用

1
PlayerPrefs.Save();

就能存储在硬盘中

  • 其他类型的数据

如果你要在存储除了上面三个数据类型的其他数据类型的话,只能降低精度或者上升精度进行处理

如:

1
2
3
4
5
6
//double
double myWight=149.99;
PlayerPrefs.SetFloat("myWight",(float)myWight);
//bool
bool sex =true;
PlayerPrefs.SetInt("sex",sex?1:0)
  • 覆盖

如果不同类型用同一键名存储,会进行覆盖

1
2
PlayerPrefs.SetInt("myHeight",175);
PlayerPrefs.SetFloat("myHeight",178.1f);

会更新成178.5f

读取数据

读取数据可以在内存或者硬盘中读取

1
2
3
4
5
PlayerPrefs.GetInt("myAge");
PlayerPrefs.GetFloat("myHeight");
PlayerPrefs.GetString("myName");
//如果找不到myAge的话就返回默认值100
PlayerPrefs.GetInt("myAge"100);
  • 读取不同类型的键值对

如果你存储的value的数据类型和读取的数据类型不同的话就找不到,会返回默认值(系统默认值为0)

1
2
3
4
PlayerPrefs.GetFloat("myHeight");
int myHeight=PlayerPrefs.GetInt("myHeight");
print(myHeight);
// 输出0
  • 判断键是否存在
1
2
3
if(PlayerPrefs.HasKey("myHeight")){
//一般里面判断多个key有没有重复
}

删除数据

1
2
3
4
5
6
//删除所有存储数据
PlayerPrefs.DeleteAll();
//删除key为"score"的数据
PlayerPrefs.DeleteKey("score");
//查找是否存在key为"score"的数据
bool exist = PlayerPrefs.HasKey("score")

存储位置

  • 在Windows平台下,存储在注册表的 HKEY_CURRENT_USER\Software[company name][product name]键下。

PlayerPrefs 存储在 HKCU\Software\[公司名称]\[产品名称]项下的注册表中

其中公司和产品名称是 在”Project Settings”中设置的名称。
运行 regedit->HKEY_CURRENT_USER->SOFTHARE->unity->unityEditor->公司名称->产品名称

  • 在Android中在 /data/data/包名/shared_prefs/pkg-name.xml中
  • 在Mac OS X平台下,存储在~/Library/Preferences文件夹,名为unity.[company name].[product name].plist。

小结

  • PlayerPrefs只适合存储少量数据,如果需要存储大量数据,建议使用其他的数据存储方式,如ScriptableObject、JSON、XML或数据库等。

  • PlayerPrefs存储的数据可以被修改或删除,因此在实际开发中需要注意数据安全性和数据的备份。

通过反射存储数据

  • 反射3剑客—— 1T 两 A
    • AType —— 用于获取 类的所有信息 字段 属性 方法 等等
    • Assembly —— 用于获取程序集 通过程序集获取Type
    • Activator —— 用于快速实例化对象
  • 判断一个类型的对象是否可以让另一个类型为自己分配空间

使用IsAssignableFrom()判断是否可以通过传入的类型为自己 分配空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//父类装子类
//是否可以从某一个类型的对象 为自己 分配 空间

Type fatherType = typeof(Father);
Type sonType = typeof(Son);

//调用者 通过该方法进行判断 判断是否可以通过传入的类型为自己 分配空间
if( fatherType.IsAssignableFrom(sonType) )
{
print("可以装");
Father f = Activator.CreateInstance(sonType) as Father;
print(f);
}
else
{
print("不能装");
}
  • 通过反射获取泛型类型

使用 GetGenericArguments()

1
2
3
4
5
6
7
8
9
10
11
12
13
List<int> list = new List<int>();
Type listType = list.GetType();
//GetGenericArguments表示泛型类型的类型实参的 Type 对象的数组。 如果当前类型不是泛型类型,则返回一个空数组。
Type[] types = listType.GetGenericArguments();
//输出的是System.Int32,这个types是泛型的类型的数组
print(types[0])


Dictionary<string, float> dic = new Dictionary<string, float>();
Type dicType = dic.GetType();
types = dicType.GetGenericArguments();
print(types[0]);//输出System.String key
print(types[1]);//输出System.Single val

封装PlayerPrefsDataMgr

单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static PlayerPrefsDataMgr instance = new PlayerPrefsDataMgr();

public static PlayerPrefsDataMgr Instance
{
get
{
return instance;
}
}

private PlayerPrefsDataMgr()
{

}

存储数据

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
/// <summary>
/// 存储数据
/// </summary>
/// <param name="data">数据对象</param>
/// <param name="keyName">数据对象的唯一key 自己控制</param>
public void SaveData( object data, string keyName )
{
//就是要通过 Type 得到传入数据对象的所有的 字段
//然后结合 PlayerPrefs来进行存储

#region 第一步 获取传入数据对象的所有字段
Type dataType = data.GetType();
//得到所有的字段
FieldInfo[] infos = dataType.GetFields();
#endregion

#region 第二步 自己定义一个key的规则 进行数据存储
//我们存储都是通过PlayerPrefs来进行存储的
//保证key的唯一性 我们就需要自己定一个key的规则

//我们自己定一个规则
// keyName_数据类型_字段类型_字段名
#endregion

#region 第三步 遍历这些字段 进行数据存储
string saveKeyName = "";
FieldInfo info;
for (int i = 0; i < infos.Length; i++)
{
//对每一个字段 进行数据存储
//得到具体的字段信息
info = infos[i];
//通过FieldInfo可以直接获取到 字段的类型 和字段的名字
//字段的类型 info.FieldType.Name
//字段的名字 info.Name;

//要根据我们定的key的拼接规则 来进行key的生成
//Player1_PlayerInfo_Int32_age
saveKeyName = keyName + "_" + dataType.Name +
"_" + info.FieldType.Name + "_" + info.Name;

//现在得到了Key 按照我们的规则
//接下来就要来通过PlayerPrefs来进行存储
//如何获取值
//info.GetValue(data)
//封装了一个方法 专门来存储值
SaveValue(info.GetValue(data), saveKeyName);
}

PlayerPrefs.Save();
#endregion
}

private void SaveValue(object value, string keyName)
{
//直接通过PlayerPrefs来进行存储了
//就是根据数据类型的不同 来决定使用哪一个API来进行存储
//PlayerPrefs只支持3种类型存储
//判断 数据类型 是什么类型 然后调用具体的方法来存储
Type fieldType = value.GetType();

//类型判断
//是不是int
if( fieldType == typeof(int) )
{
Debug.Log("存储int" + keyName);
PlayerPrefs.SetInt(keyName, (int)value);
}
else if (fieldType == typeof(float))
{
Debug.Log("存储float" + keyName);
PlayerPrefs.SetFloat(keyName, (float)value);
}
else if (fieldType == typeof(string))
{
Debug.Log("存储string" + keyName);
PlayerPrefs.SetString(keyName, value.ToString());
}
else if (fieldType == typeof(bool))
{
Debug.Log("存储bool" + keyName);
//自己顶一个存储bool的规则
PlayerPrefs.SetInt(keyName, (bool)value ? 1 : 0);
}
//如何判断 泛型类的类型呢
//通过反射 判断 父子关系
//这相当于是判断 字段是不是IList的子类
else if( typeof(IList).IsAssignableFrom(fieldType) )
{
Debug.Log("存储List" + keyName);
//父类装子类
IList list = value as IList;
//先存储 数量
PlayerPrefs.SetInt(keyName, list.Count);
int index = 0;
foreach (object obj in list)
{
//存储具体的值
SaveValue(obj, keyName + index);
++index;
}
}
//判断是不是Dictionary类型 通过Dictionary的父类来判断
else if( typeof(IDictionary).IsAssignableFrom(fieldType) )
{
Debug.Log("存储Dictionary" + keyName);
//父类装自来
IDictionary dic = value as IDictionary;
//先存字典长度
PlayerPrefs.SetInt(keyName, dic.Count);
//遍历存储Dic里面的具体值
//用于区分 表示的 区分 key
int index = 0;
foreach (object key in dic.Keys)
{
SaveValue(key, keyName + "_key_" + index);
SaveValue(dic[key], keyName + "_value_" + index);
++index;
}
}
//基础数据类型都不是 那么可能就是自定义类型
else
{
SaveData(value, keyName);
}
}

取数据

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
/// <summary>
/// 读取数据
/// </summary>
/// <param name="type">想要读取数据的 数据类型Type</param>
/// <param name="keyName">数据对象的唯一key 自己控制</param>
/// <returns></returns>
public object LoadData( Type type, string keyName )
{
//不用object对象传入 而使用 Type传入
//主要目的是节约一行代码(在外部)
//假设现在你要 读取一个Player类型的数据 如果是object 你就必须在外部new一个对象传入
//现在有Type的 你只用传入 一个Type typeof(Player) 然后我在内部动态创建一个对象给你返回出来
//达到了 让你在外部 少写一行代码的作用

//根据你传入的类型 和 keyName
//依据你存储数据时 key的拼接规则 来进行数据的获取赋值 返回出去

//根据传入的Type 创建一个对象 用于存储数据
object data = Activator.CreateInstance(type);
//要往这个new出来的对象中存储数据 填充数据
//得到所有字段
FieldInfo[] infos = type.GetFields();
//用于拼接key的字符串
string loadKeyName = "";
//用于存储 单个字段信息的 对象
FieldInfo info;
for (int i = 0; i < infos.Length; i++)
{
info = infos[i];
//key的拼接规则 一定是和存储时一模一样 这样才能找到对应数据
loadKeyName = keyName + "_" + type.Name +
"_" + info.FieldType.Name + "_" + info.Name;

//有key 就可以结合 PlayerPrefs来读取数据
//填充数据到data中
info.SetValue(data, LoadValue(info.FieldType, loadKeyName));
}
return data;
}

/// <summary>
/// 得到单个数据的方法
/// </summary>
/// <param name="fieldType">字段类型 用于判断 用哪个api来读取</param>
/// <param name="keyName">用于获取具体数据</param>
/// <returns></returns>
private object LoadValue(Type fieldType, string keyName)
{
//根据 字段类型 来判断 用哪个API来读取
if( fieldType == typeof(int) )
{
return PlayerPrefs.GetInt(keyName, 0);
}
else if (fieldType == typeof(float))
{
return PlayerPrefs.GetFloat(keyName, 0);
}
else if (fieldType == typeof(string))
{
return PlayerPrefs.GetString(keyName, "");
}
else if (fieldType == typeof(bool))
{
//根据自定义存储bool的规则 来进行值的获取
return PlayerPrefs.GetInt(keyName, 0) == 1 ? true : false;
}
else if( typeof(IList).IsAssignableFrom(fieldType) )
{
//得到长度
int count = PlayerPrefs.GetInt(keyName, 0);
//实例化一个List对象 来进行赋值
//用了反射中双A中 Activator进行快速实例化List对象
IList list = Activator.CreateInstance(fieldType) as IList;
for (int i = 0; i < count; i++)
{
//目的是要得到 List中泛型的类型
list.Add(LoadValue(fieldType.GetGenericArguments()[0], keyName + i));
}
return list;
}
else if( typeof(IDictionary).IsAssignableFrom(fieldType) )
{
//得到字典的长度
int count = PlayerPrefs.GetInt(keyName, 0);
//实例化一个字典对象 用父类装子类
IDictionary dic = Activator.CreateInstance(fieldType) as IDictionary;
Type[] kvType = fieldType.GetGenericArguments();
for (int i = 0; i < count; i++)
{
dic.Add(LoadValue(kvType[0], keyName + "_key_" + i),
LoadValue(kvType[1], keyName + "_value_" + i));
}
return dic;
}
else
{
return LoadData(fieldType, keyName);
}


return null;
}

完整代码

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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;

/// <summary>
/// PlayerPrefs数据管理类 统一管理数据的存储和读取
/// </summary>
public class PlayerPrefsDataMgr
{
private static PlayerPrefsDataMgr instance = new PlayerPrefsDataMgr();

public static PlayerPrefsDataMgr Instance
{
get
{
return instance;
}
}

private PlayerPrefsDataMgr()
{

}

/// <summary>
/// 存储数据
/// </summary>
/// <param name="data">数据对象</param>
/// <param name="keyName">数据对象的唯一key 自己控制</param>
public void SaveData( object data, string keyName )
{
//就是要通过 Type 得到传入数据对象的所有的 字段
//然后结合 PlayerPrefs来进行存储

#region 第一步 获取传入数据对象的所有字段
Type dataType = data.GetType();
//得到所有的字段
FieldInfo[] infos = dataType.GetFields();
#endregion

#region 第二步 自己定义一个key的规则 进行数据存储
//我们存储都是通过PlayerPrefs来进行存储的
//保证key的唯一性 我们就需要自己定一个key的规则

//我们自己定一个规则
// keyName_数据类型_字段类型_字段名
#endregion

#region 第三步 遍历这些字段 进行数据存储
string saveKeyName = "";
FieldInfo info;
for (int i = 0; i < infos.Length; i++)
{
//对每一个字段 进行数据存储
//得到具体的字段信息
info = infos[i];
//通过FieldInfo可以直接获取到 字段的类型 和字段的名字
//字段的类型 info.FieldType.Name
//字段的名字 info.Name;

//要根据我们定的key的拼接规则 来进行key的生成
//Player1_PlayerInfo_Int32_age
saveKeyName = keyName + "_" + dataType.Name +
"_" + info.FieldType.Name + "_" + info.Name;

//现在得到了Key 按照我们的规则
//接下来就要来通过PlayerPrefs来进行存储
//如何获取值
//info.GetValue(data)
//封装了一个方法 专门来存储值
SaveValue(info.GetValue(data), saveKeyName);
}

PlayerPrefs.Save();
#endregion
}

private void SaveValue(object value, string keyName)
{
//直接通过PlayerPrefs来进行存储了
//就是根据数据类型的不同 来决定使用哪一个API来进行存储
//PlayerPrefs只支持3种类型存储
//判断 数据类型 是什么类型 然后调用具体的方法来存储
Type fieldType = value.GetType();

//类型判断
//是不是int
if( fieldType == typeof(int) )
{
Debug.Log("存储int" + keyName);
PlayerPrefs.SetInt(keyName, (int)value);
}
else if (fieldType == typeof(float))
{
Debug.Log("存储float" + keyName);
PlayerPrefs.SetFloat(keyName, (float)value);
}
else if (fieldType == typeof(string))
{
Debug.Log("存储string" + keyName);
PlayerPrefs.SetString(keyName, value.ToString());
}
else if (fieldType == typeof(bool))
{
Debug.Log("存储bool" + keyName);
//自己顶一个存储bool的规则
PlayerPrefs.SetInt(keyName, (bool)value ? 1 : 0);
}
//如何判断 泛型类的类型呢
//通过反射 判断 父子关系
//这相当于是判断 字段是不是IList的子类
else if( typeof(IList).IsAssignableFrom(fieldType) )
{
Debug.Log("存储List" + keyName);
//父类装子类
IList list = value as IList;
//先存储 数量
PlayerPrefs.SetInt(keyName, list.Count);
int index = 0;
foreach (object obj in list)
{
//存储具体的值
SaveValue(obj, keyName + index);
++index;
}
}
//判断是不是Dictionary类型 通过Dictionary的父类来判断
else if( typeof(IDictionary).IsAssignableFrom(fieldType) )
{
Debug.Log("存储Dictionary" + keyName);
//父类装自来
IDictionary dic = value as IDictionary;
//先存字典长度
PlayerPrefs.SetInt(keyName, dic.Count);
//遍历存储Dic里面的具体值
//用于区分 表示的 区分 key
int index = 0;
foreach (object key in dic.Keys)
{
SaveValue(key, keyName + "_key_" + index);
SaveValue(dic[key], keyName + "_value_" + index);
++index;
}
}
//基础数据类型都不是 那么可能就是自定义类型
else
{
SaveData(value, keyName);
}
}

/// <summary>
/// 读取数据
/// </summary>
/// <param name="type">想要读取数据的 数据类型Type</param>
/// <param name="keyName">数据对象的唯一key 自己控制</param>
/// <returns></returns>
public object LoadData( Type type, string keyName )
{
//不用object对象传入 而使用 Type传入
//主要目的是节约一行代码(在外部)
//假设现在你要 读取一个Player类型的数据 如果是object 你就必须在外部new一个对象传入
//现在有Type的 你只用传入 一个Type typeof(Player) 然后我在内部动态创建一个对象给你返回出来
//达到了 让你在外部 少写一行代码的作用

//根据你传入的类型 和 keyName
//依据你存储数据时 key的拼接规则 来进行数据的获取赋值 返回出去

//根据传入的Type 创建一个对象 用于存储数据
object data = Activator.CreateInstance(type);
//要往这个new出来的对象中存储数据 填充数据
//得到所有字段
FieldInfo[] infos = type.GetFields();
//用于拼接key的字符串
string loadKeyName = "";
//用于存储 单个字段信息的 对象
FieldInfo info;
for (int i = 0; i < infos.Length; i++)
{
info = infos[i];
//key的拼接规则 一定是和存储时一模一样 这样才能找到对应数据
loadKeyName = keyName + "_" + type.Name +
"_" + info.FieldType.Name + "_" + info.Name;

//有key 就可以结合 PlayerPrefs来读取数据
//填充数据到data中
info.SetValue(data, LoadValue(info.FieldType, loadKeyName));
}
return data;
}

/// <summary>
/// 得到单个数据的方法
/// </summary>
/// <param name="fieldType">字段类型 用于判断 用哪个api来读取</param>
/// <param name="keyName">用于获取具体数据</param>
/// <returns></returns>
private object LoadValue(Type fieldType, string keyName)
{
//根据 字段类型 来判断 用哪个API来读取
if( fieldType == typeof(int) )
{
return PlayerPrefs.GetInt(keyName, 0);
}
else if (fieldType == typeof(float))
{
return PlayerPrefs.GetFloat(keyName, 0);
}
else if (fieldType == typeof(string))
{
return PlayerPrefs.GetString(keyName, "");
}
else if (fieldType == typeof(bool))
{
//根据自定义存储bool的规则 来进行值的获取
return PlayerPrefs.GetInt(keyName, 0) == 1 ? true : false;
}
else if( typeof(IList).IsAssignableFrom(fieldType) )
{
//得到长度
int count = PlayerPrefs.GetInt(keyName, 0);
//实例化一个List对象 来进行赋值
//用了反射中双A中 Activator进行快速实例化List对象
IList list = Activator.CreateInstance(fieldType) as IList;
for (int i = 0; i < count; i++)
{
//目的是要得到 List中泛型的类型
list.Add(LoadValue(fieldType.GetGenericArguments()[0], keyName + i));
}
return list;
}
else if( typeof(IDictionary).IsAssignableFrom(fieldType) )
{
//得到字典的长度
int count = PlayerPrefs.GetInt(keyName, 0);
//实例化一个字典对象 用父类装子类
IDictionary dic = Activator.CreateInstance(fieldType) as IDictionary;
Type[] kvType = fieldType.GetGenericArguments();
for (int i = 0; i < count; i++)
{
dic.Add(LoadValue(kvType[0], keyName + "_key_" + i),
LoadValue(kvType[1], keyName + "_value_" + i));
}
return dic;
}
else
{
return LoadData(fieldType, keyName);
}


return null;
}
}



调用Test类

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerInfo
{
public int age;
public string name;
public float height;
public bool sex;

public List<int> list;

public Dictionary<int, string> dic;

public ItemInfo itemInfo;

public List<ItemInfo> itemList;

public Dictionary<int, ItemInfo> itemDic;
}

public class ItemInfo
{
public int id;
public int num;

public ItemInfo()
{

}

public ItemInfo(int id, int num)
{
this.id = id;
this.num = num;
}
}


public class Test : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
//先清除下所有的键值
PlayerPrefs.DeleteAll();
//读取数据
PlayerInfo p = PlayerPrefsDataMgr.Instance.LoadData(typeof(PlayerInfo), "Player1") as PlayerInfo;

//游戏逻辑中 会去 修改这个玩家数据
p.age = 18;
p.name = "D_R";
p.height = 1000;
p.sex = true;

p.itemList.Add(new ItemInfo(1, 99));
p.itemList.Add(new ItemInfo(2, 199));

p.itemDic.Add(3, new ItemInfo(3, 1));
p.itemDic.Add(4, new ItemInfo(4, 2));

//游戏数据存储
PlayerPrefsDataMgr.Instance.SaveData(p, "Player1");
}

// Update is called once per frame
void Update()
{

}
}



结束语
总的来说,在游戏开发中,存储游戏数据是非常重要的,因为游戏数据决定了游戏的各个方面,例如游戏的进度、玩家的成就、游戏的设置等等

不同的方法有不同的特点和应用场景,在实际的游戏开发中,我们可以根据实际的需求选择合适的数据存储方法。如果我们只存储一些简单的数据,可以选择PlayerPrefs;如果存储一些复杂的数据,可以选择JSON或XML;如果需要存储大量的结构化数据,可以选择SQLite数据库。掌握这些存储数据的技巧将有助于我们更好地开发游戏并提供更好的用户体验。