【Unity】数据持久化--PlayerPrefs

1、PlayerPrefs是什么

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

2、存储相关

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

PlayerPrefs.SetInt("myAge", 18);
PlayerPrefs.SetFloat("myHeight", 177.5f);
PlayerPrefs.SetString("myName", "小周");

2.2 直接调用Set相关方法 只会把数据存到内存中,当游戏结束时 unity会自动把数据存储带硬盘,如果游戏不是正常结束的 而是崩溃 数据不会存到硬盘中,所以需要使用PlayerPrefs.Save()保存一下

PlayerPrefs.Save();

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

2.4 局限性

PlayerPrefs是有局限性的 它只能存储3种类型的数据

如果想要存储其他类型的数据 只能降低或者上升精度来进行存储

3、读取相关

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

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

        //int
        int age = PlayerPrefs.GetInt("myAge");
        Debug.Log(age);
        //如果找不到myAge 可以填写默认值,返回的就是默认值
        age = PlayerPrefs.GetInt("myAge", 19);
        Debug.Log(age);

        //float
        float height = PlayerPrefs.GetFloat("myHeight", 188.1f);
        Debug.Log(height);

        //string
        string myName = PlayerPrefs.GetString("myName", "zt");
        Debug.Log($"{myName} {age}");

        //第二个参数 对于我们的作用
        //就是 在得到没有的数据时 可以使用默认值来初始化基础数据

        //判断数据是否存在
        if(PlayerPrefs.HasKey("myB+Name"))
        {
            Debug.Log("存在相同的键名myName");
        }

4、删除数据 

        //删除指定键值对
        PlayerPrefs.DeleteKey("myAge");
        //删除所有存储的信息
        PlayerPrefs.DeleteAll();

5、PlayerPrefs存储的数据存储位置,

不同平台存储位置不一样

5.1 windows

PlayerPrefs存储在
HKCU\Software\[公司名称]\[产品名称]项下的注册表中//其中公司和产品名称是在“Project settings”中设置的名称。

5.2 Android

data/data/包名/shared_prefs/pkg-name.xml

5.3 IOS

Library /Preferences/[应用ID].plist

6、PlayerPrefs数据管理类

统一管理数据的存储和读取,使用反射实现 

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using static UnityEditor.LightingExplorerTableColumn;

/// <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 savaKeyName = "";
        for (int i = 0; i < infos.Length; i++)
        {
            //对每一个字段 进行数据存储
            //得到具体字段信息
            FieldInfo info = infos[i];
            //通过FieldInfo可以直接获取到 字段的类型 和字段的名字
            //字段的类型 info.FieldType.Name
            //字段的名字 info.Name;

            //player1 playerInfo
            //要根据我们定的key的拼接规则 来进行key的生成
            savaKeyName = keyName + "_" + dataType.Name
                + "_" + info.FieldType.Name + "_" + info.Name;

            //现在得到了key 安装规则存储
            //接来下使用PlayerPrefs来进行存储值
            //如何获取值
            //info.GetValue(data);
            //通过该方法专门存储值
            SaveValue(info.GetValue(data), savaKeyName);
        }

        PlayerPrefs.Save();
        #endregion
    }

    //public void SaveData<T>(T data, string keyName) where T : class
    //{
    //    SaveData(data, keyName);
    //}

    private void SaveValue(object value, string keyName)
    {
        //直接通过PlayerPrefs来进行存储了
        //就是根据数据类型的不同 来决定使用哪一个API来进行存储
        //Playerprefs只支持3种类型存储
        //判断 数据类型 是什么类型 然后调用具体的方法来存储
        Type fieldType = value.GetType();

        //类型判断
        //是不是int
        if (fieldType == typeof(int))
        {
            //为int数据加密
            int rValue = (int)value;
            rValue += 10;
            PlayerPrefs.SetInt(keyName, rValue);
        }
        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))
        {
            //自己定一个存储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类型 通过Dictionary父类来判断
        else if (typeof(IDictionary).IsAssignableFrom(fieldType))
        {
            //父类装子类
            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">想要读取数据的 数据类型</param>
    /// <param name="keyName">数据对象的唯一key 自己控制</param>
    /// <returns></returns>
    public object LoadData( Type type, string keyName)
    {
        //不用object对象传入 而使用 Type传入
        //主要目的是节约一行代码(在外部)
        //假设现在你要 读取一个P1ayer类型的数据 如果是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来读取数据
            info.SetValue(data, LoadValue(info.FieldType, loadKeyName));
        }


        return data;
    }

    public T LoadData<T>(string keyName) where T : class
    {
        return LoadData(typeof(T), keyName) as T;
    }

    /// <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.Add(LoadValue(fieldType.GetGenericArguments()[0] , keyName + i));
            }
            return list;
        }
        else if (typeof(IDictionary).IsAssignableFrom(fieldType))
        {
            //得到字典长度
            int count = PlayerPrefs.GetInt(keyName, 0);
            //实例化一个字典对象 来进行赋值
            //用来反射中的双A中 Activator进行快速实例化
            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);
        }
    }

}

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值