Unity数据存储(PlayPrefs和Json)
JSON(JavaScript Object Notation,JavaScript 对象表示法)是一种轻量级的数据交换格式,以文本形式 语法为基础,采用键值对和数组 结构(对象、数组)来组织数据,具有易读易写、跨平台、跨语言兼容的特点。在本章中主要介绍了PlayerPrefs和Json在Unity中的使用方法在上一章中我还简单介绍过ScriptableObject使用方法。
一、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使用方法

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



所有评论(0)