• 游戏数据在内存和硬盘间的双向传输过程
    • 存储方向:游戏运行时内存数据 → 硬盘存储(传统意义上的”存盘”)
    • 读取方向:硬盘存储数据 → 游戏内存加载

作用: 保存进度

PlayerPrefs

存储

类似于键值对存储 提供了存储3种数据的方法 int float string
键string 值int float string对应三种API

PlayerPrefs.SetInt(“myAge”, 18);
PlayerPrefs.SetFloat(“myHeight”, 177.5f);
PlayerPrefs.SetString(“myName”, “John”);
直接调用Set相关方法 只会把数据存到内存里
游戏结束时 Unity会自动把数据存到硬盘中
游戏不是正常结束 数据不会存硬盘
PlayerPrefs.Save();
调用这个方法就会马上储存在硬盘当中

局限性:只能存三种类型的数据
如果想要存储别的类型的数据 只能降低精度 或者上升精度来进行储存
不做加密的话可以被直接修改

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

读取

int

如果键没有值或者值被覆盖了会默认为0
int age = PlayerPrefs.GetInt(“myAge”);

如果找不到键对应的值,那么就会返回函数的第二个参数 作为默认值 其他两个的Get方法也相同
age = PlayerPrefs.GetInt(“myAge”, 100);

float

float height = PlayerPrefs.GetFloat(“myHeight”, 1000f);

string

string name = PlayerPrefs.GetString(“myName”);

判断数据是否存在

PlayerPrefs.HasKey(“myName”);

删除数据

删除指定键值对
PlayerPrefs.DeleteKey(“myAge”);
删除所有存储的信息
PlayerPrefs.DeleteAll();

数据管理类创建

单例模式:
优势:避免重复创建对象,保证数据操作的一致性

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
//不继承mono
public class PlayerPrefsDataMgr
{
//私有静态成员
private static PlayerPrefsDataMgr instance = new PlayerPrefsDataMgr();
//公共静态属性
public static PlayerPrefsDataMgr Instance
{
get
{
return instance;
}
}
//私有构造 防止外部实例化
private PlayerPrefsDateMgr()
{
}

public void SaveData(object data, string keyname)
{

}
//type传的是读取数据的 数据类型
//不用object对象传入而是用Type 目的是节约一行代码(外部)
//假设现在你要读取一个Player类型的数据 如果是object 你就需要在外面new一个对象传入
//现在用type 只用传入一个type typeof(player) 然后我在内部动态创建一个对象给你返回出来
public object LoadData( Type type, string keyname)
{
//根据传入的类型 和KeyName
//依据你存储数据时 key的拼接规则 来进行数据的获取赋值 返回出去
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
public void SaveData(object data, string keyName)
{
//获取传入数据对象的所有字段
Type dataType = data.GetType();
FieldInfo[] infos = dataType.GetFields();
//自己定义一个key的规则 进行数据存储
//通过PlayerPrefs来进行存储 保证key的唯一性 需要自己定一个key规则

//keyName_数据类型_字段类型_字段名

//遍历字段进行数据存储
string saveKeyName = "";
FieldInfo info;
for(int i = 0; i < info.length; i ++)
{
//得到具体的字段信息
info = infos[i];
//通过FieldInfo可以直接获取到字段的类型和字段的名字
//字段的类型 info.FiledType.Name
//字段的名字 info.Name
//根据自己定的拼接规则 进行Key的生成
saveKeyName = keyName + "_" + dataType.Name
+ "_" info.FieldType.Name + "_" + info.Name;

SaveValue(info.GetValue(data), saveKeyName);
}

private void SaveValue(object value, string keyName)
{
//根据数据类型的不同来决定使用哪一个API进行存储
Type fieldType = value.GetType();

//判断是不是int
if(fieldType == typeof(int))
{
PlayerPrefs.SetInt(KeyName, (int)value);
}
else if(fieldType == typeof(float))
{
PlayerPrefs.SetFloat(KeyName, (float)value);
}
else if(fieldType == typeof(string))
{
PlayerPrefs.SetString(KeyName, value.ToString());
}
else if(fieldType == typeof(bool))
{
//自己定存储bool的规则
PlayerPrefs.SetInt(KeyName, (bool)value ? 1 : 0);
}
}
}

结合反射List数据类型存储

使用typeof(IList).IsAssignableFrom()判断是否为List类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//接上处代码 
if(typeof(Ilist).IsAssignableFrom(fieldType))
{
//父类装子类
Ilist list = value as Ilist;
//先存储数量
PlayerPrefs.SetInt(keyName, list.Count);
int index = 0;
foreach(object obj in list)
{
//存储具体的值
//递归 存list当中基础数据类型
SaveValue(obj, keyName + index);
++ index;
}
}

结合反射Dictionary数据类型存储

思路和list差不多
typeof(IDictionary).IsAssignableFrom(fieldType)判断是不是dictionary

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if(typeof(IDictionary).IsAssignableFrom(fieldType))
{
//父类装子类
IDictionary dic = value as IDictionary;
//先存字典长度
PlayerPrefs.SetInt(keyName, dic.Count);
//遍历存储Dic里面的具体值
int index = 0;
foreach(object key in dic.Keys)
{
SaveValue(key, keyName + "_key_" + index);
SaveValue(dic[key], keyName + "_value_" + index);
++index;
}
}

结合反射自定义类成员存储

不是递归 重新走一遍存储

1
2
3
else {
SaveData(value, keyname);
}

结合反射读取常用数据类型

外部代码:

1
PlayerInfo p2 = PlayerPrefsDataMgr.Instance.LoadData(typeof(PlayerInfo), "Player1") as PlayerInfo;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public object LoadData( Type type, string keyname)
{
//根据传入的Type 创建一个对象 用于存储数据
object data = Activator.CreateInstance(type);
//往这个new出来的对象存储数据 填充数据
//得到所有字段
FieldInfo[] infos = type.GetFields();

string loadKeyName = "";
//用于拼接key的字符串
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 else if(fieldType == typeof(bool))
{
return PlayerPrefs.GetInt(keyName, 0) == 1 ? true : false;
}
}

结合反射读取List数据类型

读取流程类似存储倒过来

1
2
3
4
5
6
7
8
9
10
11
12
13
else if(typeof(IList).IsAssignableFrom(fieldType))
{
//得到长度
int count = PlayerPrefs.GetInt(keyName, 0);
//实例化List对象 来进行赋值
Ilist list = Activator.CreateInstance(fieldType) as list;
for(int i = 0; i < count; i ++)
{
//目的是要得到list中泛型的类型
list.Add(LoadValue(fieldType.GetGenericArguments()[0], keyName + i));
}
return list;
}

结合反射读取Dictionary数据类型

//流程与list差不多

1
2
3
4
5
6
7
8
9
10
11
12
13
else if(typeof(IDictionary).IsAssignableFrom(fieldType))
{
//得到字典长度
int count = PlayerPrefs.GetInt(keyName, 0);
IDictionary dic = Activator.CreateInstance(firldType);
Type[] kvType = field.GetGenericArguments();
for(int i = 0; i < count; i ++)
{
dic.Add(LoadValue(kvType[0], keyName + "_key_" + i),
LoadValue(kvType[1], keyName + "_key_" + i));
}
return dic;
}

结合反射读取自定义类数据类型

1
2
3
4
else 
{
return LoadData(fieldType, keyName);
}

加密思路

让Key和Value让别人看不懂

但是让别人获取到你的源码 知道了你的加密规则之后一切没有意义