A game about forced loneliness, made by TACStudios
1using System;
2using System.Linq;
3
4////REVIEW: Can we somehow make this a simple struct? The one problem we have is that we can't put struct instances as sub-assets into
5//// the import (i.e. InputActionImporter can't do AddObjectToAsset with them). However, maybe there's a way around that. The thing
6//// is that we really want to store the asset reference plus the action GUID on the *user* side, i.e. the referencing side. Right
7//// now, what happens is that InputActionImporter puts these objects along with the reference and GUID they contain in the
8//// *imported* object, i.e. right with the asset. This partially defeats the whole purpose of having these objects and it means
9//// that now the GUID doesn't really matter anymore. Rather, it's the file ID that now has to be stable.
10////
11//// If we always store the GUID and asset reference on the user side, we can put the serialized data *anywhere* and it'll remain
12//// save and proper no matter what we do in InputActionImporter.
13
14////REVIEW: should this throw if you try to assign an action that is not a singleton?
15
16////REVIEW: akin to this, also have an InputActionMapReference?
17
18namespace UnityEngine.InputSystem
19{
20 /// <summary>
21 /// References a specific <see cref="InputAction"/> in an <see cref="InputActionMap"/>
22 /// stored inside an <see cref="InputActionAsset"/>.
23 /// </summary>
24 /// <remarks>
25 /// The difference to a plain reference directly to an <see cref="InputAction"/> object is
26 /// that an InputActionReference can be serialized without causing the referenced <see cref="InputAction"/>
27 /// to be serialized as well. The reference will remain intact even if the action or the map
28 /// that contains the action is renamed.
29 ///
30 /// References can be set up graphically in the editor by dropping individual actions from the project
31 /// browser onto a reference field.
32 /// </remarks>
33 /// <seealso cref="InputActionProperty"/>
34 /// <seealso cref="InputAction"/>
35 /// <seealso cref="InputActionAsset"/>
36 public class InputActionReference : ScriptableObject
37 {
38 /// <summary>
39 /// The asset that the referenced action is part of. Null if the reference
40 /// is not initialized or if the asset has been deleted.
41 /// </summary>
42 /// <value>InputActionAsset of the referenced action.</value>
43 public InputActionAsset asset => m_Asset;
44
45 /// <summary>
46 /// The action that the reference resolves to. Null if the action
47 /// cannot be found.
48 /// </summary>
49 /// <value>The action that reference points to.</value>
50 /// <remarks>
51 /// Actions are resolved on demand based on their internally stored IDs.
52 /// </remarks>
53 public InputAction action
54 {
55 get
56 {
57 if (m_Action == null)
58 {
59 if (m_Asset == null)
60 return null;
61
62 m_Action = m_Asset.FindAction(new Guid(m_ActionId));
63 }
64
65 return m_Action;
66 }
67 }
68
69 /// <summary>
70 /// Initialize the reference to refer to the given action.
71 /// </summary>
72 /// <param name="action">An input action. Must be contained in an <see cref="InputActionMap"/>
73 /// that is itself contained in an <see cref="InputActionAsset"/>. Can be <c>null</c> in which
74 /// case the reference is reset to its default state which does not reference an action.</param>
75 /// <exception cref="InvalidOperationException"><paramref name="action"/> is not contained in an
76 /// <see cref="InputActionMap"/> that is itself contained in an <see cref="InputActionAsset"/>.</exception>
77 public void Set(InputAction action)
78 {
79 if (action == null)
80 {
81 m_Asset = default;
82 m_ActionId = default;
83 return;
84 }
85
86 var map = action.actionMap;
87 if (map == null || map.asset == null)
88 throw new InvalidOperationException(
89 $"Action '{action}' must be part of an InputActionAsset in order to be able to create an InputActionReference for it");
90
91 SetInternal(map.asset, action);
92 }
93
94 /// <summary>
95 /// Look up an action in the given asset and initialize the reference to
96 /// point to it.
97 /// </summary>
98 /// <param name="asset">An .inputactions asset.</param>
99 /// <param name="mapName">Name of the <see cref="InputActionMap"/> in <paramref name="asset"/>
100 /// (see <see cref="InputActionAsset.actionMaps"/>). Case-insensitive.</param>
101 /// <param name="actionName">Name of the action in <paramref name="mapName"/>. Case-insensitive.</param>
102 /// <exception cref="ArgumentNullException"><paramref name="asset"/> is <c>null</c> -or-
103 /// <paramref name="mapName"/> is <c>null</c> or empty -or- <paramref name="actionName"/>
104 /// is <c>null</c> or empty.</exception>
105 /// <exception cref="ArgumentException">No action map called <paramref name="mapName"/> could
106 /// be found in <paramref name="asset"/> -or- no action called <paramref name="actionName"/>
107 /// could be found in the action map called <paramref name="mapName"/> in <paramref name="asset"/>.</exception>
108 public void Set(InputActionAsset asset, string mapName, string actionName)
109 {
110 if (asset == null)
111 throw new ArgumentNullException(nameof(asset));
112 if (string.IsNullOrEmpty(mapName))
113 throw new ArgumentNullException(nameof(mapName));
114 if (string.IsNullOrEmpty(actionName))
115 throw new ArgumentNullException(nameof(actionName));
116
117 var actionMap = asset.FindActionMap(mapName);
118 if (actionMap == null)
119 throw new ArgumentException($"No action map '{mapName}' in '{asset}'", nameof(mapName));
120
121 var action = actionMap.FindAction(actionName);
122 if (action == null)
123 throw new ArgumentException($"No action '{actionName}' in map '{mapName}' of asset '{asset}'",
124 nameof(actionName));
125
126 SetInternal(asset, action);
127 }
128
129 private void SetInternal(InputActionAsset asset, InputAction action)
130 {
131 var actionMap = action.actionMap;
132 if (!asset.actionMaps.Contains(actionMap))
133 throw new ArgumentException(
134 $"Action '{action}' is not contained in asset '{asset}'", nameof(action));
135
136 m_Asset = asset;
137 m_ActionId = action.id.ToString();
138 name = GetDisplayName(action);
139
140 ////REVIEW: should this dirty the asset if IDs had not been generated yet?
141 }
142
143 /// <summary>
144 /// Return a string representation of the reference useful for debugging.
145 /// </summary>
146 /// <returns>A string representation of the reference.</returns>
147 public override string ToString()
148 {
149 try
150 {
151 var action = this.action;
152 return $"{m_Asset.name}:{action.actionMap.name}/{action.name}";
153 }
154 catch
155 {
156 if (m_Asset != null)
157 return $"{m_Asset.name}:{m_ActionId}";
158 }
159
160 return base.ToString();
161 }
162
163 internal static string GetDisplayName(InputAction action)
164 {
165 return !string.IsNullOrEmpty(action?.actionMap?.name) ? $"{action.actionMap?.name}/{action.name}" : action?.name;
166 }
167
168 /// <summary>
169 /// Return a string representation useful for showing in UI.
170 /// </summary>
171 internal string ToDisplayName()
172 {
173 return string.IsNullOrEmpty(name) ? GetDisplayName(action) : name;
174 }
175
176 /// <summary>
177 /// Convert an InputActionReference to the InputAction it points to.
178 /// </summary>
179 /// <param name="reference">An InputActionReference object. Can be null.</param>
180 /// <returns>The value of <see cref="action"/> from <paramref name="reference"/>. Can be null.</returns>
181 public static implicit operator InputAction(InputActionReference reference)
182 {
183 return reference?.action;
184 }
185
186 /// <summary>
187 /// Create a new InputActionReference object that references the given action.
188 /// </summary>
189 /// <param name="action">An input action. Must be contained in an <see cref="InputActionMap"/>
190 /// that is itself contained in an <see cref="InputActionAsset"/>. Can be <c>null</c> in which
191 /// case the reference is reset to its default state which does not reference an action.</param>
192 /// <returns>A new InputActionReference referencing <paramref name="action"/>.</returns>
193 public static InputActionReference Create(InputAction action)
194 {
195 if (action == null)
196 return null;
197 var reference = CreateInstance<InputActionReference>();
198 reference.Set(action);
199 return reference;
200 }
201
202 /// <summary>
203 /// Clears the cached <see cref="m_Action"/> field for all current <see cref="InputActionReference"/> objects.
204 /// </summary>
205 /// <remarks>
206 /// After calling this, the next call to <see cref="action"/> will retrieve a new <see cref="InputAction"/> reference from the existing <see cref="InputActionAsset"/> just as if
207 /// using it for the first time. The serialized <see cref="m_Asset"/> and <see cref="m_ActionId"/> fields are not touched and will continue to hold their current values.
208 ///
209 /// This method is used to clear the Action references when exiting PlayMode since those objects are no longer valid.
210 /// </remarks>
211 internal static void ResetCachedAction()
212 {
213 var allActionRefs = Resources.FindObjectsOfTypeAll(typeof(InputActionReference));
214 foreach (InputActionReference obj in allActionRefs)
215 {
216 obj.m_Action = null;
217 }
218 }
219
220 [SerializeField] internal InputActionAsset m_Asset;
221 // Can't serialize System.Guid and Unity's GUID is editor only so these
222 // go out as strings.
223 [SerializeField] internal string m_ActionId;
224
225 /// <summary>
226 /// The resolved, cached input action.
227 /// </summary>
228 [NonSerialized] private InputAction m_Action;
229
230 // Make annoying Microsoft code analyzer happy.
231 public InputAction ToInputAction()
232 {
233 return action;
234 }
235 }
236}