Lưu tiến trình
Assets/
Scenes/
Arena.unity
Scripts/
đã có sẵnbài này thêm
Game mobile bị ngắt liên tục: cuộc gọi, thông báo, hệ điều hành tắt app để giải phóng bộ nhớ. Nếu đóng app là mất tiến trình thì không ai quay lại chơi nữa.
Biến một class C# thường thành JSON, ghi vào persistentDataPath, mở game lên thì đọc lại.
Dùng PlayerPrefs hay file?
✓ PlayerPrefs
Cho cài đặt
✓ JSON file
Cho tiến trình chơi
Bước 1 — một class dữ liệu thuần
Class lưu trữ chỉ chứa giá trị, tuyệt đối không chứa MonoBehaviour. Bạn không serialise được GameObject — hãy ghi lại đủ thông tin để dựng lại nó.
using System;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public class SaveData
{
public int version = 1; // tăng khi đổi cấu trúc
public int level = 1;
public float health = 100f;
public int gold;
public float playTimeSeconds;
public string sceneName = "Arena";
public Vector3 playerPosition; // Vector3 thì Unity serialize được bình thường
public List<string> ownedArtifacts = new List<string>();
public List<string> equippedArtifacts = new List<string>();
public string savedAtUtc = "";
}Bước 2 — ghi và đọc
using System;
using System.IO;
using UnityEngine;
public static class SaveSystem
{
// thư mục DUY NHẤT được phép ghi trên iOS và Android
private static string Path => System.IO.Path.Combine(Application.persistentDataPath, "save.json");
public static bool Exists => File.Exists(Path);
public static void Save(SaveData data)
{
data.savedAtUtc = DateTime.UtcNow.ToString("o");
string json = JsonUtility.ToJson(data, prettyPrint: true);
string temp = Path + ".tmp";
//ghi ra file tạm rồi mới tráo vào
// nếu máy tắt giữa chừng thì file save cũ vẫn còn nguyên
File.WriteAllText(temp, json);
File.Copy(temp, Path, overwrite: true);
File.Delete(temp);
Debug.Log("Saved to " + Path);
}
public static SaveData Load()
{
if (!Exists) return new SaveData(); // lần chạy đầu tiên
try
{
string json = File.ReadAllText(Path);
SaveData data = JsonUtility.FromJson<SaveData>(json);
return Migrate(data);
}
catch (Exception e)
{
//file hỏng không được làm chết game
Debug.LogError("Save file unreadable, starting fresh: " + e.Message);
File.Move(Path, Path + ".broken");
return new SaveData();
}
}
public static void Delete()
{
if (Exists) File.Delete(Path);
}
/// bản save cũ vẫn phải chạy được sau khi bạn cập nhật game
private static SaveData Migrate(SaveData data)
{
if (data.version < 1)
{
data.gold = 0;
data.version = 1;
}
return data;
}
}Application.persistentDataPath là thư mục duy nhất ghi được trên điện thoại. Application.dataPath nằm trong gói app và chỉ đọc.
Mẹo ghi ra file tạm rồi tráo bảo vệ bạn khỏi file save ghi dở khi hệ điều hành tắt app giữa chừng.
Luôn bọc Load trong try/catch. Người dùng có file hỏng mà không mở nổi game sẽ để lại đánh giá một sao.
Trường version hôm nay không tốn gì, nhưng cứu bạn vào ngày bạn thêm trường mới ở bản 1.3.
Bước 3 — lưu vào đúng lúc
Đừng lưu mỗi khung hình, cũng đừng chỉ lưu khi người chơi bấm nút — trên điện thoại họ sẽ không kịp bấm đâu.
using UnityEngine;
public class GameSession : MonoBehaviour
{
public static GameSession Instance { get; private set; }
public SaveData Data { get; private set; }
[SerializeField] private CharacterStats playerStats; //chính là Knight
[SerializeField] private Transform player;
void Awake()
{
if (Instance != null) { Destroy(gameObject); return; }
Instance = this;
DontDestroyOnLoad(gameObject);
Data = SaveSystem.Load();
Apply(Data);
}
void Update() => Data.playTimeSeconds += Time.deltaTime;
void Collect()
{
Data.health = playerStats.currentHealth;
Data.playerPosition = player.position;
Data.sceneName = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
}
void Apply(SaveData d)
{
playerStats.currentHealth = d.health;
if (d.playerPosition != Vector3.zero) player.position = d.playerPosition;
}
/// chạy khi app bị đưa xuống nền — TRÊN MOBILE ĐÂY MỚI LÀ CÁI QUAN TRỌNG
void OnApplicationPause(bool paused)
{
if (paused) { Collect(); SaveSystem.Save(Data); }
}
//trên máy tính và khi thoát bình thường
void OnApplicationQuit()
{
Collect();
SaveSystem.Save(Data);
}
}Điểm lưu, đừng lưu liên tục
Lưu khi người chơi sang khu vực mới, kết thúc trận đánh, mua đồ, hoặc app bị đưa xuống nền.
Ghi một file JSON nhỏ mất chừng một mili giây. Nhưng ghi 60 lần mỗi giây vẫn làm mòn bộ nhớ flash.
Hiện một dấu 'đã lưu' nhỏ trong nửa giây. Người chơi không tin những game lưu âm thầm.
Tìm file để gỡ lỗi
#in đường dẫn từ trong game
Debug.Log(Application.persistentDataPath);
#lấy file save từ máy về
adb shell run-as com.yourstudio.yourgame cat files/save.json
#đường dẫn khi chạy trong editor