Unity 开发之存档、读档

803 阅读4分钟

前言

存档作为游戏开发中,必不可少的一环,毕竟要求玩家一次性通关游戏太难了。

本章主要介绍在Unity中如何实现游戏的存档、读档,有若干实现方法,可以通过Unity自带的PlayerPreb,亦或者通过JSON、xml存储。

对比

  • PlayerPreb:Unity自带的键值对持久化存储方法,用于存储一些简单的数据。
  • XML:可读性强,但是文件内容会很庞大,文件格式复杂。
  • JSON:数据格式比较简单,易于读写,但是不直观,可读性比XML差。

准备

存档类

准备一个游戏存档类,存档类只有几个关键属性(实际内容结合需要),注意必须加上[System.Serializable]用于序列化

[System.Serializable]
public class Save 
{
    public int currentHealth; //当前生命值

    public int maxHealth; //最大生命值

    public int curBulletCount; //当前子弹数量

    public Vector2 position; //人物位置

    public Save(int currentHealth, int maxHealth, int curBulletCount, Vector2 position)
    {
        this.currentHealth = currentHealth;
        this.maxHealth = maxHealth;
        this.curBulletCount = curBulletCount;
        this.position = position;
    }
    public Save()
    {
    }

    public override string ToString()
    {
        return "最大生命值:"+ maxHealth + ",当前生命值:"+currentHealth;
    }
}

其他说明

文章中提及的以下两个方法根据自己的实际需要实现

  • CreaseSave():快捷创建存档类,将需要的属性存储到Save对象,便于存储到json或者xml

  • LoadSave(Save save):读取save对象信息,应用到游戏去

PlayerPrefs

PlayerPrefs是Unity提供的本地持久化保存与读取的类,通过键值对的形式存储。 建议通过该类存储一些简单的数据,比如全局bgm是否开启等,不建议将复杂的存档信息通过 PlayerPrefs 存储

PlayerPrefs.SetString("Name",value); //储存string类变量
PlayerPrefs.SetFloat("money",value); //储存float类变量
PlayerPrefs.SetInt("age",value); //储存int类变量

PlayerPrefs.GetString("Name"); //读取string类变量
PlayerPrefs.GetFloat("money"); //读取float类变量
PlayerPrefs.GetInt("age"); //读取int类变量

PlayerPrefs.DeleteKey (key : string) //删除指定数据;
PlayerPrefs.DeleteAll() //删除全部键 ;
PlayerPrefs.HasKey (key : string) //判断数据是否存在的方法;

存储位置

在windows下,playerPrefs被存储在注册表的HKEY_CURRENT_USER\Software\Unity\UnityEditor\公司名\项目名 下,这里公司名和项目名是在project setting中设置的

  • 例子:

image.png

XML

通过xml格式来保存存档,可读性强,文件格式复杂,需要花费更多代码来解析xml

这里提供了两种方式来操作xml,一种是手动创建结点(代码较复杂,但是更灵活),一种是通过序列化(代码少,简约)

保存存档

手动创建结点

public void SaveGameByXml1()
{
    XmlDocument xmlDoc = new XmlDocument();
    //继续判断当前路径下是否有该文件,如果存在就加载(直接清空文件内容)
    if (File.Exists(filePath))
    {
        xmlDoc.Load(filePath);
        xmlDoc.RemoveAll();//清空文件结点
    }

    //创建root节点,也就是最上一层节点
    XmlElement root = xmlDoc.CreateElement("player");
    root.SetAttribute("name", "save_1");
    //记录玩家属性
    
    XmlElement currentHealth = xmlDoc.CreateElement("currentHealth");//当前生命值
    currentHealth.InnerText = player.MyCurrentHealth+ "";
    XmlElement maxHealth = xmlDoc.CreateElement("maxHealth");//最大生命值
    maxHealth.InnerText = player.MyMaxHealth+"";
    XmlElement curBulletCount = xmlDoc.CreateElement("curBulletCount");//子弹
    curBulletCount.InnerText = player.curBulletCount+"";
    XmlElement position = xmlDoc.CreateElement("position"); //当前位置
    XmlElement positionX = xmlDoc.CreateElement("positionX");
    positionX.InnerText = player.transform.position.x + "";
    XmlElement positionY = xmlDoc.CreateElement("positionY");
    positionY.InnerText = player.transform.position.y + "";
    position.AppendChild(positionX);
    position.AppendChild(positionY);
    //把节点一层一层的添加至XMLDoc中 ,请仔细看它们之间的先后顺序,这将是生成XML文件的顺序
    root.AppendChild(currentHealth);
    root.AppendChild(maxHealth);
    root.AppendChild(curBulletCount);
    root.AppendChild(position);
    xmlDoc.AppendChild(root);
    //把XML文件保存至本地
    xmlDoc.Save(filePath);
}
最终保存结构如下

