Unity数据持久化之PlayerPrefs

这里写目录标题

  • PlayerPrefs概述
  • 基本方法
  • PlayerPrefs存储位置
  • 实践小项目
    • 反射知识补充
    • 数据管理类的创建
    • 反射存储数据----常用成员
    • 反射存储数据----List成员
    • 反射存储数据----Dictionary成员
    • 反射存储数据----自定义类成员
    • 反射读取数据----常用成员
    • 反射读取数据----List成员
    • 反射读取数据----Dic成员
    • 反射读取数据----自定义类成员
    • 加密思路
    • 生成资源包

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();//获取到泛型类型stringDictionary<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.Nameinfo = 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.rhkb.cn/news/262883.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Sora-OpenAI 的 Text-to-Video 模型:制作逼真的 60s 视频片段

OpenAI 推出的人工智能功能曾经只存在于科幻小说中。 2022年&#xff0c;Openai 发布了 ChatGPT&#xff0c;展示了先进的语言模型如何实现自然对话。 随后&#xff0c;DALL-E 问世&#xff0c;它利用文字提示生成令人惊叹的合成图像。 现在&#xff0c;他们又推出了 Text-t…

选择适合你的编程语言

引言 在当今瞬息万变的技术领域中&#xff0c;选择一门合适的编程语言对于个人职业发展和技术成长至关重要。每种语言都拥有独特的设计哲学、应用场景和市场需求&#xff0c;因此&#xff0c;在决定投入时间和精力去学习哪种编程语言时&#xff0c;我们需要综合分析多个因素&a…

在 Jupyter Notebook 中查看所使用的 Python 版本和 Python 解释器路径

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 我们在做 Python 开发时&#xff0c;有时在我们的服务器上可能安装了多个 Python 版本。 使用 conda info --envs 可以列出所有的 conda 环境。当在 Linux 服务器上使用 which python 命令时&#xff0…

绿盾限制终端网络访问权限会恢复后,别的网站访问正常就是无法访问钉钉网站和下载东西

环境&#xff1a; Win10 专业版 钉钉7.5.5 绿盾7.0 问题描述&#xff1a; 绿盾限制终端网络访问权限会恢复后&#xff0c;别的网站访问正常就是无法访问钉钉网站和下载东西 解决方案&#xff1a; 排查方法 1.重置浏览器或者更换浏览器测试&#xff08;未解决&#xff09…

游戏行业洞察:分布式开源爬虫项目在数据采集与分析中的应用案例介绍

前言 我在领导一个为游戏行业巨头提供数据采集服务的项目中&#xff0c;我们面临着实时数据需求和大规模数据处理的挑战。我们构建了一个基于开源分布式爬虫技术的自动化平台&#xff0c;实现了高效、准确的数据采集。通过自然语言处理技术&#xff0c;我们确保了数据的质量和…

Flutter插件开发指南02: 事件订阅 EventChannel

Flutter插件开发指南02: 事件订阅 EventChannel 视频 https://www.bilibili.com/video/BV1zj411d7k4/ 前言 上一节我们讲了 Channel 通道&#xff0c;但是如果你是卫星定位业务&#xff0c;原生端主动推消息给 Flutter 这时候就要用到 EventChannel 通道了。 本节会写一个 1~…

Maven setting.xml 配置

目的&#xff1a;可以把我们书写的jar包发布到maven私有仓库&#xff0c;简称私仓 1. 打开云效 2.点击 非生产库-snapshot mave release仓库与snapshot仓库区别&#xff1f; 在软件开发中&#xff0c;"Maven release 仓库"和"Maven snapshot 仓库"是两种…

[极客大挑战2019]upload

该题考点&#xff1a;后缀黑名单文件内容过滤php木马的几种书写方法 phtml可以解析php代码&#xff1b;<script language"php">eval($_POST[cmd]);</script> 犯蠢的点儿&#xff1a;利用html、php空格和php.不解析<script language"php"&…

AI文生图网站测评

主要测评文章配图生成效果、绘制logo等效果 测评关键点&#xff1a;生成效果、网站易用度、是否免费 测评prompt&#xff1a;请生成一个文章内容配图&#xff0c;图片比例是3&#xff1a;2&#xff0c;文章主旨是AI既是机遇&#xff0c;也存在挑战和风险&#xff0c;要求图片…

PyTorch概述(二)---MNIST

