めーぷるのおもちゃばこ

- アイドルになりたいエンジニア女子の制作日記 -

【Unity】UnityでCSVファイルを読み込む

UnityでCSVファイルを読み込む方法の備忘録🐼

基本のやり方

CSVファイルの準備

グーグルのスプレッドシートでセルに分けて書いたものを.csv形式でダウンロードしました

f:id:maplesyrup-cs6:20191211113805p:plain
スプレッドシートはこんな感じで書いたよ



このtest.csvをダブルクリックで開くとNumbersってアプリで開かれてこんな感じで見れる。

f:id:maplesyrup-cs6:20191211113921p:plain
.csvをNumbersで開いた感じ


Unityで読み込む

この.csvファイルを、UnityのAssets>Resourcesに入れます。Resourcesフォルダがない場合は作って入れます。

f:id:maplesyrup-cs6:20191211114324p:plain
Assets>Resourcesに入れる


新しいC#スクリプトを作り、以下を記入します。

using System.IO;  //付け足す

public class CSVReader : MonoBehaviour
{
    private TextAsset _csvFile;   //CSVファイル

    private List<string[]> _csvData = new List<string[]>();  //CSVファイルの中身を入れるリスト
    
    void Start()
    {
        _csvFile = Resources.Load("test") as TextAsset;   //Resourceにある指定のパスのCSVファイルを格納
        StringReader reader = new StringReader(_csvFile.text);      //TextAssetをStringReaderに変換

        while(reader.Peek() != -1){               
            string line = reader.ReadLine();   //1行ずつ読む 
            _csvData.Add(line.Split(','));    //読みこんだDataをリストにAddする
        }
        
        for(int i = 0; i < _csvData.Count; i++){
                Debug.Log("名前::::" + _csvData[i][0] + ",  HP::::" + _csvData[i][1]);
        }
    }
}


こんな感じになります。

f:id:maplesyrup-cs6:20191211114725p:plain
結果


CSVファイル別の書き方

ちなみに、セルに分けて書かずに1つのセルにカンマで区切って書いた場合も同じようにできました。

f:id:maplesyrup-cs6:20191211125113p:plain
カンマで区切って書いた場合


違うところは、1つのセルにカンマで区切っていくつか要素をかくと、
デバッグログで表示した時になぜか " " で囲まれました。こっからここまで1つのセルの要素だよってことかな?

f:id:maplesyrup-cs6:20191211125427p:plain
カンマで区切った結果

デバッグログに表示する要素を1つ最後のやつを消したら終わりの " が消えたのでそうっぽい。

f:id:maplesyrup-cs6:20191211125804p:plain
最後の”が消えた


基本のやり方解説

テキストアセット

private TextAsset _csvFile;   //CSVファイル

TextAssetはUnityにインポートされたテキストファイルのための形式で、.csvや.html、.txtなどのファイルをUnityに入れた時にこのTextAssetに変換されるようです。
細かいことはこちらのリファレンスを↓
docs.unity3d.com
ーー

リソースのファイルを読み込む

_csvFile = Resources.Load("test") as TextAsset;

これはAssets>Resourceにあるファイルを読み込みます。
引数にはPathを渡すので、Resource下にフォルダを作って.csvファイルを入れた場合、
例えば Resource>CSV>test.csvにある時は以下のように書きます。.csvという拡張子は書かなくてオッケーです。

_csvFile = Resources.Load("CSV/test2") as TextAsset;

ーー

String Reader

StringReader reader = new StringReader(_csvFile.text); 

StringReaderはSystem.IOを書くことで使えます。
TextAssetのままだと、カンマや改行を含む文字列が全て連結されたままなので、ここでStringReaderに変換しています。

while(reader.Peek() != -1){               
    string line = reader.ReadLine();    
    _csvData.Add(line.Split(','));   
}

StringReaderの.Peek()は次に読み込める文字がなくなると-1になります。
.Peekが-1になるまで .ReadLineで1行ずつ読み込んでいます。
line.Split(",")でカンマで区切ってListにAddすることができます。



1つのセルの中で改行したい

キャラのステータスなどを登録する分には上記のでいいんですが、例えばセリフをCSVで読み込みたいときなどに、一つのセリフだけど改行入れたい。みたいなことがあると思います。

わたし、めーぷるっていうの!
パンダが大好きなんだよ!えへへ
これからもよろしくね!

みたいに、一つのセリフとして出したいけど、改行したい!みたいな。
それをやりたくて、セルの中で改行してみたり、改行マーク?の「\n」を入れてみましたが、改行する前の文字列しか取ってくれなかったり、「\n」は文字列として捉えられちゃってダメでした。

f:id:maplesyrup-cs6:20191230015317p:plain
セルの中で改行したり改行マーク入れてもダメだった

だけど改行分セルを分けてしまうのも使いにくいので、以下のようにしました。

using System.IO;
using UnityEngine.UI;

public class CSVReader : MonoBehaviour
{ 
    public Text _text;      //結果を表示するテキスト

    private Character _chara = new Character();
    private TextAsset _csvFile;
    private List<string[]> _csvData = new List<string[]>();

    //最終的なデータを入れるリストを付け足す
    private List<string> _replacedData = new List<string>();   
    
    void Start()
    {
        _csvFile = Resources.Load("CSV/test2") as TextAsset;
        StringReader reader = new StringReader(_csvFile.text);

        Debug.Log(_csvFile.text);

        while(reader.Peek() != -1){
            string line = reader.ReadLine();
            _csvData.Add(line.Split(','));
        }
        

       //////ここから先が付け足した部分///////
        for(int i = 0; i < _csvData.Count; i++)
        {
            string arr = _csvData[i][0].Replace("<>", "\n");   
            _replacedData.Add(arr);
        }

        _text.text = _replacedData[0];
    }

さっきと違う部分に注目です。

        for(int i = 0; i < _csvData.Count; i++)
        {
            string arr = _csvData[i][0].Replace("<>", "\n");   
            _replacedData.Add(arr);
        }

        _text.text = _replacedData[0];

最初に読み込んだCSVのデータの文字列から「<>」の部分を .Replaceを使って「\n」に変換しています。CSVファイルの方は、改行したい部分に<>を入れています。

f:id:maplesyrup-cs6:20191230024536p:plain
改行したいところに<>を入れる

別に「<>」じゃなくてもいいのですが、思わぬところで改行されてしまわないように、会話のなかでてこなさそうな文字列をチョイスしました。

CSVで「\n」を入れていても改行コードとして読み込んでくれないですが、stringでは改行コードとして扱ってくれるため、一度読み込んだCSVデータから特定の文字列(ここでは<>)を「\n」に変換してstringに格納することで表示した時に改行されるようにしています。

f:id:maplesyrup-cs6:20191230025344p:plain
実行結果


場面やキャラごとに整理したい

改行できるようになったら次はキャラごととか、同じキャラでも場面ごととかでセリフを読み取れるようにしたくなったりします。
以下のように、一つのCSVからパンダさんのセリフとねこちゃんのセリフを取って切り替えられるようにしました。

f:id:maplesyrup-cs6:20191230031851g:plain
キャラごとにセリフを切り替える


CSVファイルは以下のようにしています。
1の列横向きにパンダのセリフ、2の列横向きにねこちゃんのセリフを書いています。

f:id:maplesyrup-cs6:20191230160811p:plain
CSVファイル


コードは以下の通りです。

using System.IO;

public class CSVReader : MonoBehaviour
{
    private TextAsset _csvFile;
    private List<string[]> _csvData = new List<string[]>();
    private List<string> _replacedData = new List<string>();

    //最終的なデータを入れる配列をさらに作る
    private List<string[]> _lineData = new List<string[]>();

    
    void Start()
    {
        _csvFile = Resources.Load("CSV/test2") as TextAsset;
        StringReader reader = new StringReader(_csvFile.text);

        Debug.Log(_csvFile.text);

        while(reader.Peek() != -1){
            string line = reader.ReadLine();
            _csvData.Add(line.Split(','));
        }


        /////ここから先が書き換えた部分/////
        for (int y = 0; y < _csvData.Count; y++)
        {
            for (int x = 0; x < _csvData[y].Length; x++)
            {
                //x軸一列分の<>を\nに変換して新しいリストにAdd
                string arr = _csvData[y][x].Replace("<>", "\n");   
                _replacedData.Add(arr);
             }

            //横一列分の数のstring配列を作る
            string[] data = new string[_replacedData.Count];

            //横一列分をストリング配列に入れて
            for (int i = 0; i < _replacedData.Count; i++)
            {
                data[i] = _replacedData[i];
            }

            //それを最終的なデータリストに入れている
            _lineData.Add(data);

            //一時的に使用する_replacedDataのリストを空にする
            _replacedDatas.Clear();
          }
    }

    public string[] GetLines(int charaNum)
    {
        return _lineData[charaNum];
    }
}


書き換えた部分に注目です。

for (int y = 0; y < _csvData.Count; y++)
        {
            for (int x = 0; x < _csvData[y].Length; x++)
            {
                //x軸一列分の<>を\nに変換して新しいリストにAdd
                string arr = _csvData[y][x].Replace("<>", "\n");   
                _replacedData.Add(arr);
             }

            //横一列分の数のstring配列を作る
            string[] data = new string[_replacedData.Count];

            //横一列分をストリング配列に入れて
            for (int i = 0; i < _replacedData.Count; i++)
            {
                data[i] = _replacedData[i];
            }

            //それを最終的なデータリストに入れている
            _lineData.Add(data);

            //一時的に使用する_replacedDataのリストをからにする
            _replacedDatas.Clear();
          }

まず、for文の中の変数宣言が「i」ではなく「y」と「x」になっています。
これはわかりやすくするためなのですが、CSVの読み取ったListデータが

_data[縦軸][横軸]

になっていたためわかりやすくするためにyとxにしました。

f:id:maplesyrup-cs6:20191230033323p:plain
読み取り方向がわかりやすいようにxとyにしている


その時のy軸に対してx方向にあるセルを改行付きに変換して一旦リストに入れて

for (int x = 0; x < _csvData[y].Length; x++)
{
    //x軸一列分の<>を\nに変換して新しいリストにAdd
    string arr = _csvData[y][x].Replace("<>", "\n");   
    _replacedData.Add(arr);
}


そのx軸のセルの数のstring配列を作り、変換した結果が入れてあるリストをそのまま配列にぶち込みます。

//横一列分の数のstring配列を作る
string[] data = new string[_replacedData.Count];

//横一列分をストリング配列に入れて
for (int i = 0; i < _replacedData.Count; i++)
{
   data[i] = _replacedData[i];
}


そのstring配列を最終的なデータリスト List に入れて

//それを最終的なデータリストに入れている
_lineData.Add(data);


一時的に変換したものを入れていた_replacedDataを空にして
次にy軸が1進んだ時のx軸セルだけを入れれるようにします。

//一時的に使用する_replacedDataのリストをからにする
_replacedDatas.Clear();


こうすることで、最終的な_lineDataには、
_lineData[0]にはy軸0の横一列(x軸)のstring配列データが入っていることになります。
つまり、縦軸でキャラやシーンを別にして横軸にそのキャラやシーンで使うセリフを入れておけば、キャラ番号(縦軸)を指定することでそのキャラのセリフを取得することができます。

外部からキャラ番号(縦軸番号)を受け取ってその横一列分のstringデータを返します。

public string[] GetLines(int charaNum)
{
    return _lineData[charaNum];
}


セリフを表示する側はキャラの番号を渡して一度string配列に入れ、それを1ずつ進めることでボタンを押すたびにそのボタンに割り当てられたキャラのセリフが進むという形にしています。

using UnityEngine.UI;

public class LineChanger : MonoBehaviour
{        
    [SerializeField]
    private CSVReader _csvReader;

    [SerializeField]
    private Text[] _lineTextField;

    private int[] _lineNum = new int[2];

    public void LineChange(int charaNum)
    {
        string[] strings = _csvReader.GetLines(charaNum);

        if(_lineNum[charaNum] >= strings.Length) 
        {
            _lineNum[charaNum] = 0;
        }

        _lineTextField[charaNum].text = strings[_lineNum[charaNum]]; 
        _lineNum[charaNum]++;
    }
}


これで1つのCSVファイルからキャラ別にセリフを切り替えることができました!

f:id:maplesyrup-cs6:20191230031851g:plain
1つのCSVからキャラごとにセリフを切り替える




以上です。
ゲームでキャラのセリフ進めてく仕組み的なのを作りたくてやったんですが
他にもっと良いやり方あったら誰か教えてください!