1. 프로젝트 구조 설계
Assets/
├── Runtime/
│ ├── Resources/
│ │ └── Data/
│ │ ├── Stages/ # 스테이지 데이터
│ │ ├── Monsters/ # 몬스터 데이터
│ │ ├── Skills/ # 스킬 데이터
│ │ └── DropTables/ # 드롭테이블 데이터
│ └── Scripts/
│ ├── Systems/ # 게임 시스템
│ └── Utils/ # 유틸리티
└── Editor/
└── GameSystemEditor.cs # 시스템 관리자Assets/
├── Runtime/
│ ├── Resources/
│ │ └── Data/
│ │ ├── Stages/ # 스테이지 데이터
│ │ ├── Monsters/ # 몬스터 데이터
│ │ ├── Skills/ # 스킬 데이터
│ │ └── DropTables/ # 드롭테이블 데이터
│ └── Scripts/
│ ├── Systems/ # 게임 시스템
│ └── Utils/ # 유틸리티
└── Editor/
└── GameSystemEditor.cs # 시스템 관리자
2. 기본 에디터 윈도우 구현
public class GameSystemEditor : OdinEditorWindow
{
private const string BASE_PATH = "Assets/Runtime/Resources/Data";
private const string STAGES_PATH = BASE_PATH + "/Stages";
private const string MONSTERS_PATH = BASE_PATH + "/Monsters";
private const string SKILLS_PATH = BASE_PATH + "/Skills";
private const string DROPS_PATH = BASE_PATH + "/DropTables";
[MenuItem("Game/시스템 관리자")]
private static void OpenWindow()
{
var window = GetWindow<GameSystemEditor>();
window.titleContent = new GUIContent("게임 시스템 관리자");
window.Show();
CreateRequiredDirectories();
}
private static void CreateRequiredDirectories()
{
string[] paths = { STAGES_PATH, MONSTERS_PATH, SKILLS_PATH, DROPS_PATH };
foreach (var path in paths)
{
if (!AssetDatabase.IsValidFolder(path))
{
CreateDirectory(path);
}
}
AssetDatabase.Refresh();
}
}
3. 데이터 관리 기본 구조
public class GameSystemEditor : OdinEditorWindow
{
[TabGroup("스테이지")]
[ListDrawerSettings(ShowIndexLabels = true)]
public List<StageData> allStages = new List<StageData>();
[TabGroup("몬스터")]
[ListDrawerSettings(ShowIndexLabels = true)]
public List<MonsterData> monsterDatas = new List<MonsterData>();
[TabGroup("스킬")]
[ListDrawerSettings(ShowIndexLabels = true)]
public List<SkillData> skillDatas = new List<SkillData>();
protected override void OnEnable()
{
base.OnEnable();
LoadAllData();
}
private void LoadAllData()
{
allStages = new List<StageData>(Resources.LoadAll<StageData>("Data/Stages"));
monsterDatas = new List<MonsterData>(Resources.LoadAll<MonsterData>("Data/Monsters"));
skillDatas = new List<SkillData>(Resources.LoadAll<SkillData>("Data/Skills"));
}
}
Part 2: 프리뷰 시스템 구현
1. 프리뷰 클래스 구현
[Serializable]
public class MonsterPreview
{
[PreviewField(150)]
[HorizontalGroup("Preview", Width = 150)]
[LabelText("3D 모델 프리뷰")]
public GameObject monsterModel;
[BoxGroup("스탯")]
[LabelText("체력")]
[SuffixLabel("$healthSuffix", Overlay = true)]
public double health = 1000;
private string healthSuffix => FormatNumber(health);
private string FormatNumber(double number)
{
if (number >= 1e12) return $"{number / 1e12:F2}T";
if (number >= 1e9) return $"{number / 1e9:F2}B";
if (number >= 1e6) return $"{number / 1e6:F2}M";
if (number >= 1e3) return $"{number / 1e3:F2}K";
return number.ToString("F0");
}
}
2. 스킬 프리뷰 구현
[Serializable]
public class SkillPreview
{
[PreviewField(100)]
[HorizontalGroup("Preview", Width = 100)]
[LabelText("스킬 아이콘")]
public Sprite skillIcon;
[PreviewField(150)]
[HorizontalGroup("Preview", Width = 150)]
[LabelText("이펙트 프리뷰")]
public GameObject effectPrefab;
[BoxGroup("스탯")]
[LabelText("데미지")]
public double damage = 50;
[BoxGroup("설정")]
[LabelText("기본 자동사용")]
public bool defaultAutoMode = true;
}
Part 3: 데이터 관리 기능
1. 데이터 생성 기능
[TabGroup("스킬")]
[Button("새 스킬 생성")]
private void CreateNewSkill()
{
var skillData = CreateInstance<SkillData>();
string path = $"{SKILLS_PATH}/NewSkill_{skillDatas.Count + 1}.asset";
AssetDatabase.CreateAsset(skillData, path);
AssetDatabase.SaveAssets();
skillDatas.Add(skillData);
Selection.activeObject = skillData;
EditorGUIUtility.PingObject(skillData);
}
2. 데이터 검증 시스템
[TabGroup("시스템 설정")]
[Button("데이터 유효성 검사")]
private void ValidateAllData()
{
StringBuilder errors = new StringBuilder();
foreach (var skill in skillDatas)
{
if (skill.effectPrefab == null)
errors.AppendLine($"스킬 '{skill.name}': 이펙트 프리팹이 없습니다.");
if (skill.cooldown <= 0)
errors.AppendLine($"스킬 '{skill.name}': 쿨다운이 0 이하입니다.");
}
string errorMessage = errors.ToString();
if (string.IsNullOrEmpty(errorMessage))
EditorUtility.DisplayDialog("검증 완료", "모든 데이터가 유효합니다.", "확인");
else
EditorUtility.DisplayDialog("검증 실패", errorMessage, "확인");
}
3. 데이터 내보내기/가져오기
private void ExportData()
{
string path = EditorUtility.SaveFolderPanel("데이터 내보내기", "", "");
if (string.IsNullOrEmpty(path)) return;
if (editor != null)
{
ExportListToJson(editor.allStages, Path.Combine(path, "Stages.json"));
ExportListToJson(editor.monsterDatas, Path.Combine(path, "Monsters.json"));
ExportListToJson(editor.skillDatas, Path.Combine(path, "Skills.json"));
}
}
private void ExportListToJson<T>(List<T> list, string path)
{
string json = JsonUtility.ToJson(new SerializableList<T> { items = list }, true);
File.WriteAllText(path, json);
}
Part 4: 도움말 시스템
1. 도움말 텍스트 구현
[TabGroup("도움말")]
[HideLabel]
[TextArea(10, 30)]
[ShowInInspector]
private string helpGuide = @"
[스테이지 관리]
- 새 스테이지 생성: '새 스테이지 생성' 버튼으로 새로운 스테이지를 만듭니다.
- 스테이지 수정: '현재 스테이지'에서 선택한 스테이지의 속성을 수정합니다.
...";
2. PDF 및 온라인 문서 연동
[TabGroup("도움말")]
[Button("도움말 PDF 열기")]
private void OpenHelpPDF()
{
string pdfPath = "Assets/Documentation/GameSystemGuide.pdf";
if (File.Exists(pdfPath))
{
System.Diagnostics.Process.Start(pdfPath);
}
else
{
EditorUtility.DisplayDialog("알림", "도움말 PDF 파일을 찾을 수 없습니다.", "확인");
}
}
학습 내용 및 성과
1. Unity 에디터 확장 개발 방법 습득
2. Odin Inspector를 활용해봄
3. ScriptableObject 기반 데이터 관리 시스템 구축
4. 사용자 친화적인 에디터 도구 개발
향후 계획
1. 데이터 간 연관 관계 시각화 기능
2. 데이터 버전 관리 시스템
3. 실시간 협업을 위한 데이터 동기화
개선버전
아직 사소한 문제가 있지만 작동은 잘 된다.