找回密码
 立即注册
查看: 276|回复: 2

Unity中如何设置游戏存档方法归纳

[复制链接]
发表于 2022-7-8 20:00 | 显示全部楼层 |阅读模式
前言

        众所周知,存档在许多游戏中已经成为了不可或缺的一部分,先不论联机的网络游戏,一些长流程游戏,比如黑帝斯,空洞骑士等(让我咕的万恶之源属于是),这样不存档让玩家连续花30+个小时甚至更久属实是让玩家坐牢,变成了班尼特福德之类的阴间游戏。这不仅对玩家是坐牢,对测试游戏的人更是一种全新的坐牢体验:要是要测试后面的关卡有没有bug,那就一定要把前面的打了,然后测出了bug游戏运行不了改了bug要重新来,想想就离谱。但如果拿特定的存档进去测试那肯定少了很多时间和精力。
综上所述,单机游戏存档的好处如下:
1.长流程游戏可以让玩家分几次体验,失败了也可以重新读档,减少了玩家的坐牢体验
2.测试时可以直接进入特定地点进行测试,不需要从头打起不断被自己的粪作ex到
3.肯定还有但我懒得想了
对此,Unity有这几种方法进行游戏存档:
PlayerPrefs的妙用

对于存档,Unity特别在脚本里提供了PlayerPrefs类的方法。它的方法是提供给玩家int,float,string三个类型进行储存与修改。每一个变量名都对应这一个值,也就是键值对,类似于字典类型。这三个类型的变量名和值都是直接储存在文件里的,所以可以达到储存轻量数据的可能。
相关代码也很简单,具体是:
PlayerPrefs.SetString("Name",name); //储存string类变量
PlayerPrefs.SetFloat("Name",name); //储存float类变量
PlayerPrefs.SetInt("Name",name); //储存int类变量
//这三个的意思是:创建名字叫Name的变量在PlayerPrefs里,
//同时他们的值和name相等(当然name也可以直接打float,int,string类型的常量)
Str=PlayerPrefs.GetString("Name"); //读取string类变量并存在Str里
I=PlayerPrefs.GetInt("Name"); //读取int类变量并存在I里
F=PlayerPrefs.GetFloat("Name"); //读取float类变量并存在F里
//如果不存在Name,那么会返回0
PlayerPrefs.DeleteAll(); //删除所有键和值
PlayerPrefs.DeleteKey("Name"); //删除Name的键和值
PlayerPrefs.HasKey("Name"); //判断是否存在Name这个键并返回true或者false
这些代码虽然很简单,但是只能存储三个类型而且不能做成一个类,所以只能用在一些简单的存储,比如简单变量和玩家设置里,比如获得金币钻石数量啊,开不开bgm啊(开了就setint为1,然后每次进游戏getint判断是不是1,是了就开)什么的。当然如果你硬要存储玩家数据也不是不可以,就是有种拿水果刀杀牛的感觉(别问我为什么不是鸡刀,你见过鸡刀吗),不太值得属于是。
注意:
因为是存在文件里,所以你Unity调试也是运行了PlayerPrefs的代码里的,所以在停止调试后PlayerPrefs的改变不会变回来捏
(通俗一点就是你调试时setint把0改成1,终止调试时那个值还是1而不是0辣,讲那么多干嘛)
序列化和反序列化

每个字单独我都认识,咋连起来就不认识了捏?容我细说:
概念

序列化(Serialization)和反序列化(Deserialization)

在Unity中,序列化的概念为:将[对象的状态信息]转化为[Unity可以储存的形式]的自动化处理过程
那反序列化自然是:将[Unity可以储存的形式]转化为[对象的状态信息]的自动化处理过程
从广义上来说,[对象的状态信息]可以被说成[对象],而[Unity可以储存的形式]就是[可传输的字节序列]。说白了,序列化就是将Unity的文件或者脚本(对象的状态信息)转化为二进制的形式(也就是一群读不懂的数字加字母),存储在数据库,内存或者文件中(Unity可以储存的形式),也就是游戏里的“保存游戏”按钮,而反序列化则是“读取游戏”。和其他的方法不同,这些数据会以二进制的形式存储在游戏的data.txt里。
序列化最简单的使用方式是让脚本内private变量的值在Inspector面板里显示出来。众所周知(???),脚本内的public变量都会经过序列化出现在inpector面板上,而private则不会。而要想显示出来,就要在命名上方加上[SerializeField],表示变量可被序列化(私以为其中原理应该是让Unity的Inspector面板保存数据)。具体如下:
[SerializedField]
private int i;
接下来将会介绍Unity为此特制的三个类,它们分别为:
File类,FileStream类和BinaryFormatter类

