Unity遊戲框架設計之存檔管理器

珂霖發表於2024-05-01

Unity遊戲框架設計之存檔管理器

存檔管理器的主要功能是實現遊戲進度的查詢、儲存(存檔)、讀取(讀檔)和刪除(刪檔)。

存檔主要有三種實現方案。

(一)PlayerPrefs。PlayerPrefs 類用於在遊戲中儲存、刪除、修改和訪問玩家的資料。儲存的資料是持久化的,即使玩家關閉遊戲或重新啟動裝置,資料也會保留下來。缺陷:PlayerPrefs 是明文儲存的。

(二)本地儲存:JSON + 加密演算法。對於存檔操作,先將玩家需要儲存的資料封裝為實體類,然後透過 JSON 工具將實體類序列化為 JSON 字串,然後透過加密演算法對 JSON 字串進行加密,保證存檔的安全,最後以二進位制的方式輸出到本地檔案中。對於讀檔操作,則先讀取本地二進位制檔案,然後透過加密演算法解密出 JSON 字串,然後透過 JSON 工具將 JSON 字串反序列化為實體類即可。

(三)資料庫儲存。玩家資料將被儲存到遠端資料庫中。玩家在遊戲中不斷與伺服器互動,由伺服器來完成遊戲業務邏輯的處理,並在資料庫中讀取和修改玩家資料。

下述程式碼實現方案二下的存檔管理器。

程式碼設計

public class GameProgressManager : SingletonMono<GameProgressManager>
{
    [Serializable]
    public abstract class GameProgressData
    {
    }

    private static readonly (byte[], byte[]) AesKeyAndIv = new(
        Convert.FromBase64String(""), Convert.FromBase64String("")
    );

    public void CreateGameProgress<T>(T data, string filePath) where T : GameProgressData
    {
        string gameProcessDataJson = JsonUtility.ToJson(data);
        byte[] encryptedData = EncryptStringByAes(gameProcessDataJson, AesKeyAndIv.Item1, AesKeyAndIv.Item2);
        File.WriteAllBytes(filePath, encryptedData);
    }

    public void RemoveGameProgress(string filePath)
    {
        if (File.Exists(filePath))
        {
            File.Delete(filePath);
        }
    }

    public void SaveGameProgress<T>(T data, string filePath) where T : GameProgressData
    {
        string gameProcessDataJson = JsonUtility.ToJson(data);
        byte[] encryptedData = EncryptStringByAes(gameProcessDataJson, AesKeyAndIv.Item1, AesKeyAndIv.Item2);
        File.WriteAllBytes(filePath, encryptedData);
    }

    public bool ContainGameProgress(string filePath)
    {
        return File.Exists(filePath);
    }

    public T GetGameProgress<T>(string filePath) where T : GameProgressData
    {
        byte[] encryptedData = File.ReadAllBytes(filePath);
        string gameProcessDataJson = DecryptStringByAes(encryptedData, AesKeyAndIv.Item1, AesKeyAndIv.Item2);
        return JsonUtility.FromJson<T>(gameProcessDataJson);
    }

    private static byte[] EncryptStringByAes(string plainText, byte[] key, byte[] iv)
    {
        using Aes aes = Aes.Create();
        aes.Key = key;
        aes.IV = iv;
        ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
        using MemoryStream memoryStream = new MemoryStream();
        using CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);
        byte[] plaintextBytes = Encoding.UTF8.GetBytes(plainText);
        cryptoStream.Write(plaintextBytes, 0, plaintextBytes.Length);
        cryptoStream.FlushFinalBlock();
        return memoryStream.ToArray();
    }

    private static string DecryptStringByAes(byte[] cipherText, byte[] key, byte[] iv)
    {
        using Aes aes = Aes.Create();
        aes.Key = key;
        aes.IV = iv;
        ICryptoTransform cryptoTransform = aes.CreateDecryptor(aes.Key, aes.IV);
        using MemoryStream memoryStream = new MemoryStream(cipherText);
        using CryptoStream cryptoStream = new CryptoStream(memoryStream, cryptoTransform, CryptoStreamMode.Read);
        using StreamReader streamReader = new StreamReader(cryptoStream);
        return streamReader.ReadToEnd();
    }

    private static (byte[], byte[]) GenerateAesKeyAndIv()
    {
        using Aes aes = Aes.Create();
        aes.GenerateKey();
        aes.GenerateIV();
        Debug.Log("AES 金鑰 " + Convert.ToBase64String(aes.Key));
        Debug.Log("AES 向量 " + Convert.ToBase64String(aes.IV));
        return (aes.Key, aes.IV);
    }
}

程式碼說明

(一)使用前需要先生成 AES 金鑰和 AES 向量。

(二)所有遊戲存檔實體類必須繼承 GameProgressData 類。

(三)遊戲存檔實體類中的內部類必須使用 [Serializable] 特性。

後記

由於個人能力有限,文中不免存在疏漏之處,懇求大家斧正,一起交流,共同進步。

相關文章