using System;
using System.Linq;
////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
//// the import (i.e. InputActionImporter can't do AddObjectToAsset with them). However, maybe there's a way around that. The thing
//// is that we really want to store the asset reference plus the action GUID on the *user* side, i.e. the referencing side. Right
//// now, what happens is that InputActionImporter puts these objects along with the reference and GUID they contain in the
//// *imported* object, i.e. right with the asset. This partially defeats the whole purpose of having these objects and it means
//// that now the GUID doesn't really matter anymore. Rather, it's the file ID that now has to be stable.
////
//// If we always store the GUID and asset reference on the user side, we can put the serialized data *anywhere* and it'll remain
//// save and proper no matter what we do in InputActionImporter.
////REVIEW: should this throw if you try to assign an action that is not a singleton?
////REVIEW: akin to this, also have an InputActionMapReference?
namespace UnityEngine.InputSystem
{
///
/// References a specific in an
/// stored inside an .
///
///
/// The difference to a plain reference directly to an object is
/// that an InputActionReference can be serialized without causing the referenced
/// to be serialized as well. The reference will remain intact even if the action or the map
/// that contains the action is renamed.
///
/// References can be set up graphically in the editor by dropping individual actions from the project
/// browser onto a reference field.
///
///
///
///
public class InputActionReference : ScriptableObject
{
///
/// The asset that the referenced action is part of. Null if the reference
/// is not initialized or if the asset has been deleted.
///
/// InputActionAsset of the referenced action.
public InputActionAsset asset => m_Asset;
///
/// The action that the reference resolves to. Null if the action
/// cannot be found.
///
/// The action that reference points to.
///
/// Actions are resolved on demand based on their internally stored IDs.
///
public InputAction action
{
get
{
if (m_Action == null)
{
if (m_Asset == null)
return null;
m_Action = m_Asset.FindAction(new Guid(m_ActionId));
}
return m_Action;
}
}
///
/// Initialize the reference to refer to the given action.
///
/// An input action. Must be contained in an
/// that is itself contained in an . Can be null in which
/// case the reference is reset to its default state which does not reference an action.
/// is not contained in an
/// that is itself contained in an .
public void Set(InputAction action)
{
if (action == null)
{
m_Asset = default;
m_ActionId = default;
return;
}
var map = action.actionMap;
if (map == null || map.asset == null)
throw new InvalidOperationException(
$"Action '{action}' must be part of an InputActionAsset in order to be able to create an InputActionReference for it");
SetInternal(map.asset, action);
}
///
/// Look up an action in the given asset and initialize the reference to
/// point to it.
///
/// An .inputactions asset.
/// Name of the in
/// (see ). Case-insensitive.
/// Name of the action in . Case-insensitive.
/// is null -or-
/// is null or empty -or-
/// is null or empty.
/// No action map called could
/// be found in -or- no action called
/// could be found in the action map called in .
public void Set(InputActionAsset asset, string mapName, string actionName)
{
if (asset == null)
throw new ArgumentNullException(nameof(asset));
if (string.IsNullOrEmpty(mapName))
throw new ArgumentNullException(nameof(mapName));
if (string.IsNullOrEmpty(actionName))
throw new ArgumentNullException(nameof(actionName));
var actionMap = asset.FindActionMap(mapName);
if (actionMap == null)
throw new ArgumentException($"No action map '{mapName}' in '{asset}'", nameof(mapName));
var action = actionMap.FindAction(actionName);
if (action == null)
throw new ArgumentException($"No action '{actionName}' in map '{mapName}' of asset '{asset}'",
nameof(actionName));
SetInternal(asset, action);
}
private void SetInternal(InputActionAsset asset, InputAction action)
{
var actionMap = action.actionMap;
if (!asset.actionMaps.Contains(actionMap))
throw new ArgumentException(
$"Action '{action}' is not contained in asset '{asset}'", nameof(action));
m_Asset = asset;
m_ActionId = action.id.ToString();
name = GetDisplayName(action);
////REVIEW: should this dirty the asset if IDs had not been generated yet?
}
///
/// Return a string representation of the reference useful for debugging.
///
/// A string representation of the reference.
public override string ToString()
{
try
{
var action = this.action;
return $"{m_Asset.name}:{action.actionMap.name}/{action.name}";
}
catch
{
if (m_Asset != null)
return $"{m_Asset.name}:{m_ActionId}";
}
return base.ToString();
}
internal static string GetDisplayName(InputAction action)
{
return !string.IsNullOrEmpty(action?.actionMap?.name) ? $"{action.actionMap?.name}/{action.name}" : action?.name;
}
///
/// Return a string representation useful for showing in UI.
///
internal string ToDisplayName()
{
return string.IsNullOrEmpty(name) ? GetDisplayName(action) : name;
}
///
/// Convert an InputActionReference to the InputAction it points to.
///
/// An InputActionReference object. Can be null.
/// The value of from . Can be null.
public static implicit operator InputAction(InputActionReference reference)
{
return reference?.action;
}
///
/// Create a new InputActionReference object that references the given action.
///
/// An input action. Must be contained in an
/// that is itself contained in an . Can be null in which
/// case the reference is reset to its default state which does not reference an action.
/// A new InputActionReference referencing .
public static InputActionReference Create(InputAction action)
{
if (action == null)
return null;
var reference = CreateInstance();
reference.Set(action);
return reference;
}
///
/// Clears the cached field for all current objects.
///
///
/// After calling this, the next call to will retrieve a new reference from the existing just as if
/// using it for the first time. The serialized and fields are not touched and will continue to hold their current values.
///
/// This method is used to clear the Action references when exiting PlayMode since those objects are no longer valid.
///
internal static void ResetCachedAction()
{
var allActionRefs = Resources.FindObjectsOfTypeAll(typeof(InputActionReference));
foreach (InputActionReference obj in allActionRefs)
{
obj.m_Action = null;
}
}
[SerializeField] internal InputActionAsset m_Asset;
// Can't serialize System.Guid and Unity's GUID is editor only so these
// go out as strings.
[SerializeField] internal string m_ActionId;
///
/// The resolved, cached input action.
///
[NonSerialized] private InputAction m_Action;
// Make annoying Microsoft code analyzer happy.
public InputAction ToInputAction()
{
return action;
}
}
}