image.png

序列化

public void SaveGameByXml2()
{
        FileStream fileStream = new FileStream(filePath, FileMode.Create);
        StreamWriter sw = new StreamWriter(fileStream, new System.Text.UTF8Encoding(false));//编码utf8格式
        XmlSerializer x = new XmlSerializer(typeof(Save));
        Save save = CreateSave(); //创建存档
        x.Serialize(sw, save);
        sw.Close();
        fileStream.Close();
        Debug.Log(save.ToString());
}
最终保存结构如下

image.png

读取存档

读取存档也对照着保存,有两个对应方式

手动一个个读取结点

public void LoadGameByXml1()
{
    
    if (File.Exists(filePath))
    {


        XmlDocument xmlDoc = new XmlDocument();
        //根据路径将XML读取出来
        xmlDoc.Load(filePath);
        XmlElement playerXml = (XmlElement)xmlDoc.SelectSingleNode("player");
        XmlNode currentHealth = playerXml.SelectSingleNode("currentHealth");
        XmlNode maxHealth = playerXml.SelectSingleNode("maxHealth");
        XmlNode curBulletCount = playerXml.SelectSingleNode("curBulletCount");
        XmlNode position = playerXml.SelectSingleNode("position");
        XmlNode positionX = position.SelectSingleNode("positionX");
        XmlNode positionY = position.SelectSingleNode("positionY");

        Vector2 temp = new Vector2(float.Parse( positionX.InnerText), float.Parse(positionY.InnerText));

        Save save = new Save(int.Parse( currentHealth.InnerText), int.Parse(maxHealth.InnerText), int.Parse(curBulletCount.InnerText), temp);

        Debug.Log(save.ToString());

        player.MyCurrentHealth = int.Parse(currentHealth.InnerText);
        player.MyMaxHealth = int.Parse(maxHealth.InnerText);
        player.curBulletCount = int.Parse(curBulletCount.InnerText);
        player.transform.position = temp;
        TipManager.instance.ShowTip("读取成功");
        MenuManager.instance.ResumeGame();//继续游戏

    }
    else
    {
        TipManager.instance.ShowTip("没存档啊,读个锤子");
    }
}

反序列化

public void LoadGameByXml2()
{

    if (File.Exists(filePath))
    {
        FileStream fileStream = new FileStream(filePath, FileMode.Open,FileAccess.Read);
        StreamReader sr = new StreamReader(fileStream, true);//跳过xml文件开通两个字节BOM
        XmlSerializer x = new XmlSerializer(typeof(Save));
        Save save = new Save();
        save = (Save)x.Deserialize(sr);
        sr.Close();
        fileStream.Close();
        Debug.Log(save.ToString());
        LoadSave(save);//加载存档

        TipManager.instance.ShowTip("读取成功");
        MenuManager.instance.ResumeGame();//继续游戏

    }
    else
    {
        TipManager.instance.ShowTip("没存档啊,读个锤子");
    }
}

JSON

通过JSON格式来保存存档,具有一定的可读性,数据格式简单,易于读写

保存存档

    public void SaveByJson()
    {
        print("开始存档,存档位置:"+ Application.dataPath + "/Save/DataByJson.txt");
        Save save = CreateSave();//创建存档类对象
        print("转化前:" + save.ToString());

        //将对象save转化为json字符串,注意,只有Save里面为public才会被序列化到,private不会
        string jsonString = JsonUtility.ToJson(save);
        print("转化化后:"+ jsonString);
        //上面说了Json是string类型,所以命名string
        StreamWriter sw = new StreamWriter(Application.dataPath + "/Save/DataByJson.txt");
        sw.Write(jsonString); //将json字符串写入流参数   
        sw.Close();
    }
    

读取存档

 public void LoadByJson()
{
    if (File.Exists(Application.dataPath + "/Save/DataByJson.txt"))
    //判断文件是否创建
    {
        StreamReader sr = new StreamReader(Application.dataPath + "/Save/DataByJson.txt");
        //从流中读取字符串
        string jsonString = sr.ReadToEnd();
        sr.Close();
        //转化string为Save对象
        Save save = JsonUtility.FromJson<Save>(jsonString);
        //TODO 读取save对象
        
    }
    else
    {
        print("没存档啊,读个锤子");
    }
}

存档位置

image.png

存档查看

image.png

结束语

关于Unity的存档、读档方法就介绍到这里了,在开发中还需要结合实际,使用合适的方法来做,还有一种二进制存储方法,本文章未介绍。