NIST Special Database3 具体指的是一个更大的特殊数据库3&#xff1b;该数据库的内容为手写数字黑白图片&#xff1b;该数据库由美国人口普查局的雇员手写 NIST Special Database1 特殊数据库1&#xff1b;该数据库的内容为手写数字黑白图片&#xff1b;该数据库的图片由高…

Jetson Xavier NX 与笔记本网线连接 ,网络共享,ssh连接到vscode

Jetson Xavier NX 与笔记本网线连接 &#xff0c;网络共享&#xff0c;ssh连接到vscode Jetson Xavier NX桌面版需要连接显示屏、鼠标和键盘&#xff0c;操作起来并不方便&#xff0c;因此常常需要ssh远程连接到本地笔记本电脑&#xff0c;这里介绍一种连接方式&#xff0c;通过…

linux安装sqoop

目录 下载配置 下载 本地下载好上传&#xff0c;解压&#xff0c;重命名&#xff0c;注意路径 tar -zxvf /opt/sqoop/sqoop-1.4.6.tar.gz -C /opt/ mv /opt/sqoop-1.4.6.bin__hadoop-2.0.4-alpha /opt/sqoop配置 环境变量 echo export SQOOP_HOME/opt/sqoop/ >> /etc…

Ubuntu18.04有线连接后,无法设置ip地址以及显示网口设置

前提&#xff1a;首先测试过网线是完全没问题的 桌面端找不到设置网口 终端输入&#xff1a; ifconfig 没有找到网口设置和对应IP 然后查询网口驱动是否正常安装&#xff0c;输入&#xff1a; lspci | grep Ethernet 有输出说明网口驱动正常安装 然后查询电脑的ip地址&am…

图像分割标签噪声问题优化

文章目录 前言一、损失函数方面(1)t-loss(2)边缘平滑前言 在制作数据集时,标注数据时难免会存在噪声,如不同类别交界处存在模糊导致定位异常问题,训练过程梯度不稳定,网络对这部分数据的分类置信度较低(如其它中心区域的类别置信度都在0.9左右,而类别交界处的置信度…

在openEuler中通过KVM可视化安装华为FusionCompute的CNA主机

一、环境说明 在Windows物理主机上通过VMware WorkStation创建一个虚拟机&#xff08;4U4C、16GB内存&#xff0c;400GB磁盘&#xff0c;NAT网络连接&#xff09;&#xff0c;在虚拟机中安装openEuler 22.03 LTS系统&#xff0c;并将该虚拟机作为部署 FusionCompute的服务器&a…

ArcgisForJS如何实现添加含图片样式的点要素?

文章目录 0.引言1.加载底图2.获取点要素的坐标3.添加含图片样式的几何要素4.完整实现 0.引言 ArcGIS API for JavaScript 是一个用于在Web和移动应用程序中创建交互式地图和地理空间分析应用的库。本文在ArcGIS For JavaScript中使用Graphic对象来创建包含图片样式的点要素。 …

西门子200SMART SB AE01的正确用法

西门子200SMART SB AE01&#xff0c;就是1路模拟量输入的SB板。信号板直接安装在 SR/ST CPU 本体正面&#xff0c;无需占用电控柜空间&#xff0c;安装、拆卸方便快捷。有些小型的系统如果只有1路模拟量输入&#xff0c;或者模块配置中恰好缺少1路模拟量输入&#xff0c;就可以…

Clickhouse系列之连接工具连接、数据类型和数据库

基本操作 一、使用连接工具连接二、数据类型1、数字类型IntFloatDecimal 2、字符串类型StringFixedStringUUID 3、时间类型DateTimeDateTime64Date 4、复合类型ArrayEnum 5、特殊类型Nullable 三、数据库 一、使用连接工具连接 上一篇介绍了clickhouse的命令行登录&#xff0c…

紫光同创初使用

芯片PGC2KG-6LPG144 1、安装好软件接&#xff0c;加载license,有两个&#xff0c;与电脑MAC地址绑定的 2、正常使用后&#xff0c;新建个工程&#xff0c;配置管脚Tools→UCE 3、程序中有些信号被软件认为是时钟信号&#xff0c;会报错&#xff08;时钟输入I0约束在非专用时钟…

消息中间件篇之RabbitMQ-消息重复消费

一、导致重复消费的情况 1. 网络抖动。 2. 消费者挂了。 消费者消费消息后&#xff0c;当确认消息还没有发送到MQ时&#xff0c;就发生网络抖动或者消费者宕机。那当消费者恢复后&#xff0c;由于MQ没有收到消息&#xff0c;而且消费者有重试机制&#xff0c;消费者就会再一次消…