2、PlayerPrefs基础:原理、数据类型与核心API
好,咱们正式开始讲数据持久化。第一个要聊的,就是 PlayerPrefs。
说实话,这个 API 可能是 Unity 里最简单、最“无脑”的存档方式了。我刚入行那会儿,做第一个小游戏,存金币数量、存关卡进度,用的就是它。一行代码搞定,连数据库都不用学。
但简单归简单,它背后有些东西,你最好还是搞清楚。不然哪天线上项目出了 bug,你连怎么排查都不知道。
PlayerPrefs 的原理
PlayerPrefs 本质上是一个键值对存储系统。你给它一个“钥匙”(Key),再给它一个“值”(Value),它帮你存到本地硬盘上。
具体存到哪?不同平台不一样:
| 平台 | 存储位置 | 文件格式 |
|---|---|---|
| Windows | 注册表(HKCU\Software\公司名\产品名) | 二进制 |
| macOS | ~/Library/Preferences/unity.公司名.产品名.plist | plist |
| Android | /data/data/包名/shared_prefs/包名.xml | XML |
| iOS | NSUserDefaults(沙盒内) | plist |
| WebGL | 浏览器 IndexedDB | JSON |
嗯,这里要注意:PlayerPrefs 不是加密的。你存进去的数值,玩家用个文本编辑器或者手机文件管理器就能看到。所以千万别存密码、支付凭证这类敏感信息。我见过有人把微信支付密钥存进去,结果被玩家扒出来刷了十几万的道具……那场面,惨不忍睹。
支持的数据类型
PlayerPrefs 支持的数据类型非常有限,就三种:
- int —— 整数,比如金币数量、关卡编号
- float —— 浮点数,比如音量大小、玩家坐标
- string —— 字符串,比如玩家昵称、存档时间
没了?对,就这三种。
你可能会问:“那我存个 bool 值怎么办?”
我个人的习惯是用 int 代替,0 表示 false,1 表示 true。或者用 string 存 "true"/"false",然后自己解析。虽然有点土,但够用。
那存个 Vector3 呢?比如玩家位置。我一般把它序列化成字符串:"1.5,2.3,0.8",然后存成 string。取出来再 Split 一下。说白了,就是自己手动做序列化。
核心 API 详解
API 不多,我按使用频率给你列一下:
1. 存数据
// 存整数
PlayerPrefs.SetInt("Gold", 100);
// 存浮点数
PlayerPrefs.SetFloat("MusicVolume", 0.75f);
// 存字符串
PlayerPrefs.SetString("PlayerName", "张三");
// 重要!必须调用 Save,否则可能丢失
PlayerPrefs.Save();
这里有个坑,我踩过好几次:Set 之后如果不调用 Save(),数据不会立刻写入硬盘。Unity 会在应用退出时自动保存一次,但如果游戏中途闪退了,那中间 Set 的数据就全丢了。
所以我的习惯是:每次 Set 完关键数据(比如玩家通关、获得道具),立刻调一次 Save()。别偷懒。
2. 读数据
// 读整数,第二个参数是默认值
int gold = PlayerPrefs.GetInt("Gold", 0);
// 读浮点数
float volume = PlayerPrefs.GetFloat("MusicVolume", 1.0f);
// 读字符串
string name = PlayerPrefs.GetString("PlayerName", "默认名字");
第二个参数是默认值。如果这个 Key 不存在,就返回默认值。这个设计很贴心,你不需要先判断 Key 存不存在,直接 Get 就行。
但要注意:默认值类型必须匹配。你 GetInt 的时候传个字符串进去,编译器不会报错,但运行时会出问题。
3. 判断是否存在
if (PlayerPrefs.HasKey("Gold"))
{
Debug.Log("Gold 存在");
}
这个 API 我用的不多。因为 Get 的时候给个默认值就够了。但有一种场景必须用:你想知道玩家是不是第一次启动游戏。如果 HasKey("FirstLaunch") 返回 false,说明是第一次,你就初始化一些数据。
4. 删除数据
// 删除单个 Key
PlayerPrefs.DeleteKey("Gold");
// 删除所有数据(慎用!)
PlayerPrefs.DeleteAll();
DeleteAll 这个函数,我建议你只在“设置”界面加个隐藏按钮,或者测试的时候用。千万别在游戏逻辑里随便调。我曾经见过一个同事,在“返回主菜单”时调了 DeleteAll,结果玩家所有存档都没了……那用户差评刷的,啧啧。
一个完整的例子
咱们写个简单的金币系统,把上面这些 API 串起来:
public class GoldManager : MonoBehaviour
{
private const string GOLD_KEY = "PlayerGold";
void Start()
{
// 读取金币,默认给 100
int gold = PlayerPrefs.GetInt(GOLD_KEY, 100);
Debug.Log("当前金币: " + gold);
}
public void AddGold(int amount)
{
int current = PlayerPrefs.GetInt(GOLD_KEY, 100);
current += amount;
PlayerPrefs.SetInt(GOLD_KEY, current);
PlayerPrefs.Save(); // 立刻保存
}
public void ResetGold()
{
PlayerPrefs.DeleteKey(GOLD_KEY);
PlayerPrefs.Save();
}
}
你看,就这么几行代码,一个简单的存档系统就出来了。适合原型开发、小游戏、或者临时存一些设置项。
避坑指南
- Key 命名冲突: 不同模块用了相同的 Key。比如 UI 模块存了 "Volume",音频模块也存了 "Volume",互相覆盖。我后来强制团队用 "模块名_变量名" 的格式,比如 "Audio_MusicVolume"。
- 忘记 Save: 测试时一切正常,发布后玩家反馈“金币丢了”。一查,原来是没调 Save(),闪退导致数据丢失。
- 存了太多数据: 有人把整个游戏配置表用 JSON 序列化后存成 string,结果 PlayerPrefs 文件变得巨大,加载卡顿。记住,PlayerPrefs 只适合存少量简单数据。
什么时候用 PlayerPrefs?
说实话,现在项目里我很少用它了。但有些场景它依然是最佳选择:
- 游戏设置: 音量、画质、语言、是否显示教程
- 简单进度: 当前关卡、最高分、解锁了几个角色
- 原型验证: 快速做个 demo,不想搞数据库
什么时候别用?
- 存大量数据(比如几百个道具、几千条日志)
- 存敏感数据(密码、支付信息)
- 需要跨平台同步(PlayerPrefs 不支持云存档)
好,PlayerPrefs 的基础就这些。下一章咱们聊聊怎么用它做一套完整的存档系统,包括存档版本控制、数据校验这些实战技巧。