一、PlayerPrefs概述

1.1 什么是PlayerPrefs

PlayerPrefs 是一个用于存储和读取玩家偏好数据的简单键值对存储系统,主要用于保存一些轻量级的持久化数据(如玩家设置、游戏进度、高分记录等),这些数据会在游戏退出后仍然保留,下次启动时可以继续读取。

1.2 PlayerPrefs数据存储位置

  • 在Mac OS X上:PlayerPrefs数据存储在~/Library/PlayerPrefs文件夹,名为unity.[company
    name].[product name].plist,这里company和product名是在project Setting中设置的。
  • 在Windows上:PlayerPrefs数据存储在注册的HKCU\Software[company name][product
    name]键下,这里company和product名是在project setting中设置的。
  • 在Android上:**PlayerPrefs数据存储(持久化)在设备上,数据保存在SharedPreferences中。

以Windows为例
Win+R输入regedit打开注册表

在这里插入图片描述
在这里插入图片描述
以上注册表中就是游戏存储的数据位置

1.3 PlayerPrefs的使用方法

存数据

// 存储整数(如分数)
PlayerPrefs.SetInt("HighScore", 999);
// 存储浮点数(如音量)
PlayerPrefs.SetFloat("MusicVolume", 0.75f);
// 存储字符串(如玩家名称)
PlayerPrefs.SetString("PlayerName", "张三");
// 手动保存到磁盘(建议在关键节点调用,如玩家存档时)
PlayerPrefs.Save();

读数据

// 读取整数,若"HighScore"不存在则返回默认值0
int highScore = PlayerPrefs.GetInt("HighScore", 0);
// 读取浮点数,默认值0.5
float musicVolume = PlayerPrefs.GetFloat("MusicVolume", 0.5f);
// 读取字符串,默认值"游客"
string playerName = PlayerPrefs.GetString("PlayerName", "游客");
// 打印结果
Debug.Log($"最高分:{highScore},音量:{musicVolume},玩家名:{playerName}");

检查删除

// 检查键是否存在
if (PlayerPrefs.HasKey("HighScore"))
{
    Debug.Log("存在最高分数据");
}
// 删除指定键
PlayerPrefs.DeleteKey("PlayerName");
// 清空所有数据(谨慎使用!)
PlayerPrefs.DeleteAll();

实际场景示例

using UnityEngine;

public class GameSettings : MonoBehaviour
{
    // 保存设置
    public void SaveSettings(int qualityLevel, float volume, string playerName)
    {
        PlayerPrefs.SetInt("QualityLevel", qualityLevel);
        PlayerPrefs.SetFloat("Volume", volume);
        PlayerPrefs.SetString("PlayerName", playerName);
        PlayerPrefs.Save(); // 立即保存
        Debug.Log("设置已保存");
    }

    // 加载设置
    public void LoadSettings()
    {
        int quality = PlayerPrefs.GetInt("QualityLevel", 2); // 默认画质等级2
        float volume = PlayerPrefs.GetFloat("Volume", 0.8f); // 默认音量0.8
        string name = PlayerPrefs.GetString("PlayerName", "默认玩家");
        
        // 应用设置(例如设置画质、音量)
        QualitySettings.SetQualityLevel(quality);
        AudioListener.volume = volume;
        Debug.Log($"加载设置:画质{quality},音量{volume},玩家名{name}");
    }
}

仅支持 int、float、string 三种基础类型,无法直接存储复杂结构(如类、数组、字典等)。若要存储复杂数据,需手动序列化(如转 JSON 字符串后用 SetString 存储),操作繁琐且易出错。

1.4 优缺点

优点

  • 用法简单,通过 SetInt/SetFloat/SetString 等静态方法直接读写,无需处理文件细节;
  • 自动跨会话保存,游戏退出后数据不丢失;
  • 跨平台 API 一致,无需针对不同设备编写适配代码

缺点

  • 仅支持 int、float、string 三种基础类型,无法直接存储复杂结构;
  • 数据以明文存储,易被玩家篡改,安全性低;
  • 存储路径固定不可控,不适合大量数据或敏感信息(如付费状态)。

总结
PlayerPrefs 适合存储简单、非敏感的轻量级数据(如音量设置、画质等级、玩家昵称),其优势在于易用性和跨平台一致性;但对于复杂结构数据、敏感信息或大量数据,则存在明显短板,此时应选择 JSON/XML 结合文件 IO、数据库等更灵活的方案。

1.5 项目中使用PlayerPrefs代码示例

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AchiveManager : MonoBehaviour
{
    public GameObject[] lockCharacter;   //锁定状态的角色UI/模型(未解锁时显示)

    public GameObject[] unlockCharacter; //解锁状态的角色UI/模型(已解锁时显示)

    public GameObject uiNotice;         //角色解锁提示UI(父物体)
    enum Achive { unlockPotato,unlockBean } //定义角色类型:解锁土豆、解锁豆子

    Achive[] achives;             //存储所有成就的数组(用于遍历检测)

    WaitForSecondsRealtime wait;  //用于协程的等待时间(实时,不受时间缩放影响)
    private void Awake()
    {
        achives = (Achive[])Enum.GetValues(typeof(Achive));  //获取所有成就枚举值(转为数组)

        wait = new WaitForSecondsRealtime(5);    // 初始化等待时间(5秒)  

        if (!PlayerPrefs.HasKey("MyData"))   //若首次运行(无存档),初始化数据
            Init();
    }

    // 初始化存档:标记已初始化,并将所有角色设为未解锁(0=未解锁)
    void Init()
    {
        PlayerPrefs.SetInt("MyData", 1);  // 标记已初始化存档
        foreach (Achive achive in achives)
        {
            PlayerPrefs.SetInt(achive.ToString(),0);// 每个角色初始状态为0(未解锁)
        }
       
    }
  
    private void Start()
    {
        UnlockCharacter();
    }


    // 更新角色UI显示:根据存档判断是否解锁,显示对应状态的UI
    //未解锁的角色
    void UnlockCharacter()
    {
        for (int i = 0; i < lockCharacter.Length; i++) 
        {
            string achiveName = achives[i].ToString();// 获取角色名称(如"unlockPotato")

            bool isUnlock = PlayerPrefs.GetInt(achiveName) == 1;  // 1=已解锁,0=未解锁

            lockCharacter[i].SetActive(!isUnlock); // 未解锁时显示锁定UI

            unlockCharacter[i].SetActive(isUnlock);// 已解锁时显示解锁UI
        }
    }

    private void LateUpdate()
    {
         // 每帧遍历所有角色,检测是否满足解锁条件
        foreach(Achive achive in achives)
        {
            CheckAchive(achive);
        }
    }

    // 检测单个角色的解锁条件
    void CheckAchive(Achive achive)  //unlockPotato,unlockBean
    {
        bool isAchive = false;// 是否满足解锁条件

        // 根据角色类型判断解锁条件
        switch (achive)
        {
            // 土豆角色:击杀数 >= 10 时解锁(GameManager是全局管理器,存储击杀数)
            case Achive.unlockPotato:
                isAchive = GameManager.instance.kill >= 10;
                break;

            // 豆子角色:游戏时间达到最大时长时解锁(如存活到时间结束)
            case Achive.unlockBean:
                isAchive = GameManager.instance.gameTime == GameManager.instance.maxGameTime;
                break;
        }

        // 若满足条件且尚未解锁(存档中为0)
        if (isAchive && PlayerPrefs.GetInt(achive.ToString()) == 0)
        {
            PlayerPrefs.SetInt(achive.ToString(), 1);

            // 显示对应角色的解锁提示(激活UI子物体)
            for (int i = 0; i < uiNotice.transform.childCount; i++)
            {
                bool isActive = i == (int)achive;
                uiNotice.transform.GetChild(i).gameObject.SetActive(isActive);
            }

            StartCoroutine(NoticRoutine());
        }
    }

    //UI显示角色解锁
    IEnumerator NoticRoutine()
    {
        uiNotice.SetActive(true);

        AudioManager.instance.PlaySfx(AudioManager.Sfx.LevelUp);
        yield return wait;
        uiNotice.SetActive(false);
    }
}

