3、PlayerPrefs实战:保存游戏设置(音量、分辨率)、保存玩家金币数量、跨场景数据传递

好,咱们直接进入实战环节。PlayerPrefs 这东西,说白了就是 Unity 给咱们准备的一个“小本本”,专门用来记一些简单的数据。你想想看,游戏设置、玩家金币这种轻量级数据,用 PlayerPrefs 来存,简直不要太方便。

我个人习惯是,只要数据量不大、不需要加密、不需要复杂的查询,优先考虑 PlayerPrefs。它简单、快,而且跨平台自动处理,省心。

3.1 保存游戏设置:音量与分辨率

游戏设置是 PlayerPrefs 最经典的用法。玩家调了音量、改了分辨率,总不能每次进游戏都重新调吧?

咱们先看音量设置。我一般会把音量分成“主音量”、“音乐音量”、“音效音量”三个维度来存。

// 保存音量设置
public void SaveVolumeSettings(float masterVolume, float musicVolume, float sfxVolume)
{
    PlayerPrefs.SetFloat("MasterVolume", masterVolume);
    PlayerPrefs.SetFloat("MusicVolume", musicVolume);
    PlayerPrefs.SetFloat("SFXVolume", sfxVolume);
    PlayerPrefs.Save(); // 我习惯手动调用 Save,确保数据立即写入磁盘
    Debug.Log("音量设置已保存");
}

// 读取音量设置
public void LoadVolumeSettings()
{
    float masterVolume = PlayerPrefs.GetFloat("MasterVolume", 0.8f); // 默认 0.8
    float musicVolume = PlayerPrefs.GetFloat("MusicVolume", 0.7f);
    float sfxVolume = PlayerPrefs.GetFloat("SFXVolume", 0.7f);
    
    // 应用到音频管理器
    AudioManager.Instance.SetMasterVolume(masterVolume);
    AudioManager.Instance.SetMusicVolume(musicVolume);
    AudioManager.Instance.SetSFXVolume(sfxVolume);
}
小提示:GetFloat 的第二个参数是默认值。如果 PlayerPrefs 里没有这个键,就返回默认值。这样第一次运行游戏时,音量不会变成 0。

分辨率设置稍微复杂一点。因为分辨率涉及到屏幕宽高、全屏模式这些。我建议把分辨率索引和全屏标志分开存。

// 保存分辨率设置
public void SaveResolutionSettings(int resolutionIndex, bool isFullscreen)
{
    PlayerPrefs.SetInt("ResolutionIndex", resolutionIndex);
    PlayerPrefs.SetInt("IsFullscreen", isFullscreen ? 1 : 0); // 用 int 代替 bool
    PlayerPrefs.Save();
}

// 读取分辨率设置
public void LoadResolutionSettings()
{
    int resolutionIndex = PlayerPrefs.GetInt("ResolutionIndex", 2); // 默认第三个选项
    bool isFullscreen = PlayerPrefs.GetInt("IsFullscreen", 1) == 1; // 默认全屏
    
    // 应用分辨率
    Resolution[] resolutions = Screen.resolutions;
    if (resolutionIndex < resolutions.Length)
    {
        Screen.SetResolution(resolutions[resolutionIndex].width, 
                             resolutions[resolutionIndex].height, 
                             isFullscreen);
    }
}
注意:PlayerPrefs 不支持直接存 bool 类型。我一般用 int 来模拟,0 表示 false,1 表示 true。这是 Unity 社区里比较通用的做法。

3.2 保存玩家金币数量

金币数量是游戏里最常见的“货币”数据。用 PlayerPrefs 存金币,简单直接。但要注意一点:金币数据是玩家资产,不能丢

我曾经在一个项目里犯过傻,每次金币变化都直接 SetInt,但忘了调用 Save。结果玩家切后台再回来,金币回档了。嗯,从那以后我养成了“每次修改关键数据后立即 Save”的习惯。

// 保存金币
public void SaveGold(int goldAmount)
{
    PlayerPrefs.SetInt("PlayerGold", goldAmount);
    PlayerPrefs.Save(); // 关键数据,立即保存
    Debug.Log("金币已保存: " + goldAmount);
}

// 读取金币
public int LoadGold()
{
    return PlayerPrefs.GetInt("PlayerGold", 0); // 默认 0 金币
}

// 增加金币(带校验)
public void AddGold(int amount)
{
    int currentGold = LoadGold();
    currentGold += amount;
    SaveGold(currentGold);
}

