Unity 持久化数据

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • [什么是 Unity 持久化数据?](#什么是 Unity 持久化数据?)
    • 1.游戏内存数据的特性
    • [2. 持久化数据定义](#2. 持久化数据定义)
    • [3. 哪些数据需要做持久化?](#3. 哪些数据需要做持久化?)
    • [4. Unity 持久化存储通用规范](#4. Unity 持久化存储通用规范)
  • [一、PlayerPrefs(Unity 原生自带这套存储)](#一、PlayerPrefs(Unity 原生自带这套存储))
  • [二、Json 序列化存档](#二、Json 序列化存档)
  • [三、XML 序列化存档](#三、XML 序列化存档)
  • [四、二进制 Binary 存档](#四、二进制 Binary 存档)
  • 避坑

前言

本章介绍Unity 数据持久化全解:PlayerPrefs、XML、JSON、二进制四种方案。

游戏开发中数据持久化是刚需:玩家等级、金币、皮肤、关卡进度、设置参数都需要本地保存,重启游戏不丢失。

Unity 主流 4 种本地持久化方案:

PlayerPrefs (Unity 原生轻量键值对)

Json (可读性高,开发调试首选)

XML (老式配置存档,复杂层级文本存储)

二进制 Binary (加密、体积最小,大型游戏商用首选)

本文会逐一讲解原理、完整代码、适用场景、优缺点对比,全部代码可直接复制到 Unity 运行,文末附带选型对照表。

提示:以下是本篇文章正文内容,下面案例可供参考

什么是 Unity 持久化数据?

1.游戏内存数据的特性

Unity 游戏运行时,我们创建的角色、金币、关卡进度、音效设置等所有变量、类对象,全部存储在程序内存(RAM) 中。

内存数据有致命特点:

游戏关闭、闪退、手机切后台杀进程后,内存会直接清空,所有数据全部丢失;

切换场景时,不做特殊处理的临时数据也会被销毁。

举个例子:你游戏里通关拿到 1000 金币,直接关闭游戏再重新打开,金币归零 ------ 这就是非持久化临时内存数据。

2. 持久化数据定义

Unity 持久化数据,就是将游戏运行中产生的玩家数据,从内存写入本地磁盘文件 / 系统注册表,实现游戏关闭后数据不丢失,下次启动游戏可以重新读取还原的存储技术。

简单说:把数据存在电脑 / 手机硬盘里,而不是只存在游戏运行内存里,做到重启游戏数据保留。

3. 哪些数据需要做持久化?

玩家存档类:等级、金币、钻石、装备、已解锁关卡、角色皮肤;

游戏设置类:背景音乐音量、画质等级、帧率开关、触控灵敏度;

进度缓存类:剧情阅读进度、新手引导完成状态、本地离线记录;

配置缓存:离线本地活动数据、本地排行榜。

4. Unity 持久化存储通用规范

所有本地存档,官方统一推荐存储在 Application.persistentDataPath 路径:

Windows:C 盘用户目录下的隐藏文件夹,不会和游戏安装包混在一起;

Android/IOS:系统分配专属沙盒目录,拥有完整读写权限,游戏更新不会删除;

禁止使用StreamingAssets(只读,无法写入)、Application.dataPath(打包后无写入权限)存储存档。

一、PlayerPrefs(Unity 原生自带这套存储)

1.诞生初衷:解决超轻量简单数值存储需求

Unity 早期就内置PlayerPrefs,专门用来存储少量、简单的基础类型数据(int/string/float),不用引入任何文件流、序列化代码,零学习成本。

2.选用它的理由

无需创建文件、不需要处理路径、不用引入 IO 命名空间,几行代码就能读写音量、开关这类简单配置;

跨平台底层封装完成,Windows、安卓、IOS、主机全部兼容,不用适配不同平台文件系统;

读写速度极快,适合游戏启动瞬间快速读取设置。

3.不适合复杂存档的原因

底层是系统注册表 / Plist 键值数据库,不支持数组、自定义玩家类、复杂集合,大量数据存储会臃肿混乱,且明文极易被修改。

代码实列:

csharp 复制代码
using UnityEngine;

/// <summary>
/// 轻量键值对存档工具,存储游戏设置、少量数值
/// </summary>
public static class PlayerPrefsManager
{
    #region 保存
    public static void SaveInt(string key, int value)
    {
        PlayerPrefs.SetInt(key, value);
        PlayerPrefs.Save();
    }

    public static void SaveString(string key, string value)
    {
        PlayerPrefs.SetString(key, value);
        PlayerPrefs.Save();
    }

    public static void SaveFloat(string key, float value)
    {
        PlayerPrefs.SetFloat(key, value);
        PlayerPrefs.Save();
    }
    #endregion

    #region 读取
    public static int GetInt(string key, int defaultValue = 0)
    {
        return PlayerPrefs.GetInt(key, defaultValue);
    }

    public static string GetString(string key, string defaultValue = "")
    {
        return PlayerPrefs.GetString(key, defaultValue);
    }

    public static float GetFloat(string key, float defaultValue = 0f)
    {
        return PlayerPrefs.GetFloat(key, defaultValue);
    }
    #endregion

    // 清空全部本地数据
    public static void DeleteAllData()
    {
        PlayerPrefs.DeleteAll();
        PlayerPrefs.Save();
    }

    // 删除单个key数据
    public static void DeleteSingleKey(string key)
    {
        PlayerPrefs.DeleteKey(key);
        PlayerPrefs.Save();
    }
}

二、Json 序列化存档

1.诞生初衷:平衡开发效率与数据可读性

Json 是全球通用轻量文本格式,Unity 内置JsonUtility开箱即用,不需要额外第三方插件,完美弥补 PlayerPrefs 无法存储自定义类、数组的短板。

2.选用它的理由

存档文件是纯文本,记事本就能打开查看、调试,开发阶段排查存档 BUG 极其方便;

支持完整自定义实体类、数组、列表,一套实体类就能存玩家所有存档信息;

格式通用,可以和后端服务器互通 Json 数据,本地存档和网络接口数据结构统一;

代码简洁,泛型工具类一次封装,全项目通用。

3.短板存在的原因

明文文本无加密,玩家手动修改文件就能篡改金币、等级;文本格式占用体积比二进制更大,海量数据读写性能一般。

代码示例:

csharp 复制代码
using UnityEngine;
using System.IO;

/// <summary>
/// Json文本序列化存档工具,调试友好,独立游戏首选
/// </summary>
public static class JsonSaveManager
{
    // 拼接完整存档路径
    private static string GetFullPath(string fileName)
    {
        return Path.Combine(Application.persistentDataPath, $"{fileName}.json");
    }

    /// <summary>
    /// 保存自定义类对象到本地Json文件
    /// </summary>
    /// <typeparam name="T">数据实体类(必须加[Serializable])</typeparam>
    /// <param name="data">存档数据对象</param>
    /// <param name="fileName">存档文件名</param>
    public static void SaveData<T>(T data, string fileName = "SaveData")
    {
        string jsonText = JsonUtility.ToJson(data, prettyPrint: true);
        string savePath = GetFullPath(fileName);
        File.WriteAllText(savePath, jsonText);
        Debug.Log($"Json存档路径:{savePath}");
    }

    /// <summary>
    /// 读取Json文件还原数据对象
    /// </summary>
    public static T LoadData<T>(string fileName = "SaveData")
    {
        string path = GetFullPath(fileName);
        if (!File.Exists(path))
        {
            Debug.LogWarning($"Json文件不存在:{path}");
            return default;
        }

        string json = File.ReadAllText(path);
        T data = JsonUtility.FromJson<T>(json);
        return data;
    }

    /// <summary>
    /// 删除本地存档文件
    /// </summary>
    public static void DeleteSave(string fileName = "SaveData")
    {
        string path = GetFullPath(fileName);
        if (File.Exists(path))
        {
            File.Delete(path);
        }
    }
}

三、XML 序列化存档

1.诞生初衷:早期标准结构化文本存储方案

Json 普及前,XML 是行业通用分层数据格式,C# 原生提供完整 XML 序列化库,适合多层级嵌套的复杂配置文件,早年端游、编辑器配置全部基于 XML。

2.选用它的理由

严格层级标签,对树状、多层级数据描述能力更强;

老旧 Unity 项目、老后端接口只支持 XML 交互,只能选用该方案;

自带标准校验语法,配置文件格式错误能精准捕获。

3.现在逐步被 Json 替代的原因

冗余闭合标签多,相同数据下文件体积比 Json 大 30% 以上;代码书写繁琐,可读性差,现代游戏开发基本不再作为首选存档方案。

代码示例:

csharp 复制代码
using UnityEngine;
using System.IO;
using System.Xml.Serialization;

/// <summary>
/// XML序列化存档工具,老旧项目/多层级配置使用
/// </summary>
public static class XmlSaveManager
{
    private static string GetFullPath(string fileName)
    {
        return Path.Combine(Application.persistentDataPath, $"{fileName}.xml");
    }

    public static void SaveData<T>(T data, string fileName = "SaveData")
    {
        string path = GetFullPath(fileName);
        using (FileStream fs = new FileStream(path, FileMode.Create))
        {
            XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
            xmlSerializer.Serialize(fs, data);
        }
        Debug.Log($"XML存档路径:{path}");
    }

    public static T LoadData<T>(string fileName = "SaveData")
    {
        string path = GetFullPath(fileName);
        if (!File.Exists(path))
        {
            Debug.LogWarning($"XML文件不存在:{path}");
            return default;
        }

        using (FileStream fs = new FileStream(path, FileMode.Open))
        {
            XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
            return (T)xmlSerializer.Deserialize(fs);
        }
    }

    public static void DeleteSave(string fileName = "SaveData")
    {
        string path = GetFullPath(fileName);
        if (File.Exists(path))
        {
            File.Delete(path);
        }
    }
}

四、二进制 Binary 存档

1.诞生初衷:解决明文存档篡改、大容量数据、性能三大商用痛点

Json、XML 都是明文文本,玩家轻易就能修改存档数值破坏游戏平衡;二进制直接将对象转为字节流存储,文件打开全是乱码,天然防篡改,同时文件体积最小、读写性能最强。
2.选用它的理由

安全:二进制字节文件无明文,普通玩家无法直接修改存档,配合 MD5/AES 加密后几乎无法破解;

性能:字节流读写速度远高于文本读写,存储海量角色、关卡数据不会卡顿;

体积优势:相同玩家数据,二进制文件体积只有 Json 的 1/3,节省手机本地存储空间;

完整支持所有 C# 复杂类型,字典、多维数组、嵌套类都能序列化。

3.开发劣势成因

文件无法可视化查看,调试存档数据极麻烦;原生BinaryFormatter存在版本兼容问题,更新游戏新增字段后旧存档会读取失败,需要额外做版本兼容逻辑。

代码示例:

csharp 复制代码
using UnityEngine;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

/// <summary>
/// 二进制序列化存档工具,商用游戏防篡改,体积最小
/// </summary>
public static class BinarySaveManager
{
    private static string GetFullPath(string fileName)
    {
        return Path.Combine(Application.persistentDataPath, $"{fileName}.dat");
    }

    public static void SaveData<T>(T data, string fileName = "SaveData")
    {
        string path = GetFullPath(fileName);
        BinaryFormatter bf = new BinaryFormatter();
        using (FileStream fs = new FileStream(path, FileMode.Create))
        {
            bf.Serialize(fs, data);
        }
        Debug.Log($"二进制存档路径:{path}");
    }

    public static T LoadData<T>(string fileName = "SaveData")
    {
        string path = GetFullPath(fileName);
        if (!File.Exists(path))
        {
            Debug.LogWarning($"二进制文件不存在:{path}");
            return default;
        }

        BinaryFormatter bf = new BinaryFormatter();
        using (FileStream fs = new FileStream(path, FileMode.Open))
        {
            return (T)bf.Deserialize(fs);
        }
    }

    public static void DeleteSave(string fileName = "SaveData")
    {
        string path = GetFullPath(fileName);
        if (File.Exists(path))
        {
            File.Delete(path);
        }
    }
}

避坑

1.Json / 二进制序列化实体类必须加 Serializable,否则读取数据全部为空

2.不要使用 Application.dataPath 做存档路径,打包后无写入权限

3.PlayerPrefs 不支持数组、自定义类,复杂存档不要选用

4.BinaryFormatter 高版本.NET 存在安全警告,商用项目推荐 Protobuf 替代

5.Xml 序列化不支持私有字段,所有存档变量必须写 public

相关推荐
玖玥拾6 小时前
C# 语言进阶(十四)Unity UI界面开发 + 网络消息联动
服务器·开发语言·网络·ui·unity·c#·游戏引擎
郝学胜-神的一滴10 小时前
[简化版 GAMES 101] 计算机图形学 17:纹理技术从基础原理到多场景实战应用
c++·unity·游戏引擎·图形渲染·three.js·opengl·unreal
_ZHOURUI_H_1 天前
MyFramework: 同样是 Unity 游戏开发框架,和 HTFramework 的设计取向有什么不同?(下)
unity·游戏引擎·游戏开发·游戏ui·手游开发
丁小未2 天前
基于MVVM框架的XUUI 扩展的UI管理系统教程
unity·mvvm·ui框架·xuui·ui管理器
丁小未3 天前
基于MVVM框架的XUUI HelloWorld 新手教程
unity·性能优化·c#·游戏引擎
丁小未3 天前
基于MVVM框架的XUUI MoreComplex案例
unity·c#
EQ-雪梨蛋花汤3 天前
【Unity笔记】VR 一体机画面锯齿、模型边缘闪烁、接缝抖动排查:MSAA、Mipmap、Render Scale、Z-Fighting 全流程记录
笔记·unity·vr
松树戈3 天前
【Godot4精进之路】03~Godot编辑器常用界面介绍
编辑器·游戏引擎·godot
YigAin3 天前
Unity Spine 资源出现白边的解决办法
unity·游戏引擎·spine