UI 자동화를 위해 바인딩 기능 구현
- 유니티 에셋 인증 오류로 meta 재생성
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
#if UNITY_EDITOR
|
||||
|
||||
namespace UnityEngine.InputSystem.Editor
|
||||
{
|
||||
// For clarity, the tables below indicate the callback sequences of the asset modification processor and
|
||||
// asset post-processor for various user operations done on assets.
|
||||
//
|
||||
// User operation: Callback sequence:
|
||||
// ----------------------------------------------------------------------------------------
|
||||
// Save Imported(s)
|
||||
// Delete OnWillDelete(s), Deleted(s)
|
||||
// Copy Imported(s)
|
||||
// Rename OnWillMove(s,d), Imported(d), Moved(s,d)
|
||||
// Move (drag) / Cut+Paste OnWillMove(s,d), Moved(s,d)
|
||||
// ------------------------------------------------------------------------------------------------------------
|
||||
//
|
||||
// User operation: Callback/call sequence:
|
||||
// ------------------------------------------------------------------------------------------------------------
|
||||
// Save Imported(s)
|
||||
// Delete OnWillDelete(s), Deleted(s)
|
||||
// Copy Imported(s), Fix(s), Imported(s)
|
||||
// Rename OnWillMove(s,d), Imported(d), Fix(d), Moved(s,d), Imported(d)
|
||||
// Move(drag) / Cut+Paste OnWillMove(s,d), Moved(s,d)
|
||||
// ------------------------------------------------------------------------------------------------------------
|
||||
// Note that as stated in table above, JSON name changes (called "Fix" above) will only be executed when either
|
||||
// Copying, Renaming within the editor. For all other operations the name and file name would not differ.
|
||||
//
|
||||
// External user operation: Callback/call sequence:
|
||||
// ------------------------------------------------------------------------------------------------------------
|
||||
// Save Imported(s)
|
||||
// Delete Deleted(s)
|
||||
// Copy Imported(s)
|
||||
// Rename Imported(d), Deleted(s)
|
||||
// Move(drag) / Cut+Paste Imported(d), Deleted(s)
|
||||
// ------------------------------------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Callback interface for monitoring changes to assets.
|
||||
/// </summary>
|
||||
internal interface IAssetObserver
|
||||
{
|
||||
/// <summary>
|
||||
/// Callback triggered when the associated asset is imported.
|
||||
/// </summary>
|
||||
void OnAssetImported();
|
||||
|
||||
/// <summary>
|
||||
/// Callback triggered when the associated asset is moved.
|
||||
/// </summary>
|
||||
void OnAssetMoved();
|
||||
|
||||
/// <summary>
|
||||
/// Callback triggered when the associated asset is deleted.
|
||||
/// </summary>
|
||||
void OnAssetDeleted();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface representing an editor capable of editing <c>InputActionAsset</c> instances associated
|
||||
/// with an asset file in the Asset Database (ADB).
|
||||
/// </summary>
|
||||
internal interface IInputActionAssetEditor : IAssetObserver
|
||||
{
|
||||
/// <summary>
|
||||
/// A read-only string representation of the asset GUID associated with the asset being edited.
|
||||
/// </summary>
|
||||
string assetGUID { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the editor has unsaved changes compared to the associated imported source asset.
|
||||
/// </summary>
|
||||
bool isDirty { get; }
|
||||
}
|
||||
}
|
||||
|
||||
#endif // UNITY_EDITOR
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2603947746e342918fcad117e9ce6db6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,262 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEngine.InputSystem.Editor
|
||||
{
|
||||
// We want an empty editor in the inspector. Editing happens in a dedicated window.
|
||||
[CustomEditor(typeof(InputActionAsset))]
|
||||
internal class InputActionAssetEditor : UnityEditor.Editor
|
||||
{
|
||||
protected override void OnHeaderGUI()
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
}
|
||||
|
||||
#region Support abstract editor registration
|
||||
|
||||
private static readonly List<Type> s_EditorTypes = new List<Type>();
|
||||
|
||||
// Registers an asset editor type for receiving asset modification callbacks.
|
||||
public static void RegisterType<T>() where T : IInputActionAssetEditor
|
||||
{
|
||||
if (!s_EditorTypes.Contains(typeof(T)))
|
||||
s_EditorTypes.Add(typeof(T));
|
||||
}
|
||||
|
||||
// Unregisters an asset editor type from receiving asset modification callbacks.
|
||||
public static void UnregisterType<T>() where T : IInputActionAssetEditor
|
||||
{
|
||||
s_EditorTypes.Remove(typeof(T));
|
||||
}
|
||||
|
||||
public static T FindOpenEditor<T>(string path) where T : EditorWindow
|
||||
{
|
||||
var openEditors = FindAllEditorsForPath(path);
|
||||
foreach (var openEditor in openEditors)
|
||||
{
|
||||
if (openEditor.GetType() == typeof(T))
|
||||
return (T)openEditor;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Finds all asset editors associated with the asset given by path.
|
||||
public static IInputActionAssetEditor[] FindAllEditorsForPath(string path)
|
||||
{
|
||||
var guid = AssetDatabase.AssetPathToGUID(path);
|
||||
return guid != null ? FindAllEditors((editor) => editor.assetGUID == guid) :
|
||||
Array.Empty<IInputActionAssetEditor>();
|
||||
}
|
||||
|
||||
// Finds all asset editors fulfilling the given predicate.
|
||||
public static IInputActionAssetEditor[] FindAllEditors(Predicate<IInputActionAssetEditor> predicate = null)
|
||||
{
|
||||
List<IInputActionAssetEditor> editors = null;
|
||||
foreach (var type in s_EditorTypes)
|
||||
editors = FindAllEditors(type, predicate, editors);
|
||||
return editors != null ? editors.ToArray() : Array.Empty<IInputActionAssetEditor>();
|
||||
}
|
||||
|
||||
private static List<IInputActionAssetEditor> FindAllEditors(Type type,
|
||||
Predicate<IInputActionAssetEditor> predicate = null,
|
||||
List<IInputActionAssetEditor> result = null)
|
||||
{
|
||||
if (result == null)
|
||||
result = new List<IInputActionAssetEditor>();
|
||||
var editors = Resources.FindObjectsOfTypeAll(type);
|
||||
foreach (var editor in editors)
|
||||
{
|
||||
if (editor is IInputActionAssetEditor actionsAssetEditor && (predicate == null || predicate(actionsAssetEditor)))
|
||||
result.Add(actionsAssetEditor);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Asset modification processor to intercept Unity editor move or delete operations
|
||||
// Asset modification processor designed to handle the following scenarios:
|
||||
// - When an asset is about to get deleted, evaluate if there is a pending unsaved edited copy of the asset
|
||||
// open in any associated editor and in this case, prompt the user that there are unsaved changes and allow
|
||||
// the user to cancel the operation and allow to save the pending changes or confirm to delete the asset and
|
||||
// discard the pending unsaved changes (via OnAssetDeleted() notification).
|
||||
// - If the asset being deleted is not open in any editors or any open copies are not modified, no dialog
|
||||
// prompt is displayed and the asset is deleted.
|
||||
// - When an asset is about to get moved, notify any editors having the asset open about the move.
|
||||
//
|
||||
// See comments further down in this class for expected callback sequences.
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Intantiated through reflection by Unity")]
|
||||
private class InputActionAssetModificationProcessor : UnityEditor.AssetModificationProcessor
|
||||
{
|
||||
public static AssetDeleteResult OnWillDeleteAsset(string path, RemoveAssetOptions options)
|
||||
{
|
||||
if (InputActionImporter.IsInputActionAssetPath(path))
|
||||
{
|
||||
// Find any open editors associated to the asset and if any of them holds unsaved changes
|
||||
// allow the user to discard unsaved changes or cancel deletion.
|
||||
var editorWithAssetOpen = InputActionAssetEditor.FindAllEditorsForPath(path);
|
||||
foreach (var editor in editorWithAssetOpen)
|
||||
{
|
||||
if (editor.isDirty)
|
||||
{
|
||||
var result = Dialog.InputActionAsset.ShowDiscardUnsavedChanges(path);
|
||||
if (result == Dialog.Result.Cancel)
|
||||
return AssetDeleteResult.FailedDelete;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Notify all associated editors that asset will be deleted
|
||||
foreach (var editor in editorWithAssetOpen)
|
||||
editor.OnAssetDeleted();
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public static AssetMoveResult OnWillMoveAsset(string sourcePath, string destinationPath)
|
||||
{
|
||||
if (InputActionImporter.IsInputActionAssetPath(sourcePath))
|
||||
{
|
||||
var editorWithAssetOpen = InputActionAssetEditor.FindAllEditorsForPath(sourcePath);
|
||||
foreach (var editor in editorWithAssetOpen)
|
||||
editor.OnAssetMoved();
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Asset post processor to react to internal or external asset import, move, delete events.
|
||||
// Processor detecting any Unity editor internal or external (file system) changes to an asset and notifies
|
||||
// any associated asset editors about those changes via callbacks.
|
||||
//
|
||||
// Note that any editor classes interested in receiving notifications need to be registered.
|
||||
//
|
||||
// For clarity, the tables below indicate the callback sequences of the asset modification processor and
|
||||
// asset post-processor for various user operations done on assets.
|
||||
//
|
||||
// s = source file
|
||||
// d = destination file
|
||||
// * = operation may be aborted by user
|
||||
//
|
||||
// User operation: Callback sequence:
|
||||
// ----------------------------------------------------------------------------------------
|
||||
// Write (Save) Imported(s)
|
||||
// Delete OnWillDelete(s), Deleted(s)*
|
||||
// Copy Imported(s)
|
||||
// Rename OnWillMove(s,d), Imported(d), Moved(s,d)
|
||||
// Move (drag) / Cut+Paste OnWillMove(s,d), Moved(s,d)
|
||||
// ------------------------------------------------------------------------------------------------------------
|
||||
//
|
||||
// External user operation: Callback/call sequence:
|
||||
// ------------------------------------------------------------------------------------------------------------
|
||||
// Save Imported(s)
|
||||
// Delete Deleted(s)
|
||||
// Copy Imported(s)
|
||||
// Rename Imported(d), Deleted(s)
|
||||
// Move(drag) / Cut+Paste Imported(d), Deleted(s)
|
||||
// ------------------------------------------------------------------------------------------------------------
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Intantiated through reflection by Unity")]
|
||||
private class InputActionAssetPostprocessor : AssetPostprocessor
|
||||
{
|
||||
private static bool s_DoNotifyEditorsScheduled;
|
||||
private static List<string> s_Imported = new List<string>();
|
||||
private static List<string> s_Deleted = new List<string>();
|
||||
private static List<string> s_Moved = new List<string>();
|
||||
|
||||
private static void Notify(IReadOnlyCollection<string> assets,
|
||||
IReadOnlyCollection<IInputActionAssetEditor> editors, Action<IInputActionAssetEditor> callback)
|
||||
{
|
||||
foreach (var asset in assets)
|
||||
{
|
||||
var assetGuid = AssetDatabase.AssetPathToGUID(asset);
|
||||
foreach (var editor in editors)
|
||||
{
|
||||
if (editor.assetGUID != assetGuid)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
callback(editor);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void NotifyEditors()
|
||||
{
|
||||
try
|
||||
{
|
||||
// When the asset is modified outside of the editor and the importer settings are
|
||||
// visible in the Inspector the asset references in the importer inspector need to be
|
||||
// force rebuild (otherwise we gets lots of exceptions).
|
||||
ActiveEditorTracker.sharedTracker.ForceRebuild();
|
||||
|
||||
// Unconditionally find all existing editors regardless of associated asset
|
||||
var editors = InputActionAssetEditor.FindAllEditors();
|
||||
|
||||
// Abort if there are no available candidate editors
|
||||
if (editors == null || editors.Length == 0)
|
||||
return;
|
||||
|
||||
// Notify editors about asset changes
|
||||
Notify(s_Imported, editors, (editor) => editor.OnAssetImported());
|
||||
Notify(s_Deleted, editors, (editor) => editor.OnAssetDeleted());
|
||||
Notify(s_Moved, editors, (editor) => editor.OnAssetMoved());
|
||||
}
|
||||
finally
|
||||
{
|
||||
s_Imported.Clear();
|
||||
s_Deleted.Clear();
|
||||
s_Moved.Clear();
|
||||
|
||||
s_DoNotifyEditorsScheduled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void Process(string[] assets, ICollection<string> target)
|
||||
{
|
||||
foreach (var asset in assets)
|
||||
{
|
||||
// Ignore any assets with non matching extensions
|
||||
if (!InputActionImporter.IsInputActionAssetPath(asset))
|
||||
continue;
|
||||
|
||||
// Register asset in target collection for delay invocation
|
||||
target.Add(asset);
|
||||
|
||||
// If a notification execution has already been scheduled do nothing apart from registration.
|
||||
// We do this with delayed execution to avoid excessive updates interfering with ADB.
|
||||
if (!s_DoNotifyEditorsScheduled)
|
||||
{
|
||||
EditorApplication.delayCall += NotifyEditors;
|
||||
s_DoNotifyEditorsScheduled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets,
|
||||
string[] movedAssets, string[] movedFromAssetPaths, bool didDomainReload)
|
||||
{
|
||||
Process(importedAssets, s_Imported);
|
||||
Process(deletedAssets, s_Deleted);
|
||||
Process(movedAssets, s_Moved);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endif // UNITY_EDITOR
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 57f4c22a7e71744499d25ed776ef6cf2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,38 @@
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEngine.InputSystem.Editor
|
||||
{
|
||||
// Note that non-existing caching here is intentional since icon selected might be theme dependent.
|
||||
// There is no reason to cache icons unless there is a significant performance impact on the 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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 015a929bdae646a99b91b756b998233f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,599 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
using UnityEditor;
|
||||
|
||||
////TODO: option to allow referencing the original asset rather than embedding it
|
||||
|
||||
////TODO: emit indexer directly at toplevel so you can more easily look up actions dynamically
|
||||
|
||||
////TODO: put the generated code behind #if that depends on input system
|
||||
|
||||
////TODO: suffix map properties with Map or Actions (e.g. "PlayerMap" instead of "Player")
|
||||
|
||||
////TODO: unify the generated events so that performed, canceled, and started all go into a single event
|
||||
|
||||
////TODO: look up actions and maps by ID rather than by name
|
||||
|
||||
////TODO: only generate @something if @ is really needed
|
||||
|
||||
////TODO: allow having an unnamed or default-named action set which spills actions directly into the toplevel wrapper
|
||||
|
||||
////TODO: add cleanup for ActionEvents
|
||||
|
||||
////TODO: protect generated wrapper against modifications made to asset
|
||||
|
||||
////TODO: make capitalization consistent in the generated code
|
||||
|
||||
////TODO: instead of loading from JSON, generate the structure in code
|
||||
|
||||
////REVIEW: allow putting *all* of the data from the inputactions asset into the generated class?
|
||||
|
||||
namespace UnityEngine.InputSystem.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility to generate code that makes it easier to work with action sets.
|
||||
/// </summary>
|
||||
public static class InputActionCodeGenerator
|
||||
{
|
||||
private const int kSpacesPerIndentLevel = 4;
|
||||
|
||||
private const string kClassExample = @"using namespace UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
// Example of using an InputActionMap named ""Player"" from a UnityEngine.MonoBehaviour implementing callback interface.
|
||||
public class Example : MonoBehaviour, MyActions.IPlayerActions
|
||||
{
|
||||
private MyActions_Actions m_Actions; // Source code representation of asset.
|
||||
private MyActions_Actions.PlayerActions m_Player; // Source code representation of action map.
|
||||
|
||||
void Awake()
|
||||
{
|
||||
m_Actions = new MyActions_Actions(); // Create asset object.
|
||||
m_Player = m_Actions.Player; // Extract action map object.
|
||||
m_Player.AddCallbacks(this); // Register callback interface IPlayerActions.
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
m_Actions.Dispose(); // Destroy asset object.
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
m_Player.Enable(); // Enable all actions within map.
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
m_Player.Disable(); // Disable all actions within map.
|
||||
}
|
||||
|
||||
#region Interface implementation of MyActions.IPlayerActions
|
||||
|
||||
// Invoked when ""Move"" action is either started, performed or canceled.
|
||||
public void OnMove(InputAction.CallbackContext context)
|
||||
{
|
||||
Debug.Log($""OnMove: {context.ReadValue<Vector2>()}"");
|
||||
}
|
||||
|
||||
// Invoked when ""Attack"" action is either started, performed or canceled.
|
||||
public void OnAttack(InputAction.CallbackContext context)
|
||||
{
|
||||
Debug.Log($""OnAttack: {context.ReadValue<float>()}"");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}";
|
||||
|
||||
public struct Options
|
||||
{
|
||||
public string className { get; set; }
|
||||
public string namespaceName { get; set; }
|
||||
public string sourceAssetPath { get; set; }
|
||||
}
|
||||
|
||||
public static string GenerateWrapperCode(InputActionAsset asset, Options options = default)
|
||||
{
|
||||
if (asset == null)
|
||||
throw new ArgumentNullException(nameof(asset));
|
||||
|
||||
if (string.IsNullOrEmpty(options.sourceAssetPath))
|
||||
options.sourceAssetPath = AssetDatabase.GetAssetPath(asset);
|
||||
if (string.IsNullOrEmpty(options.className) && !string.IsNullOrEmpty(asset.name))
|
||||
options.className =
|
||||
CSharpCodeHelpers.MakeTypeName(asset.name);
|
||||
|
||||
if (string.IsNullOrEmpty(options.className))
|
||||
{
|
||||
if (string.IsNullOrEmpty(options.sourceAssetPath))
|
||||
throw new ArgumentException("options.sourceAssetPath");
|
||||
options.className =
|
||||
CSharpCodeHelpers.MakeTypeName(Path.GetFileNameWithoutExtension(options.sourceAssetPath));
|
||||
}
|
||||
|
||||
var writer = new Writer
|
||||
{
|
||||
buffer = new StringBuilder()
|
||||
};
|
||||
|
||||
// Header.
|
||||
writer.WriteLine(CSharpCodeHelpers.MakeAutoGeneratedCodeHeader("com.unity.inputsystem:InputActionCodeGenerator",
|
||||
InputSystem.version.ToString(),
|
||||
options.sourceAssetPath));
|
||||
|
||||
// Usings.
|
||||
writer.WriteLine("using System;");
|
||||
writer.WriteLine("using System.Collections;");
|
||||
writer.WriteLine("using System.Collections.Generic;");
|
||||
writer.WriteLine("using UnityEngine.InputSystem;");
|
||||
writer.WriteLine("using UnityEngine.InputSystem.Utilities;");
|
||||
writer.WriteLine("");
|
||||
|
||||
// Begin namespace.
|
||||
var haveNamespace = !string.IsNullOrEmpty(options.namespaceName);
|
||||
if (haveNamespace)
|
||||
{
|
||||
writer.WriteLine($"namespace {options.namespaceName}");
|
||||
writer.BeginBlock();
|
||||
}
|
||||
|
||||
// Begin class.
|
||||
writer.DocSummary($"Provides programmatic access to <see cref=\"InputActionAsset\" />, " +
|
||||
"<see cref=\"InputActionMap\" />, <see cref=\"InputAction\" /> and " +
|
||||
"<see cref=\"InputControlScheme\" /> instances defined " +
|
||||
$"in asset \"{options.sourceAssetPath}\".");
|
||||
writer.DocRemarks("This class is source generated and any manual edits will be discarded if the associated asset is reimported or modified.");
|
||||
writer.DocExample(kClassExample);
|
||||
|
||||
writer.WriteLine($"public partial class @{options.className}: IInputActionCollection2, IDisposable");
|
||||
writer.BeginBlock();
|
||||
|
||||
writer.DocSummary("Provides access to the underlying asset instance.");
|
||||
writer.WriteLine($"public InputActionAsset asset {{ get; }}");
|
||||
writer.WriteLine();
|
||||
|
||||
// Default constructor.
|
||||
writer.DocSummary("Constructs a new instance.");
|
||||
writer.WriteLine($"public @{options.className}()");
|
||||
writer.BeginBlock();
|
||||
writer.WriteLine($"asset = InputActionAsset.FromJson(@\"{asset.ToJson().Replace("\"", "\"\"")}\");");
|
||||
|
||||
var maps = asset.actionMaps;
|
||||
var schemes = asset.controlSchemes;
|
||||
foreach (var map in maps)
|
||||
{
|
||||
var mapName = CSharpCodeHelpers.MakeIdentifier(map.name);
|
||||
writer.WriteLine($"// {map.name}");
|
||||
writer.WriteLine($"m_{mapName} = asset.FindActionMap(\"{map.name}\", throwIfNotFound: true);");
|
||||
|
||||
foreach (var action in map.actions)
|
||||
{
|
||||
var actionName = CSharpCodeHelpers.MakeIdentifier(action.name);
|
||||
writer.WriteLine($"m_{mapName}_{actionName} = m_{mapName}.FindAction(\"{action.name}\", throwIfNotFound: true);");
|
||||
}
|
||||
}
|
||||
writer.EndBlock();
|
||||
writer.WriteLine();
|
||||
|
||||
writer.WriteLine($"~@{options.className}()");
|
||||
writer.BeginBlock();
|
||||
foreach (var map in maps)
|
||||
{
|
||||
var mapName = CSharpCodeHelpers.MakeIdentifier(map.name);
|
||||
writer.WriteLine($"UnityEngine.Debug.Assert(!m_{mapName}.enabled, \"This will cause a leak and performance issues, {options.className}.{mapName}.Disable() has not been called.\");");
|
||||
}
|
||||
writer.EndBlock();
|
||||
writer.WriteLine();
|
||||
|
||||
writer.DocSummary("Destroys this asset and all associated <see cref=\"InputAction\"/> instances.");
|
||||
writer.WriteLine("public void Dispose()");
|
||||
writer.BeginBlock();
|
||||
writer.WriteLine("UnityEngine.Object.Destroy(asset);");
|
||||
writer.EndBlock();
|
||||
writer.WriteLine();
|
||||
|
||||
var classNamePrefix = typeof(InputActionAsset).Namespace + "." + nameof(InputActionAsset) + ".";
|
||||
writer.DocInherit(classNamePrefix + nameof(InputActionAsset.bindingMask));
|
||||
writer.WriteLine("public InputBinding? bindingMask");
|
||||
writer.BeginBlock();
|
||||
writer.WriteLine("get => asset.bindingMask;");
|
||||
writer.WriteLine("set => asset.bindingMask = value;");
|
||||
writer.EndBlock();
|
||||
writer.WriteLine();
|
||||
|
||||
writer.DocInherit(classNamePrefix + nameof(InputActionAsset.devices));
|
||||
writer.WriteLine("public ReadOnlyArray<InputDevice>? devices");
|
||||
writer.BeginBlock();
|
||||
writer.WriteLine("get => asset.devices;");
|
||||
writer.WriteLine("set => asset.devices = value;");
|
||||
writer.EndBlock();
|
||||
writer.WriteLine();
|
||||
|
||||
writer.DocInherit(classNamePrefix + nameof(InputActionAsset.controlSchemes));
|
||||
writer.WriteLine("public ReadOnlyArray<InputControlScheme> controlSchemes => asset.controlSchemes;");
|
||||
writer.WriteLine();
|
||||
|
||||
writer.DocInherit(classNamePrefix + nameof(InputActionAsset.Contains) + "(InputAction)");
|
||||
writer.WriteLine("public bool Contains(InputAction action)");
|
||||
writer.BeginBlock();
|
||||
writer.WriteLine("return asset.Contains(action);");
|
||||
writer.EndBlock();
|
||||
writer.WriteLine();
|
||||
|
||||
writer.DocInherit(classNamePrefix + nameof(InputActionAsset.GetEnumerator) + "()");
|
||||
writer.WriteLine("public IEnumerator<InputAction> GetEnumerator()");
|
||||
writer.BeginBlock();
|
||||
writer.WriteLine("return asset.GetEnumerator();");
|
||||
writer.EndBlock();
|
||||
writer.WriteLine();
|
||||
|
||||
writer.DocInherit(nameof(IEnumerable) + "." + nameof(IEnumerable.GetEnumerator) + "()");
|
||||
writer.WriteLine("IEnumerator IEnumerable.GetEnumerator()");
|
||||
writer.BeginBlock();
|
||||
writer.WriteLine("return GetEnumerator();");
|
||||
writer.EndBlock();
|
||||
writer.WriteLine();
|
||||
|
||||
writer.DocInherit(classNamePrefix + nameof(InputActionAsset.Enable) + "()");
|
||||
writer.WriteLine("public void Enable()");
|
||||
writer.BeginBlock();
|
||||
writer.WriteLine("asset.Enable();");
|
||||
writer.EndBlock();
|
||||
writer.WriteLine();
|
||||
|
||||
writer.DocInherit(classNamePrefix + nameof(InputActionAsset.Disable) + "()");
|
||||
writer.WriteLine("public void Disable()");
|
||||
writer.BeginBlock();
|
||||
writer.WriteLine("asset.Disable();");
|
||||
writer.EndBlock();
|
||||
writer.WriteLine();
|
||||
|
||||
writer.DocInherit(classNamePrefix + nameof(InputActionAsset.bindings));
|
||||
writer.WriteLine("public IEnumerable<InputBinding> bindings => asset.bindings;");
|
||||
writer.WriteLine();
|
||||
|
||||
writer.DocInherit(classNamePrefix + nameof(InputActionAsset.FindAction) + "(string, bool)");
|
||||
writer.WriteLine("public InputAction FindAction(string actionNameOrId, bool throwIfNotFound = false)");
|
||||
writer.BeginBlock();
|
||||
writer.WriteLine("return asset.FindAction(actionNameOrId, throwIfNotFound);");
|
||||
writer.EndBlock();
|
||||
writer.WriteLine();
|
||||
|
||||
writer.DocInherit(classNamePrefix + nameof(InputActionAsset.FindBinding) + "(InputBinding, out InputAction)");
|
||||
writer.WriteLine("public int FindBinding(InputBinding bindingMask, out InputAction action)");
|
||||
writer.BeginBlock();
|
||||
writer.WriteLine("return asset.FindBinding(bindingMask, out action);");
|
||||
writer.EndBlock();
|
||||
|
||||
// Action map accessors.
|
||||
var inputActionMapClassPrefix = typeof(InputActionMap).Namespace + "." + nameof(InputActionMap) + ".";
|
||||
foreach (var map in maps)
|
||||
{
|
||||
writer.WriteLine();
|
||||
writer.WriteLine($"// {map.name}");
|
||||
|
||||
var mapName = CSharpCodeHelpers.MakeIdentifier(map.name);
|
||||
var mapTypeName = CSharpCodeHelpers.MakeTypeName(mapName, "Actions");
|
||||
|
||||
// Caching field for action map.
|
||||
writer.WriteLine($"private readonly InputActionMap m_{mapName};");
|
||||
writer.WriteLine(string.Format("private List<I{0}> m_{0}CallbackInterfaces = new List<I{0}>();", mapTypeName));
|
||||
|
||||
// Caching fields for all actions.
|
||||
foreach (var action in map.actions)
|
||||
{
|
||||
var actionName = CSharpCodeHelpers.MakeIdentifier(action.name);
|
||||
writer.WriteLine($"private readonly InputAction m_{mapName}_{actionName};");
|
||||
}
|
||||
|
||||
// Struct wrapping access to action set.
|
||||
writer.DocSummary($"Provides access to input actions defined in input action map \"{map.name}\".");
|
||||
writer.WriteLine($"public struct {mapTypeName}");
|
||||
writer.BeginBlock();
|
||||
|
||||
writer.WriteLine($"private @{options.className} m_Wrapper;");
|
||||
writer.WriteLine();
|
||||
|
||||
// Constructor.
|
||||
writer.DocSummary("Construct a new instance of the input action map wrapper class.");
|
||||
writer.WriteLine($"public {mapTypeName}(@{options.className} wrapper) {{ m_Wrapper = wrapper; }}");
|
||||
|
||||
// Getter for each action.
|
||||
foreach (var action in map.actions)
|
||||
{
|
||||
var actionName = CSharpCodeHelpers.MakeIdentifier(action.name);
|
||||
writer.DocSummary($"Provides access to the underlying input action \"{mapName}/{actionName}\".");
|
||||
writer.WriteLine(
|
||||
$"public InputAction @{actionName} => m_Wrapper.m_{mapName}_{actionName};");
|
||||
}
|
||||
|
||||
// Action map getter.
|
||||
writer.DocSummary("Provides access to the underlying input action map instance.");
|
||||
writer.WriteLine($"public InputActionMap Get() {{ return m_Wrapper.m_{mapName}; }}");
|
||||
|
||||
// Enable/disable methods.
|
||||
writer.DocInherit(inputActionMapClassPrefix + nameof(InputActionMap.Enable) + "()");
|
||||
writer.WriteLine("public void Enable() { Get().Enable(); }");
|
||||
writer.DocInherit(inputActionMapClassPrefix + nameof(InputActionMap.Disable) + "()");
|
||||
writer.WriteLine("public void Disable() { Get().Disable(); }");
|
||||
writer.DocInherit(inputActionMapClassPrefix + nameof(InputActionMap.enabled));
|
||||
writer.WriteLine("public bool enabled => Get().enabled;");
|
||||
|
||||
// Implicit conversion operator.
|
||||
writer.DocSummary($"Implicitly converts an <see ref=\"{mapTypeName}\" /> to an <see ref=\"InputActionMap\" /> instance.");
|
||||
writer.WriteLine(
|
||||
$"public static implicit operator InputActionMap({mapTypeName} set) {{ return set.Get(); }}");
|
||||
|
||||
// AddCallbacks method.
|
||||
writer.DocSummary("Adds <see cref=\"InputAction.started\"/>, <see cref=\"InputAction.performed\"/> and <see cref=\"InputAction.canceled\"/> callbacks provided via <param cref=\"instance\" /> on all input actions contained in this map.");
|
||||
writer.DocParam("instance", "Callback instance.");
|
||||
writer.DocRemarks("If <paramref name=\"instance\" /> is <c>null</c> or <paramref name=\"instance\"/> have already been added this method does nothing.");
|
||||
writer.DocSeeAlso(mapTypeName);
|
||||
writer.WriteLine($"public void AddCallbacks(I{mapTypeName} instance)");
|
||||
writer.BeginBlock();
|
||||
|
||||
// Initialize new interface.
|
||||
writer.WriteLine($"if (instance == null || m_Wrapper.m_{mapTypeName}CallbackInterfaces.Contains(instance)) return;");
|
||||
writer.WriteLine($"m_Wrapper.m_{mapTypeName}CallbackInterfaces.Add(instance);");
|
||||
|
||||
foreach (var action in map.actions)
|
||||
{
|
||||
var actionName = CSharpCodeHelpers.MakeIdentifier(action.name);
|
||||
var actionTypeName = CSharpCodeHelpers.MakeTypeName(action.name);
|
||||
|
||||
writer.WriteLine($"@{actionName}.started += instance.On{actionTypeName};");
|
||||
writer.WriteLine($"@{actionName}.performed += instance.On{actionTypeName};");
|
||||
writer.WriteLine($"@{actionName}.canceled += instance.On{actionTypeName};");
|
||||
}
|
||||
|
||||
writer.EndBlock();
|
||||
writer.WriteLine();
|
||||
|
||||
// UnregisterCallbacks method.
|
||||
writer.DocSummary("Removes <see cref=\"InputAction.started\"/>, <see cref=\"InputAction.performed\"/> and <see cref=\"InputAction.canceled\"/> callbacks provided via <param cref=\"instance\" /> on all input actions contained in this map.");
|
||||
writer.DocRemarks("Calling this method when <paramref name=\"instance\" /> have not previously been registered has no side-effects.");
|
||||
writer.DocSeeAlso(mapTypeName);
|
||||
writer.WriteLine($"private void UnregisterCallbacks(I{mapTypeName} instance)");
|
||||
writer.BeginBlock();
|
||||
foreach (var action in map.actions)
|
||||
{
|
||||
var actionName = CSharpCodeHelpers.MakeIdentifier(action.name);
|
||||
var actionTypeName = CSharpCodeHelpers.MakeTypeName(action.name);
|
||||
|
||||
writer.WriteLine($"@{actionName}.started -= instance.On{actionTypeName};");
|
||||
writer.WriteLine($"@{actionName}.performed -= instance.On{actionTypeName};");
|
||||
writer.WriteLine($"@{actionName}.canceled -= instance.On{actionTypeName};");
|
||||
}
|
||||
writer.EndBlock();
|
||||
writer.WriteLine();
|
||||
|
||||
// RemoveCallbacks method.
|
||||
writer.DocSummary($"Unregisters <param cref=\"instance\" /> and unregisters all input action callbacks via <see cref=\"{mapTypeName}.UnregisterCallbacks(I{mapTypeName})\" />.");
|
||||
writer.DocSeeAlso($"{mapTypeName}.UnregisterCallbacks(I{mapTypeName})");
|
||||
writer.WriteLine($"public void RemoveCallbacks(I{mapTypeName} instance)");
|
||||
writer.BeginBlock();
|
||||
writer.WriteLine($"if (m_Wrapper.m_{mapTypeName}CallbackInterfaces.Remove(instance))");
|
||||
writer.WriteLine($" UnregisterCallbacks(instance);");
|
||||
writer.EndBlock();
|
||||
writer.WriteLine();
|
||||
|
||||
// SetCallbacks method.
|
||||
writer.DocSummary($"Replaces all existing callback instances and previously registered input action callbacks associated with them with callbacks provided via <param cref=\"instance\" />.");
|
||||
writer.DocRemarks($"If <paramref name=\"instance\" /> is <c>null</c>, calling this method will only unregister all existing callbacks but not register any new callbacks.");
|
||||
writer.DocSeeAlso($"{mapTypeName}.AddCallbacks(I{mapTypeName})");
|
||||
writer.DocSeeAlso($"{mapTypeName}.RemoveCallbacks(I{mapTypeName})");
|
||||
writer.DocSeeAlso($"{mapTypeName}.UnregisterCallbacks(I{mapTypeName})");
|
||||
writer.WriteLine($"public void SetCallbacks(I{mapTypeName} instance)");
|
||||
writer.BeginBlock();
|
||||
////REVIEW: this would benefit from having a single callback on InputActions rather than three different endpoints
|
||||
|
||||
writer.WriteLine($"foreach (var item in m_Wrapper.m_{mapTypeName}CallbackInterfaces)");
|
||||
writer.WriteLine($" UnregisterCallbacks(item);");
|
||||
writer.WriteLine($"m_Wrapper.m_{mapTypeName}CallbackInterfaces.Clear();");
|
||||
|
||||
// Initialize new interface.
|
||||
writer.WriteLine("AddCallbacks(instance);");
|
||||
writer.EndBlock();
|
||||
writer.EndBlock();
|
||||
|
||||
// Getter for instance of struct.
|
||||
writer.DocSummary($"Provides a new <see cref=\"{mapTypeName}\" /> instance referencing this action map.");
|
||||
writer.WriteLine($"public {mapTypeName} @{mapName} => new {mapTypeName}(this);");
|
||||
}
|
||||
|
||||
// Control scheme accessors.
|
||||
foreach (var scheme in schemes)
|
||||
{
|
||||
var identifier = CSharpCodeHelpers.MakeIdentifier(scheme.name);
|
||||
|
||||
writer.WriteLine($"private int m_{identifier}SchemeIndex = -1;");
|
||||
writer.DocSummary("Provides access to the input control scheme.");
|
||||
writer.DocSeeAlso(typeof(InputControlScheme).Namespace + "." + nameof(InputControlScheme));
|
||||
writer.WriteLine($"public InputControlScheme {identifier}Scheme");
|
||||
writer.BeginBlock();
|
||||
writer.WriteLine("get");
|
||||
writer.BeginBlock();
|
||||
writer.WriteLine($"if (m_{identifier}SchemeIndex == -1) m_{identifier}SchemeIndex = asset.FindControlSchemeIndex(\"{scheme.name}\");");
|
||||
writer.WriteLine($"return asset.controlSchemes[m_{identifier}SchemeIndex];");
|
||||
writer.EndBlock();
|
||||
writer.EndBlock();
|
||||
}
|
||||
|
||||
// Generate interfaces.
|
||||
var inputActionClassReference = typeof(InputAction).Namespace + "." + nameof(InputAction) + ".";
|
||||
foreach (var map in maps)
|
||||
{
|
||||
var typeName = CSharpCodeHelpers.MakeTypeName(map.name);
|
||||
writer.DocSummary($"Interface to implement callback methods for all input action callbacks associated with input actions defined by \"{map.name}\" which allows adding and removing callbacks.");
|
||||
writer.DocSeeAlso($"{typeName}Actions.AddCallbacks(I{typeName}Actions)");
|
||||
writer.DocSeeAlso($"{typeName}Actions.RemoveCallbacks(I{typeName}Actions)");
|
||||
writer.WriteLine($"public interface I{typeName}Actions");
|
||||
writer.BeginBlock();
|
||||
|
||||
foreach (var action in map.actions)
|
||||
{
|
||||
var methodName = CSharpCodeHelpers.MakeTypeName(action.name);
|
||||
writer.DocSummary($"Method invoked when associated input action \"{action.name}\" is either <see cref=\"UnityEngine.InputSystem.InputAction.started\" />, <see cref=\"UnityEngine.InputSystem.InputAction.performed\" /> or <see cref=\"UnityEngine.InputSystem.InputAction.canceled\" />.");
|
||||
writer.DocSeeAlso(string.Concat(inputActionClassReference, "started"));
|
||||
writer.DocSeeAlso(string.Concat(inputActionClassReference, "performed"));
|
||||
writer.DocSeeAlso(string.Concat(inputActionClassReference, "canceled"));
|
||||
writer.WriteLine($"void On{methodName}(InputAction.CallbackContext context);");
|
||||
}
|
||||
|
||||
writer.EndBlock();
|
||||
}
|
||||
|
||||
// End class.
|
||||
writer.EndBlock();
|
||||
|
||||
// End namespace.
|
||||
if (haveNamespace)
|
||||
writer.EndBlock();
|
||||
|
||||
return writer.buffer.ToString();
|
||||
}
|
||||
|
||||
////TODO: move this to a shared place
|
||||
internal struct Writer
|
||||
{
|
||||
public StringBuilder buffer;
|
||||
public int indentLevel;
|
||||
|
||||
public void BeginBlock()
|
||||
{
|
||||
WriteIndent();
|
||||
buffer.Append("{\n");
|
||||
++indentLevel;
|
||||
}
|
||||
|
||||
public void EndBlock()
|
||||
{
|
||||
--indentLevel;
|
||||
WriteIndent();
|
||||
buffer.Append("}\n");
|
||||
}
|
||||
|
||||
public void WriteLine()
|
||||
{
|
||||
buffer.Append('\n');
|
||||
}
|
||||
|
||||
public void WriteLine(string text)
|
||||
{
|
||||
if (!text.All(char.IsWhiteSpace))
|
||||
{
|
||||
WriteIndent();
|
||||
buffer.Append(text);
|
||||
}
|
||||
buffer.Append('\n');
|
||||
}
|
||||
|
||||
public void Write(string text)
|
||||
{
|
||||
buffer.Append(text);
|
||||
}
|
||||
|
||||
public void WriteIndent()
|
||||
{
|
||||
for (var i = 0; i < indentLevel; ++i)
|
||||
{
|
||||
for (var n = 0; n < kSpacesPerIndentLevel; ++n)
|
||||
buffer.Append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
public void DocSummary(string text)
|
||||
{
|
||||
DocElement("summary", text);
|
||||
}
|
||||
|
||||
public void DocParam(string paramName, string text)
|
||||
{
|
||||
WriteLine($"/// <param name=\"{paramName}\">{text}</param>");
|
||||
}
|
||||
|
||||
public void DocRemarks(string text)
|
||||
{
|
||||
DocElement("remarks", text);
|
||||
}
|
||||
|
||||
public void DocInherit(string cref)
|
||||
{
|
||||
DocReference("inheritdoc", cref);
|
||||
}
|
||||
|
||||
public void DocSeeAlso(string cref)
|
||||
{
|
||||
DocReference("seealso", cref: cref);
|
||||
}
|
||||
|
||||
public void DocExample(string code)
|
||||
{
|
||||
DocComment("<example>");
|
||||
DocComment("<code>");
|
||||
|
||||
foreach (var line in code.Split('\n'))
|
||||
DocComment(line.Replace("<", "<").Replace(">", ">"));
|
||||
|
||||
DocComment("</code>");
|
||||
DocComment("</example>");
|
||||
}
|
||||
|
||||
private void DocComment(string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
WriteLine("///");
|
||||
else
|
||||
WriteLine(string.Concat("/// ", text));
|
||||
}
|
||||
|
||||
private void DocElement(string tag, string text)
|
||||
{
|
||||
DocComment($"<{tag}>");
|
||||
DocComment(text);
|
||||
DocComment($"</{tag}>");
|
||||
}
|
||||
|
||||
private void DocReference(string tag, string cref)
|
||||
{
|
||||
DocInlineElement(tag, "cref", cref);
|
||||
}
|
||||
|
||||
private void DocInlineElement(string tag, string property, string value)
|
||||
{
|
||||
DocComment($"<{tag} {property}=\"{value}\" />");
|
||||
}
|
||||
}
|
||||
|
||||
// Updates the given file with wrapper code generated for the given action sets.
|
||||
// If the generated code is unchanged, does not touch the file.
|
||||
// Returns true if the file was touched, false otherwise.
|
||||
public static bool GenerateWrapperCode(string filePath, InputActionAsset asset, Options options)
|
||||
{
|
||||
if (!Path.HasExtension(filePath))
|
||||
filePath += ".cs";
|
||||
|
||||
// Generate code.
|
||||
var code = GenerateWrapperCode(asset, options);
|
||||
|
||||
// Check if the code changed. Don't write if it hasn't.
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
var existingCode = File.ReadAllText(filePath);
|
||||
if (existingCode == code || existingCode.WithAllWhitespaceStripped() == code.WithAllWhitespaceStripped())
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write.
|
||||
EditorHelpers.CheckOut(filePath);
|
||||
File.WriteAllText(filePath, code);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // UNITY_EDITOR
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a85130d4e1e5b49878f8f51b2d4e0ded
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,460 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.AssetImporters;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
////FIXME: The importer accesses icons through the asset db (which EditorGUIUtility.LoadIcon falls back on) which will
|
||||
//// not yet have been imported when the project is imported from scratch; this results in errors in the log and in generic
|
||||
//// icons showing up for the assets
|
||||
|
||||
#pragma warning disable 0649
|
||||
namespace UnityEngine.InputSystem.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Imports an <see cref="InputActionAsset"/> from JSON.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Can generate code wrappers for the contained action sets as a convenience.
|
||||
/// Will not overwrite existing wrappers except if the generated code actually differs.
|
||||
/// </remarks>
|
||||
[ScriptedImporter(kVersion, InputActionAsset.Extension)]
|
||||
internal class InputActionImporter : ScriptedImporter
|
||||
{
|
||||
private const int kVersion = 14;
|
||||
|
||||
[SerializeField] private bool m_GenerateWrapperCode;
|
||||
[SerializeField] private string m_WrapperCodePath;
|
||||
[SerializeField] private string m_WrapperClassName;
|
||||
[SerializeField] private string m_WrapperCodeNamespace;
|
||||
|
||||
private static InlinedArray<Action> s_OnImportCallbacks;
|
||||
|
||||
public static event Action onImport
|
||||
{
|
||||
add => s_OnImportCallbacks.Append(value);
|
||||
remove => s_OnImportCallbacks.Remove(value);
|
||||
}
|
||||
|
||||
private static InputActionAsset CreateFromJson(AssetImportContext context)
|
||||
{
|
||||
////REVIEW: need to check with version control here?
|
||||
// Read JSON file.
|
||||
string content;
|
||||
try
|
||||
{
|
||||
content = File.ReadAllText(EditorHelpers.GetPhysicalPath(context.assetPath));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
context.LogImportError($"Could not read file '{context.assetPath}' ({exception})");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create asset.
|
||||
var asset = ScriptableObject.CreateInstance<InputActionAsset>();
|
||||
|
||||
// Parse JSON and configure asset.
|
||||
try
|
||||
{
|
||||
// Attempt to parse JSON
|
||||
asset.LoadFromJson(content);
|
||||
// Make sure action map names are unique within JSON file
|
||||
var names = new HashSet<string>();
|
||||
foreach (var map in asset.actionMaps)
|
||||
{
|
||||
if (!names.Add(map.name))
|
||||
{
|
||||
throw new Exception(
|
||||
"Unable to parse {context.assetPath} due to duplicate Action Map name: '{map.name}'. Make sure Action Map names are unique within the asset and reattempt import.");
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure action names are unique within each action map in JSON file
|
||||
names.Clear();
|
||||
foreach (var map in asset.actionMaps)
|
||||
{
|
||||
foreach (var action in map.actions)
|
||||
{
|
||||
if (!names.Add(action.name))
|
||||
{
|
||||
throw new Exception(
|
||||
$"Unable to parse {{context.assetPath}} due to duplicate Action name: '{action.name}' within Action Map '{map.name}'. Make sure Action Map names are unique within the asset and reattempt import.");
|
||||
}
|
||||
}
|
||||
|
||||
names.Clear();
|
||||
}
|
||||
|
||||
// Force name of asset to be that on the file on disk instead of what may be serialized
|
||||
// as the 'name' property in JSON. (Unless explicitly given)
|
||||
asset.name = NameFromAssetPath(context.assetPath);
|
||||
|
||||
// Add asset.
|
||||
////REVIEW: the icons won't change if the user changes skin; not sure it makes sense to differentiate here
|
||||
context.AddObjectToAsset("<root>", asset, InputActionAssetIconLoader.LoadAssetIcon());
|
||||
context.SetMainObject(asset);
|
||||
|
||||
// Make sure all the elements in the asset have GUIDs and that they are indeed unique.
|
||||
// Create sub-assets for each action to allow search and editor referencing/picking.
|
||||
SetupAsset(asset, context.AddObjectToAsset);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
context.LogImportError($"Could not parse input actions in JSON format from '{context.assetPath}' ({exception})");
|
||||
DestroyImmediate(asset);
|
||||
asset = null;
|
||||
}
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
public override void OnImportAsset(AssetImportContext ctx)
|
||||
{
|
||||
if (ctx == null)
|
||||
throw new ArgumentNullException(nameof(ctx));
|
||||
|
||||
foreach (var callback in s_OnImportCallbacks)
|
||||
callback();
|
||||
|
||||
var asset = CreateFromJson(ctx);
|
||||
if (asset == null)
|
||||
return;
|
||||
|
||||
if (m_GenerateWrapperCode && HasMapWithSameNameAsAsset(asset, m_WrapperClassName))
|
||||
{
|
||||
ctx.LogImportError(
|
||||
$"{asset.name}: An action map in an .inputactions asset cannot be named the same as the asset itself if 'Generate C# Class' is used. "
|
||||
+ "You can rename the action map in the asset, rename the asset itself or assign a different C# class name in the import settings.");
|
||||
}
|
||||
}
|
||||
|
||||
internal static void SetupAsset(InputActionAsset asset)
|
||||
{
|
||||
SetupAsset(asset, (identifier, subAsset, icon) =>
|
||||
AssetDatabase.AddObjectToAsset(subAsset, asset));
|
||||
}
|
||||
|
||||
private delegate void AddObjectToAsset(string identifier, Object subAsset, Texture2D icon);
|
||||
|
||||
private static void SetupAsset(InputActionAsset asset, AddObjectToAsset addObjectToAsset)
|
||||
{
|
||||
FixMissingGuids(asset);
|
||||
CreateInputActionReferences(asset, addObjectToAsset);
|
||||
}
|
||||
|
||||
private static void FixMissingGuids(InputActionAsset asset)
|
||||
{
|
||||
// Make sure all the elements in the asset have GUIDs and that they are indeed unique.
|
||||
foreach (var map in asset.actionMaps)
|
||||
{
|
||||
// Make sure action map has GUID.
|
||||
if (string.IsNullOrEmpty(map.m_Id) || asset.actionMaps.Count(x => x.m_Id == map.m_Id) > 1)
|
||||
map.GenerateId();
|
||||
|
||||
// Make sure all actions have GUIDs.
|
||||
foreach (var action in map.actions)
|
||||
{
|
||||
var actionId = action.m_Id;
|
||||
if (string.IsNullOrEmpty(actionId) || asset.actionMaps.Sum(m => m.actions.Count(a => a.m_Id == actionId)) > 1)
|
||||
action.GenerateId();
|
||||
}
|
||||
|
||||
// Make sure all bindings have GUIDs.
|
||||
for (var i = 0; i < map.m_Bindings.LengthSafe(); ++i)
|
||||
{
|
||||
var bindingId = map.m_Bindings[i].m_Id;
|
||||
if (string.IsNullOrEmpty(bindingId) || asset.actionMaps.Sum(m => m.bindings.Count(b => b.m_Id == bindingId)) > 1)
|
||||
map.m_Bindings[i].GenerateId();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateInputActionReferences(InputActionAsset asset, AddObjectToAsset addObjectToAsset)
|
||||
{
|
||||
var actionIcon = InputActionAssetIconLoader.LoadActionIcon();
|
||||
foreach (var map in asset.actionMaps)
|
||||
{
|
||||
foreach (var action in map.actions)
|
||||
{
|
||||
// Note that it is important action is set before being added to asset, otherwise the immutability
|
||||
// check within InputActionReference would throw.
|
||||
var actionReference = ScriptableObject.CreateInstance<InputActionReference>();
|
||||
actionReference.Set(action);
|
||||
addObjectToAsset(action.m_Id, actionReference, actionIcon);
|
||||
|
||||
// Backwards-compatibility (added for 1.0.0-preview.7).
|
||||
// We used to call AddObjectToAsset using objectName instead of action.m_Id as the object name. This fed
|
||||
// the action name (*and* map name) into the hash generation that was used as the basis for the file ID
|
||||
// object the InputActionReference object. Thus, if the map and/or action name changed, the file ID would
|
||||
// change and existing references to the InputActionReference object would become invalid.
|
||||
//
|
||||
// What we do here is add another *hidden* InputActionReference object with the same content to the
|
||||
// asset. This one will use the old file ID and thus preserve backwards-compatibility. We should be able
|
||||
// to remove this for 2.0.
|
||||
//
|
||||
// Case: https://fogbugz.unity3d.com/f/cases/1229145/
|
||||
var backcompatActionReference = Instantiate(actionReference);
|
||||
backcompatActionReference.name = actionReference.name; // Get rid of the (Clone) suffix.
|
||||
backcompatActionReference.hideFlags = HideFlags.HideInHierarchy;
|
||||
addObjectToAsset(actionReference.name, backcompatActionReference, actionIcon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool HasMapWithSameNameAsAsset(InputActionAsset asset, string codeClassName)
|
||||
{
|
||||
// When using code generation, it is an error for any action map to be named the same as the asset itself.
|
||||
// https://fogbugz.unity3d.com/f/cases/1212052/
|
||||
var className = !string.IsNullOrEmpty(codeClassName) ? codeClassName : CSharpCodeHelpers.MakeTypeName(asset.name);
|
||||
return (asset.actionMaps.Any(x =>
|
||||
CSharpCodeHelpers.MakeTypeName(x.name) == className ||
|
||||
CSharpCodeHelpers.MakeIdentifier(x.name) == className));
|
||||
}
|
||||
|
||||
private static void GenerateWrapperCode(string assetPath, InputActionAsset asset, string codeNamespace, string codeClassName, string codePath)
|
||||
{
|
||||
if (HasMapWithSameNameAsAsset(asset, codeClassName))
|
||||
return;
|
||||
|
||||
var wrapperFilePath = codePath;
|
||||
if (string.IsNullOrEmpty(wrapperFilePath))
|
||||
{
|
||||
// Placed next to .inputactions file.
|
||||
var directory = Path.GetDirectoryName(assetPath);
|
||||
var fileName = Path.GetFileNameWithoutExtension(assetPath);
|
||||
wrapperFilePath = Path.Combine(directory, fileName) + ".cs";
|
||||
}
|
||||
else if (wrapperFilePath.StartsWith("./") || wrapperFilePath.StartsWith(".\\") ||
|
||||
wrapperFilePath.StartsWith("../") || wrapperFilePath.StartsWith("..\\"))
|
||||
{
|
||||
// User-specified file relative to location of .inputactions file.
|
||||
var directory = Path.GetDirectoryName(assetPath);
|
||||
wrapperFilePath = Path.Combine(directory, wrapperFilePath);
|
||||
}
|
||||
else if (!wrapperFilePath.StartsWith("assets/", StringComparison.InvariantCultureIgnoreCase) &&
|
||||
!wrapperFilePath.StartsWith("assets\\", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
// User-specified file in Assets/ folder.
|
||||
wrapperFilePath = Path.Combine("Assets", wrapperFilePath);
|
||||
}
|
||||
|
||||
var dir = Path.GetDirectoryName(wrapperFilePath);
|
||||
if (!Directory.Exists(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
// Check for the case where the target file already exists.
|
||||
// If it does, we don't want to overwrite it unless it's been generated by us before.
|
||||
if (File.Exists(wrapperFilePath))
|
||||
{
|
||||
var text = File.ReadAllText(wrapperFilePath);
|
||||
var autoGeneratedMarker = "This code was auto-generated by com.unity.inputsystem";
|
||||
if (!text.Contains(autoGeneratedMarker))
|
||||
{
|
||||
throw new Exception($"The target file for Input Actions code generation already exists: {wrapperFilePath}. Since it doesn't look to contain Input generated code that we can safely overwrite, we stopped to prevent any data loss. Consider renaming. ");
|
||||
}
|
||||
}
|
||||
|
||||
var options = new InputActionCodeGenerator.Options
|
||||
{
|
||||
sourceAssetPath = assetPath,
|
||||
namespaceName = codeNamespace,
|
||||
className = codeClassName,
|
||||
};
|
||||
|
||||
|
||||
if (InputActionCodeGenerator.GenerateWrapperCode(wrapperFilePath, asset, options))
|
||||
{
|
||||
// This isn't ideal and may have side effects, but we cannot avoid compiling again.
|
||||
// Previously we attempted to run a EditorApplication.delayCall += AssetDatabase.Refresh
|
||||
// but this would lead to "error: Error building Player because scripts are compiling" in CI.
|
||||
// Previous comment here warned against not being able to reimport here directly, but it seems it's ok.
|
||||
AssetDatabase.ImportAsset(wrapperFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
internal static IEnumerable<InputActionReference> LoadInputActionReferencesFromAsset(string assetPath)
|
||||
{
|
||||
// Get all InputActionReferences are stored at the same asset path as InputActionAsset
|
||||
// Note we exclude 'hidden' action references (which are present to support one of the pre releases)
|
||||
return AssetDatabase.LoadAllAssetsAtPath(assetPath).Where(
|
||||
o => o is InputActionReference &&
|
||||
!((InputActionReference)o).hideFlags.HasFlag(HideFlags.HideInHierarchy))
|
||||
.Cast<InputActionReference>();
|
||||
}
|
||||
|
||||
private static readonly string[] s_DefaultAssetSearchFolders = new string[] { "Assets" };
|
||||
|
||||
/// <summary>
|
||||
/// Gets all <see cref="InputActionReference"/> instances available in assets in the project.
|
||||
/// By default, it only gets the assets located in the "Assets" folder.
|
||||
/// </summary>
|
||||
/// <param name="foldersPath">Optional array of directory paths to be searched.</param>
|
||||
/// <param name="skipProjectWide">If true, excludes project-wide input actions from the result.</param>
|
||||
/// <returns></returns>
|
||||
internal static IEnumerable<InputActionReference> LoadInputActionReferencesFromAssetDatabase(
|
||||
string[] foldersPath = null, bool skipProjectWide = false)
|
||||
{
|
||||
// Get all InputActionReference from assets in "Asset" folder by default.
|
||||
// It does not search inside "Packages" folder.
|
||||
const string inputActionReferenceFilter = "t:" + nameof(InputActionReference);
|
||||
var inputActionReferenceGUIDs = AssetDatabase.FindAssets(inputActionReferenceFilter,
|
||||
foldersPath ?? s_DefaultAssetSearchFolders);
|
||||
|
||||
// To find all the InputActionReferences, the GUID of the asset containing at least one action reference is
|
||||
// used to find the asset path. This is because InputActionReferences are stored in the asset database as sub-assets of InputActionAsset.
|
||||
// Then the whole asset is loaded and all the InputActionReferences are extracted from it.
|
||||
// Also, the action references are duplicated to have backwards compatibility with the 1.0.0-preview.7. That
|
||||
// is why we look for references withouth the `HideFlags.HideInHierarchy` flag.
|
||||
var inputActionReferencesList = new List<InputActionReference>();
|
||||
foreach (var guid in inputActionReferenceGUIDs)
|
||||
{
|
||||
var assetPath = AssetDatabase.GUIDToAssetPath(guid);
|
||||
foreach (var assetInputActionReference in LoadInputActionReferencesFromAsset(assetPath))
|
||||
{
|
||||
if (skipProjectWide && assetInputActionReference.m_Asset == InputSystem.actions)
|
||||
continue;
|
||||
|
||||
inputActionReferencesList.Add(assetInputActionReference);
|
||||
}
|
||||
}
|
||||
return inputActionReferencesList;
|
||||
}
|
||||
|
||||
// Add item to plop an .inputactions asset into the project.
|
||||
[MenuItem("Assets/Create/Input Actions")]
|
||||
public static void CreateInputAsset()
|
||||
{
|
||||
ProjectWindowUtil.CreateAssetWithContent("New Actions." + InputActionAsset.Extension,
|
||||
InputActionAsset.kDefaultAssetLayoutJson, InputActionAssetIconLoader.LoadAssetIcon());
|
||||
}
|
||||
|
||||
// File extension of the associated asset
|
||||
private const string kFileExtension = "." + InputActionAsset.Extension;
|
||||
|
||||
// Evaluates whether the given path is a path to an asset of the associated type based on extension.
|
||||
public static bool IsInputActionAssetPath(string path)
|
||||
{
|
||||
return path != null && path.EndsWith(kFileExtension, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
// Returns a suitable object name for an asset based on its path.
|
||||
public static string NameFromAssetPath(string assetPath)
|
||||
{
|
||||
Debug.Assert(IsInputActionAssetPath(assetPath));
|
||||
return Path.GetFileNameWithoutExtension(assetPath);
|
||||
}
|
||||
|
||||
private static bool ContainsInputActionAssetPath(string[] assetPaths)
|
||||
{
|
||||
foreach (var assetPath in assetPaths)
|
||||
{
|
||||
if (IsInputActionAssetPath(assetPath))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// This processor was added to address this issue:
|
||||
// https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-749
|
||||
//
|
||||
// When an action asset is renamed, copied, or moved in the Editor, the "Name" field in the JSON will
|
||||
// hold the old name and won't match the asset objects name in memory which is set based on the filename
|
||||
// by the scripted imported. To avoid this, this asset post-processor detects any imported or moved assets
|
||||
// with a JSON name property not matching the importer assigned name and updates the JSON name based on this.
|
||||
// This basically solves any problem related to unmodified assets.
|
||||
//
|
||||
// Note that JSON names have no relevance for editor workflows and are basically ignored by the importer.
|
||||
// Note that JSON names may be the only way to identify assets loaded from non-file sources or via
|
||||
// UnityEngine.Resources in run-time.
|
||||
//
|
||||
// Note that if an asset is is imported and a name mismatch is detected, the asset will be modified and
|
||||
// imported again, which will yield yet another callback to the post-processor. For the second iteration,
|
||||
// the name will no longer be a mismatch and the cycle will be aborted.
|
||||
private class InputActionJsonNameModifierAssetProcessor : AssetPostprocessor
|
||||
{
|
||||
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets,
|
||||
string[] movedAssets, string[] movedFromAssetPaths)
|
||||
{
|
||||
var needToInvalidate = false;
|
||||
foreach (var assetPath in importedAssets)
|
||||
{
|
||||
if (IsInputActionAssetPath(assetPath))
|
||||
{
|
||||
// Generate C# code from asset if configured via importer settings.
|
||||
// We generate from a parsed temporary asset here since loading the asset won't work here.
|
||||
var importer = GetAtPath(assetPath) as InputActionImporter;
|
||||
if (importer != null && importer.m_GenerateWrapperCode)
|
||||
{
|
||||
try
|
||||
{
|
||||
var asset = InputActionAsset.FromJson(File.ReadAllText(assetPath));
|
||||
GenerateWrapperCode(assetPath, asset, importer.m_WrapperCodeNamespace,
|
||||
importer.m_WrapperClassName, importer.m_WrapperCodePath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
|
||||
needToInvalidate = true;
|
||||
CheckAndRenameJsonNameIfDifferent(assetPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!needToInvalidate)
|
||||
needToInvalidate = ContainsInputActionAssetPath(deletedAssets);
|
||||
if (!needToInvalidate)
|
||||
needToInvalidate = ContainsInputActionAssetPath(movedAssets);
|
||||
if (!needToInvalidate)
|
||||
needToInvalidate = ContainsInputActionAssetPath(movedFromAssetPaths);
|
||||
|
||||
// Invalidate all references to make sure there are no dangling references after reimport among
|
||||
// our "live objects. We only need to invalidate loaded sub-assets and not assets/prefabs/SO etc
|
||||
// since they may still contain effectively obsolete InputActionReferences but those references
|
||||
// will resolve.
|
||||
if (needToInvalidate)
|
||||
InputActionReference.InvalidateAll();
|
||||
}
|
||||
|
||||
private static void CheckAndRenameJsonNameIfDifferent(string assetPath)
|
||||
{
|
||||
InputActionAsset asset = null;
|
||||
try
|
||||
{
|
||||
if (!File.Exists(assetPath))
|
||||
return;
|
||||
|
||||
// Evaluate whether JSON name corresponds to desired name
|
||||
asset = InputActionAsset.FromJson(File.ReadAllText(assetPath));
|
||||
var desiredName = Path.GetFileNameWithoutExtension(assetPath);
|
||||
if (asset.name == desiredName)
|
||||
return;
|
||||
|
||||
// Update JSON name by modifying the asset
|
||||
asset.name = desiredName;
|
||||
if (!EditorHelpers.WriteAsset(assetPath, asset.ToJson()))
|
||||
{
|
||||
Debug.LogError($"Unable to change JSON name for asset at \"{assetPath}\" since the asset-path could not be checked-out as editable in the underlying version-control system.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (asset != null)
|
||||
DestroyImmediate(asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // UNITY_EDITOR
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8404be70184654265930450def6a9037
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,150 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
using UnityEditor;
|
||||
using UnityEditor.AssetImporters;
|
||||
|
||||
////TODO: support for multi-editing
|
||||
|
||||
namespace UnityEngine.InputSystem.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor that allows modifying importer settings for an <see cref="InputActionImporter"/>.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(InputActionImporter))]
|
||||
internal class InputActionImporterEditor : ScriptedImporterEditor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
var inputActionAsset = GetAsset();
|
||||
|
||||
// ScriptedImporterEditor in 2019.2 now requires explicitly updating the SerializedObject
|
||||
// like in other types of editors.
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (inputActionAsset == null)
|
||||
EditorGUILayout.HelpBox("The currently selected object is not an editable input action asset.",
|
||||
MessageType.Info);
|
||||
|
||||
// Button to pop up window to edit the asset.
|
||||
using (new EditorGUI.DisabledScope(inputActionAsset == null))
|
||||
{
|
||||
if (GUILayout.Button(GetOpenEditorButtonText(inputActionAsset), GUILayout.Height(30)))
|
||||
OpenEditor(inputActionAsset);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// Project-wide Input Actions Asset UI.
|
||||
InputAssetEditorUtils.DrawMakeActiveGui(InputSystem.actions, inputActionAsset,
|
||||
inputActionAsset ? inputActionAsset.name : "Null", "Project-wide Input Actions",
|
||||
(value) => InputSystem.actions = value, !EditorApplication.isPlayingOrWillChangePlaymode);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// Importer settings UI.
|
||||
var generateWrapperCodeProperty = serializedObject.FindProperty("m_GenerateWrapperCode");
|
||||
EditorGUILayout.PropertyField(generateWrapperCodeProperty, m_GenerateWrapperCodeLabel);
|
||||
if (generateWrapperCodeProperty.boolValue)
|
||||
{
|
||||
var wrapperCodePathProperty = serializedObject.FindProperty("m_WrapperCodePath");
|
||||
var wrapperClassNameProperty = serializedObject.FindProperty("m_WrapperClassName");
|
||||
var wrapperCodeNamespaceProperty = serializedObject.FindProperty("m_WrapperCodeNamespace");
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
string defaultFileName = "";
|
||||
if (inputActionAsset != null)
|
||||
{
|
||||
var assetPath = AssetDatabase.GetAssetPath(inputActionAsset);
|
||||
defaultFileName = Path.ChangeExtension(assetPath, ".cs");
|
||||
}
|
||||
|
||||
wrapperCodePathProperty.PropertyFieldWithDefaultText(m_WrapperCodePathLabel, defaultFileName);
|
||||
|
||||
if (GUILayout.Button("…", EditorStyles.miniButton, GUILayout.MaxWidth(20)))
|
||||
{
|
||||
var fileName = EditorUtility.SaveFilePanel("Location for generated C# file",
|
||||
Path.GetDirectoryName(defaultFileName),
|
||||
Path.GetFileName(defaultFileName), "cs");
|
||||
if (!string.IsNullOrEmpty(fileName))
|
||||
{
|
||||
if (fileName.StartsWith(Application.dataPath))
|
||||
fileName = "Assets/" + fileName.Substring(Application.dataPath.Length + 1);
|
||||
|
||||
wrapperCodePathProperty.stringValue = fileName;
|
||||
}
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
string typeName = null;
|
||||
if (inputActionAsset != null)
|
||||
typeName = CSharpCodeHelpers.MakeTypeName(inputActionAsset?.name);
|
||||
wrapperClassNameProperty.PropertyFieldWithDefaultText(m_WrapperClassNameLabel, typeName ?? "<Class name>");
|
||||
|
||||
if (!CSharpCodeHelpers.IsEmptyOrProperIdentifier(wrapperClassNameProperty.stringValue))
|
||||
EditorGUILayout.HelpBox("Must be a valid C# identifier", MessageType.Error);
|
||||
|
||||
wrapperCodeNamespaceProperty.PropertyFieldWithDefaultText(m_WrapperCodeNamespaceLabel, "<Global namespace>");
|
||||
|
||||
if (!CSharpCodeHelpers.IsEmptyOrProperNamespaceName(wrapperCodeNamespaceProperty.stringValue))
|
||||
EditorGUILayout.HelpBox("Must be a valid C# namespace name", MessageType.Error);
|
||||
}
|
||||
|
||||
// Using ApplyRevertGUI requires calling Update and ApplyModifiedProperties around the serializedObject,
|
||||
// and will print warning messages otherwise (see warning message in ApplyRevertGUI implementation).
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
ApplyRevertGUI();
|
||||
}
|
||||
|
||||
private InputActionAsset GetAsset()
|
||||
{
|
||||
return assetTarget as InputActionAsset;
|
||||
}
|
||||
|
||||
protected override bool ShouldHideOpenButton()
|
||||
{
|
||||
return IsProjectWideActionsAsset();
|
||||
}
|
||||
|
||||
private bool IsProjectWideActionsAsset()
|
||||
{
|
||||
return IsProjectWideActionsAsset(GetAsset());
|
||||
}
|
||||
|
||||
private static bool IsProjectWideActionsAsset(InputActionAsset asset)
|
||||
{
|
||||
return !ReferenceEquals(asset, null) && InputSystem.actions == asset;
|
||||
}
|
||||
|
||||
private string GetOpenEditorButtonText(InputActionAsset asset)
|
||||
{
|
||||
if (IsProjectWideActionsAsset(asset))
|
||||
return "Edit in Project Settings Window";
|
||||
|
||||
return "Edit Asset";
|
||||
}
|
||||
|
||||
private static void OpenEditor(InputActionAsset asset)
|
||||
{
|
||||
// Redirect to Project-settings Input Actions editor if this is the project-wide actions asset
|
||||
if (IsProjectWideActionsAsset(asset))
|
||||
{
|
||||
SettingsService.OpenProjectSettings(InputSettingsPath.kSettingsRootPath);
|
||||
return;
|
||||
}
|
||||
|
||||
InputActionsEditorWindow.OpenEditor(asset);
|
||||
}
|
||||
|
||||
private readonly GUIContent m_GenerateWrapperCodeLabel = EditorGUIUtility.TrTextContent("Generate C# Class");
|
||||
private readonly GUIContent m_WrapperCodePathLabel = EditorGUIUtility.TrTextContent("C# Class File");
|
||||
private readonly GUIContent m_WrapperClassNameLabel = EditorGUIUtility.TrTextContent("C# Class Name");
|
||||
private readonly GUIContent m_WrapperCodeNamespaceLabel = EditorGUIUtility.TrTextContent("C# Class Namespace");
|
||||
}
|
||||
}
|
||||
#endif // UNITY_EDITOR
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 50db2526021f1429e84687ad14aa0855
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user