上篇文章,我们写好了 GameManager 的基础架构,然后今天我们来完善 SaveState 和 LoadState 函数。

Awake 函数

首先我们修改一下 Awake 函数,在 Awake 调用的时候,需要判断 instance(也就是 GameManager 对象)如果不是空的,则删除该 GameObject,然后直接跳出函数。如果是空的,则将这个 GameManager 对象指向 instance 变量(就是上章说的自己指向自己)。

为什么判断 GameManager 对象是否为空?因为如果我们回到同一场景(比如说从 Main 去到 Dungeon1 再回到 Main),这样就会出现两个 GameManager 了。所以我们需要判断,如果 instance 不为空,就代表已经有一个 GameManager 存在,那么就删掉这个新的冲突的 GameManager。

还有一种解决方式是:我们将这个 GameManager 提取出来单独放在一个 Scene 里(比如说加载界面)而这个 Scene 只会被调用一次。那么就能解决这个BUG。

引用 UnityEngine.SceneManagement 命名空间(第四行使用的 using 关键字),然后给 SceneManager 的 sceneOnload 事件加上 LoadState,当此场景被加载的时候,自动调用该 LoadState 函数。因为是个事件,所以 LoadState 函数的参数也需要做对应的更改,括号内加上 Scene scene, LoadSceneMode mode 即可。

最后在 Awake 函数里加上 DontDestroyOnload 函数,然后把 gameObject 传递进去,意味着让 Unity 知道加载新场景时不要销毁目标对象。所以 GameManager 在你切换场景的时候,不会被销毁。他会一直存在于你的游戏中。


SaveState 函数

在 SaveState 里,我们需要保存四个信息:玩家所选的皮肤、金币、经验值和武器等级。我们使用字符串来保存这些信息。字符串的构成为:皮肤序号|所持金币|经验值|武器等级 每个数值用 | 号作为分隔符(如果你的键盘上没有这个符号,可以长按 ALT 键,顺序按下数字键盘的 1 2 7 即可)。因为皮肤和武器等级系统还没做好,所以我们直接用字符串 0 作为暂时的替代数据。金币和经验值可以直接获取成员变量 gold 和 experience,由于这俩成员变量是 int 类型,所以我们需要调用 ToString 函数将其转换成字符串类型。另外,"|" 一定要用双引号,虽然说单引号也行,但是为了安全起见,我们需要用字符串而非字符。

然后把这行字符串保存在 PlayerPref 对象里。使用的是 SetString 函数,第一个参数是 key,是数据保存的唯一标识符,第二个参数则是 value 也就是数据。


LoadState 函数

在 LoadState 里,首先需要判断 PlayerPrefs 里有没有保存数据(游戏刚开始会触发一次 LoadState,这时候 PlayerPrefs 对象是没有数据的)。我们使用 HasKey 函数来检查是否存在 SaveState 数据。如果没有,则直接跳出函数。

我们声明一个数组,这个数组用来保存 PlayerPrefs 对象所储存的字符串。我们用 GetString 获得 SaveState 所保存的字符串,然后用 Split 函数将其拆散成数组。上面说到,SaveState 所保存的字符串格式是这个样子的:皮肤序号|所持金币|经验值|武器等级 所以 Split 函数我们传入字符 ‘|’ 作为拆散的标记点。这样,数组下标 0 就是皮肤序号,以此类推。

接着我们对每个数据进行对应的赋值即可。

完整 GameManager 代码如下

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

