InputEventMap.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "InputEventMap.h"
  9. #include <AzCore/EBus/EBus.h>
  10. #include <AzCore/RTTI/ReflectContext.h>
  11. #include <AzCore/Serialization/SerializeContext.h>
  12. #include <AzCore/Serialization/EditContext.h>
  13. #include <AzCore/Component/TickBus.h>
  14. #include <AzCore/RTTI/BehaviorContext.h>
  15. #include <AzCore/std/containers/set.h>
  16. #include <AzCore/std/sort.h>
  17. #include <AzFramework/Input/Buses/Requests/InputDeviceRequestBus.h>
  18. #include <AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.h>
  19. using namespace AzFramework;
  20. namespace StartingPointInput
  21. {
  22. void InputEventMap::Reflect(AZ::ReflectContext* reflection)
  23. {
  24. AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
  25. if (serializeContext)
  26. {
  27. serializeContext->Class<InputEventMap>()
  28. ->Version(2)
  29. ->Field("Input Device Type", &InputEventMap::m_inputDeviceType)
  30. ->Field("Input Name", &InputEventMap::m_inputName)
  31. ->Field("Event Value Multiplier", &InputEventMap::m_eventValueMultiplier)
  32. ->Field("Dead Zone", &InputEventMap::m_deadZone);
  33. AZ::EditContext* editContext = serializeContext->GetEditContext();
  34. if (editContext)
  35. {
  36. editContext->Class<InputEventMap>("InputEventMap", "Maps raw input to a game specific input event")
  37. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  38. ->Attribute(AZ::Edit::Attributes::NameLabelOverride, &InputEventMap::GetEditorText)
  39. ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
  40. ->DataElement(AZ::Edit::UIHandlers::ComboBox, &InputEventMap::m_inputDeviceType, "Input Device Type", "The type of input device, ex keyboard")
  41. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &InputEventMap::OnDeviceSelected)
  42. ->Attribute(AZ::Edit::Attributes::StringList, &InputEventMap::GetInputDeviceTypes)
  43. ->DataElement(AZ::Edit::UIHandlers::ComboBox, &InputEventMap::m_inputName, "Input Name", "The name of the input you want to hold ex. space")
  44. ->Attribute(AZ::Edit::Attributes::StringList, &InputEventMap::GetInputNamesBySelectedDevice)
  45. ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
  46. ->DataElement(0, &InputEventMap::m_eventValueMultiplier, "Event value multiplier", "When the event fires, the value will be scaled by this multiplier")
  47. ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
  48. ->DataElement(0, &InputEventMap::m_deadZone, "Dead zone", "An event will only be sent out if the value is above this threshold")
  49. ->Attribute(AZ::Edit::Attributes::Min, 0.0f);
  50. }
  51. if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(reflection))
  52. {
  53. behaviorContext->EBus<InputEventNotificationBus>("InputEventNotificationBus")
  54. ->Event("OnPressed", &InputEventNotificationBus::Events::OnPressed)
  55. ->Event("OnHeld", &InputEventNotificationBus::Events::OnHeld)
  56. ->Event("OnReleased", &InputEventNotificationBus::Events::OnReleased);
  57. }
  58. }
  59. }
  60. InputEventMap::InputEventMap()
  61. {
  62. if (m_inputDeviceType.empty())
  63. {
  64. auto&& deviceTypes = GetInputDeviceTypes();
  65. if (!deviceTypes.empty())
  66. {
  67. m_inputDeviceType = deviceTypes[0];
  68. OnDeviceSelected();
  69. }
  70. }
  71. }
  72. bool InputEventMap::OnInputChannelEventFiltered(const AzFramework::InputChannel& inputChannel)
  73. {
  74. const LocalUserId localUserIdOfEvent = static_cast<LocalUserId>(inputChannel.GetInputDevice().GetAssignedLocalUserId());
  75. const float value = CalculateEventValue(inputChannel);
  76. const bool isPressed = fabs(value) > m_deadZone;
  77. if (!m_wasPressed && isPressed)
  78. {
  79. SendEventsInternal(value, localUserIdOfEvent, m_outgoingBusId, &InputEventNotificationBus::Events::OnPressed);
  80. }
  81. else if (m_wasPressed && isPressed)
  82. {
  83. SendEventsInternal(value, localUserIdOfEvent, m_outgoingBusId, &InputEventNotificationBus::Events::OnHeld);
  84. }
  85. else if (m_wasPressed && !isPressed)
  86. {
  87. SendEventsInternal(value, localUserIdOfEvent, m_outgoingBusId, &InputEventNotificationBus::Events::OnReleased);
  88. }
  89. m_wasPressed = isPressed;
  90. // Return false so we don't consume the event. This should perhaps be a configurable option?
  91. return false;
  92. }
  93. float InputEventMap::CalculateEventValue(const AzFramework::InputChannel& inputChannel) const
  94. {
  95. return inputChannel.GetValue();
  96. }
  97. void InputEventMap::SendEventsInternal(float value, const AzFramework::LocalUserId& localUserIdOfEvent, const InputEventNotificationId busId, InputEventType eventType)
  98. {
  99. value *= m_eventValueMultiplier;
  100. InputEventNotificationId localUserBusId = InputEventNotificationId(localUserIdOfEvent, busId.m_actionNameCrc);
  101. InputEventNotificationBus::Event(localUserBusId, eventType, value);
  102. InputEventNotificationId wildCardBusId = InputEventNotificationId(AzFramework::LocalUserIdAny, busId.m_actionNameCrc);
  103. InputEventNotificationBus::Event(wildCardBusId, eventType, value);
  104. }
  105. void InputEventMap::Activate(const InputEventNotificationId& eventNotificationId)
  106. {
  107. const AzFramework::InputDevice* inputDevice = AzFramework::InputDeviceRequests::FindInputDevice(AzFramework::InputDeviceId(m_inputDeviceType.c_str()));
  108. if (!inputDevice || !inputDevice->IsSupported())
  109. {
  110. // The input device that this input binding would be listening for input from
  111. // is not supported on the current platform, so don't bother even activating.
  112. // Please note distinction between InputDevice::IsSupported and IsConnected.
  113. return;
  114. }
  115. const AZ::Crc32 channelNameFilter(m_inputName.c_str());
  116. const AZ::Crc32 deviceNameFilter(m_inputDeviceType.c_str());
  117. const AzFramework::LocalUserId localUserIdFilter(eventNotificationId.m_localUserId);
  118. AZStd::shared_ptr<InputChannelEventFilterInclusionList> filter = AZStd::make_shared<InputChannelEventFilterInclusionList>(channelNameFilter, deviceNameFilter, localUserIdFilter);
  119. InputChannelEventListener::SetFilter(filter);
  120. InputChannelEventListener::Connect();
  121. m_wasPressed = false;
  122. m_outgoingBusId = eventNotificationId;
  123. }
  124. void InputEventMap::Deactivate([[maybe_unused]] const InputEventNotificationId& eventNotificationId)
  125. {
  126. if (m_wasPressed)
  127. {
  128. InputEventNotificationBus::Event(m_outgoingBusId, &InputEventNotifications::OnReleased, 0.0f);
  129. }
  130. InputChannelEventListener::Disconnect();
  131. }
  132. AZStd::string InputEventMap::GetEditorText() const
  133. {
  134. return m_inputName.empty() ? "<Select input>" : m_inputName;
  135. }
  136. const AZStd::vector<AZStd::string> InputEventMap::GetInputDeviceTypes() const
  137. {
  138. AZStd::set<AZStd::string> uniqueInputDeviceTypes;
  139. AzFramework::InputDeviceRequests::InputDeviceIdSet availableInputDeviceIds;
  140. AzFramework::InputDeviceRequestBus::Broadcast(&AzFramework::InputDeviceRequests::GetInputDeviceIds,
  141. availableInputDeviceIds);
  142. for (const AzFramework::InputDeviceId& inputDeviceId : availableInputDeviceIds)
  143. {
  144. uniqueInputDeviceTypes.insert(inputDeviceId.GetName());
  145. }
  146. return AZStd::vector<AZStd::string>(uniqueInputDeviceTypes.begin(), uniqueInputDeviceTypes.end());
  147. }
  148. const AZStd::vector<AZStd::string> InputEventMap::GetInputNamesBySelectedDevice() const
  149. {
  150. AZStd::vector<AZStd::string> retval;
  151. AzFramework::InputDeviceId selectedDeviceId(m_inputDeviceType.c_str());
  152. AzFramework::InputDeviceRequests::InputChannelIdSet availableInputChannelIds;
  153. AzFramework::InputDeviceRequestBus::Event(selectedDeviceId,
  154. &AzFramework::InputDeviceRequests::GetInputChannelIds,
  155. availableInputChannelIds);
  156. for (const AzFramework::InputChannelId& inputChannelId : availableInputChannelIds)
  157. {
  158. retval.push_back(inputChannelId.GetName());
  159. }
  160. AZStd::sort(retval.begin(), retval.end());
  161. return retval;
  162. }
  163. AZ::Crc32 InputEventMap::OnDeviceSelected()
  164. {
  165. auto&& inputList = GetInputNamesBySelectedDevice();
  166. if (!inputList.empty())
  167. {
  168. m_inputName = inputList[0];
  169. }
  170. return AZ::Edit::PropertyRefreshLevels::AttributesAndValues;
  171. }
  172. ThumbstickInputEventMap::ThumbstickInputEventMap()
  173. {
  174. m_inputDeviceType = AzFramework::InputDeviceGamepad::Name;
  175. OnDeviceSelected();
  176. }
  177. void ThumbstickInputEventMap::Reflect(AZ::ReflectContext* reflection)
  178. {
  179. AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
  180. if (serializeContext)
  181. {
  182. serializeContext->Class<ThumbstickInputEventMap, InputEventMap>()
  183. ->Version(1, nullptr)
  184. ->Field("Inner Dead Zone Radius", &ThumbstickInputEventMap::m_innerDeadZoneRadius)
  185. ->Field("Outer Dead Zone Radius", &ThumbstickInputEventMap::m_outerDeadZoneRadius)
  186. ->Field("Axis Dead Zone Value", &ThumbstickInputEventMap::m_axisDeadZoneValue)
  187. ->Field("Sensitivity Exponent", &ThumbstickInputEventMap::m_sensitivityExponent)
  188. ->Field("Output Axis", &ThumbstickInputEventMap::m_outputAxis)
  189. ;
  190. AZ::EditContext* editContext = serializeContext->GetEditContext();
  191. if (editContext)
  192. {
  193. editContext->Class<ThumbstickInputEventMap>("ThumbstickInputEventMap", "Generate events from thumbstick input")
  194. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  195. ->Attribute(AZ::Edit::Attributes::NameLabelOverride, &ThumbstickInputEventMap::GetEditorText)
  196. ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
  197. ->DataElement(0, &ThumbstickInputEventMap::m_innerDeadZoneRadius, "Inner Dead Zone Radius", "The thumbstick axes vector (x,y) will be normalized between this value and Outer Dead Zone Radius")
  198. ->Attribute(AZ::Edit::Attributes::Min, 0.0f)
  199. ->Attribute(AZ::Edit::Attributes::Max, 1.0f)
  200. ->DataElement(0, &ThumbstickInputEventMap::m_outerDeadZoneRadius, "Outer Dead Zone Radius", "The thumbstick axes vector (x,y) will be normalized between Inner Dead Zone Radius and this value")
  201. ->Attribute(AZ::Edit::Attributes::Min, 0.0f)
  202. ->Attribute(AZ::Edit::Attributes::Max, 1.0f)
  203. ->DataElement(0, &ThumbstickInputEventMap::m_axisDeadZoneValue, "Axis Dead Zone Value", "The individual axis values will be normalized between this and 1.0f")
  204. ->Attribute(AZ::Edit::Attributes::Min, 0.0f)
  205. ->Attribute(AZ::Edit::Attributes::Max, 1.0f)
  206. ->DataElement(0, &ThumbstickInputEventMap::m_sensitivityExponent, "Sensitivity Exponent", "The sensitivity exponent to apply to the normalized thumbstick components")
  207. ->DataElement(AZ::Edit::UIHandlers::ComboBox, &ThumbstickInputEventMap::m_outputAxis, "Output Axis", "The axis value to output after peforming the dead-zone and sensitivity calculations")
  208. ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
  209. ->Attribute(AZ::Edit::Attributes::EnumValues, AZStd::vector<AZ::Edit::EnumConstant<OutputAxis>>
  210. {
  211. AZ::Edit::EnumConstant<OutputAxis>(OutputAxis::X, "x"),
  212. AZ::Edit::EnumConstant<OutputAxis>(OutputAxis::Y, "y")
  213. })
  214. ;
  215. }
  216. }
  217. }
  218. AZStd::string ThumbstickInputEventMap::GetEditorText() const
  219. {
  220. return m_inputName.empty() ?
  221. "<Select input>" :
  222. m_inputName + (m_outputAxis == OutputAxis::X ? " (x-axis)" : " (y-axis)");
  223. }
  224. const AZStd::vector<AZStd::string> ThumbstickInputEventMap::GetInputDeviceTypes() const
  225. {
  226. // Gamepads are currently the only device type that support thumbstick input.
  227. // We could (should) be more robust here by iterating over all input devices,
  228. // looking for any with associated input channels of type InputChannelAxis2D.
  229. AZStd::vector<AZStd::string> retval;
  230. retval.push_back(AzFramework::InputDeviceGamepad::Name);
  231. return retval;
  232. }
  233. const AZStd::vector<AZStd::string> ThumbstickInputEventMap::GetInputNamesBySelectedDevice() const
  234. {
  235. // Gamepads are currently the only device type that support thumbstick input.
  236. // We could (should) be more robust here by iterating over all input devices,
  237. // looking for any with associated input channels of type InputChannelAxis2D.
  238. AZStd::vector<AZStd::string> retval;
  239. retval.push_back(AzFramework::InputDeviceGamepad::ThumbStickAxis2D::L.GetName());
  240. retval.push_back(AzFramework::InputDeviceGamepad::ThumbStickAxis2D::R.GetName());
  241. return retval;
  242. }
  243. bool ThumbstickInputEventMap::OnInputChannelEventFiltered(const AzFramework::InputChannel& inputChannel)
  244. {
  245. // Because we are sending all thumbstick events regardless of if they are inside the dead-zone
  246. // (see InputChannelAxis2D::ProcessRawInputEvent)
  247. // ThumbstickInputEventMap components can effectively cancel themselves out if they happen to be setup
  248. // to receive input from a local user id that is signed into multiple controllers at the same
  249. // time. If the controller not being used is updated last, the (~0, ~0) events it sends every
  250. // frame cause the base InputEventMap::OnInputChannelEventFiltered function to determine that we need
  251. // to send an InputEventNotificationBus::Events::OnReleased event because m_wasPressed is set
  252. // to true by the other controller that is actually in use (and that is being updated first).
  253. //
  254. // To combat this, anytime we enter the m_wasPressed == true state we'll store a reference to
  255. // the input device id that sent the event (see below). Each time we receive an event we will
  256. // then check whether it's originating from the same input device id, and if not we will just
  257. // ignore it. Please note that while taking the address of the device id is a little sketchy,
  258. // we can do this because it's lifecycle is guaranteed to be longer than that of this object
  259. // UNLESS we ever start calling InputSystemComponent::RecreateEnabledInputDevices somewhere.
  260. //
  261. // Now in this case, the old InputDevice that owns the InputChannelId will be destroyed, but
  262. // not before the InputChannels it owns are destroyed first meaning InputChannel::ResetState
  263. // will be called, the internal state of the input channels will be reset, and an event will
  264. // be broadcast that will ultimately result in m_wasPressed being set to false and therefore
  265. // m_wasLastPressedByInputDeviceId being set to nullptr below instead of becoming a dangling
  266. // pointer. I definitely don't like this much, as it is risky behavior entirely dependent on
  267. // the internal workings of the AzFramework input system, but it's the fastest way to perform
  268. // this additional check. The alternative is to store the last pressed InputDeviceId by value,
  269. // but this would involve a (slightly) more expensive check (see InputDeviceId::operator==),
  270. // along with a string copy each time we set/reset the value (see InputDeviceId::operator=).
  271. //
  272. // At some point it may be worth looking at doing this check (or a safer version of it) in
  273. // the base InputEventMap::OnInputChannelEventFiltered function, because the 'one user logged into
  274. // multiple controllers' situation could conceivably cause strange behaviour for all types
  275. // of input bindings (albeit only if the user were to actively use both controllers at the
  276. // same time, in which case who can even really say what the correct behaviour should be?).
  277. // But that would be a far riskier change, and because it's only a problem for thumb-stick
  278. // input we're sending even when the controller is completely idle this fix will do for now.
  279. const InputDeviceId* inputDeviceId = &(inputChannel.GetInputDevice().GetInputDeviceId());
  280. if (m_wasLastPressedByInputDeviceId && m_wasLastPressedByInputDeviceId != inputDeviceId)
  281. {
  282. return false;
  283. }
  284. const bool shouldBeConsumed = InputEventMap::OnInputChannelEventFiltered(inputChannel);
  285. m_wasLastPressedByInputDeviceId = m_wasPressed ? inputDeviceId : nullptr;
  286. return shouldBeConsumed;
  287. }
  288. float ThumbstickInputEventMap::CalculateEventValue(const AzFramework::InputChannel& inputChannel) const
  289. {
  290. const AzFramework::InputChannelAxis2D::AxisData2D* axisData2D = inputChannel.GetCustomData<AzFramework::InputChannelAxis2D::AxisData2D>();
  291. if (axisData2D == nullptr)
  292. {
  293. AZ_Warning("ThumbstickInputEventMap", false, "InputChannel with id '%s' has no axis data 2D", inputChannel.GetInputChannelId().GetName());
  294. return 0.0f;
  295. }
  296. const AZ::Vector2 outputValues = ApplyDeadZonesAndSensitivity(axisData2D->m_preDeadZoneValues,
  297. m_innerDeadZoneRadius,
  298. m_outerDeadZoneRadius,
  299. m_axisDeadZoneValue,
  300. m_sensitivityExponent);
  301. // Ideally we would return both values here and allow each to be mapped to a different output
  302. // event, but that would require a greater re-factor of the StartingPointInput Gem, and there
  303. // is nothing preventing anyone from setting up one ThumbstickInputEventMap component for each
  304. // axis so it would only be a simplification/optimization.
  305. const float axisValueToReturn = (m_outputAxis == OutputAxis::X) ? outputValues.GetX() : outputValues.GetY();
  306. return axisValueToReturn;
  307. }
  308. AZ::Vector2 ThumbstickInputEventMap::ApplyDeadZonesAndSensitivity(const AZ::Vector2& inputValues, float innerDeadZone, float outerDeadZone, float axisDeadZone, float sensitivityExponent)
  309. {
  310. static const AZ::Vector2 zeroVector = AZ::Vector2::CreateZero();
  311. const AZ::Vector2 rawAbsValues(fabsf(inputValues.GetX()), fabsf(inputValues.GetY()));
  312. const float rawLength = rawAbsValues.GetLength();
  313. if (rawLength == 0.0f)
  314. {
  315. return zeroVector;
  316. }
  317. // Apply the circular dead zones
  318. const AZ::Vector2 normalizedValues = rawAbsValues / rawLength;
  319. const float postCircularDeadZoneLength = AZ::GetClamp((rawLength - innerDeadZone) / (outerDeadZone - innerDeadZone), 0.0f, 1.0f);
  320. AZ::Vector2 absValues = normalizedValues * postCircularDeadZoneLength;
  321. // Apply the per-axis dead zone
  322. const AZ::Vector2 absAxisValues = zeroVector.GetMax(rawAbsValues - AZ::Vector2(axisDeadZone, axisDeadZone)) / (outerDeadZone - axisDeadZone);
  323. // Merge the circular and per-axis dead zones. The resulting values are the smallest ones (dead zone takes priority). And restore the components sign.
  324. const AZ::Vector2 signValues(AZ::GetSign(inputValues.GetX()), AZ::GetSign(inputValues.GetY()));
  325. AZ::Vector2 values = absValues.GetMin(absAxisValues) * signValues;
  326. // Rescale the vector using the post circular dead zone length, which is the real stick vector length,
  327. // to avoid any jump in values when the stick is fully pushed along an axis and slowly getting out of the axis dead zone
  328. // Additionally, apply the sensitivity curve to the final stick vector length
  329. const float postAxisDeadZoneLength = values.GetLength();
  330. if (postAxisDeadZoneLength > 0.0f)
  331. {
  332. values /= postAxisDeadZoneLength;
  333. const float postSensitivityLength = powf(postCircularDeadZoneLength, sensitivityExponent);
  334. values *= postSensitivityLength;
  335. }
  336. return values;
  337. }
  338. } // namespace Input