Skip to content

Instantly share code, notes, and snippets.

@thebeardphantom
Last active January 27, 2024 11:06
Show Gist options
  • Save thebeardphantom/405ff72ef4621a2a9405976b66c16936 to your computer and use it in GitHub Desktop.
Save thebeardphantom/405ff72ef4621a2a9405976b66c16936 to your computer and use it in GitHub Desktop.
An example of generating ScriptableObjects and populating their serialized properties via CSV files.
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
public static partial class CsvDataGenerator
{
#region Fields
private const string OUTPUT_FOLDER_GUID = "9009c602285b7e34ab2a99ab7de80022";
#endregion
#region Methods
[MenuItem("Utils/CSV Generation/Online")]
private static void GenerateOnline()
{
Generate(true);
}
[MenuItem("Utils/CSV Generation/Offline")]
private static void GenerateOffline()
{
Generate(false);
}
private static void Generate(bool online)
{
var csvFolder = AssetDatabase.GUIDToAssetPath("6d57745f5a886444caf5743b9c90fa47");
if (online)
{
AssetDatabase.ImportAsset(
csvFolder,
ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate | ImportAssetOptions.ImportRecursive);
}
var csvs = AssetDatabase.FindAssets(
"t:TextAsset",
new[]
{
csvFolder
})
.Select(AssetDatabase.GUIDToAssetPath)
.SelectMany(AssetDatabase.LoadAllAssetRepresentationsAtPath)
.Cast<TextAsset>()
.ToDictionary(t => t, t => new CsvFile(t));
var outputFolderPath = AssetDatabase.GUIDToAssetPath(OUTPUT_FOLDER_GUID);
try
{
AssetDatabase.StartAssetEditing();
foreach (var csv in csvs.Values)
{
for (var i = 0; i < csv.EntryCount; i++)
{
var subfolderName = csv["Subfolder"][i];
var assetName = csv["AssetName"][i];
var folder = $"{outputFolderPath}/{csv.Filename}/{subfolderName}";
Directory.CreateDirectory(folder);
var assetPath = $"{folder}/{assetName}.asset";
var asset = AssetDatabase.LoadAssetAtPath<ScriptableObject>(assetPath);
if (asset == null)
{
asset = ScriptableObject.CreateInstance(csv.Filename);
AssetDatabase.CreateAsset(asset, assetPath);
}
asset.name = assetName;
var so = new SerializedObject(asset);
so.Update();
foreach (var column in csv.Columns)
{
var property = FindProperty(so, column.ColumnName);
if (property == default)
{
continue;
}
switch (property.propertyType)
{
case SerializedPropertyType.Enum:
{
var enumValueIndex = string.IsNullOrEmpty(column[i])
? 0
: Array.IndexOf(property.enumNames, column[i]);
property.enumValueIndex = enumValueIndex;
break;
}
case SerializedPropertyType.Float:
{
property.floatValue = float.Parse(column[i]);
break;
}
case SerializedPropertyType.Integer:
{
property.intValue = int.Parse(column[i]);
break;
}
case SerializedPropertyType.String:
{
property.stringValue = column[i];
break;
}
}
}
so.ApplyModifiedProperties();
EditorUtility.SetDirty(asset);
}
}
}
finally
{
AssetDatabase.StopAssetEditing();
AssetDatabase.SaveAssets();
}
}
private static SerializedProperty FindProperty(SerializedObject so, string name)
{
var sb = new StringBuilder();
var parts = name.Split('.');
foreach (var part in parts)
{
var arrayMatch = Regex.Match(part, @"(.*?)\[(\d+)\]$");
if (arrayMatch.Success)
{
var arrayName = arrayMatch.Groups[1].Value;
var arrayIndex = arrayMatch.Groups[2].Value;
sb.Append($"<{arrayName}>k__BackingField.Array.data[{arrayIndex}].");
}
else
{
sb.Append($"<{part}>k__BackingField.");
}
}
// Remove last dot
sb.Remove(sb.Length - 1, 1);
var rebuiltPath = sb.ToString();
var property = so.FindProperty(rebuiltPath);
return property;
}
#endregion
}
using System;
using System.Linq;
using UnityEngine;
public static partial class CsvDataGenerator
{
#region Types
public class CsvFile
{
#region Types
public class Column
{
#region Fields
public readonly string ColumnName;
public readonly string[] Values;
#endregion
#region Constructors
public Column(string columnName, int valueCount)
{
ColumnName = columnName;
Values = new string[valueCount];
}
#endregion
#region Methods
public string this[int index] => Values[index];
#endregion
}
#endregion
#region Fields
public readonly string Filename;
public readonly int EntryCount;
public readonly Column[] Columns;
#endregion
#region Constructors
public CsvFile(TextAsset textAsset)
{
Filename = textAsset.name;
var fileContents = textAsset.text;
var lines = fileContents.Split(Environment.NewLine);
EntryCount = lines.Length - 1;
var columnNames = lines[0].Split(',');
Columns = new Column[columnNames.Length];
for (var i = 0; i < Columns.Length; i++)
{
Columns[i] = new Column(columnNames[i], EntryCount);
}
for (var i = 0; i < EntryCount; i++)
{
var line = lines[i + 1];
var values = line.Split(',');
for (var j = 0; j < values.Length; j++)
{
Columns[j].Values[i] = values[j];
}
}
}
#endregion
#region Methods
public Column this[string columnName]
{
get
{
return Columns.SingleOrDefault(c => c.ColumnName == columnName);
}
}
#endregion
}
#endregion
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment