👌
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