Unity数据持久化之PlayerPrefs

news2025/3/10 11:12:11

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

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

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

PlayerPrefs.Save();

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

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

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

读取相关
运行时,只要使用了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");

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

判断数据是否存在。

if(PlayerPrefs.HasKey("myName"))
{
}

删除数据
删除指定键值对

PlayerPrefs.DeleteKey("myAge");

删除所有存储的信息

PlayerPrefs.DeleteAll();

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))
{
}

通过反射获取泛型类型

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

数据管理类的创建

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

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

//存储数据
//通过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);
	}
}

反射存储数据----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;
	}

反射存储数据----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;
		}
	}

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

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

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

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

反射读取数据----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;
}

反射读取数据----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;
}

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

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、名字辨识度低
但是对于PlayerPrefs不太适用,因为位置已经固定了,改不了。
看不懂
让数据的key和value让别人看不懂,俗称加密,为key和value加密。
解不出
不让别人获取到你加密的规则。
注意: 单机游戏加密只是提高别人修改你数据的门槛,只要别人获取到你的源代码,知道你的加密规则,一切都没有任何意义。但是对于一般玩家来说几乎是不可能的事情。

生成资源包

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1454779.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何用Qt实现一个无标题栏、半透明、置顶(悬浮)的窗口

在Qt框架中&#xff0c;要实现一个无标题栏、半透明、置顶&#xff08;悬浮&#xff09;的窗口&#xff0c;需要一些特定的设置和技巧。废话不多说&#xff0c;下面我将以DrawClient软件为例&#xff0c;介绍一下实现这种效果的四个要点。 要点一&#xff1a;移除标题栏&#…

⭐北邮复试刷题103. 二叉树的锯齿形层序遍历 (力扣每日一题)

103. 二叉树的锯齿形层序遍历 给你二叉树的根节点 root &#xff0c;返回其节点值的 锯齿形层序遍历 。&#xff08;即先从左往右&#xff0c;再从右往左进行下一层遍历&#xff0c;以此类推&#xff0c;层与层之间交替进行&#xff09;。 示例 1&#xff1a;输入&#xff1a…

鱼皮项目_可下载

​ 一、网址 Sign in GitLab 二、登录 以个人邮箱登录 c7 三、下载 四、打开下载文件&#xff0c;后缀zip ​

数据结构-双指针法

介绍 双指针法是一种可以在O&#xff08;n&#xff09;时间复杂度内解决数组、链表、字符串等数据结构相关的问题的方法。核心思想为使用两个指针在不同位置遍历数组或链表&#xff0c;从而实现特定操作。 常见的双指针法有 1.快慢指针&#xff1a;快指针每次移动两步&…

六个好用的Python库,绝对是瑰宝!

在当今的技术浪潮中&#xff0c;Python已经成为了一种广泛应用的编程语言。而要想充分发挥Python的威力&#xff0c;离不开各种优秀的库的支持。这些库能够提供丰富的功能和强大的工具&#xff0c;帮助开发者们更高效地完成各种任务。在本文中&#xff0c;笔者将介绍6个被誉为瑰…

流程编排(LogicFlow)

简介&#xff1a; LogicFlow 是一款流程图编辑框架&#xff0c;提供了一系列流程图交互、编辑所必需的功能和简单灵活的节点自定义、插件等拓展机制&#xff0c;方便我们快速在业务系统内满足类流程图的需求。 vue litefolw实现 liteflow-logicflow-vue实现 特性&#xff1a;…

数据库第四次实验

目录 1.建立数据表并插入数据 2 视图的创建 2.1 行列子集视图的创建 2.2 多表视图 2.3视图上建立视图 2.4分组视图 2.5带表达式的视图 3 删除视图 ​​​​​​​​​​​​​​4 查询视图 ​​​​​​​5 更新视图 5.1 修改某一个属性 5.2 删除一条数据 5.3 插入…

HTTP/1.1 如何优化?

问你一句:「你知道 HTTP/1.1 该如何优化吗?」 我们可以从下面这三种优化思路来优化 HTTP/1.1 协议: 尽量避免发送 HTTP 请求在需要发送 HTTP 请求时&#xff0c;考虑如何减少请求次数减少服务器的 HTTP 响应的数据大小 下面&#xff0c;就针对这三种思路具体看看有哪些优化…

力扣 第 385 场周赛 解题报告 | 珂学家 | 字典树专场

