ゲームデータについて(3)固定データのつづき

前記事の続きです。 本記事では作りかけのコンバータの修正とゲームデータ管理クラスの作成をしてきます。

コンバータの修正

前記事で作成したAssets/Editor/FixedDataConverter.csを開いて以下のように修正します。

using UnityEngine;
using UnityEngine.Assertions;
using UnityEditor;
using System.Collections.Generic; // ここを追加.
using System.IO;
using System.Runtime.Serialization.Formatters.Binary; // ここを追加.
using NPOI.XSSF.UserModel;
using NPOI.SS.UserModel;

#if UNITY_EDITOR
public class FixedDataConverter : EditorWindow
{
    // ここからを追加.
    // Excelファイルのヘッダ行数.
    private const int ROW_HEADER_NUM = 1;

    // Excelファイルのカラム定義.
    private enum MONSTER_COLUMN
    {
        ID,
        COLUMN_B, // 不使用.
        HP,
        MP,
        ATK,
        DEF,
        AGI,
    }
    // ここまでを追加.

(略)

    public void ConvertMonster()
    {
        using (FileStream readFileStream = new FileStream("Assets/Editor/GameData/fixedData.xlsx", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
        {
            XSSFWorkbook book = new XSSFWorkbook(readFileStream);
            if (book == null)
            {
                Assert.IsTrue(false);
                return;
            }

            ISheet sheet = book.GetSheet("Monster");
            if (sheet == null)
            {
                Assert.IsTrue(false);
                return;
            }

            // ここからを追加.
            int rowFirst = sheet.FirstRowNum + ROW_HEADER_NUM;
            int rowNum = sheet.LastRowNum - rowFirst + 1;

            List<FixedMonsterData> monsterList = FixedDataManager.monsterList;
            monsterList.list.Clear();

            for (int rowCnt = 0; rowCnt < rowNum; rowCnt++)
            {
                IRow row = sheet.GetRow(rowFirst + rowCnt);
                if (row == null)
                {
                    continue;
                }

                FixedMonsterData data = new FixedMonsterData();

                for (int columnCnt = row.FirstCellNum; columnCnt <= row.LastCellNum; columnCnt++)
                {
                    ICell cell = row.GetCell(columnCnt);
                    if (cell == null)
                    {
                        continue;
                    }

                    switch ((MONSTER_COLUMN)columnCnt)
                    {
                        case MONSTER_COLUMN.ID:
                            {
                                Assert.AreEqual((int)cell.NumericCellValue, rowCnt);
                            }
                            break;
                        case MONSTER_COLUMN.HP:
                            {
                                data.hp = (int)cell.NumericCellValue;
                            }
                            break;
                        case MONSTER_COLUMN.MP:
                            {
                                data.mp = (int)cell.NumericCellValue;
                            }
                            break;
                        case MONSTER_COLUMN.ATK:
                            {
                                data.attack = (int)cell.NumericCellValue;
                            }
                            break;
                        case MONSTER_COLUMN.DEF:
                            {
                                data.defense = (int)cell.NumericCellValue;
                            }
                            break;
                        case MONSTER_COLUMN.AGI:
                            {
                                data.agilty = (int)cell.NumericCellValue;
                            }
                            break;
                        default:
                            {
                                break;
                            }
                    }
                }
                monsterList.Add(data);
            }

            byte[] serializedData = null;

            using (MemoryStream memoryStream = new MemoryStream())
            {
                BinaryFormatter binaryFormatter = new BinaryFormatter();
                binaryFormatter.Serialize(memoryStream, monsterList);
                serializedData = memoryStream.ToArray();
            }

            using (FileStream serializeFile = new FileStream("Assets/ExternalResources/Resources/FixedData/FixedMonsterData.bytes", FileMode.Create))
            {
                serializeFile.Write(serializedData, 0, serializedData.Length);
            }

            Debug.Log("Succeeded in converting : FixedMonsterData");
            // ここまでを追加.
        }
    }
}
#endif // UNITY_EDITOR

コンバートボタンを押してAssets/ExternalResources/Resources/FixedDataに以下にファイルFixedMonsterData.bytesが作成されることを確認します。
(ディレクトリがないと失敗するのであらかじめ作成しておく)

ゲームデータ管理クラスの作成

固定データを含むゲームデータ全般を管理するクラスを作成します。
Assets/GameDataにGameDataManager.csというファイルを作成します。

public static class GameDataManager
{
    static GameDataManager()
    {
    }

    public static void Initialize()
    {
        // 固定データのロード.
        FixedDataManager.Serialize();
    }
}

適当な場所で

GameDataManager.Initialize();

を呼んで固定データが正常に読まれることを確認します。
その後

FixedDataManager.GetMonsterData(int);

で固定データを取得できることを確認します。
これで固定データを編集・コンバート・ロードする処理ができました。

余談

今回はゲームが参照する固定データをバイナリファイルとして管理する方法を紹介しましたが、昨今のPCゲームではゲームデータをXMLファイルとして管理しているケースも見られます。
XMLファイルで管理することのメリットは今回説明したようなコンバータを作る手間がなく、Excelのような専用のアプリケーションも必要としないという点が挙げられます。
デメリットとしては可視性が高くユーザーに容易に編集されてしまう点でしょうか。
(バイナリファイルも暗号化しなければ大した差はありませんが)
ただ、そのPCゲームがModを許容(あるいは推奨)するスタンスであれば、ユーザーがゲームを改変できてしまうというのはデメリットとは言えないのかもしれません。