Unityでハイスコア保存方法の実装資料

本資料では、Unityでハイスコアの保存方法をPlayerPrefsまたはJSONから選択できる実装方法について、ポリモーフィズムを利用し、インスペクターから選択可能な形で実装する例を紹介します。
以下、2種類のアプローチ([SerializeReference] を利用する方法と、enum を利用する方法)について詳しく解説します。


1. 概要

  • 目的:
    ハイスコアの保存方法として、PlayerPrefsとJSON形式による保存を柔軟に切り替え可能なシステムを実装する。
  • アプローチ:
    • ポリモーフィズムの活用:
      抽象クラス(またはインターフェース)を定義し、保存処理(SaveScore)と読み込み処理(LoadScore)を共通化する。
    • 実装の分割:
      • PlayerPrefsSaver:UnityのPlayerPrefsを使用する実装
      • JsonSaver:JSON形式でファイルに保存する実装
    • インスペクターからの選択:
      [SerializeReference] 属性または enum を利用し、インスペクター上で保存方法を選択可能にする。

2. 実装例

2.1. 例1: [SerializeReference] を利用する方法

推奨するフォルダ構成例

  • ランタイム用スクリプト:
    • Assets/Scripts/HighScoreSaver.cs(抽象クラス)
    • Assets/Scripts/PlayerPrefsSaver.cs
    • Assets/Scripts/JsonSaver.cs
    • Assets/Scripts/ScoreData.cs
    • Assets/Scripts/HighScoreManager.cs
  • エディタ用スクリプト(カスタムエディターなど):
    • Assets/Editor/HighScoreManagerEditor.cs
using System.IO;
using UnityEngine;

public abstract class HighScoreSaver
{
    public abstract void SaveScore(int score);
    public abstract int LoadScore();
}
using UnityEngine;

[System.Serializable]
public class PlayerPrefsSaver : HighScoreSaver
{
    private string key = "HighScore";
    public override void SaveScore(int score)
    {
        PlayerPrefs.SetInt(key, score);
        PlayerPrefs.Save();
    }
    public override int LoadScore()
    {
        return PlayerPrefs.GetInt(key, 0);
    }
}
using System.IO;
using UnityEngine;

[System.Serializable]
public class JsonSaver : HighScoreSaver
{
    private string fileName = "highscore.json";
    public override void SaveScore(int score)
    {
        string json = JsonUtility.ToJson(new ScoreData(score));
        File.WriteAllText(Path.Combine(Application.persistentDataPath, fileName), json);
    }
    public override int LoadScore()
    {
        string path = Path.Combine(Application.persistentDataPath, fileName);
        if (File.Exists(path))
        {
            string json = File.ReadAllText(path);
            ScoreData data = JsonUtility.FromJson<ScoreData>(json);
            return data.score;
        }
        return 0;
    }
}
[System.Serializable]
public class ScoreData
{
    public int score;
    public ScoreData(int score)
    {
        this.score = score;
    }
}
public class HighScoreManager : MonoBehaviour
{
    [SerializeReference]
    public HighScoreSaver highScoreSaver;

    public int currentScore;

    public void SaveHighScore()
    {
        if (highScoreSaver != null)
        {
            highScoreSaver.SaveScore(currentScore);
        }
    }

    public void LoadHighScore()
    {
        if (highScoreSaver != null)
        {
            currentScore = highScoreSaver.LoadScore();
        }
    }
}

注意:
[SerializeReference] を利用すると、インスペクター上で具象クラスのインスタンスを直接割り当てることができますが、標準のインスペクターでは選択用のUIが整備されていないため、カスタムエディターの作成が必要な場合もあります。

HighScoreManagerEditor.cs

csharpコピーするusing UnityEngine;
using UnityEditor;
using System;
using System.Linq;

[CustomEditor(typeof(HighScoreManager))]
public class HighScoreManagerEditor : Editor
{
    public override void OnInspectorGUI()
    {
        // デフォルトのインスペクター表示
        DrawDefaultInspector();

        HighScoreManager manager = (HighScoreManager)target;

        EditorGUILayout.Space();
        EditorGUILayout.LabelField("HighScore Saver", EditorStyles.boldLabel);

        // [SerializeReference] のフィールドの状態に応じた表示
        if (manager.highScoreSaver == null)
        {
            EditorGUILayout.HelpBox("Saverが未設定です。", MessageType.Info);
            if (GUILayout.Button("Add Saver"))
            {
                // GenericMenu を利用して、派生クラスの一覧を表示
                GenericMenu menu = new GenericMenu();

                // AppDomain から HighScoreSaver を継承した全ての具象クラスを取得
                var saverTypes = AppDomain.CurrentDomain.GetAssemblies()
                                    .SelectMany(assembly => assembly.GetTypes())
                                    .Where(type => type.IsSubclassOf(typeof(HighScoreSaver)) && !type.IsAbstract)
                                    .ToArray();

                foreach (var type in saverTypes)
                {
                    menu.AddItem(new GUIContent(type.Name), false, () =>
                    {
                        // 選択された型のインスタンスを生成してフィールドに割り当て
                        manager.highScoreSaver = (HighScoreSaver)Activator.CreateInstance(type);
                        EditorUtility.SetDirty(manager);
                    });
                }
                menu.ShowAsContext();
            }
        }
        else
        {
            // すでにSaverが設定されている場合、型名を表示し削除ボタンを用意
            EditorGUILayout.LabelField("Current Saver:", manager.highScoreSaver.GetType().Name);
            if (GUILayout.Button("Remove Saver"))
            {
                manager.highScoreSaver = null;
                EditorUtility.SetDirty(manager);
            }
        }
    }
}

解説

  • Reflection を利用した派生クラスの取得:
    AppDomain.CurrentDomain.GetAssemblies() を利用し、全アセンブリから HighScoreSaver を継承している非抽象クラスを取得しています。
  • GenericMenu による選択肢の表示:
    取得した各型について、メニュー項目を追加し、選択時に Activator.CreateInstance を使用してインスタンスを生成、highScoreSaver に割り当てます。
  • 状態に応じたUI表示:
    Saver が未設定の場合は「Add Saver」ボタンを表示、すでに設定されている場合は現在の型名と「Remove Saver」ボタンを表示しています。

このようにカスタムエディターを実装することで、標準のインスペクターでは扱いにくい [SerializeReference] フィールドの編集を直感的に行えるようになります。


2.2. 例2: enum を利用する方法

推奨するフォルダ構成例

  • ランタイム用スクリプト (Assets/Scripts 以下など):
    • Assets/Scripts/HighScoreSaver.cs(抽象クラス)
    • Assets/Scripts/PlayerPrefsSaver.cs
    • Assets/Scripts/JsonSaver.cs
    • Assets/Scripts/ScoreData.cs
    • Assets/Scripts/HighScoreManager.cs
  • エディタ用スクリプト (今回のenum版では不要ですが、カスタムエディターがある場合は Assets/Editor 以下に配置する)

インスペクター上で enum による選択肢を用いて、保存方法を切り替えるシンプルな実装例です。

using UnityEngine;
using System.IO;

public enum SaverType
{
    PlayerPrefs,
    JSON
}

public class HighScoreManager : MonoBehaviour
{
    // インスペクターから保存方法を選択するための enum
    public SaverType saverType;
    private HighScoreSaver highScoreSaver;

    public int currentScore;

    void Awake()
    {
        switch (saverType)
        {
            case SaverType.PlayerPrefs:
                highScoreSaver = new PlayerPrefsSaver();
                break;
            case SaverType.JSON:
                highScoreSaver = new JsonSaver();
                break;
        }
    }

    public void SaveHighScore()
    {
        if (highScoreSaver != null)
        {
            highScoreSaver.SaveScore(currentScore);
        }
    }

    public void LoadHighScore()
    {
        if (highScoreSaver != null)
        {
            currentScore = highScoreSaver.LoadScore();
        }
    }
}
public abstract class HighScoreSaver
{
    public abstract void SaveScore(int score);
    public abstract int LoadScore();
}
public class PlayerPrefsSaver : HighScoreSaver
{
    private string key = "HighScore";
    public override void SaveScore(int score)
    {
        PlayerPrefs.SetInt(key, score);
        PlayerPrefs.Save();
    }
    public override int LoadScore()
    {
        return PlayerPrefs.GetInt(key, 0);
    }
}
public class JsonSaver : HighScoreSaver
{
    private string fileName = "highscore.json";
    public override void SaveScore(int score)
    {
        string json = JsonUtility.ToJson(new ScoreData(score));
        File.WriteAllText(Path.Combine(Application.persistentDataPath, fileName), json);
    }
    public override int LoadScore()
    {
        string path = Path.Combine(Application.persistentDataPath, fileName);
        if (File.Exists(path))
        {
            string json = File.ReadAllText(path);
            ScoreData data = JsonUtility.FromJson<ScoreData>(json);
            return data.score;
        }
        return 0;
    }
}
[System.Serializable]
public class ScoreData
{
    public int score;
    public ScoreData(int score)
    {
        this.score = score;
    }
}

解説:
この方法は、インスペクター上で単純な enum を使って保存方法を選択できるため、実装がシンプルで分かりやすくなります。Awake メソッドで選択に応じた実装クラスを生成するため、拡張性も確保できます。


3. 実行用サンプル

HighScoreManager の機能を呼び出すサンプルコードです。
このサンプルでは、同じ GameObject にアタッチされた HighScoreManager コンポーネントを取得し、S キーでランダムなハイスコアを保存、L キーで保存されたハイスコアを読み込む例を示しています。

using UnityEngine;

public class HighScoreDemo : MonoBehaviour
{
    private HighScoreManager highScoreManager;

    void Awake()
    {
        // 同じ GameObject にアタッチされた HighScoreManager コンポーネントを取得
        highScoreManager = GetComponent<HighScoreManager>();

        if (highScoreManager == null)
        {
            Debug.LogError("HighScoreManager コンポーネントが見つかりません!");
        }
    }

    void Update()
    {
        // S キーを押すとランダムなスコアを保存
        if (Input.GetKeyDown(KeyCode.S))
        {
            highScoreManager.currentScore = Random.Range(0, 100);
            highScoreManager.SaveHighScore();
            Debug.Log("保存: ハイスコア " + highScoreManager.currentScore);
        }

        // L キーを押すと保存されたスコアを読み込み
        if (Input.GetKeyDown(KeyCode.L))
        {
            highScoreManager.LoadHighScore();
            Debug.Log("読込: ハイスコア " + highScoreManager.currentScore);
        }
    }
}

4. まとめ

  • ポリモーフィズムの活用:
    共通の抽象クラス HighScoreSaver を定義することで、異なる保存方法(PlayerPrefs、JSON)を同一のインターフェイスで扱うことができ、将来的な拡張が容易です。
  • インスペクターからの選択:
  • [SerializeReference] を利用すると、具象クラスのインスタンスを直接割り当て可能ですが、カスタムエディターの実装が必要な場合があります。
  • enum を利用する方法は、シンプルに選択肢を提供でき、実装も直感的です。

以上の実装例を参考に、プロジェクトの要件に合わせたハイスコアの保存システムを構築してください。