유니티에서 프로젝트에서 리소스를 체계적으로 관리하려면, 리소스를 타입별로 분류하고, 손쉽게 액세스할 수 있어야 합니다.
리소스 타입 관리
- 오디오, 텍스처와 같은 리소스 타입을 미리 정의
- 각 타입별로 파일 확장자, 아이콘, 하위 폴더 구조를 지정 가능
파일 탐색 및 관리
- 프로젝트 폴더 및 운영체제의 탐색기에서 폴더 열기
- 리소스 타입별로 파일 추가, 삭제 및 새로고침 가능
에디터 창 통합
- 유니티 메뉴에서 쉽게 접근 가능 (Tools > Resource Manager)
리소스 폴더 관리
리소스 타입에 따라 자동으로 생성된 하위 폴더를 관리할 수 있습니다.
- 파일 추가(Add File): 선택한 폴더에 새로운 파일 추가
- 폴더 열기(Show in OOO): Unity 프로젝트 창 또는 운영체제 탐색기에서 폴더 열기
- 파일 새로고침(Refresh List): 폴더의 파일 목록을 업데이트
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Collections.Generic;
using System.Linq;
public class ResourceManagerEditor : EditorWindow
{
// 기본 설정값
private const string BASE_MENU_PATH = "Tools/Resource Manager %#F1";
private const string BASE_RESOURCES_PATH = "Assets/Resources";
// 리소스 타입별 설정을 위한 구조체
public struct ResourceTypeConfig
{
public string TypeName; // 리소스 종류 (Audio, Texture 등)
public string[] FileExtensions; // 허용할 파일 확장자
public string IconName; // Unity 에디터에서 표시할 아이콘
public string[] SubFolders; // 리소스 타입별 하위 폴더 구조
}
// 리소스 타입별 설정 정보를 담은 딕셔너리
private static readonly Dictionary<string, ResourceTypeConfig> ResourceTypes = new Dictionary<string, ResourceTypeConfig>
{
// 오디오 파일 설정
{
"Sounds", new ResourceTypeConfig
{
TypeName = "Sounds",
FileExtensions = new[] { "mp3", "wav" },
IconName = "AudioClip Icon",
SubFolders = new[] { "BGM", "Effect" }
}
},
// 텍스처 파일 설정
{
"Textures", new ResourceTypeConfig
{
TypeName = "Textures",
FileExtensions = new[] { "png", "jpg", "jpeg" },
IconName = "Texture Icon",
SubFolders = new[] { "UI", "Background", "Character" }
}
}
};
private string _selectedResourceType;
private string _selectedFilePath;
private Dictionary<string, Vector2> _scrollPositions;
[MenuItem(BASE_MENU_PATH, false, 100000)]
public static void ShowWindow()
{
GetWindow<ResourceManagerEditor>("Resource Manager");
}
// 윈도우가 활성화될 때 호출되는 초기화
private void OnEnable()
{
_scrollPositions = new Dictionary<string, Vector2>();
if (string.IsNullOrEmpty(_selectedResourceType) && ResourceTypes.Count > 0)
{
_selectedResourceType = ResourceTypes.Keys.First();
}
foreach (string type in ResourceTypes.Keys)
{
foreach (string subFolder in ResourceTypes[type].SubFolders)
{
_scrollPositions[$"{type}_{subFolder}"] = Vector2.zero;
}
}
}
private void OnGUI()
{
DrawHeader();
DrawResourceTypeSelector();
if (!string.IsNullOrEmpty(_selectedResourceType) && ResourceTypes.ContainsKey(_selectedResourceType))
{
DrawResourceSections();
}
}
private void DrawHeader()
{
GUILayout.Label("Resource Manager", EditorStyles.boldLabel);
EditorGUILayout.Space();
EditorGUILayout.HelpBox("Manage your resource files in respective folders. Refresh the lists and add new files by selecting them from your computer.", MessageType.Info);
EditorGUILayout.Space();
}
// 리소스 타입 드롭다운
private void DrawResourceTypeSelector()
{
EditorGUILayout.BeginHorizontal();
GUILayout.Label("Resource Type:", GUILayout.Width(100));
string[] typeNames = ResourceTypes.Keys.ToArray();
int currentIndex = System.Array.IndexOf(typeNames, _selectedResourceType);
int newIndex = EditorGUILayout.Popup(currentIndex, typeNames, GUILayout.Width(150));
if (newIndex >= 0 && newIndex < typeNames.Length)
{
_selectedResourceType = typeNames[newIndex];
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
}
// 리소스 섹션
private void DrawResourceSections()
{
var config = ResourceTypes[_selectedResourceType];
EditorGUILayout.BeginHorizontal();
// 화면 너비를 섹션 수로 나누어 각 섹션의 너비 계산
float sectionWidth = (EditorGUIUtility.currentViewWidth - 30) / config.SubFolders.Length;
// 각 서브폴더별 섹션 그리기
foreach (string subFolder in config.SubFolders)
{
string folderPath = Path.Combine(BASE_RESOURCES_PATH, config.TypeName, subFolder);
DrawSection(subFolder, folderPath, config, sectionWidth);
}
EditorGUILayout.EndHorizontal();
}
// 개별 섹션
private void DrawSection(string sectionName, string folderPath, ResourceTypeConfig config, float sectionWidth)
{
string scrollKey = $"{config.TypeName}_{sectionName}";
EditorGUILayout.BeginVertical(GUI.skin.box, GUILayout.Width(sectionWidth));
GUILayout.Label(sectionName, EditorStyles.boldLabel);
if (GUILayout.Button($"Show in Project"))
{
ShowFolderInProject(folderPath);
}
if (GUILayout.Button($"Show in Explorer"))
{
ShowFolderInExplorer(folderPath);
}
if (GUILayout.Button($"Refresh List"))
{
RefreshList(folderPath);
}
EditorGUILayout.LabelField($"Files in {sectionName}:");
_scrollPositions[scrollKey] = EditorGUILayout.BeginScrollView(_scrollPositions[scrollKey], GUILayout.Height(150));
foreach (string ext in config.FileExtensions)
{
DrawFiles(folderPath, $"*.{ext}", config.IconName);
}
EditorGUILayout.EndScrollView();
EditorGUILayout.Space();
if (GUILayout.Button($"Add File"))
{
AddFile(folderPath, config.FileExtensions);
}
EditorGUILayout.EndVertical();
}
// Unity 프로젝트 창에서 폴더 열기
private void ShowFolderInProject(string folderPath)
{
EnsureDirectoryExists(folderPath);
string assetPath = folderPath.Replace("\\", "/");
Object folderObject = AssetDatabase.LoadAssetAtPath<Object>(assetPath);
Selection.activeObject = folderObject;
EditorUtility.FocusProjectWindow();
}
// 탐색기에서 폴더 열기
private void ShowFolderInExplorer(string folderPath)
{
EnsureDirectoryExists(folderPath);
string fullPath = Path.GetFullPath(folderPath);
System.Diagnostics.Process.Start(fullPath);
}
// 파일 목록 새로고침
private void RefreshList(string folderPath)
{
EnsureDirectoryExists(folderPath);
AssetDatabase.Refresh();
Debug.Log($"[Editor] {folderPath} list refreshed.");
}
// 지정된 경로의 파일들
private void DrawFiles(string folderPath, string searchPattern, string iconName)
{
if (!Directory.Exists(folderPath)) return;
string[] files = Directory.GetFiles(folderPath, searchPattern);
foreach (string file in files)
{
DrawFileEntry(file, iconName);
}
}
// 개별 파일 항목
private void DrawFileEntry(string filePath, string iconName)
{
EditorGUILayout.BeginHorizontal();
GUILayout.Label(EditorGUIUtility.Load(iconName) as Texture2D, GUILayout.Width(20), GUILayout.Height(20));
EditorGUILayout.LabelField(Path.GetFileName(filePath));
EditorGUILayout.EndHorizontal();
}
// 새 파일 추가
private void AddFile(string folderPath, string[] allowedExtensions)
{
string extensions = string.Join(",", allowedExtensions);
_selectedFilePath = EditorUtility.OpenFilePanel("Select File", "", extensions);
if (!string.IsNullOrEmpty(_selectedFilePath))
{
EnsureDirectoryExists(folderPath);
string fileName = Path.GetFileName(_selectedFilePath);
string destinationPath = Path.Combine(folderPath, fileName);
File.Copy(_selectedFilePath, destinationPath, true);
AssetDatabase.Refresh();
Debug.Log($"[Editor] File '{fileName}' added to {folderPath}.");
ShowNotification(new GUIContent($"'{fileName}' added to {folderPath}!"));
}
}
// 디렉토리 존재 여부를 확인하고 없으면 생성
private void EnsureDirectoryExists(string path)
{
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
AssetDatabase.Refresh();
}
}
}
#endif
리소스 타입 추가 방법
리소스 타입 추가 방법 ResourceTypes 딕셔너리에 새로운 리소ResourceTypes 딕셔너리에 새로운 리소스 타입을 정의하면 됩니다. 아래는 오디오와 텍스처 외에 Animations라는 리소스 타입을 추가하는 예시입니다.
- TypeName: 리소스 타입 이름(예: Animations). 폴더 구조의 상위 이름으로 사용됩니다.
- FileExtensions: 해당 타입에서 허용하는 파일 확장자 목록(예: .anim, .controller)
- IconName: Unity 에디터에서 사용할 아이콘 이름
- SubFolders: 리소스 타입 하위 폴더 구조(예: Character, Environment, UI)
- (Resources/Animations/Character)
- (Resources/Animations/Environment)
- (Resources/Animations/UI)
{
"Animations", new ResourceTypeConfig
{
TypeName = "Animations",
FileExtensions = new[] { "anim", "controller" },
IconName = "AnimatorController Icon",
SubFolders = new[] { "Character", "Environment", "UI" }
}
}