i-school - Dictionary の実装例
 Dictionary の実装例を提示します。また List との違いを比較し、採用時のポイントを解説します。

 なお、サンプルコードの Dictionary の初期化には Target-typed new 表現を利用しています。



1.アイテムと弾薬の管理


 以下は、アイテムと弾薬の管理にDictionaryを活用する例です。

 このコードは、アイテムと弾薬の生成、管理、および使用を Dictionary によりサポートします。
enum を作成してそれぞれのキーとし、所持している数を値とします。


using System.Collections.Generic;

public enum ItemType
{
    HealthPotion,
    AmmoPack,
    Shield,
}

public enum AmmoType
{
    PistolAmmo,
    ShotgunShells,
    RifleAmmo,
}

public class GameManager : MonoBehaviour
{
    // アイテムのデータベース
    private Dictionary<ItemType, int> itemInventory = new ();

    // 弾薬のデータベース
    private Dictionary<AmmoType, int> ammoInventory = new ();

    private void Start()
    {
        // 初期アイテムと弾薬の設定
        itemInventory[ItemType.HealthPotion] = 3;
        itemInventory[ItemType.AmmoPack] = 5;
        itemInventory[ItemType.Shield] = 1;

        ammoInventory[AmmoType.PistolAmmo] = 50;
        ammoInventory[AmmoType.ShotgunShells] = 20;
        ammoInventory[AmmoType.RifleAmmo] = 100;

        // アイテムの使用例
        UseItem(ItemType.HealthPotion);
        UseItem(ItemType.AmmoPack);

        // 弾薬の使用例
        UseAmmo(AmmoType.PistolAmmo, 10);
        UseAmmo(AmmoType.ShotgunShells, 5);
    }

    // アイテムを使用するメソッド
    private void UseItem(ItemType itemType)
    {
        if (itemInventory.ContainsKey(itemType) && itemInventory[itemType] > 0)
        {
            // アイテムを使用する処理をここに追加
            Debug.Log($"アイテムを使用 : { itemType }");
            itemInventory[itemType]--;
        }
        else
        {
            Debug.Log($"アイテムが不足しています : { itemType }");
        }
    }

    // 弾薬を使用するメソッド
    private void UseAmmo(AmmoType ammoType, int amount)
    {
        if (ammoInventory.ContainsKey(ammoType) && ammoInventory[ammoType] >= amount)
        {
            // 弾薬を使用する処理をここに追加
            Debug.Log($"弾薬を使用 : { ammoType } (数量 : { amount })");
            ammoInventory[ammoType] -= amount;
        }
        else
        {
            Debug.Log($"弾薬が不足しています : { ammoType }");
        }
    }
}

 このサンプルコードでは、2つのDictionaryを使用してアイテムと弾薬を管理しています。

 Startメソッドで初期値を設定し、UseItemとUseAmmoメソッドを使用してアイテムと弾薬を使用できます。
アイテムと弾薬の種類はenumで管理され、それぞれの種類に対応する数量がDictionaryで追跡されます。

 アイテムと弾薬を使用する際には、Dictionaryをチェックして存在するか、数量が十分かどうかを確認し、必要に応じて減算します。

 このコードは、ゲーム内のアイテムと弾薬の管理に役立ち、追加の機能やロジックを組み込む際の基盤となります。


2.各プレイヤーのスコア管理


 プレイヤーのスコアを管理するためにDictionaryを使用できます。

 プレイヤーの名前をキーとし、スコアを値として保存します。


using System.Collections.Generic;

public class ScoreManager : MonoBehaviour
{
    private Dictionary<string, int> playerScores = new ();

    private void Start()
    {
        // プレイヤーのスコアの初期化
        playerScores["Player1"] = 0;
        playerScores["Player2"] = 0;
    }

    // スコアを増やすメソッド
    public void IncreaseScore(string playerName, int points)
    {
        if (playerScores.ContainsKey(playerName))
        {
            playerScores[playerName] += points;
        }
        else
        {
            Debug.Log($"プレイヤーが存在しません : { playerName }");
        }
    }
}

 このコードでは、プレイヤーごとにスコアを追跡し、IncreaseScoreメソッドでスコアを増やすことができます。


3.敵のドロップアイテム


 敵がアイテムをドロップする場合、Dictionaryを使用してアイテムの種類とドロップ確率を管理できます。
enum を作成してキーとし、ドロップ確率の値を値とします。


using System.Collections.Generic;

public enum ItemType
{
    HealthPotion,
    AmmoPack,
    Shield,
}