项目中使用PlayerPrefs解锁成就,下面我将修改成使用Json方法

二、Json概述

2.1 什么是Json

JSON(JavaScript Object Notation,JavaScript 对象表示法)是一种轻量级的数据交换格式,以文本形式 语法为基础,采用键值对和数组 结构(对象、数组)来组织数据,具有易读易写、跨平台、跨语言兼容的特点。

2.2 Json数据存储位置

JSON 本身是一种数据格式(文本字符串),并不像 PlayerPrefs 那样有固定的存储位置。它的存储位置完全由开发者通过文件操作手动指定,通常根据平台特性和需求选择合适的路径。

2.3 Unity中如何解析Json文件(JsonUtility和LitJson)

在 Unity 中使用 JSON 存储和读取数据,核心是序列化(对象转 JSON 字符串)和反序列化(JSON 字符串转对象),配合文件 IO 操作实现持久化。

2.3.1 JsonUtility

Unity 自带 JsonUtility 类,适合处理简单结构化数据(支持类、结构体、数组,但不支持字典、私有字段等)。

定义可序列化的数据类
首先创建一个存储数据的类,必须添加 [System.Serializable] 特性,否则无法被序列化

[System.Serializable] // 关键:标记为可序列化
public class PlayerData
{
    public string playerName; // 玩家名
    public int level; // 等级
    public float health; // 生命值
    public bool isVip; // 是否为VIP
    public string[] items; // 物品列表(支持数组)
}

序列化(对象 → JSON 字符串)
将 PlayerData 对象转换为 JSON 文本

// 创建一个玩家数据对象
PlayerData data = new PlayerData();
data.playerName = "张三";
data.level = 15;
data.health = 80.5f;
data.isVip = false;
data.items = new string[] { "剑", " potion" };
// 序列化为 JSON 字符串(第二个参数为 true 时格式化输出,便于阅读)
string jsonString = JsonUtility.ToJson(data, true);
// 输出结果(格式化后):
// {
//   "playerName": "张三",
//   "level": 15,
//   "health": 80.5,
//   "isVip": false,
//   "items": [
//     "剑",
//     " potion"
//   ]
// }

保存 JSON 到本地文件(持久化)
通过文件 IO 操作(System.IO.File)将 JSON 字符串写入本地路径(推荐 Application.persistentDataPath,跨平台可读写)

using System.IO;
using UnityEngine;

public class JsonSaver : MonoBehaviour
{
    // 保存数据到文件
    public void SaveToFile(PlayerData data)
    {
        // 1. 序列化对象为 JSON 字符串
        string json = JsonUtility.ToJson(data, true);

        // 2. 定义保存路径(跨平台路径)
        string filePath = Path.Combine(Application.persistentDataPath, "player_save.json");

        // 3. 写入文件(覆盖式写入)
        File.WriteAllText(filePath, json);

        Debug.Log("JSON 已保存到:" + filePath);
    }
}

从文件读取并反序列化(JSON 字符串 → 对象)

// 从文件加载数据
public PlayerData LoadFromFile()
{
    // 1. 定义文件路径(与保存路径一致)
    string filePath = Path.Combine(Application.persistentDataPath, "player_save.json");

    // 2. 检查文件是否存在
    if (File.Exists(filePath))
    {
        // 3. 读取 JSON 字符串
        string json = File.ReadAllText(filePath);

        // 4. 反序列化为 PlayerData 对象
        PlayerData data = JsonUtility.FromJson<PlayerData>(json);

        Debug.Log("加载成功:玩家 " + data.playerName + ",等级 " + data.level);
        return data;
    }
    else
    {
        Debug.LogError("文件不存在!返回默认数据");
        return new PlayerData(); // 返回空对象或默认数据
    }
}

