0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

csvからアセットファイルを作成する話

Posted at

今回はUnityでゲームを開発中に、登場するキャラクターのマスターデータをScriptableObjectに詰めていくときに、膨大なデータをひとつひとつ詰めていくのは億劫で、Editor拡張を使って一括でアセットファイルを作ってみたので共有です。

環境

macOS Ventura 13.3.1

Unity 2021.3.20f1

Editor拡張って??

Editor拡張とは、Unityエディタの機能を拡張したり、そのためのスクリプトのことを言います。

今回はデータをインポートするために使いますが、独自のウィンドウを作成したり、ボタンの色を変えたり、作りたいゲームに応じて様々な拡張機能をつくることができます。

Editor拡張を書くときはUnityEditorクラスを使います。こちらはGitHubで公開されているので下から参考にできます。

前準備

ScriptableObjectの作成

マスタデータとなるキャラクターのデータをScriptableObjectとして作成します。(ポケモンのキャラクターを想定して書いていきます)

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

// ポケモンのマスターデータ
public class PokeonBase : ScriptableObject
{
    // 名前、タイプ
    public string Name;
    public PokemonType Type1;
    public PokemonType Type2;

    // ステータス
    public int Hp;
    public int Attack;
    public int Defense;
    public int SpecialAttack;
    public int SpecialDefense;
    public int Speed;

}

// タイプマスタ
public enum PokemonType
{
    None,
    ノーマル,
    ほのお,
    みず,
    でんき,
    くさ,
    こおり,
    かくとう,
    どく,
    じめん,
    ひこう,
    エスパー,
    むし,
    いわ,
    ゴースト,
    ドラゴン,
    あく,
    はがね
}

今回はキャラクターに「名前」「タイプ」「ステータス」を持たせます。
タイプはPokemonTypeというクラスを別で作成してそれを使うことにします。

csvファイルの準備

ScriptableObjectに詰め込んでいくためのデータを準備します。

二度手間になるかもしれませんが、今回はポケモンの攻略サイトからExcelにデータを落とし込んで、csvでエクスポートしていきます。

お世話になるサイトは毎度お馴染み「ポケモン徹底攻略」さん。通称「ポケ徹」。

ポケモンの図鑑情報や詳細なデータを一覧で見れたり、バトルデータやポケモンの育成論など、ポケモンバトルには欠かせないサイトになっています。

作成したcsvの中身はこちら(一部)

PokemonBase.csv
番号,名前,Type1,Type2,Hp,Attack,Defense,SpecialAttack,SpecialDefense,Spped,,,
1,フシギダネ,くさ,どく,45,49,49,65,65,45,,,
2,フシギソウ,くさ,どく,60,62,63,80,80,60,,,
3,フシギバナ,くさ,どく,80,82,83,100,100,80,,,
4,ヒトカゲ,ほのお,,39,52,43,60,50,65,,,
5,リザード,ほのお,,58,64,58,80,65,80,,,
6,リザードン,ほのお,ひこう,78,84,78,109,85,100,,,
7,ゼニガメ,みず,,44,48,65,50,64,43,,,
8,カメール,みず,,59,63,80,65,80,58,,,
9,カメックス,みず,,79,83,100,85,105,78,,,

今回は一旦ホウエン地方まで(図鑑番号386のデオキシス)作っていきます。

インポートを実行するcsvファイルをセットできるScriptableObjectを作成

UnityのInspectorウィンドウに読み込むcsvをセットしてインポートできるようにしていきます。
データインポート用のScriptableObjectアセットを作成して行く感じです。

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

[CreateAssetMenu(menuName = "MyScriptable/Create CSV Importer")]
public class CsvImporter : ScriptableObject
{
    public TextAsset csvFile;
}

インポート対象のcsvファイルへの参照をセットしたいのでそのためのフィールドを作成します。TextAssetで大丈夫です。

CreateAssetMenuでインポート用のアセットファイルを作成できるようにしておきます。

このアセットファイルを作成して選択してからInspectorウィンドウを見てみると
スクリーンショット 2023-05-01 0.19.00.png
csvファイルをセットするフィールドが出てきています。ここにインポートしたいcsvデータをセットする感じです。

