Unity 永久儲存資料 使用 Json 格式


建立時間: 2023年1月6日 21:15
更新時間: 2023年6月7日 18:37

說明

這次我打算要在遊戲上儲存最高分的紀錄,我的需求有以下2點

  1. 儲存的格式使用 JSON
  2. 要永久保存,避免更新 iOS 版本時被洗掉

為了方便,我設計了第一版的簡單儲存機制,這個版本方便簡單好用,全 Unity 內建程式,沒有使用任何外掛,但檔案沒有加密,對於有安全性考量的資料,建議加密,或者儲存在伺服器端

儲存程式

使用靜態類別,讀取和儲存方法可以直接使用,

SimpleStorage.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;

public static class SimpleStorage
{
    /// <summary>
    /// 成就檔案路徑
    /// </summary>
    public static readonly string AchievementFile =
        $"{Application.persistentDataPath}/Achievement.json";

    /// <summary>
    /// 讀取檔案,找不到或發生任何例外都會回傳空物件
    /// </summary>
    /// <typeparam name="T">資料型態</typeparam>
    /// <param name="path">路徑</param>
    /// <returns></returns>
    public static T Load<T>(string path)
    {
        try
        {
            string jsonData = File.ReadAllText(path);

            return JsonUtility.FromJson<T>(jsonData);
        }
        catch (FileNotFoundException)
        {
            Debug.Log($"找不到 {path}");
        }
        catch (Exception exception)
        {
            Debug.LogError(exception.Message);
        }

        return (T)Activator.CreateInstance(typeof(T));
    }


    /// <summary>
    /// 存檔
    /// </summary>
    /// <param name="path">路徑</param>
    /// <param name="data">資料</param>
    public static void Save(string path, object data)
    {
        string jsonData = JsonUtility.ToJson(data);
        File.WriteAllText(path, jsonData);
    }
}

Application.persistentDataPath 是 Unity 用來永久儲存資料的路徑

JsonUtility 這個類別是用來處理將物件轉換成 Json 格式或者將 Json 格式轉換回物件

(T)Activator.CreateInstance(typeof(T)) 這個是一種叫反射實例化的程式,T 在 c# 代表泛型,也就是 T 可以是我自定義的 MyClass 或 MyOtherClass 等等,但是程式不能寫 return new T(),所以就有了 (T)Activator.CreateInstance(typeof(T)),但這個寫法不能實例化建構子有參數的類別,否則會發生錯誤,在 Load() 方法中,如果沒發生意外,那就會回傳 T 型態的物件,發生任何意外都會回傳 T 型態,剛實例化的物件。

儲存結構

現在的設計是每一個檔案會儲存一個類別,類別需自行定義,這裡假設我要儲存最高分,這裡預期都會有預設值,在找不到檔案時,實例化類別時就會使用預設值

Achievement.cs

/// <summary>
/// 成就紀錄
/// </summary>
public class Achievement
{
    /// <summary>
    /// 最高分
    /// </summary>
    public int TopScore = 0;
}

使用範例

假設有一個遊戲分數 score,在玩遊戲的過程中會不斷地變化,在遊戲生命週期結束時,我就要更新最高分。

Example.cs

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

public class Example : MonoBehaviour
{
    /// <summary>
    /// 遊戲分數
    /// </summary>
    private int score;

    private void OnDestroy()
    {
        Achievement achievement = SimpleStorage.Load<Achievement>(
            SimpleStorage.AchievementFile);

        if (achievement.TopScore < score)
        {
            achievement.TopScore = score;
            SimpleStorage.Save(SimpleStorage.AchievementFile, achievement);
        }
    }
}

最後你就能在你想要顯示最高分的地方,使用上面的讀取程式,取得最高分。

更新版 SimpleStorage

原版 SimpleStorage 是共享靜態類別,此版本是抽象類別,子類別需繼承 SimpleStorage,功能都一樣,只是在呼叫讀取、存檔時寫的程式比較短。

SimpleStorage.cs

using System;
using System.IO;
using UnityEngine;

/// <summary>
/// 簡易的手機版儲存腳本
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class SimpleStorage<T> where T : SimpleStorage<T>, new()
{
    /// <summary>
    /// 預設的存檔路徑
    /// </summary>
    protected static readonly string FilePath =
        $"{Application.persistentDataPath}/{typeof(T).Name}.json";

    /// <summary>
    /// 讀取預設檔案,找不到或發生任何例外都會回傳空物件
    /// </summary>
    /// <returns></returns>
    public static T Load()
    {
        try
        {
            string jsonData = File.ReadAllText(FilePath);

            return JsonUtility.FromJson<T>(jsonData);
        }
        catch (FileNotFoundException)
        {
            Debug.Log($"找不到 {FilePath}");
        }
        catch (Exception exception)
        {
            Debug.LogError(exception.Message);
        }

        return new T();
    }

    /// <summary>
    /// 儲存在預設檔案
    /// </summary>
    public void Save()
    {
        string jsonData = JsonUtility.ToJson(this);
        File.WriteAllText(FilePath, jsonData);
    }
}

使用方式

假設我實作一個設定存檔,紀錄遊戲設定。

SettingsStorage.cs

/// <summary>
/// 設定存擋
/// </summary>
public class SettingsStorage : SimpleStorage<SettingsStorage>
{
    /// <summary>
    /// 背景音樂是否開啟
    /// </summary>
    public bool BackgroundMusicOn = true;

    /// <summary>
    /// 音效是否開啟
    /// </summary>
    public bool SoundEffectOn = true;
}

接著我就可以如下使用,以下是一個開關 UI。

BGMToggle.cs

using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// 背景音樂開關
/// </summary>
public class BGMToggle : MonoBehaviour
{
    private Toggle toggle;
    private SettingsStorage settingsStorage;

    private void Awake()
    {
        toggle = GetComponent<Toggle>();
        settingsStorage = SettingsStorage.Load();
        toggle.isOn = settingsStorage.BackgroundMusicOn;
    }

    /// <summary>
    /// 切換開關
    /// </summary>
    /// <param name="isOn">開關</param>
    public void OnValueChange(bool isOn)
    {
        settingsStorage.BackgroundMusicOn = isOn;
        settingsStorage.Save();
        MusicPlayer.Instance.AudioSource.volume = isOn ? 1f : 0f;
    }
}

可以看到 Load()Save() 不用再帶入參數了。

結論

大家可以依照自己的專案需求,考慮要使用哪個版本,然後再進行調整。

觀看次數: 1578
datajsonloadpersistentsaveunity
按讚追蹤 Enjoy 軟體 Facebook 粉絲專頁
每週分享資訊技術

一杯咖啡的力量,勝過千言萬語的感謝。

支持我一杯咖啡,讓我繼續創作優質內容,與您分享更多知識與樂趣!