2.3.2 LitJson
下载路径:https://litjson.net
在这里插入图片描述
在这里插入图片描述
将scr文件夹中Litjson文件导入到Unity即可使用Litjson

区别

序列化简单对象

// 数据类
[System.Serializable]
public class PlayerData {
    public string name;
    public int level;
}

// JsonUtility 用法
PlayerData data = new PlayerData { name = "张三", level = 5 };
string json = JsonUtility.ToJson(data); // 无需额外引用

// LitJSON 用法(需导入 LitJSON.dll 并添加 using LitJSON;)
string json = JsonMapper.ToJson(data);

处理字典(复杂类型)

// 字典类型数据
var stats = new Dictionary<string, int> {
    { "力量", 10 },
    { "敏捷", 8 }
};

// JsonUtility:不支持字典,直接序列化会失败
// string json = JsonUtility.ToJson(stats); // 报错

// LitJSON:完美支持字典
string json = JsonMapper.ToJson(stats); 
// 输出:{"力量":10,"敏捷":8}

序列化 Unity 内置类型(如 Vector3)

Vector3 pos = new Vector3(1, 2, 3);

// JsonUtility:原生支持
string json = JsonUtility.ToJson(pos); 
// 输出:{"x":1.0,"y":2.0,"z":3.0}

// LitJSON:不原生支持,需手动处理
// 方法1:转换为自定义类
public class Vector3Data { public float x; public float y; public float z; }
Vector3Data posData = new Vector3Data { x = pos.x, y = pos.y, z = pos.z };
string json = JsonMapper.ToJson(posData);

// 方法2:注册自定义转换器(复杂但一劳永逸)

2.4 项目中使用JsonUtility代码示例

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Events;

public class AchiveManager : MonoBehaviour
{
    public GameObject[] lockCharacter;   // 锁定状态的角色UI/模型
    public GameObject[] unlockCharacter; // 解锁状态的角色UI/模型
    public GameObject uiNotice;         // 角色解锁提示UI(父物体)

    // 定义角色类型枚举
    enum Achive { unlockPotato, unlockBean }

    Achive[] achives;             // 存储所有成就的数组
    WaitForSecondsRealtime wait;  // 协程等待时间
    private AchiveData data;      // 用于序列化的解锁数据对象
    private string savePath;      // JSON保存路径

    private void Awake()
    {
        achives = (Achive[])Enum.GetValues(typeof(Achive));

        //协程时间
        wait = new WaitForSecondsRealtime(5);
        // 初始化JSON保存路径(持久化目录下的achive_data.json)
        savePath = Path.Combine(Application.persistentDataPath, "achive_data.json");

        Debug.Log(Application.persistentDataPath);
        // 首次运行时初始化数据,否则加载已有数据
        if (!File.Exists(savePath))
            InitData();
        else
            LoadData();
    }

    // 初始化解锁数据(所有角色默认未解锁)
    void InitData()
    {
        data = new AchiveData();
        // 初始化所有成就为未解锁(0=未解锁,1=已解锁)
        data.unlockStates = new int[achives.Length];
        for (int i = 0; i < data.unlockStates.Length; i++)
        {
            data.unlockStates[i] = 0;
        }
        SaveData(); // 保存初始化数据
    }

    // 保存数据到JSON文件
    void SaveData()
    {
        // 序列化数据为JSON字符串
        string json = JsonUtility.ToJson(data);
        // 写入文件
        File.WriteAllText(savePath, json);
    }

