保存游戏
本课属于 Coddy Unity 学习旅程的“Unity 游戏架构”单元。第 31 课,共 64 课。
存档是一个以 JSON 格式写入的对象。把存档需要的所有内容都放进一个 [System.Serializable] 类中,从游戏中填入数据,然后将它的 JsonUtility.ToJson 写入一个生命周期比游戏更长的位置。这个地牢会保存层数、金币和找到的钥匙:
[System.Serializable]
public class DungeonSave
{
public int floor;
public int gold;
public List<string> keys;
}using System.Collections.Generic;
using System.IO;
using UnityEngine;
public class Dungeon : MonoBehaviour
{
public int floor = 1;
public int gold;
public List<string> keys = new List<string>();
private string path;
void Start()
{
path = Path.Combine(Application.persistentDataPath, "dungeon.json");
if (File.Exists(path))
{
DungeonSave save = JsonUtility.FromJson<DungeonSave>(File.ReadAllText(path));
floor = save.floor;
gold = save.gold;
keys = save.keys;
}
}
public void Save()
{
DungeonSave save = new DungeonSave();
save.floor = floor;
save.gold = gold;
save.keys = keys;
File.WriteAllText(path, JsonUtility.ToJson(save));
}
}Application.persistentDataPath 是 Unity 在每个平台上为游戏提供的文件夹,游戏关闭后和更新后都会保留。Path.Combine 会用平台对应的分隔符,将它和文件名组合起来。File.WriteAllText(path, text) 会写入文件并替换旧文件,File.Exists 会告诉你文件是否存在,File.ReadAllText 会读回文件内容(这三个都来自 System.IO)。游戏自己的文件夹可能是只读的,所以存档要放在 persistentDataPath 中,不能放在其他地方。在 Awake 或 Start 中构建路径:Unity 不允许在字段初始化器中使用 persistentDataPath。
存档类只保存存档需要的内容,游戏会把值复制到类中以保存,并从类中复制出来以加载。小型存档可以存入 PlayerPrefs,而不是文件:PlayerPrefs.SetString("dungeon", json) 和 PlayerPrefs.GetString("dungeon") 可以保存和读取相同的 JSON。在发生值得保存的事情时保存,例如到达检查点时,或点击“保存”按钮时:正常退出时会运行 OnApplicationQuit,但手机可能会在后台直接关闭游戏,而不运行它。
挑战
中等将岛屿保存到 island.json。
- 在
IslandSave.cs中,编写一个[System.Serializable]类IslandSave,包含public Vector3 playerPosition;和public List<Vector3> flags;。 - 在
Start()中,将path设置为Path.Combine(Application.persistentDataPath, "island.json")。 Save()将包含transform.position和flags的 IslandSave 保存为 JSON 文件,写入path。Start()会读取已有文件,恢复位置,并对每个旗帜调用PlantFlag。
按“开始”,插上旗帜,然后点击“保存”,停止后再点击“开始”:旗帜就会回来了。然后点击“检查”。
亲手试试
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.InputSystem;
public class Explorer : MonoBehaviour
{
public GameObject flagPrefab;
public float speed = 4f;
private List<Vector3> flags = new List<Vector3>();
private InputAction moveAction;
void Start()
{
moveAction = InputSystem.actions.FindAction("Move");
// 在这里加载 island.json
}
void Update()
{
Vector2 input = moveAction.ReadValue<Vector2>();
transform.Translate(new Vector3(input.x, 0f, input.y) * speed * Time.deltaTime);
if (Keyboard.current.spaceKey.wasPressedThisFrame)
{
PlantFlag(transform.position);
}
}
void PlantFlag(Vector3 position)
{
flags.Add(position);
Instantiate(flagPrefab, new Vector3(position.x, 0.5f, position.z), Quaternion.identity);
}
// Save 按钮会调用这个方法
public void Save()
{
// 将玩家的位置和旗帜写入 island.json
}
}
Unity 游戏架构中的所有课
自己练习: Unity 在线编辑器