본문 바로가기
TIL

CSV 기반 아이템 SO 수정 시 CSV 자동 업데이트 기능 구현

by vvin39 2025. 7. 2.

내일배움캠프 59일차 TIL

인스펙터 창에서 ItemSO의 값을 수정하면, 해당 내용을 CSV 파일에 자동 반영하는 에디터 툴을 만들었다. 이를 구현하기 위해선 다음과 같은 흐름으로 작업이 진행했다.

목표 기능 요약

  1. ItemSO ScriptableObject의 값이 변경되면
  2. 지정한 ItemData.csv의 해당 행(idx 기반)을 찾아
  3. 변경 내용을 반영한 후 저장한다

필요 요소 정리

1. ItemSOPostprocessor.cs (Asset 변경 감지)

  • AssetModificationProcessor를 사용해 SO가 저장될 때 감지

2. ItemSOToCSVUpdater.cs (CSV 업데이트 로직)

  • ItemSO를 읽어서 지정된 ItemData.csv의 행을 수정

 

1. ItemSOPostprocessor.cs

#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEngine;

public class ItemSOPostprocessor : AssetPostprocessor
{
    static void OnPostprocessAllAssets(
        string[] importedAssets, string[] deletedAssets,
        string[] movedAssets, string[] movedFromAssetPaths)
    {
        foreach (var path in importedAssets)
        {
            if (path.EndsWith(".asset") && path.Contains("/08_ScriptableObjects/"))
            {
                ItemSO item = AssetDatabase.LoadAssetAtPath<ItemSO>(path);
                if (item != null)
                {
                    ItemSOToCSVUpdater.UpdateCSV(item);
                }
            }
        }
    }
}
#endif

 

2. ItemSOToCSVUpdater.cs

#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Collections.Generic;

public static class ItemSOToCSVUpdater
{
    static string csvPath = "Assets/09_Settings/ItemData.csv"; // 경로 수정 가능

    public static void UpdateCSV(ItemSO item)
    {
        if (!File.Exists(csvPath))
        {
            Debug.LogError("[CSV] 파일이 존재하지 않습니다: " + csvPath);
            return;
        }

        var lines = new List<string>(File.ReadAllLines(csvPath));

        if (lines.Count < 1) return;

        // 헤더에서 컬럼 인덱스 매핑
        var header = lines[0].Split(',');
        Dictionary<string, int> colMap = new();
        for (int i = 0; i < header.Length; i++)
            colMap[header[i].Trim()] = i;

        // 해당 item.idx에 해당하는 행 찾기
        for (int i = 1; i < lines.Count; i++)
        {
            var cols = lines[i].Split(',');
            if (cols.Length < 1) continue;

            if (cols[colMap["idx"]].Trim() == item.idx)
            {
                // 기존 데이터를 수정
                cols[colMap["itemName"]] = item.itemName;
                cols[colMap["description"]] = item.description.Replace(",", " ");
                cols[colMap["ItemTypes"]] = item.itemTypes.ToString().Replace(", ", "|");
                cols[colMap["stackable"]] = item.stackable ? "y" : "n";
                cols[colMap["maxStack"]] = item.maxStack.ToString();

                // 필요 시 ToolData, EquipableData 등도 추가 반영
                if (item.itemTypes.HasFlag(ItemType.Tool) && item.toolData != null)
                {
                    if (colMap.TryGetValue("toolType", out int idx)) cols[idx] = item.toolData.toolType.ToString();
                    if (colMap.TryGetValue("getAmount", out idx)) cols[idx] = item.toolData.getAmount.ToString();
                    if (colMap.TryGetValue("luck", out idx)) cols[idx] = item.toolData.luck.ToString();
                    if (colMap.TryGetValue("durability", out idx)) cols[idx] = item.toolData.durability.ToString();
                    if (colMap.TryGetValue("atkSpd", out idx)) cols[idx] = item.toolData.atkSpd.ToString();
                }

                if (item.itemTypes.HasFlag(ItemType.Equipable) && item.equipableData != null)
                {
                    if (colMap.TryGetValue("equipableType", out int idx)) cols[idx] = item.equipableData.equipableType.ToString();
                    if (colMap.TryGetValue("maxHealth", out idx)) cols[idx] = item.equipableData.maxHealth.ToString();
                    if (colMap.TryGetValue("atk", out idx)) cols[idx] = item.equipableData.atk.ToString();
                    if (colMap.TryGetValue("def", out idx)) cols[idx] = item.equipableData.def.ToString();
                    if (colMap.TryGetValue("spd", out idx)) cols[idx] = item.equipableData.spd.ToString();
                    if (colMap.TryGetValue("crt", out idx)) cols[idx] = item.equipableData.crt.ToString();
                }

                if (item.itemTypes.HasFlag(ItemType.Eatable) && item.eatableData != null)
                {
                    if (colMap.TryGetValue("recoverHP", out int idx)) cols[idx] = item.eatableData.recoverHP.ToString();
                    if (colMap.TryGetValue("fullness", out idx)) cols[idx] = item.eatableData.fullness.ToString();
                    if (colMap.TryGetValue("slimeGauge", out idx)) cols[idx] = item.eatableData.slimeGauge.ToString();
                    if (colMap.TryGetValue("duration", out idx)) cols[idx] = item.eatableData.duration.ToString();
                    if (colMap.TryGetValue("eatableRottable", out idx)) cols[idx] = item.eatableData.rottenable ? "y" : "n";
                }

                // 수정한 line으로 다시 합치기
                lines[i] = string.Join(",", cols);
                break;
            }
        }

        // 다시 쓰기
        File.WriteAllLines(csvPath, lines);
        AssetDatabase.Refresh();
        Debug.Log($"[CSV] {item.itemName} 수정 반영 완료");
    }
}
#endif

 

참고

  • ItemTypes는 "Tool|Equipable" 형식으로 "|" 구분자로 CSV에 저장됩니다.
  • description은 "," 때문에 CSV에서 깨질 수 있어 공백으로 대체 저장합니다.
  • idx는 고유값이므로 중복되지 않도록 주의해야 합니다.

 

사용법 요약

  1. ItemSO.asset을 수정 후 저장 (Ctrl+S 또는 Unity 저장)
  2. 자동으로 ItemData.csv의 해당 행이 수정됨
  3. 로그에 [CSV] 아이템명 수정 반영 완료 출력 확인

 

 

 

최근댓글

최근글

skin by © 2024 ttuttak