File类

文件类,即用来对文件进行基本操作,比如说创建、复制、剪切、打开等,可以直接赋值到后面的FileStream类
太长不看版:操作文件的
在使用该类前,请注意使用System.IO的命名空间(IO肯定是input,output辣),也就是:using System.IO
FileStream类

文件流类,即用来读取、写入操作系统文件的,可以进行读取、写入、保存、关文件流的功能,可以把文件转化为字节流的形式
注意:干完活记得把文件流关了,File类可以直接赋值FileStream类
在使用该类前,请注意使用System.IO的命名空间(IO肯定是input,output辣),也就是:using System.IO
综上所述呢,我们可以把文件看成包裹,对包裹本身的操作,比如创建个包裹,移动个包裹什么的就用File类,但是如果要对包裹里面的东西进行操作,那就得使用FileStream了。
BinaryFormatter类

Binary的意思是二进制,而Formatter则是格式化程序,那干什么自然不用我说了,就是以二进制的形式对对象进行序列化和反序列化
在使用该类前,请注意使用System.Runtime.Serialization.Formatters.Binary的命名空间,也就是:using System.Runtime.Serialization.Formatters.Binary
应用

首先创建一个c#脚本命名为Save(名字随便啥都可以)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable] //直接告诉Unity这个类可被序列化
public class Save //不要MonoBehaviour,因为直接作为一个类,不需要挂物体上
{
    public int coins;
    public float playerPositionX;
    public float playerPositionY;
}
接着,我们可以在另一个脚本里写下这些方法:
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
//命名空间肯定不止这么少,就是打出来提醒一下

public Save CreateSave(){ //创建一个Save对象存储当前游戏数据
    Save save=new Save();
    save.Coins=GameManager.Instance.coins;
    save.playerPositionX=player.transform.position.x;
    save.playerPositionY=player.transform.postion.y;
    return Save;
}

public void SaveBySerialization(){
    Save save=CreateSave();
    //获取当前的游戏数据存在Save对象里
    BinaryFormatter bf=new BinaryFormatter();
    //创建一个二进制形式
    FileStream  fs=File.Create(Application.persistentDataPath+"/Data.yj");
    //这里指使用持久路径创建一个文件流并将其保存在Data.yj里(具体在哪就不打了,反正创建了)
    //由于持久路径在Windows系统是隐藏的,所以无法找到Data.yj本身
    //如果想看到,可以改成dataPath(就像下文json的代码里一样)
    //文件后缀可以随便改,甚至是自定义的(比如我这里用了yj)
    bf.Serialize(fs,save);
    //将Save对象转化为字节
    fs.Close();
    //把文件流关了
}
这样,我们就完成了序列化(保存)的全过程:
创建一个二进制格式化对象->新建文件流->序列化Save对象->关闭文件流
接下来就是读取:
private void LoadByDeserialization(){
    if(File.Exists(Application.persistentDataPath+"/Data.yj"))
    //判断文件是否创建
    {
        BinaryFormatter bf=new BinaryFormatter();
        FileStream fs=File.Open(Application.persistentDataPath+"/Data.yj",FileMode.Open);//打开文件
        Save save=bf.Deserialize(fs) as Save;
//反序列化并将数据储存至save(因为返回变量类型不对,所以要强制转换为Save类
        fs.Close();
//关文件流
        GameManager.Instance.coins=save.coins;
        player.transform.position=new Vector2(save.playerPositionX,save.playerPositionY);
//赋值
    }else{
        Debug.LogError("Data Not Found");
    }
}
就这样,一个简单的游戏存档就做好了,这些方法可以挂载在按钮或者特定场景上,使得可以正常进行存档读档的操作。如果你想储存场景内的怪物数据的话,建议使用vector类型进行存储呢,比如说:
for(int i=0;i<save.isDead.Count;i++){
    if(GameManager.instance.enemies==null){
        if(!save.isDead)//如果敌人在我们保存之后死了
        {
            float enemyPosX=save.enemyPositionX;
            float enemyPosY=save.enemyPositionY;
            Enemy newEnemy=Instantiate(enemyPrefab,new Vector2(enemyPosX,enemyPosY),Quaternion.identity);
            GameManager.Instance.enemies=newBat;
            //把本该活着的敌人数据进行填充
        }else{
            float enemyPosX=save.enemyPositionX;
            float enemyPosY=save.enemyPositionY;
            Enemy newEnemy=Instantiate(enemyPrefab,new Vector2(enemyPosX,enemyPosY),Quaternion.identity);
        }
    }
}
新建的类也应该是:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable] //直接告诉Unity这个类可被序列化
public class Save //不要MonoBehaviour,因为直接作为一个类,不需要挂物体上
{
    public int coins;
    public float playerPositionX;
    public float playerPositionY;
   
    public List<float> enemyPositionX=new List<float>();
    public List<float> enemyPositionY=new List<float>();
    public List<bool> isDead=new List<bool>();
}
总结

序列化和反序列化看似很好解决了大量数据存储的问题,但是你要想啊,序列化以后的文件全是二进制,这鬼也看不懂啊(除了计算机捏),那修改也更是问题。那有没有什么序列化以后属于一个人能看懂的东西捏?这就要看:
另一种序列化方式——JSON

{json},搁着一看还以为是jvavscript(js),那它如何来实现存档读档呢?还是从概念入手把
概念

JSON

JSON,全称是JavaScript对象表示法(JavaScript Object Notation),它主要是用来在客户端和服务端进行交互数据的,它属于轻量级语言。它的格式以”键值对“的形式作为展示,而且键的名字可以自己定义,不理解的可以看看下面:
var Save={//大括号表示对象
    "coins":10,
    "playerPositionX":10.866,
    "playerPositionY":-21,
    "isDead":[true,false,false,true,true] //中括号表示数组
}json由两部分构成:一个是键名(Key),也就是变量的名字,是String类型的;另一个就是值(value),它可以是int,bool,float,数组,甚至是对象类型。它们组合在一起,也就是上文所说的”键值对“(Key Value Pair),有点字典内味了哈(字典也是键值对啊kora)。而json文件本身也是String类型的文本。
看到这里也应该明白json和js的区别了(你认真的???):JavaScript是一种程序语言,而json则是一种数据格式,一种语法。因为它没有使用任何函数或者命令,仅仅是以文字为基础,易于阅读和编写,作为数据交换语言显然很适合。虽然JavaScript里自带方法可以让js和json互相转化,但本质上两者还是不属于一个东西。
虽然可以使用js可以和json互相转化,但是Unity的语言是C#啊。。。不过不用担心,Unity也有相应的方法让json可以和C#相互转化(JsonUtility类)。而下文也是通过这种方式实现的。
提示:其实可以通过库进行json数据的解析,比如.Net库的http://Json.Net和LitJson.dll。之所以使用JsonUtility类是因为它是Unity自带的,而且解析更快,产生的垃圾更少。
流写入类(StreamWriter类)

和FileSream类差不多,也是对系统文件进行写入的(没有读取功能捏),但是FileStream类读取的是字节数组,适用于非文本文件,所以不太适合读取String类型的JSON文件
注意:写完也记得把流关了
流读取类(StreamReader类)

和FileStream类差不多,也是对系统文件进行读取的(没有写入功能捏),但是FileStream类读取的是字节数组,适用于非文本文件,所以不太适合读取String类型的JSON文件
注意:写完也记得把流关了
就是复制粘贴改了的,略略略
应用

//不需要加别的命名空间,简直爽死(bushi
private void SaveByJSON()
{
    Save save= CreateSave();
    //创建一个Save实例存储游戏数据(CreateSave函数在上面)
    String JsonString=JsonUtility.ToJson(save);
    //将对象save转化为json字符串
    //上面说了Json是string类型,所以命名string
    StreamWriter sw=new StreamWriter(Application.dataPath+"/Data.yj");
//persistentDataPath是隐藏文件的,所以你找不到Data.yj的所在地,
//而dataPath就不会隐藏,同时文件后缀也可以乱取
    sw.Write(JsonString);
//将json字符串写入流参数
    sw.Close();
//把流关了
}

private void LoadByJSON()
{
    if(File.Exists(Application.dataPath+"Data.yj"))
    //判断文件是否创建
    {
        StreamReader sr=new StreamReader(Application.dataPath+"/Data.yj");
        //从流中读取字符串
        String JsonString=sr.ReadToEnd();
//ReadToEnd()方法可以读取从流当前位置到结尾的所有字符
//还有Read()方法,但是只读了一个字符,还有更多方法捏懒得打了
        sr.Close();
//把流关了
        Save save=JsonUtility.FromJson<Save>(JsonString);
//该方法属于泛型方法T,需要给出明确的类型定义,所以要写<Save>
        GameManager.Instance.coins=save.coins;
        player.transform.position=new Vector2(save.playerPosition.x,save.playerPositionY);
        //属于是常规方式了
    }
    else{
        Debug.LogError("File Not Found.");
    }
}
就这样,我们使用json完成了游戏的存档和读档操作
小贴士:

json方法里的泛型和强制转换属于两码事,前者是一个方法,可以满足多个类型的实现(但是要使用<>标明类型)而后者只能完成一个类型,所以需要在前面加括号,或者使用as对错误的类型进行强制转换
总结

json看似完美的解决了文件可读性的问题(如果我们打开文件,看到的便是json本来的文本样式),但是json背后还是有着缺点:当在人机都需要识别数据时(比如说配置文件),json的可读性也会变得差,如果你要储存复杂场景,比如树的坐标,比如草的坐标,那一堆堆的数组表示的坐标肯定看得你想要坐牢。在这里提醒一下,当你打开json文件时,里面可不会有换行符一行行给你打的清楚,而是全部拧成了一团。那么,有没有一种文件保存类型,既可以在Unity里保存和读取数据,也可以具有良好的结构性和描述性呢?那就是最后一个我要介绍的最后一个类型:
XML

概念

XML

XML,意思是可扩展标记语言(eXtensible Markup Language)。为什么不叫EML呢?因为这样和eml(发邮件的文件后缀格式)重名了。。。
咳咳,言归正传,它专门被设计用来结构化“存储”和”传输“数据或者信息。它可以在不兼容的系统之间轻松交换数据,简化数据共享。而且,XML数据以纯文本方式进行存储(本质就是字符串),可以给任何阅读设备使用。
有些人听着可能觉得:这不就是html ?事实上两者确实是有区别的:html主要显示网站上的内容(文字、图片等),事实上,我们文章一开头网易云音乐放的真夜的歌(\真夜/\真夜/\真夜/\真夜/\真夜/\真夜/)(突发恶疾.jpg)就是通过html实现的:
阿巴<iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id=1399789328&auto=1&height=66"></iframe>阿巴
在一个网站中,HTML负责网页内容的显示,CSS负责内容的颜色和排版,javascript负责进行一些交互功能的实现。(这些是前端的知识了属于是)
而XML相比于HTML,XML更在意里面存储的信息。比如说XML里面的标签可以自定义,而HTML都是预先定义好的,所以XML更能胜任这个存档的职能。
接下来就有点重要了:
为什么我们说XML相比于json更具有结构性呢?这是因为XML中的每个元素形成了一个文档树。我们可以创建一个玩家的根节点或者敌人的根节点,然后把这些对象应该拥有的变量通过AppendChild的方式附录到根节点上。这样人们想要看玩家的数据,只要找到玩家的节点一个个看就可以了,而不要像json一样挤在一起,难以分辨。下面应用里我手打的XML应该可以体现这一点。
XmlDocument类

