赞
踩
本博客主要记录PlayerPrefs的使用,比较长
数据持久化就是将内存中的数据模型转化为存储模型,以及将存储模型转化为内存中数据模型的通称
简述:将游戏中的数据存储到硬盘,然后可以下次进游戏的时候读取硬盘中的数据到内存。
是Unity提供的可以用于存储玩家数据的公共类
示例:
PlayerPrefs.SetInt("myAge", 18);
PlayerPrefs.SetFloat("myHeight", 177.5f);
PlayerPrefs.SetString("myName", "Adeng");
PlayerPrefs.Save();
bool sex = true;
PlayerPrefs.SetInt("sex", sex ? 1 : 0);
//int int age = PlayerPrefs.GetInt("myAge"); print(age); //前提是 如果找不到myAge对应的值 就会返回函数的第二个参数 默认值 age = PlayerPrefs.GetInt("myAge", 100); print(age); //float float height = PlayerPrefs.GetFloat("myHeight", 1000f); print(height); //string string name = PlayerPrefs.GetString("myName"); print(name); //判断数据是否存在 if( PlayerPrefs.HasKey("myName") ) { print("存在myName对应的键值对数据"); } //删除指定键值对 PlayerPrefs.DeleteKey("myAge"); //删除所有存储的信息 PlayerPrefs.DeleteAll();
现在有玩家信息类,有名字,年龄,攻击力,防御力等成员。另有装备信息类,装备类中有id,数量两个成员。
现在为其封装两个方法,一个用来存储数据,一个用来读取数据
注意,装备是个list,所以存储时候需要遍历
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Item { public int id; public int num; } public class Player { public string name; public int age; public int atk; public int def; //拥有的装备信息 public List<Item> itemList; //这个变量 是一个 存储和读取的一个唯一key标识 private string keyName; /// <summary> /// 存储数据 /// </summary> public void Save() { PlayerPrefs.SetString(keyName +"_name", name); PlayerPrefs.SetInt(keyName + "_age", age); PlayerPrefs.SetInt(keyName + "_atk", atk); PlayerPrefs.SetInt(keyName + "_def", def); //存储有多少个装备 PlayerPrefs.SetInt(keyName + "_ItemNum", itemList.Count); for (int i = 0; i < itemList.Count; i++) { //存储每一个装备的信息 PlayerPrefs.SetInt(keyName + "_itemID" + i, itemList[i].id); PlayerPrefs.SetInt(keyName + "_itemNum" + i, itemList[i].num); } PlayerPrefs.Save(); } /// <summary> /// 读取数据 /// </summary> public void Load(string keyName) { //记录你传入的标识 this.keyName = keyName; name = PlayerPrefs.GetString(keyName + "_name", "未命名"); age = PlayerPrefs.GetInt(keyName + "_age", 18); atk = PlayerPrefs.GetInt(keyName + "_atk", 10); def = PlayerPrefs.GetInt(keyName + "_def", 5); //得到有多少个装备 int num = PlayerPrefs.GetInt(keyName + "_ItemNum", 0); //初始化容器 itemList = new List<Item>(); Item item; for (int i = 0; i < num; i++) { item = new Item(); item.id = PlayerPrefs.GetInt(keyName + "_itemID" + i); item.num = PlayerPrefs.GetInt(keyName + "_itemNum" + i); itemList.Add(item); } } } public class Lesson1_Exercises : MonoBehaviour { // Start is called before the first frame update void Start() { //现在有玩家信息类,有名字,年龄,攻击力,防御力等成员 //现在为其封装两个方法,一个用来存储数据,一个用来读取数据 //Player p = new Player(); //p.Load(); //print(p.name); //print(p.age); //print(p.atk); //print(p.def); //p.name = "唐老狮"; //p.age = 22; //p.atk = 40; //p.def = 10; 改了过后存储 //p.Save(); Player p = new Player(); p.Load("Player1"); p.Save(); Player p2 = new Player(); p2.Load("Player2"); p.Save(); //装备信息 //print(p.itemList.Count); //for (int i = 0; i < p.itemList.Count; i++) //{ // print("道具ID:" + p.itemList[i].id); // print("道具数量:" + p.itemList[i].num); //} 为玩家添加一个装备 //Item item = new Item(); //item.id = 1; //item.num = 1; //p.itemList.Add(item); //item = new Item(); //item.id = 2; //item.num = 2; //p.itemList.Add(item); } // Update is called once per frame void Update() { } }
要在游戏中做一个排行榜功能,排行榜主要记录玩家名(可重复),玩家得分,玩家通关时间,请用PlayerPrefs存储读取排行榜相关信息
注意:这个名字可以重复就代表了不能单独的将名字当做key
using System.Collections; using System.Collections.Generic; using UnityEngine; /// <summary> /// 排行榜具体信息 /// </summary> public class RankListInfo { public List<RankInfo> rankList; public RankListInfo() { Load(); } /// <summary> /// 新加排行榜信息 /// </summary> public void Add(string name , int score, int time) { rankList.Add(new RankInfo(name, score, time)); } public void Save() { //存储有多少条数据 PlayerPrefs.SetInt("rankListNum", rankList.Count); for (int i = 0; i < rankList.Count; i++) { RankInfo info = rankList[i]; PlayerPrefs.SetString("rankInfo" + i, info.playerName); PlayerPrefs.SetInt("rankScore" + i, info.playerScore); PlayerPrefs.SetInt("rankTime" + i, info.playerTime); } } private void Load() { int num = PlayerPrefs.GetInt("rankListNum", 0); rankList = new List<RankInfo>(); for (int i = 0; i < num; i++) { RankInfo info = new RankInfo(PlayerPrefs.GetString("rankInfo" + i), PlayerPrefs.GetInt("rankScore" + i), PlayerPrefs.GetInt("rankTime" + i)); rankList.Add(info); } } } /// <summary> /// 排行榜单条信息 /// </summary> public class RankInfo { public string playerName; public int playerScore; public int playerTime; public RankInfo(string name, int score, int time) { playerName = name; playerScore = score; playerTime = time; } } public class Lesson2_Exercises : MonoBehaviour { // Start is called before the first frame update void Start() { #region 练习题一 //将知识点一中的练习题,改为可以支持存储多个玩家信息 #endregion #region 练习题二 //要在游戏中做一个排行榜功能 //排行榜主要记录玩家名(可重复),玩家得分,玩家通关时间 //请用PlayerPrefs存储读取排行榜相关信息 RankListInfo rankList = new RankListInfo(); print(rankList.rankList.Count); for (int i = 0; i < rankList.rankList.Count; i++) { print("姓名" + rankList.rankList[i].playerName); print("分数" + rankList.rankList[i].playerScore); print("时间" + rankList.rankList[i].playerTime); } rankList.Add("唐老狮", 100, 99); rankList.Save(); #endregion } // Update is called once per frame void Update() { } }
以下是不同平台的位置,
要注意这个存储文件是可以找到然后手动去改的
所以要注意加密再去存储
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Lesson2_SaveWhere : MonoBehaviour { // Start is called before the first frame update void Start() { #region 知识点一 PlayerPrefs存储的数据存在哪里? //不同平台存储位置不一样 #region Windows //PlayerPrefs 存储在 //HKCU\Software\[公司名称]\[产品名称] 项下的注册表中 //其中公司和产品名称是 在“Project Settings”中设置的名称。 //运行 regedit //HKEY_CURRENT_USER //SOFTWARE //Unity //UnityEditor //公司名称 //产品名称 #endregion #region Android // /data/data/包名/shared_prefs/pkg-name.xml #endregion #region IOS // /Library/Preferences/[应用ID].plist #endregion #endregion #region 知识点二 PlayerPrefs数据唯一性 //PlayerPrefs中不同数据的唯一性 //是由key决定的,不同的key决定了不同的数据 //同一项目中 如果不同数据key相同 会造成数据丢失 //要保证数据不丢失就要建立一个保证key唯一的规则 #endregion } // Update is called once per frame void Update() { } }
首先封装要用到反射的知识点,
这边打算封装成一个传入类对象,内部函数就会自动检索内部的可存储变量,自动存储,
并且取数据的时候,传入类名就可以,
因为,你并不知道以后这个函数被传入哪个对象,每个对象的方法和属性都是不同的,
所以只有反射才能做出这样的功能。
在程序运行时,通过反射可以得到其它程序集或者自己程序集代码的各种信息
类,函数,变量,对象等等,实例化它们,执行它们,操作它们
具体可看下面链接
关于反射的文章
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class Father { } public class Son { } public class Reflection : MonoBehaviour { // Start is called before the first frame update void Start() { //反射3剑客—— 1T 两 A //Type —— 用于获取 类的所有信息 字段 属性 方法 等等 //Assembly —— 用于获取程序集 通过程序集获取Type //Activator —— 用于快速实例化对象 #region 知识点二 判断一个类型的对象是否可以让另一个类型为自己分配空间 //父类装子类 //是否可以从某一个类型的对象 为自己 分配 空间 Type fatherType = typeof(Father); Type sonType = typeof(Son); //调用者 通过该方法进行判断 判断是否可以通过传入的类型为自己 分配空间 if( fatherType.IsAssignableFrom(sonType) ) { print("可以装"); Father f = Activator.CreateInstance(sonType) as Father; print(f); } else { print("不能装"); } #endregion #region 知识点三 通过反射获取泛型类型 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 print(types[1]);//输出System.Single #endregion } // Update is called once per frame void Update() { } }
进行封装的目的是可以更加便捷的使用PlayerPrefs,只需要传入想要封装的类型。即使这个类型是用户自定义的类。
看下面代码:
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; } }
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() { } }
通过上面可以看出仅仅通过一句就可以实现存和取
取: PlayerInfo p = PlayerPrefsDataMgr.Instance.LoadData(typeof(PlayerInfo), “Player1”) as PlayerInfo;
存: PlayerPrefsDataMgr.Instance.SaveData(p, “Player1”);
一般来说单机游戏的加密只是提高别人修改你的数据的门槛,只要源代码泄漏,知道加密规则后,一切加密都没意义。但是对于一个普通玩家说,普通的加密就可以了。
PlayerPrefs位置固定了,第一条是没法用的
可以简单的加密:
例如 加密int类型的,原有基础加减,例如原本是玩家是50级,但是你存60级,取得时候减去10,就是50,这样破解者去找50的时候,就找不到。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。