前言
- 本内容为本人原创提供
- 本内特为学习者提供原创完整的工程源码下载,可参考学习或其他用途。
- 本内容从玩家天赋相关属性出发,对数据存取操作,一步一步推进,能直观感受。
- 如果后续有其他的存取游戏数据的方式,将继续提供给大家借鉴参考,希望能协助大家完成自己项目。
- PlayerPrefs以往只作为存取小数据的方法,区别以往,本文核心用PlayerPrefs实战,教学存取复杂游戏的数据,并且解决存取繁琐的核心问题。
目录
一、什么是PlayerPrefs存取
PlayerPrefs unity自带的API,用于存取游戏数据而用。
可用于Windows、Linux 、macOS 、Android 、WebGL 、iOS 平台。
本文实例为windows平台。
主要函数
DeleteAll | 从偏好中删除所有键和值。请谨慎使用。 |
DeleteKey | 从偏好中删除 key 及其对应值。 |
GetFloat | 返回偏好设置文件中与 key 对应的值(如果存在)。 |
GetInt | 返回偏好设置文件中与 key 对应的值(如果存在)。 |
GetString | 返回偏好设置文件中与 key 对应的值(如果存在)。 |
HasKey | 如果 key 在偏好中存在,则返回 true。 |
Save | 将所有修改的偏好写入磁盘。 |
SetFloat | 设置由 key 标识的偏好的值。 |
SetInt | 设置由 key 标识的偏好的值。 |
SetString | 设置由 key 标识的偏好的值。 |
注:表格数据源自unity手册
用放大镜看,发现只能存取寥寥几个 string、int、float 数据,感觉上 list、dictionary 无法存取,要么存取就很复杂,甚至觉得,数据一多,多存档的游戏都不好分类,甚至删除某条存档。
本文主要就是解锁PlayerPrefs存取复杂游戏数据。
二、认识PlayerPrefs
- PlayerPrefs.SetFloat("Health", 50.0F)
- PlayerPrefs.GetFloat("Health", 0);
PlayerPrefs为接口,SetFloat为方法函数,Health为key键标识,50.0F为变量。
这条方法就是保存一条为Health的浮点数据。
GetInt、GetString、SetInt、SetString 参考上面即可。
- DeleteAll 删除所有存档,极为特殊情况用下就好了。
- DeleteKey 数据不多情况下可以用下,数据多了很繁琐,不建议多数据游戏中采用。
主要讲这几个函数方法。
三、unity实战
本次实战采用游戏中天赋属性单元展开,对天赋属性进行存档和读档操作。
1、数据存取操作的起始工作
1.1、天赋属性:
首先,创建一个天赋属性类,把天赋内容属性规范好,当然也能创建结构体。如果数据多,并且比较复杂,可以选择类,操作起来更有优势
[System.Serializable]
public class Talent
{
public int talent_id;
public int talent_lv;
public string talent_name;
public int talent_needpoint;
public int talent_needstar;
public string talent_des;
public List<Talentskill> map_skilllist;
}
如何在 inspector 面板上显示类的变量,可以用 [System.Serializable]。
需要注意的是,这里不用 { get; set; } ,因为用了,在 inspector 面板中不会显示变量。
1.2、游戏管理类
创建一个游戏管理类,并单例它
private static Gamemanager _instance;
public static Gamemanager Instance
{
get
{
if (_instance == null)
{
_instance = new Gamemanager();
}
return _instance;
}
}
1.3、文件路径
与以往的xml、json、excel、二进制存取不同,这里不需要设置文件路径。
因为 PlayerPrefs 被存储在注册表的 HKEY_CURRENT_USER\Software\Unity\UnityEditor\
2、保存数据
很多人觉得 PlayerPrefs 存取无法做到多存档,其实它可以做多存档,因为游戏数据被存在注册表,所以我们不能以之前的存取思路来做存取功能。
2.1、声明一个头文件名
private string dataTitlestr = "d_" + "data1";
这一步很关键,声明 string 变量,是在多存档时,这个变量可作为删除某条存档的标识符号,至于怎么删除,后面会单独讲。
2.2、PlayerPrefs 常规用法
PlayerPrefs存数据时,都是按变量的排序存,这点和二进制存取有点类似。
//PlayerPres常规存
public void NorSavePlayerPrefsDoc()
{
PlayerPrefs.SetString(dataTitlestr, "存档一");
for (int i = 0; i < Gamemanager.Instance.talentList.Count; i++)
{
Talent talent = Gamemanager.Instance.talentList[i].myTalent;
PlayerPrefs.SetInt(dataTitlestr + "talent_id" + i, talent.talent_id);
PlayerPrefs.SetInt(dataTitlestr + "talent_lv" + i, talent.talent_lv);
PlayerPrefs.SetString(dataTitlestr + "talent_name" + i, talent.talent_name);
PlayerPrefs.SetInt(dataTitlestr + "talent_needpoint" + i, talent.talent_needpoint);
PlayerPrefs.SetInt(dataTitlestr + "talent_needstar" + i, talent.talent_needstar);
PlayerPrefs.SetString(dataTitlestr + "talent_des" + i, talent.talent_des);
for (int j = 0; j < talent.map_skilllist.Count; j++)
{
Talentskill skill = talent.map_skilllist[j];
PlayerPrefs.SetInt(dataTitlestr + "skill_id" + i + "_" + j, skill.skill_id);
PlayerPrefs.SetInt(dataTitlestr + "skill_name" + i + "_" + j, skill.skill_id);
PlayerPrefs.SetFloat(dataTitlestr + "skill_atc" + i + "_" + j, skill.skill_id);
}
}
}
注意: + i + "_" + j 是 key键 的唯一标识,如果不加序列,会有重复的 key,那么保存时后面的数据会取代前面的数据。
这个序号用 i 即可。如果时字典,则不用担心,因为字典的key本身就是唯一标识。
2.3、PlayerPrefs 特殊用法
先保存存档抬头,作为多存档的标识
PlayerPrefs.SetString(dataTitlestr, "存档一");
其后遍历某项游戏数据,这是天赋数据
for (int i = 0; i < Gamemanager.Instance.talentList.Count; i++)
{
...
}
循环 内部有,获取天赋类,并获取内部遍历集合
Talent talent = Gamemanager.Instance.talentList[i].myTalent;
Type type = talent.GetType();
FieldInfo[] fieldTypes = type.GetFields();
用反射方法,反射类中变量存
最终读取代码为
public void SavePlayerPrefsDoc()
{
PlayerPrefs.SetString(dataTitlestr, "存档一");
for (int i = 0; i < Gamemanager.Instance.talentList.Count; i++)
{
Talent talent = Gamemanager.Instance.talentList[i].myTalent;
Type type = talent.GetType();
FieldInfo[] fieldTypes = type.GetFields();
foreach (FieldInfo field in fieldTypes)
{
//判断是否为list
if (field.Name.Contains("list"))
{
for (int j = 0; j < talent.map_skilllist.Count; j++)
{
Talentskill skill = talent.map_skilllist[j];
Type childtype = skill.GetType();
FieldInfo[] childfieldTypes = childtype.GetFields();
foreach (var item in childfieldTypes)
{
//这里不能用 field.GetType();
if (item.FieldType == typeof(int))
{
int num = (int)item.GetValue(skill);
PlayerPrefs.SetInt(dataTitlestr + item.Name + i + "_" + j, (int)item.GetValue(skill));
}
else if (item.FieldType == typeof(string))
{
string str = item.GetValue(skill).ToString();
PlayerPrefs.SetString(dataTitlestr + item.Name + i + "_" + j, item.GetValue(skill).ToString());
}
else if (item.FieldType == typeof(float))
{
string str = item.GetValue(skill).ToString();
PlayerPrefs.SetFloat(dataTitlestr + item.Name + i + "_" + j, (float)item.GetValue(skill));
}
}
}
}
else if (field.Name.Contains("dic"))
{
//...略
}
else
{
//这里不能用 field.GetType();
if (field.FieldType == typeof(int))
{
int num = (int)field.GetValue(talent);
PlayerPrefs.SetInt(dataTitlestr + field.Name + i, (int)field.GetValue(talent));
}
else if (field.FieldType == typeof(string))
{
string str = field.GetValue(talent).ToString();
PlayerPrefs.SetString(dataTitlestr + field.Name + i, field.GetValue(talent).ToString());
}
else if (field.FieldType == typeof(float))
{
PlayerPrefs.SetFloat(dataTitlestr + field.Name + i, (int)field.GetValue(talent));
}
}
}
}
}
这样做,就算大量声明,只需要简单判断类似是 int、string、float、数组,字典,就能批量储存。
比如玩家有30个变量声明,物品有20变量声明,武功有20个声明,统统只需要判断类似是int、string、float、数组,字典即可,不用一条条去保存了。
3、读取数据
如果读取的是数组,先声明一个临时数组
List<Talentattribute> temptalentList = new List<Talentattribute>(Gamemanager.Instance.talentList);
主要目的是防止读取时,防止读取时原数组值被新读取的值替换,造成数据错乱。
List<Talentattribute> temptalentList = new List<Talentattribute>(Gamemanager.Instance.talentList);
for (int i = 0; i < temptalentList.Count; i++)
{
Talent talent = Gamemanager.Instance.talentList[i].myTalent;
Type type = talent.GetType();
FieldInfo[] fieldTypes = type.GetFields();
foreach (FieldInfo field in fieldTypes)
{
if (field.Name.Contains("list"))
{
for (int j = 0; j < talent.map_skilllist.Count; j++)
{
Talentskill skill = talent.map_skilllist[j];
Type childtype = skill.GetType();
FieldInfo[] childfieldTypes = childtype.GetFields();
foreach (var item in childfieldTypes)
{
//这里不能用 field.GetType();
if (item.FieldType == typeof(int))
{
item.SetValue(skill, PlayerPrefs.GetInt(dataTitlestr + item.Name + i + "_" + j));
}
else if (item.FieldType == typeof(string))
{
item.SetValue(skill, PlayerPrefs.GetString(dataTitlestr + item.Name + i + "_" + j));
}
else if (item.FieldType == typeof(float))
{
item.SetValue(skill, PlayerPrefs.GetFloat(dataTitlestr + item.Name + i + "_" + j));
}
}
}
}
else if (field.Name.Contains("dic"))
{
//...略
}
else
{
if (field.FieldType == typeof(int))
{
int num = PlayerPrefs.GetInt(dataTitlestr + field.Name + i);
field.SetValue(talent, PlayerPrefs.GetInt(dataTitlestr + field.Name + i));
}
else if (field.FieldType == typeof(string))
{
field.SetValue(talent, PlayerPrefs.GetString(dataTitlestr + field.Name + i));
}
else if (field.FieldType == typeof(float))
{
field.SetValue(talent, PlayerPrefs.GetFloat(dataTitlestr + field.Name + i));
}
}
}
}
读取时,也需要加序列号 + i + "_" + j,不然依然会只会读取同类型同一个值
这个序列号就是 PlayerPrefs 存取数组类型的key键的唯一标识。
这么设计,就是为了让 PlayerPrefs 也能存取大量数据,它与 二进制 的区别就是,二进制存取可以根据变量排序直接读取。
PlayerPrefs 正因为没有 二进制 这个功能,那么我们就设计一个排序功能,根据 list 的 index 排序,将其保存到 key 中,从而得到唯一标识。
这是 PlayerPrefs 保存和读取大量数据的最关键点。
4、删除数据
如果是一条数据,可以用DeleteAll。
如果是少量数据删除某个游戏数据,可以用 DeleteKey
如果是很多很多数据,请用
public void DeletePlayerPrefsDoc()
{
PlayerPrefs.SetString(dataTitlestr, string.Empty);
}
这里只要根据 dataTitlestr,将其设置为空值,就可以将该条数据之下所有数据不在明化。
什么意思?
打个比方,如果dataTitlestr 键的 值 为空,那么我们就隐藏这条存档的UI,从而达到等同删除的效果。
也可用以用 HasKey 判断删除数据,不过用省力方法达到一样效果就好啦。
四、运行图示
五、总结
- 与 xml/jsong/excel/二进制存取数据,不同,PlayerPrefs 不用设置存取路径
- PlayerPrefs 存取数据必须设置key的唯一标识符。
- 与二进制存取一样,数组,字典都需要有限存取它们的count。
- 与二进制存取不同的是,不需要保证变量存取顺序。
写着写着就这么多了,可能不是特别全,不介意费时就看看吧。有时间还会接着更新。