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]
特性。
後記
由於個人能力有限,文中不免存在疏漏之處,懇求大家斧正,一起交流,共同進步。