Today I needed a lightweight and easy way of showing a dropdown with files or folders to pick from the StreamingAssets folder. So I implemented this:
#if UNITY_EDITOR
namespace Nementic
{
using System;
using System.IO;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
public class FileDropdown : AdvancedDropdown
{
/// <summary>
/// Specifies the name of the top-level node. If not set, the root directory name will be used.
/// </summary>
public string rootName;
/// <summary>
/// An optional filter to apply to files, e.g. *.txt to only show text files.
/// </summary>
public string fileFilter;
private readonly DirectoryInfo rootDirectory;
private readonly Action<CallbackInfo> onFileSelected;
private readonly Action<CallbackInfo, object> onFileSelectedAdvanced;
private readonly object userData;
public FileDropdown(AdvancedDropdownState state, string directoryPath, Action<CallbackInfo> onFileSelected)
: this(state, directoryPath)
{
this.onFileSelected = onFileSelected;
}
public FileDropdown(AdvancedDropdownState state, string directoryPath, Action<CallbackInfo, object> onFileSelected, object userData)
: this(state, directoryPath)
{
this.onFileSelectedAdvanced = onFileSelected;
this.userData = userData;
}
private FileDropdown(AdvancedDropdownState state, string directoryPath) : base(state)
{
this.minimumSize = new Vector2(200, 300);
this.rootDirectory = new DirectoryInfo(directoryPath);
}
protected override AdvancedDropdownItem BuildRoot()
{
if (string.IsNullOrEmpty(rootName))
rootName = rootDirectory.Name;
var root = new AdvancedDropdownItem(rootName);
AddFileSystemEntries(root, rootDirectory);
return root;
}
private void AddFileSystemEntries(AdvancedDropdownItem root, DirectoryInfo directory)
{
foreach (DirectoryInfo subDirectory in directory.EnumerateDirectories())
{
var folder = new FileDropdownItem(subDirectory.Name, subDirectory.FullName);
folder.AddChild(new FileDropdownItem("Select Folder", subDirectory.FullName));
AddFileSystemEntries(folder, subDirectory);
root.AddChild(folder);
}
foreach (FileInfo file in directory.EnumerateFiles(fileFilter, SearchOption.TopDirectoryOnly))
{
var child = new FileDropdownItem(file.Name, file.FullName);
root.AddChild(child);
}
}
protected override void ItemSelected(AdvancedDropdownItem item)
{
var fileItem = (FileDropdownItem)item;
var info = new CallbackInfo(fileItem.name, fileItem.fullName);
onFileSelected?.Invoke(info);
onFileSelectedAdvanced?.Invoke(info, userData);
}
private class FileDropdownItem : AdvancedDropdownItem
{
public readonly string fullName;
public FileDropdownItem(string name, string fullName) : base(name)
{
this.fullName = fullName.Replace(@"\", "/");
}
}
/// <summary>
/// Provides information about the selected file or directory.
/// </summary>
public struct CallbackInfo
{
/// <summary>
/// The name of the file (including its extension) or directory.
/// </summary>
public readonly string name;
/// <summary>
/// The full path of the file or directory.
/// </summary>
public readonly string fullName;
public CallbackInfo(string name, string fullName)
{
this.name = name;
this.fullName = fullName;
}
}
}
}
#endif
And here is an example of how to use it:
public class MyComponent : UnityEngine.MonoBehaviour
{
public string levelName;
}
#if UNITY_EDITOR
[UnityEditor.CustomEditor(typeof(MyComponent))]
public class MyComponentEditor : UnityEditor.Editor
{
private UnityEditor.IMGUI.Controls.AdvancedDropdownState dropdownState;
public override void OnInspectorGUI()
{
Rect controlRect = UnityEditor.EditorGUILayout.GetControlRect();
Rect buttonRect = controlRect;
controlRect.width -= 30;
UnityEditor.EditorGUI.PropertyField(controlRect, serializedObject.FindProperty("levelName"));
buttonRect.xMin = controlRect.xMax + 4;
buttonRect.height -= 1;
if (GUI.Button(buttonRect, new GUIContent(".."), UnityEditor.EditorStyles.miniButton))
{
if (dropdownState == null)
dropdownState = new UnityEditor.IMGUI.Controls.AdvancedDropdownState();
var dropdown = new FileDropdown(dropdownState, Application.streamingAssetsPath, OnFileSelected)
{
rootName = "My Custom Files & Folders",
fileFilter = "*.txt"
};
dropdown.Show(buttonRect);
}
}
private void OnFileSelected(FileDropdown.CallbackInfo info)
{
serializedObject.Update();
serializedObject.FindProperty("levelName").stringValue = info.fullName;
serializedObject.ApplyModifiedProperties();
}
}
#endif
The FileDropdown makes it easy to select a number of files from a specific folder or its children or the folders themselves. With this I implemented a level data loading system which can either take a specific file to load, a series of file paths to load randomly or pick a random file from a folder by only setting the folder path. This allowed for a flexible workflow because it was possible to setup folders such as “Easy” and then continue to modify its content without having to fix object references on the components etc.