Skip to content

Commit c16dddf

Browse files
committed
Version 2.0
1 parent 685ed4a commit c16dddf

35 files changed

+963
-390
lines changed

.editorconfig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
root = true
1+
root = true
22

33
[*]
44
indent_style = tab
5-
indent_size = 4
5+
indent_size = 4

CHANGELOG.md

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,33 @@
11
# Changelog
2+
All notable changes to Scriptable Object Menu will be documented in this file.
23

3-
## [1.0.3] - 2021-12-20
4-
Added ScriptableObject Menu as a Unity package.
5-
Added dialog path synchronisation with the Project Window.
6-
Added assembly filtering.
4+
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
5+
6+
## [2.0.0] - 2023-01-29
7+
8+
### Added
9+
- Shortcut to create assets from selected types in the Project Window
10+
- Shortcut to clone assets from selected types in the Project Window
11+
- Ability to open newly created scripts with associated application
12+
- Ability to sort asset menu items alpha-numerically
13+
- Ability to group asset menu items by assembly
14+
- Editor only functions to the default template file
15+
- Configurable properties to the Project Settings window
16+
- Full support for user defined assemblies
17+
18+
### Changed
19+
- Optimised type discovery via TypeCache (for v19.2+ users)
20+
- Refactored for full integration with the Project Window
21+
22+
### Removed
23+
- Access modifiers from the default template file
24+
25+
## [1.3.0] - 2021-12-20
26+
27+
### Added
28+
- Unity package support
29+
- Assembly filtering
30+
- Dialog path synchronisation with the Project Window
31+
32+
## [1.0.0] - 2019-12-24
33+
- Initial commit

CHANGELOG.md.meta

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Editor.meta

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Editor/Actions.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Editor/Actions/CloneAssetAction.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System;
2+
using UnityEditor;
3+
using UnityEngine;
4+
5+
namespace ScriptableObjectMenu
6+
{
7+
internal sealed class CloneAssetAction : MenuActionBase
8+
{
9+
/// <summary>
10+
/// The path of the asset to be cloned.
11+
/// </summary>
12+
private string m_AssetPath;
13+
14+
[MenuItem(EDITOR_CREATE_MENU_PATH + "Clone", true, EDITOR_CREATE_MENU_PRIORITY + EDITOR_CREATE_MENU_SEPARATOR)]
15+
private static bool Validate ()
16+
{
17+
return m_Settings.IsValid && IsScriptableObject(Selection.activeObject);
18+
}
19+
20+
[MenuItem(EDITOR_CREATE_MENU_PATH + "Clone", false, EDITOR_CREATE_MENU_PRIORITY + EDITOR_CREATE_MENU_SEPARATOR)]
21+
private static void Initiate ()
22+
{
23+
var path = AssetDatabase.GetAssetPath(Selection.activeObject);
24+
InvokeAction<CloneAssetAction>(path, EDITOR_ASSET_ICON_RESOURCE).m_AssetPath = path;
25+
}
26+
27+
protected override sealed void Execute (string path)
28+
{
29+
if (!AssetDatabase.CopyAsset(m_AssetPath, path))
30+
{
31+
throw new Exception($"Failed to clone asset at path '{path}'.");
32+
}
33+
34+
var asset = UpdateAssetDatabase<ScriptableObject>(path);
35+
36+
if (asset == null)
37+
{
38+
throw new Exception($"Failed to clone asset at path '{path}'.");
39+
}
40+
41+
PingProjectWindow(asset);
42+
43+
if (m_Settings.LogOnSuccess)
44+
{
45+
Debug.Log($"Asset cloned at path '{path}'");
46+
}
47+
}
48+
}
49+
}
Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using UnityEditor;
5+
using UnityEngine;
6+
using Object = UnityEngine.Object;
7+
8+
namespace ScriptableObjectMenu
9+
{
10+
internal sealed class CreateAssetAction : MenuActionBase
11+
{
12+
/// <summary>
13+
/// The object HideFlags that are incompatible with creation.
14+
/// </summary>
15+
private const HideFlags INVALID_HIDE_FLAGS = HideFlags.DontSaveInEditor | HideFlags.HideInHierarchy;
16+
17+
/// <summary>
18+
/// The Project Window selection filter flags.
19+
/// </summary>
20+
private const SelectionMode SELECTION_FLAGS = SelectionMode.ExcludePrefab | SelectionMode.DeepAssets;
21+
22+
/// <summary>
23+
/// The asset menu item cache.
24+
/// </summary>
25+
private static List<ContextMenuItem> m_ItemCache;
26+
27+
/// <summary>
28+
/// The asset type to be created.
29+
/// </summary>
30+
private Type m_AssetType;
31+
32+
[MenuItem(EDITOR_CREATE_MENU_PATH + "Asset", true, EDITOR_CREATE_MENU_PRIORITY)]
33+
private static bool Validate ()
34+
{
35+
return !EditorApplication.isCompiling && m_Settings.IsValid;
36+
}
37+
38+
[MenuItem(EDITOR_CREATE_MENU_PATH + "Asset", false, EDITOR_CREATE_MENU_PRIORITY)]
39+
private static void Initiate ()
40+
{
41+
if (m_ItemCache == null || m_Settings.IsDirty)
42+
{
43+
m_Settings.IsDirty = false;
44+
GenerateItemCache();
45+
}
46+
47+
if (m_ItemCache.Count > 0)
48+
{
49+
var menu = CreateContextMenu();
50+
ContextMenuWindow.Display(menu);
51+
}
52+
else if (EditorUtility.DisplayDialog("Error", "No Scriptable Object(s) found.\n\n" +
53+
"Would you like to create a new script?", "Create", "Cancel"))
54+
{
55+
CreateScriptAction.Initiate();
56+
}
57+
}
58+
59+
private static void GenerateItemCache ()
60+
{
61+
m_ItemCache = new List<ContextMenuItem>();
62+
63+
#if UNITY_2019_2_OR_NEWER
64+
65+
// From TypeCache
66+
foreach (var type in TypeCache.GetTypesDerivedFrom<ScriptableObject>())
67+
{
68+
if (IsScriptableObject(type) && IsAssemblyIncluded(type.Assembly))
69+
{
70+
var path = CreateMenuPath(type);
71+
var item = CreateMenuItem(type, path);
72+
m_ItemCache.Add(item);
73+
}
74+
}
75+
#else
76+
// From Domain
77+
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
78+
{
79+
if (IsAssemblyIncluded(asm))
80+
{
81+
foreach (var type in asm.GetTypes())
82+
{
83+
if (IsScriptableObject(type))
84+
{
85+
var path = CreateMenuPath(type);
86+
var item = CreateMenuItem(type, path);
87+
m_ItemCache.Add(item);
88+
}
89+
}
90+
}
91+
}
92+
#endif
93+
if (m_Settings.SortItemsByName)
94+
{
95+
m_ItemCache.Sort((x, y) => CompareAlphaNumeric(x.Path, y.Path));
96+
}
97+
}
98+
99+
private static GenericMenu CreateContextMenu ()
100+
{
101+
var menu = new GenericMenu { allowDuplicateNames = !m_Settings.GroupItemsByAssembly };
102+
103+
// From selection
104+
if (m_Settings.IncludeSelectedTypes && TryGetSelectedTypes(out var types))
105+
{
106+
if (m_Settings.SortItemsByName)
107+
{
108+
types.Sort((x, y) => CompareAlphaNumeric(x.Name, y.Name));
109+
}
110+
111+
foreach (var type in types)
112+
{
113+
var item = CreateMenuItem(type, type.Name);
114+
menu.AddItem(item.Content, false, item.Function);
115+
}
116+
117+
menu.AddSeparator(string.Empty);
118+
}
119+
120+
// From cache
121+
foreach (var item in m_ItemCache)
122+
{
123+
menu.AddItem(item.Content, false, item.Function);
124+
}
125+
126+
return menu;
127+
}
128+
129+
private static string CreateMenuPath (Type type)
130+
{
131+
// Convert namespace to path
132+
var path = type.FullName.Replace('.', '/');
133+
134+
// Prefix assembly name
135+
if (m_Settings.GroupItemsByAssembly)
136+
{
137+
path = $"{GetAssemblyRootName(type.Assembly)}/{path}";
138+
}
139+
140+
return path;
141+
}
142+
143+
private static ContextMenuItem CreateMenuItem (Type type, string path)
144+
{
145+
return new ContextMenuItem(path, new GUIContent(path), () =>
146+
{
147+
var name = Path.ChangeExtension(type.Name, EDITOR_ASSET_FILE_EXTENSION);
148+
InvokeAction<CreateAssetAction>(name, EDITOR_ASSET_ICON_RESOURCE).m_AssetType = type;
149+
});
150+
}
151+
152+
private static bool TryGetSelectedTypes (out List<Type> types)
153+
{
154+
types = new List<Type>();
155+
156+
// Gather objects
157+
if (Selection.count == 0)
158+
{
159+
return false;
160+
}
161+
162+
var objects = Selection.GetFiltered<Object>(SELECTION_FLAGS);
163+
164+
if (objects.Length == 0)
165+
{
166+
return false;
167+
}
168+
169+
// Extract types
170+
foreach (var obj in objects)
171+
{
172+
switch (obj)
173+
{
174+
case ScriptableObject asset:
175+
TryAdd(types, asset.GetType());
176+
break;
177+
178+
case MonoScript script:
179+
TryAdd(types, script.GetClass());
180+
break;
181+
}
182+
}
183+
184+
return types.Count > 0;
185+
186+
static void TryAdd (List<Type> types, Type type)
187+
{
188+
if (type != null &&
189+
!types.Contains(type) &&
190+
IsScriptableObject(type) &&
191+
IsAssemblyIncluded(type.Assembly))
192+
{
193+
types.Add(type);
194+
}
195+
}
196+
}
197+
198+
protected sealed override void Execute (string path)
199+
{
200+
var asset = CreateInstance(m_AssetType);
201+
202+
if (asset == null)
203+
{
204+
throw new Exception($"Failed to create asset at path '{path}'.");
205+
}
206+
207+
// Reject invalid HideFlags
208+
if ((asset.hideFlags & INVALID_HIDE_FLAGS) != 0)
209+
{
210+
DestroyImmediate(asset, true);
211+
throw new Exception($"Asset of type '{m_AssetType.Name}'" +
212+
" cannot be saved due to its 'HideFlags' settings.");
213+
}
214+
215+
AssetDatabase.CreateAsset(asset, path);
216+
PingProjectWindow(asset);
217+
218+
if (m_Settings.LogOnSuccess)
219+
{
220+
Debug.Log($"Asset created at path '{path}'");
221+
}
222+
}
223+
}
224+
}
Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)