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

相关推荐
狂人开飞机7 小时前
14、游戏手感与视觉打磨
游戏·游戏引擎·godot
狂人开飞机12 小时前
15、音频系统
游戏引擎·godot
狂人开飞机13 小时前
16、粒子线条与视觉特效
游戏引擎·godot
天天喝旺仔14 小时前
Elasticsearch 全文检索实战:Lucene 倒排索引与 NoSQL 文档检索落地
elasticsearch·搜索引擎·全文检索·nosql·lucene
心前阳光1 天前
Unity发布PC软件图标不能改变
unity·游戏引擎
an86950011 天前
unity如何在visual studio中打断点debug
unity·游戏引擎·visual studio
xcLeigh1 天前
Unity基础:使用Transform控制物体移动——Translate与position详解
unity·游戏引擎
FairGuard手游加固1 天前
Unity小游戏加密:global-metadata.dat与AssetBundle保护
游戏·unity·游戏引擎
狂人开飞机1 天前
11、UI系统
游戏引擎·godot
真鬼1231 天前
【Unity AI】Untiy链接cursor与MCP
unity·游戏引擎