Editor拡張で読み込みボタンを作成して、インポート処理を実装

Editor拡張を行うときはスクリプトファイルをEditorフォルダの中に置きます。このEditorフォルダは特殊なフォルダで、ここに置かれたスクリプトはUnityエディタでのみ使うスクリプトとして扱われます。

EditorフォルダはAssetフォルダより下であればどこに配置しても大丈夫です。

今回はAssetフォルダ直下に作成しました。
このEditor直下にEditor拡張スクリプトを作成していきます。

ざっと中身を先に書きます。

CsvImporterEditor
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(CsvImporter))]
public class CsvImporterEditor : Editor
{
    public override void OnInspectorGUI()
    {
        var csvImporter = target as CsvImporter;
        DrawDefaultInspector();

        if (GUILayout.Button("data create"))
        {
            Debug.Log(csvImporter.csvFile.name);

            Debug.Log("ポケモンデータの作成を開始します。");
            SetPokemonCsvDataToScritableObject(csvImporter
        }
    }

    void SetPokemonCsvDataToScritableObject(CsvImporter csvImporter)
    {
        // パースを実行
        if (csvImporter.csvFile == null)
        {
            Debug.LogWarning(csvImporter.name + " : 読み込むCSVファイルがセットされていません。");
            return;
        }

        // csvファイルをstring形式に変換
        string csvText = csvImporter.csvFile.text;

        // 改行ごとにパース
        string[] afterParse = csvText.Split("\n");
        Debug.Log(afterParse.Length);

        // ヘッダー行を除いてインポート
        for (int i = 1; i < afterParse.Length; i++)
        {
            string[] parseByComma = afterParse[i].Split(",");
            Debug.Log(parseByComma);
            int column = 1;

            if (parseByComma[0] == "")
            {
                continue;
            }

            // 行数をIDとしてファイルを作成
            string path = "Assets/Resources/Pokemons/" + i.ToString() + ".asset";

            // EnemyDataのインスタンスをメモリ上に作成
            var pokemonData = CreateInstance<PokeonBase>();

            // 名前
            pokemonData.Name = parseByComma[column];

            // Type1
            column += 1;
            string type1 = parseByComma[column];
            if (type1 == "ノーマル")
            {
                pokemonData.Type1 = PokemonType.ノーマル;
            }
            else if (type1 == "ほのお")
            {
                pokemonData.Type1 = PokemonType.ほのお;
            }
            else if (type1 == "みず")
            {
                pokemonData.Type1 = PokemonType.みず;
            }
            else if (type1 == "でんき")
            {
                pokemonData.Type1 = PokemonType.でんき;
            }
            else if (type1 == "くさ")
            {
                pokemonData.Type1 = PokemonType.くさ;
            }
            else if (type1 == "こおり")
            {
                pokemonData.Type1 = PokemonType.こおり;
            }
            else if (type1 == "エスパー")
            {
                pokemonData.Type1 = PokemonType.エスパー;
            }
            else if (type1 == "かくとう")
            {
                pokemonData.Type1 = PokemonType.かくとう;
            }
            else if (type1 == "どく")
            {
                pokemonData.Type1 = PokemonType.どく;
            }
            else if (type1 == "じめん")
            {
                pokemonData.Type1 = PokemonType.じめん;
            }
            else if (type1 == "ひこう")
            {
                pokemonData.Type1 = PokemonType.ひこう;
            }
            else if (type1 == "むし")
            {
                pokemonData.Type1 = PokemonType.むし;
            }
            else if (type1 == "いわ")
            {
                pokemonData.Type1 = PokemonType.いわ;
            }
            else if (type1 == "ゴースト")
            {
                pokemonData.Type1 = PokemonType.ゴースト;
            }
            else if (type1 == "ドラゴン")
            {
                pokemonData.Type1 = PokemonType.ドラゴン;
            }
            else if (type1 == "あく")
            {
                pokemonData.Type1 = PokemonType.あく;
            }
            else if (type1 == "はがね")
            {
                pokemonData.Type1 = PokemonType.はがね;
            }


            // Type2
            column += 1;
            string type2 = parseByComma[column];
            if (type2 == "ノーマル")
            {
                pokemonData.Type2 = PokemonType.ノーマル;
            }
            else if (type2 == "ほのお")
            {
                pokemonData.Type2 = PokemonType.ほのお;
            }
            else if (type2 == "みず")
            {
                pokemonData.Type2 = PokemonType.みず;
            }
            else if (type2 == "でんき")
            {
                pokemonData.Type2 = PokemonType.でんき;
            }
            else if (type2 == "くさ")
            {
                pokemonData.Type2 = PokemonType.くさ;
            }
            else if (type2 == "こおり")
            {
                pokemonData.Type2 = PokemonType.こおり;
            }
            else if (type2 == "エスパー")
            {
                pokemonData.Type2 = PokemonType.エスパー;
            }
            else if (type2 == "かくとう")
            {
                pokemonData.Type1 = PokemonType.かくとう;
            }
            else if (type2 == "どく")
            {
                pokemonData.Type2 = PokemonType.どく;
            }
            else if (type2 == "じめん")
            {
                pokemonData.Type2 = PokemonType.じめん;
            }
            else if (type2 == "ひこう")
            {
                pokemonData.Type2 = PokemonType.ひこう;
            }
            else if (type2 == "むし")
            {
                pokemonData.Type2 = PokemonType.むし;
            }
            else if (type2 == "いわ")
            {
                pokemonData.Type2 = PokemonType.いわ;
            }
            else if (type2 == "ゴースト")
            {
                pokemonData.Type2 = PokemonType.ゴースト;
            }
            else if (type2 == "ドラゴン")
            {
                pokemonData.Type2 = PokemonType.ドラゴン;
            }
            else if (type2 == "あく")
            {
                pokemonData.Type2 = PokemonType.あく;
            }
            else if (type2 == "はがね")
            {
                pokemonData.Type2 = PokemonType.はがね;
            }
            else if (type2 == "")
            {
                pokemonData.Type2 = PokemonType.None;
            }

            // Hp
            column += 1;
            pokemonData.Hp = int.Parse(parseByComma[column]);

            // Attack
            column += 1;
            pokemonData.Attack = int.Parse(parseByComma[column]);

            // Defense
            column += 1;
            pokemonData.Defense = int.Parse(parseByComma[column]);

            // sAttack
            column += 1;
            pokemonData.SpecialAttack = int.Parse(parseByComma[column]);

            // sDefense
            column += 1;
            pokemonData.SpecialDefense = int.Parse(parseByComma[column]);

            // Speed
            column += 1;
            pokemonData.Speed = int.Parse(parseByComma[column]);


            // インスタンス化したものをアセットとして保存
            var asset = (PokeonBase)AssetDatabase.LoadAssetAtPath(path, typeof(PokeonBase));
            if (asset == null)
            {
                // 指定のパスにファイルが存在しない場合は新規作成
                AssetDatabase.CreateAsset(pokemonData, path);
            }
            else
            {
                // 指定のパスに既に同名のファイルが存在する場合は更新
                EditorUtility.CopySerialized(pokemonData, asset);
                AssetDatabase.SaveAssets();
            }
            AssetDatabase.Refresh();
        }
        Debug.Log(csvImporter.name + " : ポケモンデータの作成が完了しました。");
    }
}

data createボタンを作成し、それをトリガーにしてcsvファイルをインポートします。
中の処理は単純にcsvデータを行ごとにパースしていきアセットファイルを繰り返し作成しているだけです。

このスクリプトを保存しUnityエディタに戻ってみるとdata createボタンが表示されているはずです。
Csv Fileに先ほど作成したcsvファイルをセットして、data createを押すとcsvのデータを元にPokmeonBaseからアセットファイルが指定したパスに作成されていきます。
スクリーンショット 2023-05-01 0.37.17.png

まとめ

いやー、楽だw

開発中のめんどくさい作業も効率化するためにコードを書いていこうとする感じは、やっとプログラマーに慣れて来たかなと思いました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?