public class EnemyDropManager : MonoBehaviour
{
    private Dictionary<ItemType, float> dropTable = new ();

    private void Start()
    {
        // アイテムのドロップ確率の設定
        dropTable[ItemType.HealthPotion] = 0.3f;
        dropTable[ItemType.AmmoPack] = 0.2f;
        dropTable[ItemType.Shield] = 0.1f;
    }

    // 敵がアイテムをドロップするメソッド
    public ItemType DropItem()
    {
        float randomValue = Random.value;
        foreach (var kvp in dropTable)
        {
            if (randomValue < kvp.Value)
            {
                return kvp.Key;
            }
            randomValue -= kvp.Value;
        }
        // ドロップアイテムがない場合
        return ItemType.None;
    }
}

 このコードでは、ドロップ確率を持つアイテムと、ランダムにアイテムをドロップするメソッドがあります。
アイテムがドロップされる確率はDictionaryで管理されています。


4.クエスト進行の管理


 ゲーム内のクエストの進行状況をDictionaryを使用して管理できます。
クエストIDをキーとし、進行状況を enum として作成し、列挙子を値として保存します。


using System.Collections.Generic;

public enum QuestStatus
{
    InProgress,
    Completed,
    Failed,
}

public class QuestManager : MonoBehaviour
{
    private Dictionary<int, QuestStatus> questStatus = new ();

    private void Start()
    {
        // クエストの初期状態を設定
        questStatus[1] = QuestStatus.InProgress;
        questStatus[2] = QuestStatus.InProgress;
    }

    // クエストの進行状況を更新するメソッド
    public void UpdateQuestStatus(int questID, QuestStatus status)
    {
        if (questStatus.ContainsKey(questID))
        {
            questStatus[questID] = status;
        }
        else
        {
            Debug.Log($"クエストが存在しません : { questID }");
        }
    }
}

 このコードでは、クエストの進行状況を追跡し、UpdateQuestStatusメソッドで更新できます。


5.サーバーデータの送受信


 サーバー関係のデータ送受信にDictionaryを使用する一般的なケースは、サーバーからクライアントに送信されるデータを表現するためです。

 以下に、サーバーから送信されたプレイヤー情報をDictionaryを使って受信し、それをクライアントで利用するサンプルコードを示します。


サーバーから送信されたデータの例


<json>
{
    "players": {
        "player1": {
            "name": "Alice",
            "score": 100
        },
        "player2": {
            "name": "Bob",
            "score": 200
        },
        "player3": {
            "name": "Charlie",
            "score": 150
        }
    }
}

 このデータをクライアントで受信し、Dictionaryを使用して処理します。


クライアント側で受信する例



using System.Collections.Generic;
using UnityEngine;

public class PlayerInfo
{
    public string name;
    public int score;
}

public class ServerDataReceiver : MonoBehaviour
{
    // サーバーから送信されたデータを格納するDictionary
    private Dictionary<string, PlayerInfo> playerData = new ();

    // サーバーからのデータを受信する関数
    public void ReceiveServerData(string jsonString)
    {
        // JSONデータをデシリアライズしてDictionaryに格納
        Dictionary<string, Dictionary<string, object>> serverData = MiniJSON.Json.Deserialize(jsonString) as Dictionary<string, Dictionary<string, object>>;

        if (serverData != null && serverData.ContainsKey("players"))
        {
            Dictionary<string, object> players = serverData["players"];
            foreach (var player in players)
            {
                string playerId = player.Key;
                Dictionary<string, object> playerInfoDict = player.Value as Dictionary<string, object>;

                // PlayerInfoオブジェクトを作成してDictionaryに追加
                PlayerInfo playerInfo = new PlayerInfo
                {
                    name = playerInfoDict["name"].ToString(),
                    score = int.Parse(playerInfoDict["score"].ToString())
                };

                playerData[playerId] = playerInfo;
            }
        }
    }

    // クライアントでデータを利用する例
    public void UsePlayerData()
    {
        if (playerData.ContainsKey("player1"))
        {
            PlayerInfo player1Info = playerData["player1"];
            Debug.Log("Player1 Name: " + player1Info.name);
            Debug.Log("Player1 Score: " + player1Info.score);
        }
    }
}

 このサンプルコードでは、サーバーから送信されたJSONデータを受信し、MiniJSONを使用してデシリアライズします。
デシリアライズされたデータはDictionaryとして取得し、クライアント内で利用可能な形に変換しています。

 受信したデータをDictionaryに格納することで、各プレイヤーの情報を効率的に管理できます。
