暫無描述
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ProjectWideActionsAsset.cs 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. #if UNITY_EDITOR && UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
  2. using System;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Linq;
  6. using UnityEditor;
  7. using UnityEngine.InputSystem.Utilities;
  8. namespace UnityEngine.InputSystem.Editor
  9. {
  10. internal static class ProjectWideActionsAsset
  11. {
  12. private const string kDefaultAssetName = "InputSystem_Actions";
  13. private const string kDefaultAssetPath = "Assets/" + kDefaultAssetName + ".inputactions";
  14. private const string kDefaultTemplateAssetPath = "Packages/com.unity.inputsystem/InputSystem/Editor/ProjectWideActions/ProjectWideActionsTemplate.json";
  15. internal static class ProjectSettingsProjectWideActionsAssetConverter
  16. {
  17. private const string kAssetPathInputManager = "ProjectSettings/InputManager.asset";
  18. private const string kAssetNameProjectWideInputActions = "ProjectWideInputActions";
  19. class ProjectSettingsPostprocessor : AssetPostprocessor
  20. {
  21. private static bool migratedInputActionAssets = false;
  22. #if UNITY_2021_2_OR_NEWER
  23. private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths, bool didDomainReload)
  24. #else
  25. private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
  26. #endif
  27. {
  28. if (!migratedInputActionAssets)
  29. {
  30. MoveInputManagerAssetActionsToProjectWideInputActionAsset();
  31. migratedInputActionAssets = true;
  32. }
  33. if (!Application.isPlaying)
  34. {
  35. // If the Library folder is deleted, InputSystem will fail to retrieve the assigned Project-wide Asset because this look-up occurs
  36. // during initialization while the Library is being rebuilt. So, afterwards perform another check and assign PWA asset if needed.
  37. var pwaAsset = ProjectWideActionsBuildProvider.actionsToIncludeInPlayerBuild;
  38. if (InputSystem.actions == null && pwaAsset != null)
  39. InputSystem.actions = pwaAsset;
  40. }
  41. }
  42. }
  43. private static void MoveInputManagerAssetActionsToProjectWideInputActionAsset()
  44. {
  45. var objects = AssetDatabase.LoadAllAssetsAtPath(EditorHelpers.GetPhysicalPath(kAssetPathInputManager));
  46. if (objects == null)
  47. return;
  48. var inputActionsAsset = objects.FirstOrDefault(o => o != null && o.name == kAssetNameProjectWideInputActions) as InputActionAsset;
  49. if (inputActionsAsset != default)
  50. {
  51. // Found some actions in the InputManager.asset file
  52. //
  53. string path = ProjectWideActionsAsset.kDefaultAssetPath;
  54. if (File.Exists(EditorHelpers.GetPhysicalPath(path)))
  55. {
  56. // We already have a path containing inputactions, find a new unique filename
  57. //
  58. // eg Assets/InputSystem_Actions.inputactions ->
  59. // Assets/InputSystem_Actions (1).inputactions ->
  60. // Assets/InputSystem_Actions (2).inputactions ...
  61. //
  62. string[] files = Directory.GetFiles("Assets", "*.inputactions");
  63. List<string> names = new List<string>();
  64. for (int i = 0; i < files.Length; i++)
  65. {
  66. names.Add(System.IO.Path.GetFileNameWithoutExtension(files[i]));
  67. }
  68. string unique = ObjectNames.GetUniqueName(names.ToArray(), kDefaultAssetName);
  69. path = "Assets/" + unique + ".inputactions";
  70. }
  71. var json = inputActionsAsset.ToJson();
  72. InputActionAssetManager.SaveAsset(EditorHelpers.GetPhysicalPath(path), json);
  73. Debug.Log($"Migrated Project-wide Input Actions from '{kAssetPathInputManager}' to '{path}' asset");
  74. // Update current project-wide settings if needed (don't replace if already set to something else)
  75. //
  76. if (InputSystem.actions == null || InputSystem.actions.name == kAssetNameProjectWideInputActions)
  77. {
  78. InputSystem.actions = (InputActionAsset)AssetDatabase.LoadAssetAtPath(path, typeof(InputActionAsset));
  79. Debug.Log($"Loaded Project-wide Input Actions from '{path}' asset");
  80. }
  81. }
  82. // Handle deleting all InputActionAssets as older 1.8.0 pre release could create more than one project wide input asset in the file
  83. foreach (var obj in objects)
  84. {
  85. if (obj is InputActionReference)
  86. {
  87. var actionReference = obj as InputActionReference;
  88. AssetDatabase.RemoveObjectFromAsset(obj);
  89. Object.DestroyImmediate(actionReference);
  90. }
  91. else if (obj is InputActionAsset)
  92. {
  93. AssetDatabase.RemoveObjectFromAsset(obj);
  94. }
  95. }
  96. AssetDatabase.SaveAssets();
  97. }
  98. }
  99. // Returns the default asset path for where to create project-wide actions asset.
  100. internal static string defaultAssetPath => kDefaultAssetPath;
  101. // Returns the default template JSON content.
  102. internal static string GetDefaultAssetJson()
  103. {
  104. return File.ReadAllText(EditorHelpers.GetPhysicalPath(kDefaultTemplateAssetPath));
  105. }
  106. // Creates an asset at the given path containing the default template JSON.
  107. internal static InputActionAsset CreateDefaultAssetAtPath(string assetPath = kDefaultAssetPath)
  108. {
  109. return CreateAssetAtPathFromJson(assetPath, File.ReadAllText(EditorHelpers.GetPhysicalPath(kDefaultTemplateAssetPath)));
  110. }
  111. // These may be moved out to internal types if decided to extend validation at a later point.
  112. /// <summary>
  113. /// Interface for reporting asset verification errors.
  114. /// </summary>
  115. internal interface IReportInputActionAssetVerificationErrors
  116. {
  117. /// <summary>
  118. /// Reports a failure to comply to requirements with a message meaningful to the user.
  119. /// </summary>
  120. /// <param name="message">User-friendly error message.</param>
  121. void Report(string message);
  122. }
  123. /// <summary>
  124. /// Interface for asset verification.
  125. /// </summary>
  126. internal interface IInputActionAssetVerifier
  127. {
  128. /// <summary>
  129. /// Verifies the given asset.
  130. /// </summary>
  131. /// <param name="asset">The asset to be verified</param>
  132. /// <param name="reporter">The reporter to be used to report failure to meet requirements.</param>
  133. public void Verify(InputActionAsset asset, IReportInputActionAssetVerificationErrors reporter);
  134. }
  135. /// <summary>
  136. /// Verifier managing verification and reporting of asset compliance with external requirements.
  137. /// </summary>
  138. class Verifier : IReportInputActionAssetVerificationErrors
  139. {
  140. private readonly IReportInputActionAssetVerificationErrors m_Reporter;
  141. // Default verification error reporter which generates feedback as debug warnings.
  142. private class DefaultInputActionAssetVerificationReporter : IReportInputActionAssetVerificationErrors
  143. {
  144. public void Report(string message)
  145. {
  146. Debug.LogWarning(message);
  147. }
  148. }
  149. /// <summary>
  150. /// Constructs a an instance associated with the given reporter.
  151. /// </summary>
  152. /// <param name="reporter">The associated reporter instance. If null, a default reporter will be constructed.</param>
  153. public Verifier(IReportInputActionAssetVerificationErrors reporter = null)
  154. {
  155. m_Reporter = reporter ?? new DefaultInputActionAssetVerificationReporter();
  156. errors = 0;
  157. }
  158. #region IReportInputActionAssetVerificationErrors interface
  159. /// <inheritdoc cref="IReportInputActionAssetVerificationErrors"/>
  160. public void Report(string message)
  161. {
  162. ++errors;
  163. try
  164. {
  165. m_Reporter.Report(message);
  166. }
  167. catch (Exception e)
  168. {
  169. // Only log unexpected but non-fatal exception
  170. Debug.LogException(e);
  171. }
  172. }
  173. #endregion
  174. /// <summary>
  175. /// Returns the total number of errors seen in verification (accumulative).
  176. /// </summary>
  177. public int errors { get; private set; }
  178. /// <summary>
  179. /// Returns <c>true</c> if the number of reported errors in verification is zero, else <c>false</c>.
  180. /// </summary>
  181. public bool isValid => errors == 0;
  182. private static List<Func<IInputActionAssetVerifier>> s_VerifierFactories;
  183. /// <summary>
  184. /// Registers a factory instance.
  185. /// </summary>
  186. /// <param name="factory">The factory instance.</param>
  187. /// <returns>true if successfully added, <c>false</c> if the factory have already been registered.</returns>
  188. public static bool RegisterFactory(Func<IInputActionAssetVerifier> factory)
  189. {
  190. if (s_VerifierFactories == null)
  191. s_VerifierFactories = new List<Func<IInputActionAssetVerifier>>(1);
  192. if (s_VerifierFactories.Contains(factory))
  193. return false;
  194. s_VerifierFactories.Add(factory);
  195. return true;
  196. }
  197. /// <summary>
  198. /// Unregisters a factory instance that has previously been registered.
  199. /// </summary>
  200. /// <param name="factory">The factory instance to be removed.</param>
  201. /// <returns>true if successfully unregistered, <c>false</c> if the given factory instance could not be found.</returns>
  202. public static bool UnregisterFactory(Func<IInputActionAssetVerifier> factory)
  203. {
  204. return s_VerifierFactories.Remove(factory);
  205. }
  206. /// <summary>
  207. /// Verifies the given project-wide input action asset using all registered verifiers.
  208. /// </summary>
  209. /// <param name="asset">The asset to be verified.</param>
  210. /// <returns><c>true</c> if no verification errors occurred, else <c>false</c>.</returns>
  211. /// <remarks>
  212. /// Throws <c>System.ArgumentNullException</c> if <c>asset</c> is <c>null</c>.
  213. ///
  214. /// If any registered factory and/or verifier instance throws an exception this will be evaluated
  215. /// as a verification error since the execution of the verifier could not continue. However, any
  216. /// exceptions thrown will be caught and logged but not stop execution of the calling thread.
  217. /// </remarks>
  218. bool Verify(InputActionAsset asset)
  219. {
  220. if (asset == null)
  221. throw new ArgumentNullException(nameof(asset));
  222. if (s_VerifierFactories == null || s_VerifierFactories.Count == 0)
  223. return true;
  224. var instance = new Verifier(m_Reporter);
  225. foreach (var factory in s_VerifierFactories)
  226. {
  227. try
  228. {
  229. factory.Invoke().Verify(asset, instance);
  230. }
  231. catch (Exception e)
  232. {
  233. // Only log unexpected but non-fatal exception and count to fail verification
  234. ++errors;
  235. Debug.LogException(e);
  236. }
  237. }
  238. return errors == 0;
  239. }
  240. /// <summary>
  241. /// Verifies the given project-wide input action asset using all registered verifiers.
  242. /// </summary>
  243. /// <param name="asset">The asset to be verified.</param>
  244. /// <param name="reporter">The reporter to be used. If this argument is <c>null</c> the default reporter will be used.</param>
  245. /// <returns><c>true</c> if no verification errors occurred, else <c>false</c>.</returns>
  246. /// <remarks>Throws <c>System.ArgumentNullException</c> if <c>asset</c> is <c>null</c>.</remarks>
  247. public static bool Verify(InputActionAsset asset, IReportInputActionAssetVerificationErrors reporter = null)
  248. {
  249. return (s_VerifierFactories == null || s_VerifierFactories.Count == 0) || new Verifier(reporter).Verify(asset);
  250. }
  251. }
  252. internal static bool Verify(InputActionAsset asset, IReportInputActionAssetVerificationErrors reporter = null)
  253. {
  254. return Verifier.Verify(asset, reporter);
  255. }
  256. internal static bool RegisterInputActionAssetVerifier(Func<IInputActionAssetVerifier> factory)
  257. {
  258. return Verifier.RegisterFactory(factory);
  259. }
  260. internal static bool UnregisterInputActionAssetVerifier(Func<IInputActionAssetVerifier> factory)
  261. {
  262. return Verifier.UnregisterFactory(factory);
  263. }
  264. // Creates an asset at the given path containing the given JSON content.
  265. private static InputActionAsset CreateAssetAtPathFromJson(string assetPath, string json)
  266. {
  267. // Note that the extra work here is to override the JSON name from the source asset
  268. var inputActionAsset = InputActionAsset.FromJson(json);
  269. inputActionAsset.name = InputActionImporter.NameFromAssetPath(assetPath);
  270. InputActionAssetManager.SaveAsset(assetPath, inputActionAsset.ToJson());
  271. return AssetDatabase.LoadAssetAtPath<InputActionAsset>(assetPath);
  272. }
  273. }
  274. }
  275. #endif // UNITY_EDITOR && UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS