👌

UnityでCSVファイルをアセット用に変換する

に公開

webで調べてみるとちらほら出るが、あまり簡易的に行えている印象がない。
そこで、『指定されたパスにあるcsvを読んで、指定されたパスにあるScriptableObjectのDBに入れる処理』を、Unity上部のメニューバーから実行できるようにするためのスクリプトを紹介します。
([MenuItem("Tools/CSV/Import Sample.csv")] がメニューバーからの実行パス)
イメージは、カードゲームを想定して、id, cardName, valueを持つcsvファイルを読み込む感じ。

行っている処理としては、文字列として読み込んで、エラーがないように検証しながらパースしている感じ

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

public class TmpCardData : ScriptableObject
{
    [Header("Informations")]
    public int id;
    public string cardName;
    public float value;
}

public class TmpCardDB : ScriptableObject
{
    public TmpCardData[] cardDatas;
}


public static class CsvSample
{
    private const string SampleCsvPath = "Assets/Editor/Sample.csv";
    private const string SampleOutputPath = "Assets/Editor/SampleOutput.txt";

    private struct SampleCsvRow
    {
        public int id;
        public string cardName;
        public float value;
    }

    [MenuItem("Tools/CSV/Import Sample.csv")]
    public static void ImportSampleCsv()
    {
        var cardDb = AssetDatabase.LoadAssetAtPath<TmpCardDB>(SampleOutputPath);
        if (cardDb == null)
        {
            Debug.LogError($"CardImport: Could not load CardDB asset at {SampleOutputPath}");
            return;
        }

        var csvAsset = AssetDatabase.LoadAssetAtPath<TextAsset>(SampleCsvPath);
        if (csvAsset == null)
        {
            Debug.LogError($"CardImport: Could not find CSV at {SampleCsvPath}");
            return;
        }

        var cards = CreateCardDataList(csvAsset);

        RemoveExistingCards(cardDb);
        AddCardsToDatabase(cardDb, cards);

        EditorUtility.SetDirty(cardDb);
        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
    }

    private static void RemoveExistingCards(TmpCardDB cardDb)
    {
        cardDb.cardDatas = Array.Empty<TmpCardData>();
        var subAssets = AssetDatabase.LoadAllAssetRepresentationsAtPath(SampleOutputPath);
        foreach (var asset in subAssets)
        {
            if (asset is TmpCardData)
            {
                UnityEngine.Object.DestroyImmediate(asset, true);
            }
        }
    }

    private static void AddCardsToDatabase(TmpCardDB cardDb, List<TmpCardData> cards)
    {
        cardDb.cardDatas = cards.ToArray();
        for (int i = 0; i < cardDb.cardDatas.Length; i++)
        {
            var card = cardDb.cardDatas[i];
            card.name = $"{card.id}_{card.cardName}";
            AssetDatabase.AddObjectToAsset(card, cardDb);
        }
    }

    private static List<TmpCardData> CreateCardDataList(TextAsset csvAsset)
    {
        return CreateCardDataList(csvAsset.text);
    }

    private static List<TmpCardData> CreateCardDataList(string csvText)
    {
        var cards = new List<TmpCardData>();
        if (string.IsNullOrEmpty(csvText))
        {
            return cards;
        }

        foreach (var entry in EnumerateRows(csvText))
        {
            if (!TryParseRow(entry.cells, out var row, out var error))
            {
                Debug.LogError($"CardImport: line {entry.lineNumber}: {error}");
                continue;
            }

            cards.Add(CreateCard(row));
        }

        return cards;
    }

    private static IEnumerable<(int lineNumber, string[] cells)> EnumerateRows(string csvText)
    {
        using var reader = new StringReader(csvText);
        int lineNumber = 0;
        bool skipHeader = true;

        while (true)
        {
            var line = reader.ReadLine();
            if (line == null)
            {
                yield break;
            }

            lineNumber++;

            if (string.IsNullOrWhiteSpace(line))
            {
                continue;
            }

            if (skipHeader)
            {
                skipHeader = false;
                continue;
            }

            yield return (lineNumber, SplitCsvLine(line));
        }
    }
    private static bool TryParseRow(string[] cells, out SampleCsvRow row, out string error)
    {
        row = default;
        error = string.Empty;

        if (cells.Length < 3)
        {
            error = $"Expected 3 columns but got {cells.Length}.";
            return false;
        }

        row.cardName = cells[1];
        row.value = float.TryParse(cells[2], out var value) ? value : 0f;
        return true;
    }

    private static string[] SplitCsvLine(string line)
    {
        var raw = line.Split(',');
        for (int i = 0; i < raw.Length; i++)
        {
            raw[i] = raw[i].Trim();
        }

        return raw;
    }

    private static TmpCardData CreateCard(SampleCsvRow row)
    {
        var card = ScriptableObject.CreateInstance<TmpCardData>();
        card.id = row.id;
        card.cardName = row.cardName;
        card.value = row.value;
        return card;
    }
}

Discussion