-
Notifications
You must be signed in to change notification settings - Fork 325
NEW: Allow selection of InputActionReferences from project-wide actions #1779
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
698dc8c
f23f587
1b22b98
bdda2ec
0193ad2
3d3ec21
4e16a51
5fb9517
7b3f850
48f17b5
bdc0d07
560e8c4
a26eace
1fe575a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
#if UNITY_EDITOR | ||
using UnityEditor; | ||
|
||
namespace UnityEngine.InputSystem.Editor | ||
{ | ||
/// <summary> | ||
/// Provides access to icons associated with <see cref="InputActionAsset"/> and <see cref="InputActionReference"/>. | ||
/// </summary> | ||
internal static class InputActionAssetIconLoader | ||
{ | ||
private const string kActionIcon = "Packages/com.unity.inputsystem/InputSystem/Editor/Icons/InputAction.png"; | ||
private const string kAssetIcon = "Packages/com.unity.inputsystem/InputSystem/Editor/Icons/InputActionAsset.png"; | ||
|
||
/// <summary> | ||
/// Attempts to load the icon associated with an <see cref="InputActionAsset"/>. | ||
/// </summary> | ||
/// <returns>Icon resource reference or <code>null</code> if the resource could not be loaded.</returns> | ||
internal static Texture2D LoadAssetIcon() | ||
{ | ||
return (Texture2D)EditorGUIUtility.Load(kAssetIcon); | ||
} | ||
|
||
/// <summary> | ||
/// Attempts to load the icon associated with an <see cref="InputActionReference"/> sub-asset of an | ||
/// <see cref="InputActionAsset"/>. | ||
/// </summary> | ||
/// <returns>Icon resource reference or <code>null</code> if the resource could not be loaded.</returns> | ||
internal static Texture2D LoadActionIcon() | ||
{ | ||
return (Texture2D)EditorGUIUtility.Load(kActionIcon); | ||
} | ||
} | ||
} | ||
|
||
#endif // #if UNITY_EDITOR |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -86,8 +86,16 @@ private static InputActionAsset CreateNewActionAsset() | |
} | ||
} | ||
|
||
// Create sub-asset for each action. This is so that users can select individual input actions from the asset when they're | ||
// trying to assign to a field that accepts only one action. | ||
CreateInputActionReferences(asset); | ||
|
||
AssetDatabase.SaveAssets(); | ||
|
||
return asset; | ||
} | ||
|
||
private static void CreateInputActionReferences(InputActionAsset asset) | ||
{ | ||
var maps = asset.actionMaps; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need a check here to make sure the asset doesn't already have the InputActionReferences before they are created? var actionReference = ScriptableObject.CreateInstance(); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess not as its only called from CreateNewActionAsset |
||
foreach (var map in maps) | ||
{ | ||
foreach (var action in map.actions) | ||
|
@@ -97,10 +105,51 @@ private static InputActionAsset CreateNewActionAsset() | |
AssetDatabase.AddObjectToAsset(actionReference, asset); | ||
} | ||
} | ||
} | ||
|
||
AssetDatabase.SaveAssets(); | ||
/// <summary> | ||
/// Updates the input action references in the asset by updating names, removing dangling references | ||
/// and adding new ones. | ||
/// </summary> | ||
/// <param name="asset"></param> | ||
internal static void UpdateInputActionReferences() | ||
{ | ||
var asset = GetOrCreate(); | ||
var existingReferences = InputActionImporter.LoadInputActionReferencesFromAsset(asset).ToList(); | ||
|
||
return asset; | ||
// Check if referenced input action exists in the asset and remove the reference if it doesn't. | ||
foreach (var actionReference in existingReferences) | ||
{ | ||
var action = asset.FindAction(actionReference.action.id); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When testing the branch I have a NullReferenceException here in PlayMode after some edits and entering PlayMode (Not sure about exact steps to reproduce, will update his comment if I can reproduce again):
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, interesting. Good catch! Was this when doing a bunch of Undo/Redo actions? I'll try to replicate it as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure, my main test case have been creating, deleting, undo, redo, renaming, select again in picker, repeat.... Haven't found this particular one again though. |
||
if (action == null) | ||
{ | ||
actionReference.Set(null); | ||
AssetDatabase.RemoveObjectFromAsset(actionReference); | ||
} | ||
} | ||
|
||
// Check if all actions have a reference | ||
foreach (var action in asset) | ||
{ | ||
var actionReference = existingReferences.FirstOrDefault(r => r.m_ActionId == action.id.ToString()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Feels like we could do this faster, e.g. by generating a hash in the above foreach look and then checking if its in the hash here - rather than splining the eixtingReferences list on every action in the asset. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks. We probably can. I'll leave it for the end in case the direction in this PR is accepted.
jfreire-unity marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// The input action doesn't have a reference, create a new one. | ||
if (actionReference == null) | ||
{ | ||
var actionReferenceNew = ScriptableObject.CreateInstance<InputActionReference>(); | ||
actionReferenceNew.Set(action); | ||
AssetDatabase.AddObjectToAsset(actionReferenceNew, asset); | ||
} | ||
else | ||
{ | ||
// Update the name of the reference if it doesn't match the action name. | ||
if (actionReference.name != InputActionReference.GetDisplayName(action)) | ||
{ | ||
AssetDatabase.RemoveObjectFromAsset(actionReference); | ||
actionReference.name = InputActionReference.GetDisplayName(action); | ||
AssetDatabase.AddObjectToAsset(actionReference, asset); | ||
jfreire-unity marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// Note: If not UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS we do not use a custom property drawer and | ||
// picker for InputActionReferences but rather rely on default (classic) object picker. | ||
#if UNITY_EDITOR && UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS | ||
|
||
using UnityEditor; | ||
using UnityEditor.Search; | ||
|
||
namespace UnityEngine.InputSystem.Editor | ||
{ | ||
/// <summary> | ||
/// Custom property drawer in order to use the "Advanced Picker" from UnityEditor.Search. | ||
/// </summary> | ||
[CustomPropertyDrawer(typeof(InputActionReference))] | ||
internal sealed class InputActionReferencePropertyDrawer : PropertyDrawer | ||
{ | ||
private readonly SearchContext m_Context = UnityEditor.Search.SearchService.CreateContext(new[] | ||
{ | ||
InputActionReferenceSearchProviders.CreateInputActionReferenceSearchProviderForAssets(), | ||
InputActionReferenceSearchProviders.CreateInputActionReferenceSearchProviderForProjectWideActions(), | ||
}, string.Empty, SearchConstants.PickerSearchFlags); | ||
|
||
|
||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) | ||
{ | ||
// Sets the property to null if the action is not found in the asset. | ||
ValidatePropertyWithDanglingInputActionReferences(property); | ||
jfreire-unity marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
ObjectField.DoObjectField(position, property, typeof(InputActionReference), label, | ||
m_Context, SearchConstants.PickerViewFlags); | ||
} | ||
|
||
static void ValidatePropertyWithDanglingInputActionReferences(SerializedProperty property) | ||
{ | ||
if (property?.objectReferenceValue is InputActionReference reference) | ||
{ | ||
// Check only if the reference is a project-wide action. | ||
if (reference?.asset?.name == ProjectWideActionsAsset.kAssetName) | ||
{ | ||
var action = reference?.asset?.FindAction(reference.action.id); | ||
if (action is null) | ||
{ | ||
property.objectReferenceValue = null; | ||
property.serializedObject.ApplyModifiedProperties(); | ||
} | ||
} | ||
} | ||
jfreire-unity marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
} | ||
|
||
#endif |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
#if UNITY_EDITOR && UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS | ||
using System; | ||
using System.Collections.Generic; | ||
using UnityEditor; | ||
using UnityEditor.Search; | ||
using UnityEngine.Search; | ||
|
||
namespace UnityEngine.InputSystem.Editor | ||
{ | ||
internal static class SearchConstants | ||
{ | ||
// SearchFlags: these flags are used to customize how search is performed and how search | ||
// results are displayed in the advanced object picker. | ||
// Note: SearchFlags.Packages is not currently used and hides all results from packages. | ||
internal static readonly SearchFlags PickerSearchFlags = SearchFlags.Sorted | SearchFlags.OpenPicker; | ||
|
||
// Search.SearchViewFlags : these flags are used to customize the appearance of the PickerWindow. | ||
internal static readonly Search.SearchViewFlags PickerViewFlags = SearchViewFlags.DisableBuilderModeToggle | ||
| SearchViewFlags.DisableInspectorPreview | ||
| SearchViewFlags.ListView | ||
| SearchViewFlags.DisableSavedSearchQuery; | ||
} | ||
|
||
internal static class InputActionReferenceSearchProviders | ||
{ | ||
const string k_AssetFolderSearchProviderId = "AssetsInputActionReferenceSearchProvider"; | ||
const string k_ProjectWideActionsSearchProviderId = "ProjectWideInputActionReferenceSearchProvider"; | ||
|
||
// Search provider for InputActionReferences for all assets in the project, without project-wide actions. | ||
internal static SearchProvider CreateInputActionReferenceSearchProviderForAssets() | ||
jfreire-unity marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
return CreateInputActionReferenceSearchProvider(k_AssetFolderSearchProviderId, | ||
"Asset Input Actions", | ||
// Show the asset path in the description. | ||
(obj) => AssetDatabase.GetAssetPath((obj as InputActionReference).asset), | ||
() => InputActionImporter.LoadInputActionReferencesFromAssetDatabase()); | ||
} | ||
|
||
// Search provider for InputActionReferences for project-wide actions | ||
internal static SearchProvider CreateInputActionReferenceSearchProviderForProjectWideActions() | ||
{ | ||
return CreateInputActionReferenceSearchProvider(k_ProjectWideActionsSearchProviderId, | ||
"Project-Wide Input Actions", | ||
(obj) => "(Project-Wide Input Actions)", | ||
() => InputActionImporter.LoadInputActionReferencesFromAsset(ProjectWideActionsAsset.GetOrCreate())); | ||
} | ||
|
||
private static SearchProvider CreateInputActionReferenceSearchProvider(string id, string displayName, | ||
Func<Object, string> createItemFetchDescription, Func<IEnumerable<Object>> fetchAssets) | ||
{ | ||
// Match icon used for sub-assets from importer for InputActionReferences. | ||
// We assign description+label in FilteredSearch but also provide a fetchDescription+fetchLabel below. | ||
// This is needed to support all zoom-modes for an unknown reason. | ||
// Also, fetchLabel/fetchDescription and what is provided to CreateItem is playing different | ||
// roles at different zoom levels. | ||
var inputActionReferenceIcon = InputActionAssetIconLoader.LoadActionIcon(); | ||
|
||
return new SearchProvider(id, displayName) | ||
{ | ||
priority = 25, | ||
fetchDescription = FetchLabel, | ||
fetchItems = (context, items, provider) => FilteredSearch(context, provider, FetchLabel, createItemFetchDescription, | ||
fetchAssets, "(Project-Wide Input Actions)"), | ||
fetchLabel = FetchLabel, | ||
fetchPreview = (item, context, size, options) => inputActionReferenceIcon, | ||
fetchThumbnail = (item, context) => inputActionReferenceIcon, | ||
toObject = (item, type) => item.data as Object, | ||
}; | ||
} | ||
|
||
// Custom search function with label matching filtering. | ||
private static IEnumerable<SearchItem> FilteredSearch(SearchContext context, SearchProvider provider, | ||
Func<Object, string> fetchObjectLabel, Func<Object, string> createItemFetchDescription, Func<IEnumerable<Object>> fetchAssets, string description) | ||
{ | ||
foreach (var asset in fetchAssets()) | ||
{ | ||
var label = fetchObjectLabel(asset); | ||
if (!label.Contains(context.searchText, System.StringComparison.InvariantCultureIgnoreCase)) | ||
continue; // Ignore due to filtering | ||
yield return provider.CreateItem(context, asset.GetInstanceID().ToString(), label, createItemFetchDescription(asset), | ||
null, asset); | ||
} | ||
} | ||
|
||
// Note that this is overloaded to allow utilizing FetchLabel inside fetchItems to keep label formatting | ||
// consistent between CreateItem and additional fetchLabel calls. | ||
private static string FetchLabel(Object obj) | ||
{ | ||
return obj.name; | ||
} | ||
|
||
private static string FetchLabel(SearchItem item, SearchContext context) | ||
{ | ||
return FetchLabel((item.data as Object) !); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line doesn't compile. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you clarify in what circumstances (editor version, OS, etc) ? I haven't seen any compile errors on my side and others, or CI. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry this was my mistake, I was trying to enable global actions on an earlier Unity Editor version that isn't supported. |
||
} | ||
} | ||
} | ||
#endif |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,7 +35,7 @@ public override void OnActivate(string searchContext, VisualElement rootElement) | |
// Note that focused element will be set if we are navigating back to | ||
// an existing instance when switching setting in the left project settings panel since | ||
// this doesn't recreate the editor. | ||
if (m_RootVisualElement.focusController.focusedElement != null) | ||
if (m_RootVisualElement?.focusController?.focusedElement != null) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I noticed you picked up this diff from the original branch, just wanted to clarify this is a general bug fix relating to a post around potential focus controller bug and isn't really related to the subject of the PR, I think it is fine to add anyway but might require its own FIX line in the CHANGELOG / commit message. |
||
OnEditFocus(null); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this part?
&& o.name != "InputManager
InputManager data shouldn't be an InputActionReference should it ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, in theory it shouldn't.
But when removing an input action reference from the asset, it seems to show InputManage in the picker window. This was a just a quick fix. Something to improve, depending on the time we have to understand what's happening in InputManager.asset.