• Unity数据持久化之PlayerPrefs


    PlayerPrefs概述

    什么是数据持久化
    数据持久化就是将内存中的数据模型转换为存储模型,以及将存储模型转换为内存中的数据模型的统称。即将游戏数据存储到硬盘,硬盘中数据读取到游戏中,也就是传统意义上的存盘。

    基本方法

    PlayerPrefs是什么
    Unity提供的可以用于存储读取玩家数据的公共类
    存储相关
    PlayerPrefs的数据存储,类似于键值对存储,一个键对应一个值。
    提供了存储3中数据的方法:int、float、string
    键:string类型
    值:int float string对应3种API

    PlayerPrefs.SetInt("myAge",18);
    PlayerPrefs.SetString("myHeight",177f);
    PlayerPrefs.SetString("myName","123");
    
    • 1
    • 2
    • 3

    直接调用Set相关方法,只会把数据存到内存里。当游戏结束时,Unity会自动把数据存到硬盘中。如果游戏不是正常结束的,而是崩溃,数据是不会存到硬盘中的。

    只要调用该方法,就会马上存储到硬盘中。

    PlayerPrefs.Save();
    
    • 1

    PlayerPrefs是有局限性的,它只能存3种类型的数据,如果想要存储别的类型的数据,只能降低精度,或者上升精度来进行存储。

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

    PlayerPrefs.SetFloat("myAge",20.2f);
    
    • 1

    读取相关
    运行时,只要使用了Set相关方法设置了对应键值对,即使没有马上存储在Save在本地,也可以读取出信息。

    //int
    int age = PlayerPrefs.GetInt("myAge");
    age = PlayerPrefs.GetInt("myAge",100);
    //float
    float height = PlayerPrefs.GetFloat("myHeight",1000f);
    //string
    string name = PlayerPrefs.GetString("myName");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    第二个参数默认值的作用就是在得到没有的数据的时候,就可以用它来进行基础数据的初始化。

    判断数据是否存在。

    if(PlayerPrefs.HasKey("myName"))
    {
    }
    
    • 1
    • 2
    • 3

    删除数据
    删除指定键值对

    PlayerPrefs.DeleteKey("myAge");
    
    • 1

    删除所有存储的信息

    PlayerPrefs.DeleteAll();
    
    • 1

    PlayerPrefs存储位置

    PlayerPrefs存储的数据存在哪里?
    1、Windows
    PlayerPrefs存储在:HKCU\Software\[公司名称]\[产品名称]项下的注册表中
    其中公司和产品名称是在“Project Settings”中设置的名称

    查找过程:
    运行regedit
    HKEY_CURRENT_USER
    SOFTWARE
    Unity
    UnityEditor
    公司名称
    产品名称
    2、Android
    /data/data/包名/shared_prefs/pkg-name.xml
    3、IOS
    /Library/Preferences/[应用ID].plist
    PlayerPrefs数据唯一性
    PlayerPrefs中不同数据的唯一性,是由key决定的,不同的key决定了不同的数据。同一项目中,如果不同数据key相同,会造成数据丢失。要保证数据不丢失就要建立一个保证key唯一的规则。

    实践小项目

    反射知识补充

    反射相关内容请移步:C#进阶学习
    反射知识回顾
    反射3剑客
    1、Type:用于获取类的所有信息、字段、属性、方法等等。
    2、Assembly:用于获取程序集,通过程序集获取Type
    3、Activator:用于快速实例化对象。
    判断一个类型的对象是否可以让另一个类型为自己分配空间(父类装子类)

    public class Father
    {
    }
    public class Son:Father
    {
    }
    Type fatherType = typeof(Father);
    Type sonType = typeof(Son);
    //判断是否可以通过传入的类型为自己分配空间
    if(fatherType.IsAssignableFrom(sonType))
    {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    通过反射获取泛型类型

    List<string> list = new List<string>();
    Type listType = list.GetType();
    Type[] types = listType.GetGenericArguments();//获取到泛型类型string
    
    Dictionary<string,float> dic = new Dictionary<string,float>();
    Type dicType = dic.GetType();
    types = dicType.GetGenericArguments();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    数据管理类的创建

    //PlayerPrefs数据管理类,统一管理数据的存储和读取
    public class PlayerPrefsDataMgr
    {
    	private static PlayerPrefsDataMgr instance = new PlayerPrefsDataMgr();
    	public static PlayerPrefsDataMgr Instance
    	{
    		get
    		{
    			return instance;
    		}
    	}
    	private PlayerPrefsDataMgr(){}//私有构造
    	
    	//存储数据
    	public void SaveData(object data,string keyName)
    	{
    	}
    	//读取数据
    	public object LoadData(Type type,string 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

    反射存储数据----常用成员

    //存储数据
    //通过Type得到传入数据对象的所有的字段,然后结合PlayerPrefs来进行存储
    public void SaveData(object data,string keyName)
    {
    	//获取传入数据对象的所有字段
    	Type dataType = data.GetType();	
    	FieldInfo[] infos = dataTypes.GetFields();
    	//自己定义一个key规则,进行数据存储
    	//keyName_数据类型_字段类型_字段名
    	
    	//遍历这些字段进行数据存储
    	string saveKeyName = "";
    	FieldInfo info;
    	for(int i = 0;i < infos.Length;i++)
    	{
    		//字段的类型:info.FieldType.Name
    		//字段的名字:info.Name
    		info = infos[i];//得到具体的字段信息
    		saveKeyName = keyName + "_" + dataType.Name + "_" + info.FieldType.Name + "_" + info.Name;
    		//封装一个方法进行存储
    		//获取值:info.GetValue(data)
    		SaveValue(info.GetValue(data),saveKeyName);
    	}
    }
    private void SaveValue(object value,string keyName)
    {
    	//PlayerPrefs只支持3种类型存储
    	//判断数据类型是什么类型,然后调用具体的方法来存储
    	Type fieldType = value.GetType();
    	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))
    	{
    		PlayerPrefs.SetInt(keyName,(bool)value ? 1 : 0);
    	}
    }
    
    • 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

    反射存储数据----List成员

    private void SaveValue(object value,string keyName)
    {
    	//PlayerPrefs只支持3种类型存储
    	//判断数据类型是什么类型,然后调用具体的方法来存储
    	Type fieldType = value.GetType();
    	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))
    	{
    		PlayerPrefs.SetInt(keyName,(bool)value ? 1 : 0);
    	}
    	//判断字段是不是IList的子类
    	else if(typeof(IList).IsAssignableFrom(fieldType))
    	{
    		//父类装子类
    		IList list = value as IList;
    		//先存储数量
    		PlayerPrefs.SetInt(keyName,list.Count);
    		//存储具体的值
    		int index = 0;
    		foreach(object obj in list)
    		{
    			SaveValue(obj,keyName + index);
    			++index;
    	}
    
    • 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

    反射存储数据----Dictionary成员

    private void SaveValue(object value,string keyName)
    {
    	//PlayerPrefs只支持3种类型存储
    	//判断数据类型是什么类型,然后调用具体的方法来存储
    	Type fieldType = value.GetType();
    	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))
    	{
    		PlayerPrefs.SetInt(keyName,(bool)value ? 1 : 0);
    	}
    	//判断字段是不是IList的子类
    	else if(typeof(IList).IsAssignableFrom(fieldType))
    	{
    		//父类装子类
    		IList list = value as IList;
    		//先存储数量
    		PlayerPrefs.SetInt(keyName,list.Count);
    		//存储具体的值
    		int index = 0;
    		foreach(object obj in list)
    		{
    			SaveValue(obj,keyName + index);
    			++index;
    	}
    	else if(typeof(IDictionary).IsAssignableFrom(fieldType))
    	{
    		IDictionary dic = value as IDictionary;
    		//存字典长度
    		PlayerPrefs.SetInt(keyName,dic.Count);
    		//遍历存储Dic里面的具体值
    		int index = 0;
    		foreach(object key in dic)
    		{
    			SaveValue(key,keyName + "_key_" + index);
    			SaveValue(dic[key],keyName + "_value_" + index);
    			++index;
    		}
    	}
    
    • 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

    反射存储数据----自定义类成员

    private void SaveValue(object value,string keyName)
    {
    	//PlayerPrefs只支持3种类型存储
    	//判断数据类型是什么类型,然后调用具体的方法来存储
    	Type fieldType = value.GetType();
    	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))
    	{
    		PlayerPrefs.SetInt(keyName,(bool)value ? 1 : 0);
    	}
    	//判断字段是不是IList的子类
    	else if(typeof(IList).IsAssignableFrom(fieldType))
    	{
    		//父类装子类
    		IList list = value as IList;
    		//先存储数量
    		PlayerPrefs.SetInt(keyName,list.Count);
    		//存储具体的值
    		int index = 0;
    		foreach(object obj in list)
    		{
    			SaveValue(obj,keyName + index);
    			++index;
    	}
    	else if(typeof(IDictionary).IsAssignableFrom(fieldType))
    	{
    		IDictionary dic = value as IDictionary;
    		//存字典长度
    		PlayerPrefs.SetInt(keyName,dic.Count);
    		//遍历存储Dic里面的具体值
    		int index = 0;
    		foreach(object key in dic)
    		{
    			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

    反射读取数据----常用成员

    public object LoadData(Type type,string keyName)
    {
    	//根据你传入的Type,创建一个对象用于存储数据
    	object data = Activator.CreateInstance(type);
    	//得到所有字段
    	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来读取数据
    		info.SetValue(data,LoadValue(info.FieldType,loadKeyName));
    	}
    	return data;
    }
    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;
    	}
    	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

    反射读取数据----List成员

    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对象进行赋值
    		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;
    	}
    	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

    反射读取数据----Dic成员

    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对象进行赋值
    		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).IsAsssignableFrom(fieldType))
    	{
    		//得到字典的长度
    		int count = PlayerPrefs.GetInt(keyName,0);
    		//实例化一个字典对象,用父类装子类
    		IDictionary dic = Activator.CreateInstance(fieldType);
    		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;
    	}
    	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

    反射读取数据----自定义类成员

    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对象进行赋值
    		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).IsAsssignableFrom(fieldType))
    	{
    		//得到字典的长度
    		int count = PlayerPrefs.GetInt(keyName,0);
    		//实例化一个字典对象,用父类装子类
    		IDictionary dic = Activator.CreateInstance(fieldType);
    		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

    加密思路

    找不到
    1、把存在硬盘上的内容放在一个不容易找到的地方
    2、多层文件夹包裹
    3、名字辨识度低
    但是对于PlayerPrefs不太适用,因为位置已经固定了,改不了。
    看不懂
    让数据的key和value让别人看不懂,俗称加密,为key和value加密。
    解不出
    不让别人获取到你加密的规则。
    注意: 单机游戏加密只是提高别人修改你数据的门槛,只要别人获取到你的源代码,知道你的加密规则,一切都没有任何意义。但是对于一般玩家来说几乎是不可能的事情。

    生成资源包

    在这里插入图片描述
    在这里插入图片描述

  • 相关阅读:
    【hbuilder】运行不了部分项目 , 打开终端 无法输入指令
    什么是响应式设计(Responsive Design)和移动优先设计的概念
    基于springboot地方废物回收机构管理系统设计与实现-计算机毕业设计源码+lw文档
    翻译文本的软件有哪些?这几个翻译工具你可以试试看
    【原型模式】设计模式系列:高效克隆的艺术(深入解析)
    56资源网系统源码搭建知识付费-含源码
    git checkout 命令
    JWT的基础介绍
    2022-08-18 第四小组 修身课 学习笔记(every day)
    利用sham-link实现路由还原
  • 原文地址:https://blog.csdn.net/K_CRACKING/article/details/136091923