用来保存,加载和解析XML文件,通过这个类可以对xml文件进行创建、保存、读取、添加以及删除。
不仅如此,这个类还有一个静态方法:xmlDocument.CreateElement("node");它可以在xml文档中创建一个“元素节点”(名字当然是自定义的,也就是标签),
注意:使用XmlDocument类前需要引入System.Xml命名空间哟~
应用

using System.Xml; //还是提醒一下

private void SaveByXML()
{
    Save save=CreateSave();
    XmlDocument xmlDocument=new XmlDocument();
   
    #region CreateXML elements        
    XmlElement root=XmlDocument.CreateElement("Save");
    //创建一个参数名为"Save"的xml元素,这个元素名字叫root
    //这么取在脚本里就更好理解了
    root.SetAttribute("FileName","File_01");
   
    XmlElement coinElement=xmlDocument.CreateElement("Coins");
    coinElement.InnerText=save.coins.ToString();
    //创建金币保存的元素,并将金币数以字符串形式导入
    root.AppendChild(coinElement);
    //将coinElement这一元素附录至根节点root上
   
    XmlElement playerPositionXElement=xmlDocument.CreateElement("PlayerPositionX");
    playerPositionXElement.InnerText=save.playerPositionX.ToString();
    root.AppendChild(playerPositionXElement);
    //接下来就是重复步骤:创建节点、储存数据、将节点附录至根节点
   
    XmlElement playerPositionYElement=xmlDocument.CreateElement("PlayerPositionY");
    playerPositionYElement.InnerText=save.playerPositionY.ToString();
    root.AppendChild(playerPositionYElement);
   
    #endregion
    xmlDocument.AppendChild(root);
    //最后要把根节点附录在文件上,以便于文件可以保存   
    xmlDocument.Save(Application.dataPath+"DataXML.yj");
    //把数据保存在文件“DataXML.yj”里
    if(File.Exists(Application.dataPath+"DataXML.yj")){
        Debug.Log("XML FILE SAVED");
    }
}
接下来,如果你保存了并且打开了DataXML.yj,里面会是这样的:
<Save FileName="File_01">
    <Coins>20</Coins>
    <PlayerPositionX>2.5</PlayerPositionX>
    <PlayerPositionY>-6</PlayerPositionY>
</Save>从这里我们就能看得很清楚了:在root节点(名字被我们命名为Save)下有三个子节点:Coins、PlayerPositionX、PlayerPositionY,它们都嵌套在了Save这个根节点下。相信通过这个更能理解XML文件的优越结构性了。

接下来就是读取:
private void LoadByXML()
{
    if(File.Exists(Application.dataPath+"/DataXML.yj"))
    {
        Save save=new Save();
        
        XmlDocument xmlDocument=new XmlDocument();
        xmlDocument.Load(Applicatiom.dataPath+"/DataXML.yj");
        //创建并读取保存的XML文件
        XmlNodeList coins=xmlDocument.GetElementsByTagName("Coins");
        //寻找标签名称来找到保存在文件里的金币数
        int coincount=int.Parse(coins[0].InnerText);
        //MARKER 为什么是[0]呢?因为如果标签名为Coins的有很多的话,就会重复
        //所以返回的是List集合类型,第一个为[0],第二个为[1]
        //将String类的金币数通过Parse转化为int并存进变量里
        save.coins=coincount;
        XmlNodeList positionX=xml.DocumentXGetElementsByTagName("positionX");
        float positionXcount=float.Parse(positionX[0].InnerText);
        save.playerPositionX=positionXcount;
        
        XmlNodeList positionY=xml.Document.GetElementsByTagName("positionY");
        float positionYcount=float.Parse(positionY[0].InnerText);
        save.playerPositionY=positionYcount;
        
        GameManager.instance.coins=save.coins;
        player.transform.position=new Vector2(save.playerPositionX,save.playerPositionY);
    }
    else
    {
        Debug.LogError("NOT FOUND FILE");
    }
}
这样看XML可能还不太清楚,所以我打算在把敌人的数据也保存在XML里面,这样就得改成:
using System.Xml; //还是提醒一下