// 减少金币(带校验)
public bool SpendGold(int amount)
{
    int currentGold = LoadGold();
    if (currentGold >= amount)
    {
        currentGold -= amount;
        SaveGold(currentGold);
        return true; // 消费成功
    }
    else
    {
        Debug.LogWarning("金币不足!");
        return false; // 消费失败
    }
}
核心要点:金币操作一定要有“校验逻辑”。增加金币时检查上限,减少金币时检查是否足够。别让玩家金币变成负数,那会出大问题。

3.3 跨场景数据传递

跨场景传数据,很多新手会用静态变量或者 GameObject.DontDestroyOnLoad。但说实话,用 PlayerPrefs 来做跨场景数据传递,其实更稳妥。

为什么?因为静态变量在场景切换时可能会被重置(比如场景重新加载时),而 PlayerPrefs 的数据是持久化在磁盘上的,只要你不删,它就在那。

我举个例子:玩家在“关卡选择场景”选了一个关卡,然后跳转到“游戏场景”。这个关卡 ID 怎么传过去?

// 场景A:选择关卡时保存
public void SelectLevel(int levelId)
{
    PlayerPrefs.SetInt("CurrentLevelID", levelId);
    PlayerPrefs.Save();
    SceneManager.LoadScene("GameScene");
}

// 场景B:游戏开始时读取
public void StartGame()
{
    int levelId = PlayerPrefs.GetInt("CurrentLevelID", 1); // 默认第1关
    LevelManager.Instance.LoadLevel(levelId);
    
    // 用完后,我建议清理掉这个临时数据
    PlayerPrefs.DeleteKey("CurrentLevelID");
}
小技巧:跨场景传递的临时数据,用完后记得 DeleteKey 清理掉。不然下次不小心读到旧数据,会出 bug。我习惯在数据使用后立即清理。

再比如,玩家在“商店场景”买了道具,然后回到“主场景”。道具购买状态怎么传?

// 商店场景:购买道具
public void BuyItem(string itemId)
{
    // 保存购买状态
    PlayerPrefs.SetInt("Item_" + itemId + "_Purchased", 1);
    PlayerPrefs.Save();
    
    // 保存当前选中的道具
    PlayerPrefs.SetString("SelectedItem", itemId);
    PlayerPrefs.Save();
    
    SceneManager.LoadScene("MainScene");
}

// 主场景:读取道具状态
public void LoadPlayerItems()
{
    string selectedItem = PlayerPrefs.GetString("SelectedItem", "");
    if (!string.IsNullOrEmpty(selectedItem))
    {
        bool isPurchased = PlayerPrefs.GetInt("Item_" + selectedItem + "_Purchased", 0) == 1;
        if (isPurchased)
        {
            // 应用道具效果
            ApplyItemEffect(selectedItem);
        }
    }
}

3.4 实战中的避坑指南

讲几个我踩过的坑,你们注意一下。

坑点 说明 解决方案
忘记调用 Save() 数据只存在内存里,没写入磁盘 关键数据修改后立即 Save()
键名拼写错误 Set 和 Get 的键名不一致 用常量或枚举管理键名
默认值不合理 Get 时没给默认值,返回 0 或空 始终提供合理的默认值
数据未清理 临时数据残留,影响后续逻辑 用完后 DeleteKey 清理

我曾经在一个项目里,因为键名拼写错误,导致玩家音量设置死活不生效。排查了半天才发现是 "MasterVolume" 写成了 "MasterVolum"(少了个 e)。从那以后,我统一用常量来管理键名。

// 用常量管理键名,避免拼写错误
public static class PlayerPrefsKeys
{
    public const string MASTER_VOLUME = "MasterVolume";
    public const string MUSIC_VOLUME = "MusicVolume";
    public const string SFX_VOLUME = "SFXVolume";
    public const string RESOLUTION_INDEX = "ResolutionIndex";
    public const string IS_FULLSCREEN = "IsFullscreen";
    public const string PLAYER_GOLD = "PlayerGold";
    public const string CURRENT_LEVEL_ID = "CurrentLevelID";
    public const string SELECTED_ITEM = "SelectedItem";
}

// 使用示例
PlayerPrefs.SetFloat(PlayerPrefsKeys.MASTER_VOLUME, 0.8f);
总结一下:PlayerPrefs 适合存游戏设置、玩家金币、跨场景临时数据。记住三点:
1. 关键数据修改后立即 Save()
2. 用常量管理键名,避免拼写错误
3. 临时数据用完后及时清理

好了,这一章的内容就这些。下一章咱们会讲 PlayerPrefs 的进阶用法,包括数据加密、批量操作、以及如何用 PlayerPrefs 实现简单的存档系统。到时候见。