    // 从JSON文件加载数据
    void LoadData()
    {
        // 读取JSON字符串
        string json = File.ReadAllText(savePath);
        // 反序列化为数据对象
        data = JsonUtility.FromJson<AchiveData>(json);
    }

    private void Start()
    {
        UpdateCharacterUI(); // 初始化角色UI显示
    }

    // 更新角色UI显示(根据解锁状态)
    void UpdateCharacterUI()
    {
        for (int i = 0; i < lockCharacter.Length; i++)
        {
            // 从数据中获取解锁状态(1=已解锁,0=未解锁)
            bool isUnlock = data.unlockStates[i] == 1;
            lockCharacter[i].SetActive(!isUnlock);   // 未解锁显示锁定UI
            unlockCharacter[i].SetActive(isUnlock);  // 已解锁显示解锁UI
        }
    }

    private void LateUpdate()
    {
        // 每帧检测所有成就解锁条件
        foreach (Achive achive in achives)
        {
            CheckAchive(achive);
        }
    }

    // 检测单个角色的解锁条件
    void CheckAchive(Achive achive)
    {
        bool isAchive = false;
        int achiveIndex = (int)achive; // 转换为数组索引

        // 判断解锁条件(与原逻辑一致)
        switch (achive)
        {
            case Achive.unlockPotato:
                isAchive = GameManager.instance.kill >= 10;
                break;
            case Achive.unlockBean:
                isAchive = GameManager.instance.gameTime == GameManager.instance.maxGameTime;
                break;
        }

        // 若满足条件且尚未解锁
        if (isAchive && data.unlockStates[achiveIndex] == 0)
        {
            data.unlockStates[achiveIndex] = 1; // 标记为已解锁
            SaveData(); // 立即保存数据

            // 显示解锁提示UI
            for (int i = 0; i < uiNotice.transform.childCount; i++)
            {
                uiNotice.transform.GetChild(i).gameObject.SetActive(i == achiveIndex);
            }

            StartCoroutine(NoticRoutine());
            UpdateCharacterUI(); // 刷新UI显示
        }
    }

    // 解锁提示UI显示协程
    IEnumerator NoticRoutine()
    {
        uiNotice.SetActive(true);
        AudioManager.instance.PlaySfx(AudioManager.Sfx.LevelUp);
        yield return wait;
        uiNotice.SetActive(false);
    }

    // 用于JSON序列化的数据类(必须标记[Serializable])
    [Serializable]
    private class AchiveData
    {
        public int[] unlockStates; // 存储每个角色的解锁状态(与Achive枚举顺序对应)
    }
}

2.5 项目中使用LitJson代码示例

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using LitJSON; // 引入 LitJSON 命名空间

public class AchiveManager : MonoBehaviour
{
    public GameObject[] lockCharacter;   // 锁定状态的角色UI/模型
    public GameObject[] unlockCharacter; // 解锁状态的角色UI/模型
    public GameObject uiNotice;         // 角色解锁提示UI(父物体)

    // 定义角色类型枚举
    enum Achive { unlockPotato, unlockBean }

    Achive[] achives;             // 存储所有成就的数组
    WaitForSecondsRealtime wait;  // 协程等待时间
    private AchiveData data;      // 用于序列化的解锁数据对象
    private string savePath;      // JSON保存路径

    private void Awake()
    {
        achives = (Achive[])Enum.GetValues(typeof(Achive));

        // 协程时间
        wait = new WaitForSecondsRealtime(5);
        // 初始化JSON保存路径(持久化目录下的achive_data.json)
        savePath = Path.Combine(Application.persistentDataPath, "achive_data.json");

        Debug.Log(Application.persistentDataPath);
        // 首次运行时初始化数据,否则加载已有数据
        if (!File.Exists(savePath))
            InitData();
        else
            LoadData();
    }

    // 初始化解锁数据(所有角色默认未解锁)
    void InitData()
    {
        data = new AchiveData();
        // 初始化所有成就为未解锁(0=未解锁,1=已解锁)
        data.unlockStates = new int[achives.Length];
        for (int i = 0; i < data.unlockStates.Length; i++)
        {
            data.unlockStates[i] = 0;
        }
        SaveData(); // 保存初始化数据
    }

    // 保存数据到JSON文件(LitJSON 版本)
    void SaveData()
    {
        // 序列化数据为JSON字符串(LitJSON 的 JsonMapper.ToJson)
        string json = JsonMapper.ToJson(data);
        // 写入文件
        File.WriteAllText(savePath, json);
    }

    // 从JSON文件加载数据(LitJSON 版本)
    void LoadData()
    {
        // 读取JSON字符串
        string json = File.ReadAllText(savePath);
        // 反序列化为数据对象(LitJSON 的 JsonMapper.ToObject)
        data = JsonMapper.ToObject<AchiveData>(json);
    }

    private void Start()
    {
        UpdateCharacterUI(); // 初始化角色UI显示
    }

    // 更新角色UI显示(根据解锁状态)
    void UpdateCharacterUI()
    {
        for (int i = 0; i < lockCharacter.Length; i++)
        {
            // 从数据中获取解锁状态(1=已解锁,0=未解锁)
            bool isUnlock = data.unlockStates[i] == 1;
            lockCharacter[i].SetActive(!isUnlock);   // 未解锁显示锁定UI
            unlockCharacter[i].SetActive(isUnlock);  // 已解锁显示解锁UI
        }
    }

    private void LateUpdate()
    {
        // 每帧检测所有成就解锁条件
        foreach (Achive achive in achives)
        {
            CheckAchive(achive);
        }
    }

    // 检测单个角色的解锁条件
    void CheckAchive(Achive achive)
    {
        bool isAchive = false;
        int achiveIndex = (int)achive; // 转换为数组索引

        // 判断解锁条件(与原逻辑一致)
        switch (achive)
        {
            case Achive.unlockPotato:
                isAchive = GameManager.instance.kill >= 10;
                break;
            case Achive.unlockBean:
                isAchive = GameManager.instance.gameTime == GameManager.instance.maxGameTime;
                break;
        }

        // 若满足条件且尚未解锁
        if (isAchive && data.unlockStates[achiveIndex] == 0)
        {
            data.unlockStates[achiveIndex] = 1; // 标记为已解锁
            SaveData(); // 立即保存数据

            // 显示解锁提示UI
            for (int i = 0; i < uiNotice.transform.childCount; i++)
            {
                uiNotice.transform.GetChild(i).gameObject.SetActive(i == achiveIndex);
            }

            StartCoroutine(NoticRoutine());
            UpdateCharacterUI(); // 刷新UI显示
        }
    }

    // 解锁提示UI显示协程
    IEnumerator NoticRoutine()
    {
        uiNotice.SetActive(true);
        AudioManager.instance.PlaySfx(AudioManager.Sfx.LevelUp);
        yield return wait;
        uiNotice.SetActive(false);
    }

    // 用于LitJSON序列化的数据类(无需[Serializable]特性)
    private class AchiveData
    {
        public int[] unlockStates; // 存储每个角色的解锁状态(与Achive枚举顺序对应)
    }
}

三、总结

在本章中主要介绍了PlayerPrefs和Json在Unity中的使用方法
在上一章中我还简单介绍过ScriptableObject使用方法
在这里插入图片描述

在这里插入图片描述

Logo

这里是一个专注于游戏开发的社区,我们致力于为广大游戏爱好者提供一个良好的学习和交流平台。我们的专区包含了各大流行引擎的技术博文,涵盖了从入门到进阶的各个阶段,无论你是初学者还是资深开发者,都能在这里找到适合自己的内容。除此之外,我们还会不定期举办游戏开发相关的活动,让大家更好地交流互动。加入我们,一起探索游戏开发的奥秘吧!

更多推荐