PlayerPrefs

知识点一 PlayerPrefs是什么

是Unity提供的可以用于存储读取玩家数据的公共类

知识点二 存储相关

PlayerPrefs的数据存储 类似于键值对存储 一个键对应一个值
提供了存储3种数据的方法 int float string
键: string类型
值:int float string 对应3种API

PlayerPrefs.SetInt(“myAge”, 18);
PlayerPrefs.SetFloat(“myHeight”, 177.5f);
PlayerPrefs.SetString(“myName”, “唐老狮”);

直接调用Set相关方法 只会把数据存到内存里
当游戏结束时 Unity会自动把数据存到硬盘中
如果游戏不是正常结束的 而是崩溃 数据是不会存到硬盘中的
只要调用该方法 就会马上存储到硬盘中
PlayerPrefs.Save();

PlayerPrefs是有局限性的 它只能存3种类型的数据
如果你想要存储别的类型的数据 只能降低精度 或者上升精度来进行存储
bool sex = true;
PlayerPrefs.SetInt(“sex”, sex ? 1 : 0);

如果不同类型用同一键名进行存储 会进行覆盖
PlayerPrefs.SetFloat(“myAge”, 20.2f);

知识点三 读取相关

注意 运行时 只要你Set了对应键值对
即使你没有马上存储Save在本地
也能够读取出信息

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

反射

通过反射获取泛型

知识点一 反射知识回顾

反射3剑客—— 1T 两 A
Type —— 用于获取 类的所有信息 字段 属性 方法 等等
Assembly —— 用于获取程序集 通过程序集获取Type
Activator —— 用于快速实例化对象

知识点二 判断一个类型的对象是否可以让另一个类型为自己分配空间

父类装子类
是否可以从某一个类型的对象 为自己 分配 空间

Type fatherType = typeof(Father);
Type sonType = typeof(Son);

调用者 通过该方法进行判断 判断是否可以通过传入的类型为自己 分配空间
if( fatherType.IsAssignableFrom(sonType) )
{
print(“可以装”);
Father f = Activator.CreateInstance(sonType) as Father;
print(f);
}
else
{
print(“不能装”);
}

知识点三 通过反射获取泛型类型

List list = new List();
Type listType = list.GetType();

Type[] types = listType.GetGenericArguments();
print(types[0]);

Dictionary<string, float> dic = new Dictionary<string, float>();
Type dicType = dic.GetType();
types = dicType.GetGenericArguments();
print(types[0]);
print(types[1]);

实战

排行榜

//排行榜
public class paihangbang
{
   //用来存取每一条排行信息
 public List<paihan> pai ;
 
 //排行榜的名字
 public String playername;

 //初始化排行榜并给予名字
public paihangbang(String playername)
 {
pai = new List<paihan>();
     this.playername = playername;
     Load(playername);
 }

 //用来保存或者覆盖对应名字的排行榜
 public void Save()
   {
       //写入当前排行榜名字(主要是给下面用)
       PlayerPrefs.SetString(playername,playername);
       //记录当前排行榜的排行信息数量
       PlayerPrefs.SetInt(playername+"_Count",pai.Count);
       //写入当前数组的排行信息
       for (int i = 0; i < pai.Count; i++)
       {
           PlayerPrefs.SetString(playername+"_player_"+i,pai[i].player);
           PlayerPrefs.SetInt(playername+"_feng_"+i,pai[i].feng);
           PlayerPrefs.SetInt(playername+"_time_"+i,pai[i].time);
       }
       //保存进入注册表
       PlayerPrefs.Save();

   }

 //用来读取对应名字的排行榜(私有化,不需要在外部被调用,因为构造函数已经调用了它)
   private void Load(String playername)
   {
       //获取当前排行榜名字(主要是给下面用)
       this.playername = PlayerPrefs.GetString(playername, playername);
       //获得对应名字的排行榜的排行信息数量
       int num = PlayerPrefs.GetInt(playername + "_Count");
       //读取回数组中的排行信息
      for (int i = 0; i < num; i++)
      {
          paihan a = new paihan();
          a.player= PlayerPrefs.GetString(playername+"_player_"+i);
          a.feng= PlayerPrefs.GetInt(playername+"_feng_"+i);
          a.time= PlayerPrefs.GetInt(playername+"_time_"+i);
          pai.Add(a);
      }
   }
   
   //用来添加排行信息
   public void Add(String player,int feng,int time)
   {
       paihan a = new paihan();
       a.player = player;
       a.feng = feng;
       a.time = time;
       pai.Add(a);
   }
   
   //输出排行榜信息
   public void print()
   {
       for (int i = 0; i < pai.Count; i++)
       {
           paihan a = new paihan();
           a.player= PlayerPrefs.GetString(playername+"_player_"+i);
           a.feng= PlayerPrefs.GetInt(playername+"_feng_"+i);
           a.time= PlayerPrefs.GetInt(playername+"_time_"+i);
           Debug.Log("名字:"+a.player+"分数:"+a.feng+"时间:"+a.time);
       }
   }
}


//排行信息,用来记录单独的每一条信息,然后存入paihanban的数组里
public class paihan
{
   public String player;
   public int feng;
   public int time;
   
}

//运行
public class PlayerScript : MonoBehaviour
{
 
   void Start()
   {
//每次都清除掉注册表
       PlayerPrefs.DeleteAll();
       
       //创建一个名字为player1的排行榜
       paihangbang player1= new paihangbang("player1");
       //给排行榜注入两个排行信息
       player1.Add("小王",1,2);
       player1.Add("大王",3,2);
       //保存
       player1.Save();
       //打印排行榜
       player1.print();
   } 

}

带装备系统的玩家信息

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

万用型PlayerPrefs存储工具

用处

可以对大部分变量进行数据持久化处理

要点

  1. fieldInfo.FieldType和fieldInfo.GetType() 不同!!!!!前者会获得fieldinfo所含的元素本身的type,后者则是直接获得fieldinfo的type!!!!在下文代码中倘若你在使用 fieldInfo.FieldType的时候用成gettype,不但会导致save的时候type统统相同,增加标识符重复的风险,还会导致Load时传入的type是fieldinfo type,此type将无法通过if循环来鉴定!!!!

2.fieldInfo本身是一个发现类字段(你可以当成一个父类),不要认为它即是代数组中的元素,当你想要获得fieldInfo内部的object的时候,你需要fieldInfo.getvalue(想获得的类对象) ,而不是直接把fieldInfo本身传进去!!!

工具类playerprefs(单例):


/// PlayerPrefs数据管理类 统一管理数据的存储和读取

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);
           //为int数据加密
           int rValue = (int)value;
           rValue += 10;
           PlayerPrefs.SetInt(keyName, rValue);
       }
       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) )
       {
           //解密 减10
           return PlayerPrefs.GetInt(keyName, 0) - 10;
       }
       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()
    {
        //读取数据
        PlayerInfo p = PlayerPrefsDataMgr.Instance.LoadData(typeof(PlayerInfo), "Player1") as PlayerInfo;

        //游戏逻辑中 会去 修改这个玩家数据
        p.age = 18;
        p.name = "唐老狮";
        p.height = 1000;
        p.sex = true;

        p.itemList.Add(new ItemInfo(1, 99));
        p.itemList.Add(new ItemInfo(2, 199));

        //存了一次数据 再执行这的代码 里面已经有3的数据了 字典key不能重复 所以报错
        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()
    {
        
    }
}