private void SaveByXML()
{
    Save save=CreateSave();
    XmlDocument xmlDocument=new XmlDocument();
   
    #region CreateXML elements
        
    XmlElement root=XmlDocument.CreateElement("Save");
    //创建一个参数名为"Save"的xml元素,这个元素名字叫root
    //这么取在脚本里就更好理解了
    root.SetAttribute("FileName","File_01");
   
    XmlElement coinElement=xmlDocument.CreateElement("Coins");
    coinElement.InnerText=save.coins.ToString();
    //创建金币保存的元素,并将金币数以字符串形式导入
    root.AppendChild(coinElement);
    //将coinElement这一元素附录至根节点root上
   
    XmlElement playerPositionXElement=xmlDocument.CreateElement("PlayerPositionX");
    playerPositionXElement.InnerText=save.playerPositionX.ToString();
    root.AppendChild(playerPositionXElement);
    //接下来就是重复步骤:创建节点、储存数据、将节点附录至根节点
   
    XmlElement playerPositionYElement=xmlDocument.CreateElement("PlayerPositionY");
    playerPositionYElement.InnerText=save.playerPositionY.ToString();
    root.AppendChild(playerPositionYElement);
   
    XmlElement enemy,enemyPositionX,enemyPositionY,isDead;
    for(int i=0;i<save.enemyPositionX.Count;i++){
        enemy=xmlDocument.CreateElement("Enemy");
        enemyPositionX=xmlDocument.CreateElement("EnemyPositionX");
        enemyPositionY=xmlDocument.CreateElement("EnemyPositionY");
        isDead<span class="p">=xmlDocument.CreateElement("IsDead");
        enemyPositionX.InnerText=save.enemyPositionX.ToString();
        enemyPositionY.InnerText=save.enemyPositionY.ToString();
        isDead.InnerText=save.isDead.ToString();
        enemy.AppendChild(enemyPositionX);
        enemy.AppendChild(enemyPositionY);
        enemy.AppendChild(isDead);
        root.AppendChild(enemy);
    }
   
    #endregion
    xmlDocument.AppendChild(root);
    //最后要把根节点附录在文件上,以便于文件可以保存   
    xmlDocument.Save(Application.dataPath+"DataXML.yj");
    //把数据保存在文件“DataXML.yj”里
    if(File.Exists(Application.dataPath+"DataXML.yj")){
        Debug.Log("XML FILE SAVED");
    }
}
而XML文档也会变成这个样子:
<Save FileName="File_01">
    <Coins>20</Coins>
    <PlayerPositionX>2.5</PlayerPositionX>
    <PlayerPositionY>-6</PlayerPositionY>
    <Enemy>
        <EnemyPositionX>-2.1</EnemyPositionX>
        <EnemyPositionY>2</EnemyPositionY>
        <IsDead>True</IsDead>
    </Enemy>
    <Enemy>
        <EnemyPositionX>-2.1</EnemyPositionX>
        <EnemyPositionY>2</EnemyPositionY>
        <IsDead>False</IsDead>
    </Enemy>
    <Enemy>
        <EnemyPositionX>-0.1</EnemyPositionX>
        <EnemyPositionY>5</EnemyPositionY>
        <IsDead>True</IsDead>
    </Enemy>
    <Enemy>
        <EnemyPositionX>2.1</EnemyPositionX>
        <EnemyPositionY>2</EnemyPositionY>
        <IsDead>False</IsDead>
    </Enemy>
    <Enemy>
        <EnemyPositionX>-21</EnemyPositionX>
        <EnemyPositionY>20</EnemyPositionY>
        <IsDead>False</IsDead>
    </Enemy>