前言 整体评价 这是一场字典树专场&#xff0c;除了t3这个小模拟之外&#xff0c;1&#xff0c;2&#xff0c;4皆可用字典树搞定。 T4感觉做法挺多的&#xff0c;其实&#xff0c;但是字典树应该效率最高的。 T1. 统计前后缀下标对 I 思路: 模拟 O ( n 2 ) O(n^2) O(n2)全遍…

沁恒CH32V30X学习笔记03--64位systick

systick CH32F2x 系列产品Cortex-M3 内核自带了一个 24 位自减型计数器(SysTick timer)。支持 HCLK 或 HCLK/8 作为时基,具有较高优先级别(6)。一般可用于操作系统的时基。 CH32V3x 系列产品内核自带了一个 64 位加减计数器(SysTick),支持 HCLK 或者 HCLK/8 作为时基,…

VQ30 广告点击的高峰期(order by和limit的连用)

代码 select hour(click_time) as click_hour ,count(hour(click_time)) as click_cnt from user_ad_click_time group by click_hour order by click_cnt desc limit 1知识点 order by和limit的连用&#xff0c;取出所需结果 YEAR() 返回统计的年份 MONTH() 返回统计的月份 D…

【C++初阶】deque容器的介绍以及为什么stack和queue选择deque的作为底层容器适配器

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习C和算法 ✈️专栏&#xff1a;C航路 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&#x1…

【C语言】实现栈

目录 &#xff08;一&#xff09;栈 &#xff08;二&#xff09;头文件 &#xff08;三&#xff09;功能实现 &#xff08;1&#xff09;初始化栈 &#xff08;2&#xff09; 栈的销毁 &#xff08;3&#xff09;压栈 &#xff08;4&#xff09; 出栈 &#xff08;5&a…

软件实例分享,乒乓球俱乐部会员系统管理软件教程

软件实例分享&#xff0c;乒乓球俱乐部会员系统管理软件教程 一、前言 以下软件程序教程以 佳易王乒乓球馆计时计费软件V17.0为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 多种计费方式&#xff0c;可以按单价&#xff0c;也可以按时间段 可…

VR直播:只需五步,即可实现直播“黑科技”

现如今&#xff0c;VR直播的应用范围较为广泛&#xff0c;有很多人可能在现场见过VR直播的拍摄设备&#xff0c;不仅有高性能的电脑、VR相机&#xff0c;还有专业的灯光和拍摄机器等。只需要五步&#xff0c;就可以实现安全、高效的VR全景直播。 首先是专业全景采集设备进行全景…

用纯HTML写一个凭证并打印

最近有个需求&#xff0c;需要通过网页把单子打印出来&#xff0c;就用html实现了一个&#xff0c;主要使用了windwos自带的print打印&#xff0c;全部代码如下&#xff1a; <!DOCTYPE html> <html><head><meta http-equiv"Content-Type" cont…

Java21 + SpringBoot3使用Spring Security时如何在子线程中获取到认证信息

文章目录 前言原因分析解决方案方案1&#xff1a;手动设置线程中的认证信息方案2&#xff1a;使用DelegatingSecurityContextRunnable创建线程方案3&#xff1a;修改Spring Security安全策略通过设置JVM参数修改安全策略通过SecurityContextHolder修改安全策略 总结 前言 近日…

Mysql开启bin-log日志

目录 一、安装配置 二、mysqlbinlog命令 一、安装配置 yum -y install mariadb mariadb-server#安装mysql数据库#默认配置文件/etc/my.cnfvim /etc/my.cnflog-binmariadb-bin #开启二进制日志 systemctl restart mariadb#会在/car/lib/mysql/产生二进制日志文件&#xff0…

Java_方法(重载方法签名等详解)

在之前我们学习C语言时&#xff0c;当我们想要重复使用某段代码的功能时&#xff0c;我们会将这段代码定义为一个函数&#xff0c;而在java中我们把这段重复使用的代码叫做方法。 方法的定义 类体的内容分为变量的声明和方法的定义&#xff0c;方法的定义包括两部分&#xff1…

全网最详细的从0到1的turbo pnpm monorepo的前端工程化项目[搭建篇]

全网最详细的从0到1的turbo pnpm monorepo的前端工程化项目[搭建篇] 引言相关环境技术栈初始化工程安装turbo配置pnpm-workspace安装husky安装lint-staged安装eslint安装prettier配置 .editorconfig配置 .gitignore初步项目结构结语 引言 最近各种原因&#xff0c;生活上的&am…