Unity - 存读档机制简析
本文旨在于简要分析Unity中的两种存档机制,即:PlayerPrefs数据持久化方法及Serialization数据序列化方法
较比于源项目,我另加了JSON方法、XML方法等及一些Unity设置,更便于读者在使用中理解Unity的存档机制。核心脚本为Game.cs


一、PlayerPrefs 数据持久化方法
- 存储原理:采用键值对(key与value)的方法,将游戏数据储存到本地,是一种Unity自带的储存方法。
- 储存类型:仅支持int、float、string三种
- 储存地址:详见官方文档 PlayerPrefs - Unity Documentation
- 读写示例:
//项目内未展示该用法,但以下代码即为常规用法
//新建存档
PlayerPrefs.SetInt("Score", 20);
PlayerPrefs.SetFloat("Health", 100.0F);
PlayerPrefs.SetString("Name",m_PlayerName);
//检验存档信息
if(!PlayerPrefs.HasKey("Name"))
return;
//读取存档
socre = PlayerPrefs.GetInt("Score");
health = PlayerPrefs.GetFloat("Health");
m_PlayerName = PlayerPrefs.GetString("Name");
//删除存档
PlayerPrefs.DeleteKey("Score");
- 优缺点:虽然以这种方式存储游戏数据方便快捷,但是当数据量庞大以后,键值对的大量创建使用,不仅脚本控制繁琐,也有可能造成资源的浪费。因此,只建议对一些基础数据,例如图像设置、声音设置等采用该方法存储。
二、Serialization 序列化方法
- 存储原理:将对象(Object)转换为数据流(stream of bytes),再经过文件流存储到本地的方法。
- 对象(Object):可以是Unity中的任何文件或是脚本
- 数据流(stream of bytes):
- 序列化反序列化:
- Serialization:对象-->数据流
- Deserialization:数据流-->对象
- 序列化的方法:
- 二进制方法
- JSON方法
- XML方法
1. 二进制存储(Binary Formatter):
//存档信息的类:
[System.Serializable]
public class Save
{
public int hits = 0;
public int shots = 0;
public List<int> livingTargetPositions = new List<int>();
public List<int> livingTargetsTypes = new List<int>();
}
//设置游戏数值
public void SetGame(Save save)
{
hits = save.hits;
shots = save.shots;
for (int i = 0; i < save.livingTargetPositions.Count; i++)
{
int position = save.livingTargetPositions[i];
Target target = targets[position].GetComponent<Target>();
target.ActivateRobot((RobotTypes)save.livingTargetsTypes[i]);
target.GetComponent<Target>().ResetDeathTimer();
}
}
//存档函数:
public void SaveGame()
{
//1. 序列化过程
//创建save对象保存游戏信息
Save save = CreateSaveGameObject();
string filePath = Application.dataPath + "/gameSaveBySerialize.save";
//2. 创建二进制格式化程序及文件流
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Create(filePath);
//3. 将save对象序列化到file流
bf.Serialize(file, save);
file.Close();
}
//读档函数:
public void LoadGame()
{
string filePath = Application.dataPath + "/gameSaveBySerialize.save";
//1. 检验目标位置是否有存档
if (File.Exists(filePath))
{
//2. 创建二进制格式化程序,打开文件流
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Open(filePath, FileMode.Open);
//3. 将file流反序列化到save对象
Save save = (Save)bf.Deserialize(file);
file.Close();
//从save对象读取信息到本地
SetGame(save);
}
else
Debug.Log("No gamesaved!");
}
2. JSON方法:
/*
* 注意:使用JSON存档方法需要用到LitJson库,LitJson.dll文件可在项目Assets目录下找到。
* 使用方法:将LitJson.dll拖拽到个人项目Assets目录下即可
*/
//JSON存档函数:
public void SaveAsJson()
{
//1. 创建save对象保存游戏信息
Save save = CreateSaveGameobject();
string path = Application.dataPath + "/gameSaveByJson.json";
//2. 利用JsonMapper将save对象转换为Json格式的字符串
string saveJsonStr = JsonMapper.ToJson(save);
//3. 创建StreamWriter,将Json字符串写入文件中
StreamWriter sw = new StreamWriter(path);
sw.Write(saveJsonStr);
sw.Close();
}
//JSON读档函数:
public void LoadAsJson()
{
string path = Application.dataPath + "/gameSaveByJson.json";
//1. 检验目标位置是否有存档
if(File.Exists(path))
{
//2. 创建一个StreamReader,用来读取流
StreamReader sr = new StreamReader(path);
//3. 将读取到的流赋值给jsonStr
string jsonStr = sr.ReadToEnd();
sr.Close();
//4. 将字符串jsonStr转换为Save对象
Save save = JsonMapper.ToObject<Save>(jsonStr);
//从save对象读取信息到本地
SetGame(save);
}
else
Debug.Log("No gamesaved!");
}
JSON存档格式:
{
"livingTargetPositions":[0,1,2,4],
"livingTargetsTypes":[2,2,2,1],
"hits":1,
"shots":8
}
3. XML方法:
//XML存储
public void SaveAsXml()
{
Save save = CreateSaveGameObject();
//创建XML文件的存储路径
string filePath = Application.dataPath + "/gameSaveByXML.txt";
//创建XML文档
XmlDocument xmlDoc = new XmlDocument();
//创建根节点,即最上层节点
XmlElement root = xmlDoc.CreateElement("save");
//设置根节点中的值
root.SetAttribute("name", "saveFile1");
//创建XmlElement
XmlElement target;
XmlElement targetPosition;
XmlElement targetType;
//遍历save中存储的数据,将数据转换成XML格式
for (int i = 0; i < save.livingTargetPositions.Count; i++)
{
target = xmlDoc.CreateElement("target");
targetPosition = xmlDoc.CreateElement("targetPosition");
//设置InnerText值
targetPosition.InnerText = save.livingTargetPositions[i].ToString();
targetType = xmlDoc.CreateElement("targetType");
targetType.InnerText = save.livingTargetsTypes[i].ToString();
//设置节点间的层级关系 root -- target -- (targetPosition, monsterType)
target.AppendChild(targetPosition);
target.AppendChild(targetType);
root.AppendChild(target);
}
//设置射击数和分数节点并设置层级关系
XmlElement shots = xmlDoc.CreateElement("shoots");
shots.InnerText = save.shots.ToString();
root.AppendChild(shots);
XmlElement hits = xmlDoc.CreateElement("hits");
hits.InnerText = save.hits.ToString();
root.AppendChild(hits);
xmlDoc.AppendChild(root);
xmlDoc.Save(filePath);
if (File.Exists(Application.dataPath + "/gameSaveByXML.txt"))
{
Debug.Log("Saving as XML");
}
}
//XML读取
public void LoadAsXml()
{
string filePath = Application.dataPath + "/gameSaveByXML.txt";
if (File.Exists(filePath))
{
Save save = new Save();
//加载XML文档
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(filePath);
//通过节点名称来获取元素,结果为XmlNodeList类型
XmlNodeList targets = xmlDoc.GetElementsByTagName("target");
//遍历所有的target节点,并获得子节点和子节点的InnerText
if (targets.Count != 0)
{
foreach (XmlNode target in targets)
{
//把得到的值存储到save中
XmlNode targetPosition = target.ChildNodes[0];
int targetPositionIndex = int.Parse(targetPosition.InnerText);
save.livingTargetPositions.Add(targetPositionIndex);
XmlNode targetType = target.ChildNodes[1];
int targetTypeIndex = int.Parse(targetType.InnerText);
save.livingTargetsTypes.Add(targetTypeIndex);
}
}
//得到存储的射击数和分数
XmlNodeList shoots = xmlDoc.GetElementsByTagName("shoots");
int shootNumCount = int.Parse(shoots[0].InnerText);
save.shots = shootNumCount;
XmlNodeList hits = xmlDoc.GetElementsByTagName("hits");
int hitsCount = int.Parse(hits[0].InnerText);
save.hits = hitsCount;
SetGame(save);
}
else
{
Debug.Log("No game saved!");
}
}
XML存档格式:
<save name="saveFile1">
<target>
<targetPosition>0</targetPosition>
<targetType>2</targetType>
</target>
<target>
<targetPosition>1</targetPosition>
<targetType>2</targetType>
</target>
<target>
<targetPosition>2</targetPosition>
<targetType>2</targetType>
</target>
<target>
<targetPosition>3</targetPosition>
<targetType>2</targetType>
</target>
<shoots>13</shoots>
<hits>3</hits>
</save>
三、总述
无论是数据持久化方法还是序列化方法都可以实现Unity的存档机制。数据持久化方法操作方便,适用于数值较少的小项目。序列化方法的存档格式较为规范,其中二进制方法操作简单,但可读性差;JSON方法存档格式规范易读,具有一定的可读性;XML方法操作繁琐,但是存档格式可读性强,JSON和XML存档都可以用文本读取便于查看。
综上所述,Unity存档机制众多,但还应按照个人项目需求选择合适的存档方法。
四、参考
- PlayerPrefs - Unity Documentation
- How to Save and Load a Game in Unity - raywenderlich
- 对于PlayerPrefs学习以及储存的研究 - 果vinegar
- Save&Load Unity存档读档的学习总结 - JoharWong
- C#中File和FileStream的用法 - 忆汐辰
- Application.dataPath - Unity Documentation
Unity - 存读档机制简析的更多相关文章
- Linux VFS机制简析(二)
Linux VFS机制简析(二) 接上一篇Linux VFS机制简析(一),本篇继续介绍有关Address space和address operations.file和file operations. ...
- Unity3D — —存读档【转载】
详细可参考此篇博文: Unity序列化之XML,JSON--------合成与解析 简单例子(SiKi学院教程): using System.Collections; using System.Col ...
- Linux VFS机制简析(一)
Linux VFS机制简析(一) 本文主要基于Linux内核文档,简单分析Linux VFS机制,以期对编写新的内核文件系统(通常是给分布式文件系统编写内核客户端)的场景有所帮助. 个人渊源 切入正文 ...
- Linux内存管理机制简析
Linux内存管理机制简析 本文对Linux内存管理机制做一个简单的分析,试图让你快速理解Linux一些内存管理的概念并有效的利用一些管理方法. NUMA Linux 2.6开始支持NUMA( Non ...
- Binder机制简析(三)
注册Service Service组件运行在Server进程中,首先要将Service注册到Service Manager中,再启动一个Binder线程池来等待和处理Client的通信请求. 注册过程 ...
- Linux内核poll/select机制简析
0 I/O多路复用机制 I/O多路复用 (I/O multiplexing),提供了同时监测若干个文件描述符是否可以执行IO操作的能力. select/poll/epoll 函数都提供了这样的机制,能 ...
- Magento 缓存机制简析
在知道缓存机制前,首先需要知道,Magento的路由机制,这边就不做赘述了,百度一大堆. 下面一个简单的缓存生效流程: A:首先在页面开始时,Magento在app\code\core\Mage\Co ...
- linux-2.6.38poll机制简析(以tiny6410按键中断程序为基础)
一.应用程序 /* struct pollfd { int fd; //文件描述符 short events; //表示请求检测的事件 short revents; //表示检测之后返回的事件 }; ...
- DPDK多核多线程机制简析
DPDK通过在多核设备上,创建多个线程,每个线程绑定到单独的核上,减少线程调度的开销,以提高性能. DPDK的线程分为控制线程和数据线程,控制线程一般绑定到MASTER核上,主要是接受用户配置,并传递 ...
随机推荐
- HDU 6052
题意略. 思路:我们单独考虑每种颜色的贡献,颜色c对答案的贡献 == 含有c的矩形个数,这就是在考查我们计数的能力,暴力可过. #include<bits/stdc++.h> #defin ...
- springBoot日志框架自动配置与原理
1.日志框架 小张:开发一个大型系统: 1.System.out.println(""):将关键数据打印在控制台:去掉?写在一个文件? 2.框架来记录系统的一些运行时信息: ...
- JAVA 泛型中的通配符 T,E,K,V,?
前言 Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许开发者在编译时检测到非法的类型. 泛型的本质是参数化类型,也就是说所操作的数据 ...
- 大型互联网公司分布式ID方案总结
ID是数据的唯一标识,传统的做法是利用UUID和数据库的自增ID,在互联网企业中,大部分公司使用的都是Mysql,并且因为需要事务支持,所以通常会使用Innodb存储引擎,UUID太长以及无序,所以并 ...
- MySQL5.7.27报错[Err] 1055 - Expression #1 of ORDER BY clause is not in GROUP BY clause and contains nonaggregated
mysql5.7.27在运行更新语句时出现如下情况,mysql5.6之前没有这种情况出现. of ORDER BY clause is not in GROUP BY clause and conta ...
- ST算法 Sliding Window algorithm template
ST算法(Sliding Window):A easy way to slove the substring problems algorithm template to slove substrin ...
- JMeter特点&性能测试工具选型的原则&模拟压力的原理
1.JMeter自身的特点 1)开源.轻量级.更适合自动化和持续集成(100M左右,LoadRunner 4G左右) 2)学习难度大 3)资料少.全英文 2. 性能测试工具选型的原则 1)成本 a.工 ...
- Codeforces 939 D Love Rescue
Love Rescue 题意:Valya 和 Tolya 是一对情侣, 他们的T恤和头巾上都有小写字母,但是女朋友嫌弃男朋友上T恤上的字不和她的头巾上的字一样,就很生气, 然后来了一个魔法师, 它可以 ...
- CodeForces 1105E Helping Hiasat 最大独立集
Helping Hiasat 题解: 如果我们把连续的2出现的人都相互连边的话, 题目就是问最大独立集的答案是多少. 求最大独立集可以将图变成反图, 然后求最大团. 代码: #include<b ...
- 不权威的国产CPU发展历程
最近进行了一些国产化相关工作 趁着周末有时间,自己整理一下这段时间的学习内容. 毕竟不是处理器和芯片的业内人士,里面多有纰漏,请谅解. 希望可以作为入门学习的简单知识. 1.0 远古时代 unix 世 ...