#if UNITY_EDITOR && UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS using System; using System.Collections.Generic; using System.IO; using System.Linq; using UnityEditor; using UnityEngine.InputSystem.Utilities; namespace UnityEngine.InputSystem.Editor { internal static class ProjectWideActionsAsset { private const string kDefaultAssetName = "InputSystem_Actions"; private const string kDefaultAssetPath = "Assets/" + kDefaultAssetName + ".inputactions"; private const string kDefaultTemplateAssetPath = "Packages/com.unity.inputsystem/InputSystem/Editor/ProjectWideActions/ProjectWideActionsTemplate.json"; internal static class ProjectSettingsProjectWideActionsAssetConverter { private const string kAssetPathInputManager = "ProjectSettings/InputManager.asset"; private const string kAssetNameProjectWideInputActions = "ProjectWideInputActions"; class ProjectSettingsPostprocessor : AssetPostprocessor { private static bool migratedInputActionAssets = false; #if UNITY_2021_2_OR_NEWER private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths, bool didDomainReload) #else private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) #endif { if (!migratedInputActionAssets) { MoveInputManagerAssetActionsToProjectWideInputActionAsset(); migratedInputActionAssets = true; } if (!Application.isPlaying) { // If the Library folder is deleted, InputSystem will fail to retrieve the assigned Project-wide Asset because this look-up occurs // during initialization while the Library is being rebuilt. So, afterwards perform another check and assign PWA asset if needed. var pwaAsset = ProjectWideActionsBuildProvider.actionsToIncludeInPlayerBuild; if (InputSystem.actions == null && pwaAsset != null) InputSystem.actions = pwaAsset; } } } private static void MoveInputManagerAssetActionsToProjectWideInputActionAsset() { var objects = AssetDatabase.LoadAllAssetsAtPath(EditorHelpers.GetPhysicalPath(kAssetPathInputManager)); if (objects == null) return; var inputActionsAsset = objects.FirstOrDefault(o => o != null && o.name == kAssetNameProjectWideInputActions) as InputActionAsset; if (inputActionsAsset != default) { // Found some actions in the InputManager.asset file // string path = ProjectWideActionsAsset.kDefaultAssetPath; if (File.Exists(EditorHelpers.GetPhysicalPath(path))) { // We already have a path containing inputactions, find a new unique filename // // eg Assets/InputSystem_Actions.inputactions -> // Assets/InputSystem_Actions (1).inputactions -> // Assets/InputSystem_Actions (2).inputactions ... // string[] files = Directory.GetFiles("Assets", "*.inputactions"); List names = new List(); for (int i = 0; i < files.Length; i++) { names.Add(System.IO.Path.GetFileNameWithoutExtension(files[i])); } string unique = ObjectNames.GetUniqueName(names.ToArray(), kDefaultAssetName); path = "Assets/" + unique + ".inputactions"; } var json = inputActionsAsset.ToJson(); InputActionAssetManager.SaveAsset(EditorHelpers.GetPhysicalPath(path), json); Debug.Log($"Migrated Project-wide Input Actions from '{kAssetPathInputManager}' to '{path}' asset"); // Update current project-wide settings if needed (don't replace if already set to something else) // if (InputSystem.actions == null || InputSystem.actions.name == kAssetNameProjectWideInputActions) { InputSystem.actions = (InputActionAsset)AssetDatabase.LoadAssetAtPath(path, typeof(InputActionAsset)); Debug.Log($"Loaded Project-wide Input Actions from '{path}' asset"); } } // Handle deleting all InputActionAssets as older 1.8.0 pre release could create more than one project wide input asset in the file foreach (var obj in objects) { if (obj is InputActionReference) { var actionReference = obj as InputActionReference; AssetDatabase.RemoveObjectFromAsset(obj); Object.DestroyImmediate(actionReference); } else if (obj is InputActionAsset) { AssetDatabase.RemoveObjectFromAsset(obj); } } AssetDatabase.SaveAssets(); } } // Returns the default asset path for where to create project-wide actions asset. internal static string defaultAssetPath => kDefaultAssetPath; // Returns the default template JSON content. internal static string GetDefaultAssetJson() { return File.ReadAllText(EditorHelpers.GetPhysicalPath(kDefaultTemplateAssetPath)); } // Creates an asset at the given path containing the default template JSON. internal static InputActionAsset CreateDefaultAssetAtPath(string assetPath = kDefaultAssetPath) { return CreateAssetAtPathFromJson(assetPath, File.ReadAllText(EditorHelpers.GetPhysicalPath(kDefaultTemplateAssetPath))); } // These may be moved out to internal types if decided to extend validation at a later point. /// /// Interface for reporting asset verification errors. /// internal interface IReportInputActionAssetVerificationErrors { /// /// Reports a failure to comply to requirements with a message meaningful to the user. /// /// User-friendly error message. void Report(string message); } /// /// Interface for asset verification. /// internal interface IInputActionAssetVerifier { /// /// Verifies the given asset. /// /// The asset to be verified /// The reporter to be used to report failure to meet requirements. public void Verify(InputActionAsset asset, IReportInputActionAssetVerificationErrors reporter); } /// /// Verifier managing verification and reporting of asset compliance with external requirements. /// class Verifier : IReportInputActionAssetVerificationErrors { private readonly IReportInputActionAssetVerificationErrors m_Reporter; // Default verification error reporter which generates feedback as debug warnings. private class DefaultInputActionAssetVerificationReporter : IReportInputActionAssetVerificationErrors { public void Report(string message) { Debug.LogWarning(message); } } /// /// Constructs a an instance associated with the given reporter. /// /// The associated reporter instance. If null, a default reporter will be constructed. public Verifier(IReportInputActionAssetVerificationErrors reporter = null) { m_Reporter = reporter ?? new DefaultInputActionAssetVerificationReporter(); errors = 0; } #region IReportInputActionAssetVerificationErrors interface /// public void Report(string message) { ++errors; try { m_Reporter.Report(message); } catch (Exception e) { // Only log unexpected but non-fatal exception Debug.LogException(e); } } #endregion /// /// Returns the total number of errors seen in verification (accumulative). /// public int errors { get; private set; } /// /// Returns true if the number of reported errors in verification is zero, else false. /// public bool isValid => errors == 0; private static List> s_VerifierFactories; /// /// Registers a factory instance. /// /// The factory instance. /// true if successfully added, false if the factory have already been registered. public static bool RegisterFactory(Func factory) { if (s_VerifierFactories == null) s_VerifierFactories = new List>(1); if (s_VerifierFactories.Contains(factory)) return false; s_VerifierFactories.Add(factory); return true; } /// /// Unregisters a factory instance that has previously been registered. /// /// The factory instance to be removed. /// true if successfully unregistered, false if the given factory instance could not be found. public static bool UnregisterFactory(Func factory) { return s_VerifierFactories.Remove(factory); } /// /// Verifies the given project-wide input action asset using all registered verifiers. /// /// The asset to be verified. /// true if no verification errors occurred, else false. /// /// Throws System.ArgumentNullException if asset is null. /// /// If any registered factory and/or verifier instance throws an exception this will be evaluated /// as a verification error since the execution of the verifier could not continue. However, any /// exceptions thrown will be caught and logged but not stop execution of the calling thread. /// bool Verify(InputActionAsset asset) { if (asset == null) throw new ArgumentNullException(nameof(asset)); if (s_VerifierFactories == null || s_VerifierFactories.Count == 0) return true; var instance = new Verifier(m_Reporter); foreach (var factory in s_VerifierFactories) { try { factory.Invoke().Verify(asset, instance); } catch (Exception e) { // Only log unexpected but non-fatal exception and count to fail verification ++errors; Debug.LogException(e); } } return errors == 0; } /// /// Verifies the given project-wide input action asset using all registered verifiers. /// /// The asset to be verified. /// The reporter to be used. If this argument is null the default reporter will be used. /// true if no verification errors occurred, else false. /// Throws System.ArgumentNullException if asset is null. public static bool Verify(InputActionAsset asset, IReportInputActionAssetVerificationErrors reporter = null) { return (s_VerifierFactories == null || s_VerifierFactories.Count == 0) || new Verifier(reporter).Verify(asset); } } internal static bool Verify(InputActionAsset asset, IReportInputActionAssetVerificationErrors reporter = null) { return Verifier.Verify(asset, reporter); } internal static bool RegisterInputActionAssetVerifier(Func factory) { return Verifier.RegisterFactory(factory); } internal static bool UnregisterInputActionAssetVerifier(Func factory) { return Verifier.UnregisterFactory(factory); } // Creates an asset at the given path containing the given JSON content. private static InputActionAsset CreateAssetAtPathFromJson(string assetPath, string json) { // Note that the extra work here is to override the JSON name from the source asset var inputActionAsset = InputActionAsset.FromJson(json); inputActionAsset.name = InputActionImporter.NameFromAssetPath(assetPath); InputActionAssetManager.SaveAsset(assetPath, inputActionAsset.ToJson()); return AssetDatabase.LoadAssetAtPath(assetPath); } } } #endif // UNITY_EDITOR && UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS