Unity - 存读档机制简析

本文旨在于简要分析Unity中的两种存档机制,即:PlayerPrefs数据持久化方法Serialization数据序列化方法

较比于源项目,我另加了JSON方法、XML方法等及一些Unity设置,更便于读者在使用中理解Unity的存档机制。核心脚本为Game.cs


一、PlayerPrefs 数据持久化方法

  1. 存储原理:采用键值对(key与value)的方法,将游戏数据储存到本地,是一种Unity自带的储存方法。
  2. 储存类型:仅支持int、float、string三种
  3. 储存地址:详见官方文档 PlayerPrefs - Unity Documentation
  4. 读写示例:
//项目内未展示该用法,但以下代码即为常规用法
//新建存档
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 序列化方法

  1. 存储原理:将对象(Object)转换为数据流(stream of bytes),再经过文件流存储到本地的方法。

    • 对象(Object):可以是Unity中的任何文件或是脚本
    • 数据流(stream of bytes):
  2. 序列化反序列化:
    • Serialization:对象-->数据流
    • Deserialization:数据流-->对象
  3. 序列化的方法:
    • 二进制方法
    • 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存档机制众多,但还应按照个人项目需求选择合适的存档方法。


四、参考

原文地址:https://www.cnblogs.com/SouthBegonia/p/11616825.html

时间: 2024-12-01 17:42:20

Unity - 存读档机制简析的相关文章

安卓Binder机制简析

转自:http://www.linuxidc.com/Linux/2011-07/39271.htm 摘要 Binder是Android系统进程间通信(IPC)方式之一.Linux已经拥有管道,system V IPC,socket等IPC手段,却还要倚赖Binder来实现进程间通信,说明Binder具有无可比拟的优势.深入了解Binder并将之与传统 IPC做对比有助于我们深入领会进程间通信的实现和性能优化.本文将对Binder的设计细节做一个全面的阐述,首先通过介绍Binder通信模型和 B

linux-2.6.38poll机制简析(以tiny6410按键中断程序为基础)

一.应用程序 /* struct pollfd { int fd; //文件描述符 short events; //表示请求检测的事件 short revents; //表示检测之后返回的事件 }; */ int fd; struct pollfd fds[1]; // 只用poll函数来检测一个描述符 fd = open("/dev/tiny6410_button", 0); fds[0].fd = fd; //存放文件描述符 fds[0].events = POLLIN; //有数

DPDK多核多线程机制简析

DPDK通过在多核设备上,创建多个线程,每个线程绑定到单独的核上,减少线程调度的开销,以提高性能. DPDK的线程分为控制线程和数据线程,控制线程一般绑定到MASTER核上,主要是接受用户配置,并传递配置参数给数据线程等:数据线程主要是处理数据包. 一.初始化 1.rte_eal_cpu_init()函数中,通过读取/sys/devices/system/cpu/cpuX/下的相关信息,确定当前系统有哪些CPU核,已经每个核属于哪个CPU Socket. 2.eal_parse_args()函数

异步消息处理机制 简析

1.什么是异步消息处理? 答:对于普通的线程来说,执行完run()方法内的代码后线程就结束了.而异步消息处理线程是指:线程启动后会进入一个无限循环体之中,每执行一次,从线程内部的消息队列中取出一个消息,并回调相应的消息处理函数,执行完一个消息后则继续循环.如果消息队列为空,线程会暂停(一般也就是我们调用休眠方法),直到消息队列中又新的消息. 2.什么时候使用异步消息处理? 答:当我们在处理下载或是其他需要长时间执行的任务时,如果直接把处理函数放Activity的OnCreate或是OnStart

Unity开发之存档和读档的三种实现方式

此文内容源自siki学院视频,仅供学习!视频链接地址:http://www.sikiedu.com/course/129 工程使用Unity 2017.3.0f3 (64-bit) 老司机读博客,了解存档读档主体实现方式即可,仅供借鉴参考,菜鸟可以去文章结尾下载源码,或者去上面的链接直接观看视频..... 首先,创建一个Save类用于保存数据 [System.Serializable]public class Save{ public List<int> livingTargetPositio

Unity3D研究之asset bundle 格式简析详解

Unity3D 的 asset bundle 的格式并没有公开.但为了做更好的差异更新,我们还是希望了解其打包格式.这样可以制作专门的差异比较合并工具,会比直接做二进制差异比较效果好的多.因为可以把 asset bundle 内的数据拆分为独立单元,只对变更的单元做差异比较即可. 网上能查到的资料并不是官方给出的,最为流行的是一个叫做 disunity 的开源工具.它是用 java 编写的,只有源代码,而没有给出格式说明(而后者比代码重要的多).通过阅读 disunity 的代码,我整理出如下记

最简单的基于FFMPEG的转码程序分析 +ffmpga代码简析(转 +总结)

模块:  libavcodec    - 编码解码器         libavdevice   - 输入输出设备的支持         libavfilter   - 视音频滤镜支持         libavformat   - 视音频等格式的解析         libavutil     - 工具库         libpostproc   - 后期效果处理         libswscale    - 图像颜色.尺寸转换 1. ffmpga代码简析 1.1 av_log() av_

Android -- MediaPlayer内部实现简析

Android -- MediaPlayer内部实现简析 在之前的博客中,已经介绍了使用MediaPlayer时要注意的内容.现在,这里就通过一个MediaPlayer代码实例,来进一步分析MediaPlayer内部是如何运作.实现的:当然这里的分析只截止到底层调用播放器之前,因为播放器这块实在是没搞懂. 我们使用的例子来源于之前MediaPlayer Playback译文中的官方实例: String url = "http://........"; // your URL here

Android属性动画简析

简析 大家知道,我们在开发一款产品的时候为了达到良好的用户体验,我们可以在应用中适当的加上一些动画效果,譬如平移.缩放.旋转等等,但是这些常用的动画在Android很早期的版本中就存在了,我们称之为传统动画,传统动画一般分为Tween动画和Frame动画,这也是我们最常用的的动画,统称为Animation.传统的Animation动画实现上是通过不停的调用View的onDraw方法来重新绘制View来实现的. 在Android3.0以后,Google为Android新增了属性动画框架Animat