public class GameManager : MonoBehaviour
{
public static GameManager instance;

private void Awake()
{
if (instance != null)
{
Destroy(gameObject);
return;
}

instance = this;
SceneManager.sceneLoaded += LoadState;
DontDestroyOnLoad(gameObject);
}

// 游戏资源
public List<Sprite> playerSprites;
public List<Sprite> weaponSprites;
public List<int> weaponPrices; // 武器升级所需要的价格
public List<int> xpTable; // 升到下一级所需的经验值

// 参考对象
public Player player;

// 逻辑类
public int gold;
public int experience;

// 保存游戏状态
/*
* 要保存:
* INT preferedSkin
* INT gold
* INT experience
* INT weaponLevel
*/
public void SaveState()
{
string state = "";

state += "0" + "|"; // 皮肤
state += gold.ToString() + "|"; // 金币
state += experience.ToString() + "|"; // 经验值
state += "0"; // 武器等级

PlayerPrefs.SetString("SaveState", state);

Debug.Log("Save State");
}

// 加载游戏状态
public void LoadState(Scene scene, LoadSceneMode mode)
{
if (!PlayerPrefs.HasKey("SaveState"))
{
return;
}

string[] data = PlayerPrefs.GetString("SaveState").Split('|');
// "皮肤|金币|经验值|武器等级"

// TODO: 更改角色皮肤
gold = int.Parse(data[1]);
experience = int.Parse(data[2]);
// TODO: 更改武器等级

Debug.Log("Load State");
}
}

其实 SaveState 函数里,保存数据时我们可以使用 SetInt 函数代替 SetString 函数。这样我们可以保存比较大量的游戏数据,并且不容易搞混。


保存游戏的时机

由上,当场景被加载的时候,会调用一次 LoadScene。所以加载场景没问题了,接下来我们要知道什么时候需要保存信息。我选择保存的时机为切换地点(触碰传送门)的时候。所以我们修改传送门 Portal.cs 的碰撞脚本如下:

using UnityEngine;

public class Portal : Collidable
{
public string[] sceneNames;

protected override void OnCollide(Collider2D collider)
{
if (collider.name == "Player")
{
// 保存游戏信息
GameManager.instance.SaveState();

// 传送玩家到随机的地图上。
string sceneName = sceneNames[Random.Range(0, sceneNames.Length)]; // 随机获取一个地图名称
UnityEngine.SceneManagement.SceneManager.LoadScene(sceneName); // 加载地图
}
}
}

都做好之后,我们开始游戏,然后选中 Game Manager 游戏物件,然后将其 Gold 属性增加至 10,接下来往上走到传送门,当玩家到第二个场景的时候,会发现 10 这个数值也被带过去 Dungeon1 场景了。也会发现因为调用了 DontDestroyOnLoad 函数和,所以 Game Manager 游戏物件会被带到 Dungeon1 场景。

我们再次启动游戏,也会发现游戏会自动加载我们的 10 个黄金。

用代码删除存档

当我们需要删除档案,在某个地方(比如说新建游戏的按钮或者开始游戏的 Awake 函数)调用 PlayerPrefs.DeleteAll() 函数即可。

删除游戏存档(高级版,删除存档)

当我们调用 SaveState 函数,那么游戏数据将会保存在 PlayerPrefs 里。根据 Unity 官方文档 中,不同的系统和发行方式,PlayerPrefs 保存在不同的位置。这里我们的开发环境是 Windows10,我们来讲解怎么删除 PlayerPrefs 所保存的数据。

点击 Windows 键 + R 打开运行窗口,然后输入 regedit,点击回车。

在注册表里找到此路径(影响路径的因素有很多,我经过一番摸索,在这里找到本项目的保存路径):

HKEY_CURRENT_USER\SOFTWARE\Unity\UnityEditor\DefaultCompany\TopDown Dungeon

  • HKEY_CURRENT_USER\SOFTWARE\Unity\UnityEditor 这是因为我们现在在 Unity Editor 里进行开发,所以它保存在里面
  • DefaultCompany 可以在 Project Setting 面板里进行修改。这是公司名字
  • TopDown Dungeon 也是可以在 Project Setting 面板里修改。这是游戏名字

然后直接删除 TopDown Dungeon 这个文件夹就行了。你可以从里面找到 SaveState 字段,也就是我们定义保存的数据,他是个二进制的字段,但是点进去之后,会在右侧看到所保存的字段:0|10|0|0

删除之后,重新运行游戏,就会发现游戏将不会读取数据了。直到下一次保存之前,都是全新档。

那么这一部分学习了怎么去保存游戏信息。