また、必要に応じてデータの更新や処理を行うことができます。


6.List と Dictionary の比較と使い分け方


 DictionaryとListは、C#でデータを管理するための異なるコレクション型です。
それぞれの使い分けには注意が必要です。以下に、DictionaryとListの主な違いと注意点を比較して解説します。


1.Dictionaryの特徴と注意点


  キーと値のペア

     Dictionaryはキーと値のペアを持つデータ構造です。キーを使って値にアクセスします。
    これは、一意の識別子を持つ要素を管理するのに適しています。例えば、プレイヤー名とスコア、アイテムの種類と数量など。

  高速な検索

     Dictionaryはキーに対する値を高速に検索できるため、大規模なデータセットにおいて効率的です。
    検索時間はデータのサイズに依存しないため、リストよりも適しています。

  不可視性(シリアライズ不可)

     Dictionaryはインスペクターには表示されません。
    通常、キーと値のペアを表現するためにカスタムエディタを実装するか、シリアライズ可能なカスタムクラスを使用する必要があります。



 Dictionaryはキーが一意でなければなりません。重複するキーを持つことはできません。
キーのハッシュコードが不変である必要があります。つまり、キーが変更されないようにする必要があります。


2.Listの特徴と注意点


  要素の一覧

     Listは要素の一覧を管理するためのデータ構造で、要素へのアクセスはインデックスを使用します。
    これは、順序が重要であり、要素の重複を許容する場合に適しています。

  データの順序

     Listは要素が追加された順序を保持します。これは、順序が重要な場合、例えばプレイヤーのターンの順序など、リストを選択する理由です。

  可視性(シリアライズ可能)

     Listは SerializeField属性、あるいは public 修飾子で宣言することでインスペクターに表示されます。
    List内の要素はリストとして表示され、要素の数や内容を直接確認、および要素を編集したり、並び順を変えたりすることができます。



 Listは要素の検索が遅いです。要素を特定の条件で検索する場合、Dictionaryよりも時間がかかることがあります。
要素の順序が維持されるため、要素の挿入や削除がDictionaryよりも遅い場合があります。


3.使い分けのポイント


  データの一意性

     キーと値のペアが必要であり、データの一意性が重要な場合はDictionaryを選択します。プレイヤーのスコア、アイテムの数量などが該当します。

  要素の順序
 
     要素の順序が重要であり、重複が許容される場合はListを使用します。プレイヤーのターン順序、アイテムのリストなどが該当します。

  データアクセスの効率

     データの高速な検索やアクセスが必要な場合はDictionaryが適しています。特定のキーを持つデータを素早く取得する必要がある場合に役立ちます。

  要素の追加/削除

     要素の順序が変更されることなく、要素の追加と削除が頻繁に行われる場合はListを使用します。リストは要素の追加と削除がDictionaryよりも効率的です。

  可視化とデバッグ効率

     ListはUnityのインスペクターで簡単に表示できますが、Dictionaryは通常カスタムエディタやカスタムクラスを使用して表示する必要があるという違いがあります。
    ただし、プラグイン(アセット)によっては、Dictionaryの直接的なサポートを提供するものも存在します。


4.シリアライズの方法


 List の場合には、以下のように、SerializeField属性か、public 修飾子を付与して変数の宣言を行います。

[SerializeField]
private List<GameObject> gameObjectsList;

public List<GameObject> gameObjectsList;

 このようなリストを持つスクリプトをアタッチした場合、Unityのインスペクターに gameObjectsList 変数が表示されます。
そのままインスペクターでも要素を追加、削除、編集することができます。



 Dictionary の場合にはそのままではシリアライズされずインスペクターには表示されませんので、
カスタムクラスを作成することで対応します。

[System.Serializable]
public class SerializableDictionary<TKey, TValue>
{
    public List<TKey> keys;
    public List<TValue> values;
}

 このように SerializableDictionary のようなカスタムクラスを作成し、これを使用してDictionaryをインスペクターで表示することができます。



 以上のように、適切なコレクション型を選択することは、ゲームのパフォーマンスとコードの保守性に影響を与える重要な決定です。
プロジェクトの要件とニーズに応じて、DictionaryとListを適切に使い分けることが大切です。


7.まとめ


 これらのサンプルコードは、Dictionaryを使用してゲーム内の様々な情報や状態を管理する方法を示しています。

 Dictionaryは非常に柔軟で強力なデータ構造であり、ゲーム内の多くの機能を実装する際に役立ちます。