</Save>这样看起来确实就很舒适了(相比于json文件)
在读取环节也要加一点东西:
private void LoadByXML()
{
    if(File.Exists(Application.dataPath+"/DataXML.yj"))
    {
        Save save=new Save();
        
        XmlDocument xmlDocument=new XmlDocument();
        xmlDocument.Load(Applicatiom.dataPath+"/DataXML.yj");
        //创建并读取保存的XML文件
        XmlNodeList coins=xmlDocument.GetElementsByTagName("Coins");
        //寻找标签名称来找到保存在文件里的金币数
        int coincount=int.Parse(coins[0].InnerText);
        //MARKER 为什么是[0]呢?因为如果标签名为Coins的有很多的话,就会重复
        //所以返回的是List集合类型,第一个为[0],第二个为[1]
        //将String类的金币数通过Parse转化为int并存进变量里
        save.coins=coincount;
        XmlNodeList positionX=xml.DocumentXGetElementsByTagName("positionX");
        float positionXcount=float.Parse(positionX[0].InnerText);
        save.playerPositionX=positionXcount;
        
        XmlNodeList positionY=xml.Document.GetElementsByTagName("positionY");
        float positionYcount=float.Parse(positionY[0].InnerText);
        save.playerPositionY=positionYcount;
        if(enemy.Count!=0){
            for(int i=0;i<enemy.Count;i++){
                XmlNodeList enemyPositionX=xml.Document.GetElementsByTagName("EnemyPositionX");
                float enemyPosX=float.Parse(enemyPositionX.InnerText);
                save.enemyPositionX.Add(enemyPosX);
//由于对象save中敌人坐标声明为List集合,所以需要List.Add进行数值的添加
                XmlNodeList enemyPositionY=xml.Document.GetElementsByTagName("EnemyPositionY");
                float enemyPosY=float.Parse(enemyPositionY.InnerText);
                save.enemyPositionY。Add(enemyPosY);
                XmlNodeList isDead=xml.Document.GetElementsByTagName("IsDead");
            }
        }
        
        GameManager.instance.coins=save.coins;
        player.transform.position=new Vector2(save.playerPositionX,save.playerPositionY);
        
        for(int i=0;i<save.isDead.Count;i++){
    if(GameManager.instance.enemies==null){
        if(!save.isDead)//如果敌人在我们保存之后死了
        {
            float enemyPosX=save.enemyPositionX;
            float enemyPosY=save.enemyPositionY;
            Enemy newEnemy=Instantiate(enemyPrefab,new Vector2(enemyPosX,enemyPosY),Quaternion.identity);
            GameManager.Instance.enemies=newBat;
            //把本该活着的敌人数据进行填充
        }else{
            float enemyPosX=save.enemyPositionX;
            float enemyPosY=save.enemyPositionY;
            Enemy newEnemy=Instantiate(enemyPrefab,new Vector2(enemyPosX,enemyPosY),Quaternion.identity);
        }
    }
}
    }
    else
    {
        Debug.LogError("NOT FOUND FILE");
    }
}
总结

XML从个人观感上确实好了不少,可是你看着这代码。。。属实让人头皮发麻。毕竟你要在存储内容的基础上还要加上结构层次,多点代码量看上去也是理所应当的。
All In All

其实把对象储存进文件的方式还有很多,只是目前主流方式就是这四种了。PlayerPrefs可以较为简单的进行存档读档,但是却只能进行一些简单的操作(比如玩家设置等轻量数据),存储的数值也只局限于int,float,string三种类型,连List类都无法支持。序列化和反序列化看似可以解决数据的存储和读取,而且存档的内容也不至于让别人那么容易地进行篡改。但是改不了你自己也读不懂啊,而且还要加上很长的命名空间。json相对而言就不用加上一些复杂的命名空间,存储的文档也让人看得懂。但是储存的东西一下多了,你打开文档时,那不带换行的原始json文件又给你带来一个全新的坐牢体验。XML就很好的解决了文档可读性的问题,但是相对的代码量也提高了114514倍,命名空间也加了一个。关于这四种方式的解决问题,建议读者根据自己的实际情况进行选择。不过在我看来,json应该是最佳的选择把。。。

可以来我的博客坐坐:www.karmotrine.fun
发表于 2022-7-8 20:01 | 显示全部楼层
好棒的帖子!写的真好,谢谢你(>﹏<)
发表于 2022-7-8 20:04 | 显示全部楼层
听口音就是老二次元了,还是二次元懂二次元,生动有趣,通俗易懂
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-9-22 05:27 , Processed in 0.092162 second(s), 25 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表