[Unity/DevLog] AI '안티그래비티'와 함께 0부터 만드는 정통 SRPG - 7일차

"AI로 나온 모델링 수동으로 익스포트하다가 너무 불편하여 자동으로 텍스쳐와 메테리얼을 자동으로 익스포트하고 애니메이션 타입을 자동으로 휴먼으로 바꿔주는 툴을 제작하였습니다."
"혼자 개발하지만 혼자가 아닙니다."
본 프로젝트는 구글 딥마인드의 최신 Agentic AI, **'안티그래비티(Antigravity)'**와의 페어 프로그래밍을 통해 진행하고 있습니다.
🤝 AI '안티그래비티'와의 협업
7일차인 오늘은 반복적인 리소스 세팅 작업을 효율화하기 위해 에디터 툴 개발에 집중했습니다. 저의 경험과, 복잡한 Unity API를 빠르게 훑어주는 AI의 협업으로 번거로운 임포트 과정을 단 한 번의 클릭으로 자동화했습니다.
📅 7일차 목표: 리소스 파이프라인 자동화 (FBX Auto Setup)
캐릭터 모델링 파일을 프로젝트에 넣을 때마다 반복되는 Rig 설정, 텍스처 추출, 머티리얼 정리 과정을 자동화하는 'FBX 임포트 세팅 툴' 개발을 목표로 설정했습니다.
1. Rig 설정 자동화 (Rig: Humanoid)
모든 캐릭터 FBX의 Animation Type을 Humanoid로 변경하고, 아바타를 생성하는 과정을 자동화했습니다.
- 핵심 로직: ModelImporter의 animationType을 수정 후 반드시 SaveAndReimport()를 호출하여 변경 사항을 물리적으로 적용.
2. 스마트 폴더 구조화 (Folder Refactoring)
임포트된 FBX가 FBX라는 하위 폴더에 있을 경우, 상위 폴더를 기준으로 Textures와 Materials 폴더를 자동 생성하고 파일을 정리합니다.
- 기술 포인트: path.Replace("\\", "/")를 통한 경로 정규화로 OS 간 경로 인식 오류 방지.
3. 머티리얼 추출 및 리매핑 (Extract & Remap)
내부에 임베디드된 머티리얼을 외부 파일로 추출하고, 추출된 파일을 FBX가 다시 참조하도록 연결(Remap)합니다.
- 발전된 설계: 머티리얼 위치를 Embedded에서 InPrefab으로 강제 전환 후 추출하여 유실되는 에셋이 없도록 처리.
4. 쉐이더 안정성 확보 (Safety Setup)
추출 과정에서 발생할 수 있는 '분홍색(Pink) 머티리얼' 문제를 방지하기 위해, 유효하지 않은 쉐이더 발견 시 Standard 쉐이더로 자동 복구하는 로직을 추가했습니다.
🐛 버그 사냥 (Troubleshooting)
| 문제점 | 원인 | 해결책 |
| 경로 인식 오류 | Windows 백슬래시(\)와 Unity API 간 충돌 | 모든 경로를 /로 치환하는 ValidatePath 함수 도입 |
| 머티리얼 추출 실패 | External (Legacy) 모드에서의 에셋 로드 제한 | 추출 전 materialLocation을 InPrefab으로 변경 및 재임포트 |
| 분홍색(Pink) 머티리얼 | 내부 머티리얼의 쉐이더 정보가 깨지거나 누락됨 | Instantiate 후 쉐이더 유효성 검사 및 Standard 강제 할당 |
| 설정 저장 팝업 발생 | 에디터 상에서 변경된 설정이 디스크에 반영 안 됨 | importer.SaveAndReimport() 명시적 호출로 즉시 저장 |
📝 최종 완성 코드 (FBXAutoSetupTools.cs)
using UnityEngine;
using UnityEditor;
using System.IO;
namespace SRPG.Editor
{
public class FBXAutoSetupTools
{
[MenuItem("Assets/SRPG/Auto Setup FBX (Humanoid + Extract)", false, 100)]
public static void AutoSetupSelectedFBX()
{
foreach (var obj in Selection.objects)
{
string path = AssetDatabase.GetAssetPath(obj);
if (string.IsNullOrEmpty(path)) continue;
path = ValidatePath(path);
ModelImporter importer = AssetImporter.GetAtPath(path) as ModelImporter;
if (importer == null) continue;
Debug.Log($"Processing FBX: {path}");
// 1. Rig 설정
bool needReimport = false;
if (importer.animationType != ModelImporterAnimationType.Human)
{
importer.animationType = ModelImporterAnimationType.Human;
importer.avatarSetup = ModelImporterAvatarSetup.CreateFromThisModel;
needReimport = true;
}
if (needReimport)
{
importer.SaveAndReimport();
AssetDatabase.Refresh();
importer = AssetImporter.GetAtPath(path) as ModelImporter;
}
// 2. 경로 계산
string fbxFolder = Path.GetDirectoryName(path);
string targetRoot = fbxFolder;
if (Path.GetFileName(fbxFolder).Equals("FBX", System.StringComparison.OrdinalIgnoreCase))
targetRoot = Path.GetDirectoryName(fbxFolder);
string textureFolder = ValidatePath(Path.Combine(targetRoot, "Textures"));
string materialFolder = ValidatePath(Path.Combine(targetRoot, "Materials"));
if (!Directory.Exists(textureFolder)) Directory.CreateDirectory(textureFolder);
if (!Directory.Exists(materialFolder)) Directory.CreateDirectory(materialFolder);
AssetDatabase.Refresh();
// 3. 텍스처 추출
try
{
importer.ExtractTextures(textureFolder);
AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
}
catch (System.Exception) { }
// 4. 머티리얼 추출 및 리매핑
if (importer.materialLocation != ModelImporterMaterialLocation.InPrefab)
{
importer.materialLocation = ModelImporterMaterialLocation.InPrefab;
importer.SaveAndReimport();
AssetDatabase.Refresh();
importer = AssetImporter.GetAtPath(path) as ModelImporter;
}
var assets = AssetDatabase.LoadAllAssetsAtPath(path);
int matCount = 0;
foreach (var asset in assets)
{
if (asset is Material mat)
{
if (mat.name.StartsWith("Default-Material")) continue;
string newAssetPath = ValidatePath(Path.Combine(materialFolder, mat.name + ".mat"));
Material finalMat = AssetDatabase.LoadAssetAtPath<Material>(newAssetPath);
if (finalMat == null)
{
finalMat = Object.Instantiate(mat);
finalMat.name = mat.name;
finalMat.hideFlags = HideFlags.None;
if (finalMat.shader == null || finalMat.shader.name.Contains("InternalErrorShader"))
{
finalMat.shader = Shader.Find("Standard");
}
AssetDatabase.CreateAsset(finalMat, newAssetPath);
}
importer.AddRemap(new AssetImporter.SourceAssetIdentifier(mat), finalMat);
matCount++;
}
}
if (matCount > 0)
{
importer.SaveAndReimport();
}
}
AssetDatabase.Refresh();
Debug.Log("FBX Auto Setup Complete.");
}
private static string ValidatePath(string path) => path.Replace("\\", "/");
[MenuItem("Assets/SRPG/Auto Setup FBX (Humanoid + Extract)", true)]
public static bool ValidateAutoSetup()
{
if (Selection.activeObject == null) return false;
string path = AssetDatabase.GetAssetPath(Selection.activeObject);
return !string.IsNullOrEmpty(path) && (path.EndsWith(".fbx", System.StringComparison.OrdinalIgnoreCase));
}
}
}
📝 7일차 마무리하며
단순한 코딩 같지만, Unity 내부 임포트 파이프라인의 선후 관계를 정확히 이해해야 했던 작업이었습니다. AI '안티그래비티' 덕분에 API 문서를 뒤지는 시간을 대폭 줄이고 실질적인 로직 구현과 예외 처리에만 집중할 수 있었습니다.
이제 대량의 캐릭터 리소스가 들어와도 두렵지 않네요! 다음 단계로는 오늘 준비한 리소스를 바탕으로 전투 애니메이션 및 스킬 연출 시스템을 구축해 보겠습니다.
'AI > Unity' 카테고리의 다른 글
| Unity 6 Unity.AI.Toolkit.EditorTask 무한로딩 (0) | 2026.01.20 |
|---|---|
| AntiGravity를 이용한 SRPG만들기 8일차 (툰쉐이더 적용) (0) | 2026.01.20 |
| AnityGravity를 이용한 SRPG만들기 6일차 (0) | 2026.01.18 |
| tripo3d에서 FBX 추출 후 유니티에 적용하기 - 애니메이션 (2). (3) | 2026.01.18 |
| tripo3d에서 FBX 추출 후 유니티에 적용하기. (0) | 2026.01.18 |