Input context component (#4152)

* Create a new InputContextComponent.

- An InputContextComponent is used to configure (at edit time) the data necessary to create an AzFrameowrk::InputContext (at run time). The lifecycle of any InputContextComponent is controlled by the AZ::Entity it is attached to (adhering to the same rules as any other AZ::Component), and the InputContext which it owns is created/destroyed when the component is activated/deactivated.

- The underlying AzFramework::InputContext and AzFramework::InputMapping* classes already exist, along with comprehensive unit tests (see Code/Framework/AzFramework/Tests/InputTests.cpp). All changes in this PR are simply to allow input contexts / input mappings to be defined/edited from the editor using data.

- The new InputContextComponent is similar in many respects to the InputConfigurationComponent found in the StartingPointInput Gem, the main difference being that the user-defined input mapping objects the new component creates are identical (from the perspective of any input consumer) to 'raw' engine-defined input channels (the existing AzFramework::InputMapping class inherits from the existing AzFramework::InputChannel class). This means that any system which consumes input (in either C++ or lua) can seamlessly interchange between obtaining input using either the 'raw' engine-defined input channels (eg. InputDeviceGamepad::Button::A, InputDeviceKeyboard::Key::AlphanumericA, etc.) or any user-defined input mapping, which can now be defined from the editor using data. Ultimately I would like to deprecate the StartingPointInput::InputConfigurationComponent in favour of this new InputContextComponent, which isn't realistic at present because the former is used pervasively throughout many different projects, and it integrates with ScriptCanvas in a way that I have yet to replicate, but this is a step towards the goal of a unified, engine-wide input context/input mapping solution that can be used interchangeably by any system, project, or Gem.

Signed-off-by: bosnichd <bosnichd@amazon.com>

* Edited some reflected property descriptions for clarity.

Signed-off-by: bosnichd <bosnichd@amazon.com>

* Fix clang builds:
- Add a missing virtual destructor.
- Fix the scope of two reflected property attribute functions.

Signed-off-by: bosnichd <bosnichd@amazon.com>

* Fix for InputContextComponent being reflected twice in some cirsumstances.

Signed-off-by: bosnichd <bosnichd@amazon.com>

* Updates in response to PR feedback.

Signed-off-by: bosnichd <bosnichd@amazon.com>

* More updates based on review feedback.

Signed-off-by: bosnichd <bosnichd@amazon.com>
This commit is contained in:
bosnichd
2021-09-16 12:26:57 -06:00
committed by GitHub
parent 87ac025575
commit 86ccf1c86e
11 changed files with 752 additions and 0 deletions
@@ -16,6 +16,7 @@
#include <AzFramework/Components/AzFrameworkConfigurationSystemComponent.h>
#include <AzFramework/Entity/GameEntityContextComponent.h>
#include <AzFramework/FileTag/FileTagComponent.h>
#include <AzFramework/Input/Contexts/InputContextComponent.h>
#include <AzFramework/Input/System/InputSystemComponent.h>
#include <AzFramework/Render/GameIntersectorComponent.h>
#include <AzFramework/Scene/SceneSystemComponent.h>
@@ -47,6 +48,7 @@ namespace AzFramework
AzFramework::CreateScriptDebugAgentFactory(),
AzFramework::AssetSystem::AssetSystemComponent::CreateDescriptor(),
AzFramework::InputSystemComponent::CreateDescriptor(),
AzFramework::InputContextComponent::CreateDescriptor(),
#if !defined(AZCORE_EXCLUDE_LUA)
AzFramework::ScriptComponent::CreateDescriptor(),
@@ -55,6 +55,10 @@ namespace AzFramework
// Allocator
AZ_CLASS_ALLOCATOR(InputContext, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
// Type Info
AZ_RTTI(InputContext, "{D17A85B2-405F-40AB-BBA7-F118256D39AB}", InputDevice);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] name Unique, will be truncated if exceeds InputDeviceId::MAX_NAME_LENGTH = 64
@@ -0,0 +1,172 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzFramework/Input/Contexts/InputContextComponent.h>
#include <AzFramework/Input/Mappings/InputMappingAnd.h>
#include <AzFramework/Input/Mappings/InputMappingOr.h>
#include <AzCore/Debug/Trace.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
void InputContextComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("InputContextService", 0xa2734425));
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputContextComponent::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<InputContextComponent, AZ::Component>()
->Version(0)
->Field("Unique Name", &InputContextComponent::m_uniqueName)
->Field("Input Mappings", &InputContextComponent::m_inputMappings)
->Field("Local Player Index", &InputContextComponent::m_localPlayerIndex)
->Field("Input Listener Priority", &InputContextComponent::m_inputListenerPriority)
->Field("Consumes Processed Input", &InputContextComponent::m_consumesProcessedInput)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<InputContextComponent>("Input Context",
"An input context is a collection of input mappings, which map 'raw' input to custom input channels (ie. events).")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::Category, "Input")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c))
->DataElement(AZ::Edit::UIHandlers::Default, &InputContextComponent::m_uniqueName, "Unique Name",
"The name of the input context, unique among all active input contexts and input devices.\n"
"This will be truncated if its length exceeds that of InputDeviceId::MAX_NAME_LENGTH = 64")
->DataElement(AZ::Edit::UIHandlers::Default, &InputContextComponent::m_inputMappings, "Input Mappings",
"The list of all input mappings that will be created when the input context is activated.")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::SpinBox, &InputContextComponent::m_localPlayerIndex, "Local Player Index",
"The local player index that this context will receive input from (0 based, -1 means all controllers).\n"
"Will only work on platforms such as PC where the local user id corresponds to the local player index.\n"
"For other platforms, SetLocalUserId must be called at runtime with the id of a logged in user.")
->Attribute(AZ::Edit::Attributes::Min, -1)
->Attribute(AZ::Edit::Attributes::Max, 3)
->DataElement(AZ::Edit::UIHandlers::SpinBox, &InputContextComponent::m_inputListenerPriority, "Input Listener Priority",
"The priority used to sort the input context relative to all other input event listeners.\n"
"Higher numbers indicate greater priority.")
->Attribute(AZ::Edit::Attributes::Min, InputChannelEventListener::GetPriorityLast())
->Attribute(AZ::Edit::Attributes::Max, InputChannelEventListener::GetPriorityFirst())
->DataElement(AZ::Edit::UIHandlers::CheckBox, &InputContextComponent::m_consumesProcessedInput, "Consumes Processed Input",
"Should the input context consume input that is processed by any of its input mappings?")
;
}
}
InputMapping::ConfigBase::Reflect(context);
InputMappingAnd::Config::Reflect(context);
InputMappingOr::Config::Reflect(context);
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputContextComponent::~InputContextComponent()
{
Deactivate();
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputContextComponent::Init()
{
// The local player index that this component will receive input from (0 base, -1 wildcard)
// can be set from data, but will only work on platforms where the local user id corresponds
// to a local player index. For other platforms SetLocalUserId must be called at runtime with
// the id of a logged in local user, which will overwrite anything that is set here from data.
const LocalUserId localUserId = (m_localPlayerIndex == -1) ? LocalUserIdAny : aznumeric_cast<AZ::u32>(m_localPlayerIndex);
SetLocalUserId(localUserId);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputContextComponent::Activate()
{
InputContextComponentRequestBus::Handler::BusConnect(GetEntityId());
CreateInputContext();
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputContextComponent::Deactivate()
{
ResetInputContext();
InputContextComponentRequestBus::Handler::BusDisconnect(GetEntityId());
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputContextComponent::SetLocalUserId(LocalUserId localUserId)
{
// Create a new filter, or reset any existing one if we have been passed LocalUserIdAny.
if (localUserId != LocalUserIdAny)
{
m_localUserIdFilter = AZStd::make_shared<InputChannelEventFilterInclusionList>(InputChannelEventFilter::AnyChannelNameCrc32,
InputChannelEventFilter::AnyDeviceNameCrc32,
aznumeric_cast<AZ::u32>(m_localPlayerIndex));
}
else
{
m_localUserIdFilter.reset();
}
// Set the filter if the input context has already been created.
if (m_inputContext)
{
m_inputContext->SetFilter(m_localUserIdFilter);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputContextComponent::CreateInputContext()
{
if (m_uniqueName.empty())
{
AZ_Error("InputContextComponent", false, "Cannot create input context with empty name.");
return;
}
if (InputDeviceRequests::FindInputDevice(InputDeviceId(m_uniqueName.c_str())))
{
AZ_Error("InputContextComponent", false,
"Cannot create input context '%s' with non-unique name.", m_uniqueName.c_str());
return;
}
if (m_inputMappings.empty())
{
AZ_Error("InputContextComponent", false,
"Cannot create input context '%s' with no input mappings.", m_uniqueName.c_str());
return;
}
// Create the input context.
InputContext::InitData initData;
initData.autoActivate = true;
initData.filter = m_localUserIdFilter;
initData.priority = m_inputListenerPriority;
initData.consumesProcessedInput = m_consumesProcessedInput;
m_inputContext = AZStd::make_unique<InputContext>(m_uniqueName.c_str(), initData);
// Create and add all input mappings.
for (const InputMapping::ConfigBase* inputMapping : m_inputMappings)
{
inputMapping->CreateInputMappingAndAddToContext(*m_inputContext);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputContextComponent::ResetInputContext()
{
m_inputContext.reset();
}
} // namespace AzFramework
@@ -0,0 +1,129 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzFramework/Input/Contexts/InputContext.h>
#include <AzFramework/Input/Mappings/InputMapping.h>
#include <AzFramework/Input/User/LocalUserId.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Component/ComponentBus.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/string/string.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
class InputContextComponentRequests : public AZ::ComponentBus
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
//! Set the local user id that the InputContextComponent should process input from
//! \param[in] localUserId Local user id the InputContextComponent should process input from
virtual void SetLocalUserId(LocalUserId localUserId) = 0;
};
using InputContextComponentRequestBus = AZ::EBus<InputContextComponentRequests>;
////////////////////////////////////////////////////////////////////////////////////////////////
//! An InputContextComponent is used to configure (at edit time) the data necessary to create an
//! InputContext (at run time). The life cycle of any InputContextComponent is controlled by the
//! AZ::Entity it is attached to, adhering to the same rules as any other AZ::Component, and the
//! InputContext which it owns is created/destroyed when the component is activated/deactivated.
class InputContextComponent : public AZ::Component
, public InputContextComponentRequestBus::Handler
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// AZ::Component Setup
AZ_COMPONENT(InputContextComponent, "{321689F8-A572-47D7-9D1C-EF9E0D2CD472}");
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AZ::ComponentDescriptor::GetProvidedServices
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AZ::ComponentDescriptor::Reflect
static void Reflect(AZ::ReflectContext* context);
////////////////////////////////////////////////////////////////////////////////////////////
//! Default Constructor
InputContextComponent() = default;
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
~InputContextComponent() override;
protected:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AZ::Component::Init
void Init() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AZ::Component::Activate
void Activate() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AZ::Component::Deactivate
void Deactivate() override;
////////////////////////////////////////////////////////////////////////////////////////////
// \ref AzFramework::InputContextComponentRequests::SetLocalUserId
void SetLocalUserId(LocalUserId localUserId) override;
private:
////////////////////////////////////////////////////////////////////////////////////////////
//! Create the input context.
void CreateInputContext();
////////////////////////////////////////////////////////////////////////////////////////////
//! Reset the input context.
void ResetInputContext();
////////////////////////////////////////////////////////////////////////////////////////////
//! The list of all input mappings that will be created when the input context is activated.
//! Reflected to EditContext, then used to create and add input mapping classes in Activate.
AZStd::vector<InputMapping::ConfigBase*> m_inputMappings;
////////////////////////////////////////////////////////////////////////////////////////////
//! The name of the input context, unique among all active input contexts and input devices.
//! This will be truncated if its length exceeds that of InputDeviceId::MAX_NAME_LENGTH = 64
//! Reflected to EditContext, then used to create the unique input context class in Activate.
AZStd::string m_uniqueName;
////////////////////////////////////////////////////////////////////////////////////////////
//! Input context that is created and owned by this component. Not reflected to EditContext.
AZStd::unique_ptr<InputContext> m_inputContext;
////////////////////////////////////////////////////////////////////////////////////////////
//! Filter used to determine whether an input event should be handled by this input context.
//! Not reflected, but created inside SetLocalUserId if needed to fliter by a local user id.
AZStd::shared_ptr<InputChannelEventFilterInclusionList> m_localUserIdFilter;
////////////////////////////////////////////////////////////////////////////////////////////
//! The local player index that this component will receive input from (0 base, -1 wildcard).
//! Will only work on platforms where the local user id corresponds to the local player index.
//! For other platforms, SetLocalUserId must be called at runtime with id of a logged in user.
//! Reflected to EditContext, then used if needed to create the local user id filter in Init.
AZ::s32 m_localPlayerIndex = -1;
////////////////////////////////////////////////////////////////////////////////////////////
//! The priority used to sort the input context relative to all other input event listeners.
//! Reflected to EditContext, then used to create the unique input context class in Activate.
AZ::s32 m_inputListenerPriority = InputChannelEventListener::GetPriorityDefault();
////////////////////////////////////////////////////////////////////////////////////////////
//! Should the input context consume input that is processed by any of its input mappings?
//! Reflected to EditContext, then used to create the unique input context class in Activate.
bool m_consumesProcessedInput = false;
};
} // namespace AzFramework
@@ -9,9 +9,156 @@
#include <AzFramework/Input/Mappings/InputMapping.h>
#include <AzFramework/Input/Contexts/InputContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/sort.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
void InputMapping::InputChannelNameFilteredByDeviceType::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<InputMapping::InputChannelNameFilteredByDeviceType>()
->Version(0)
->Field("Input Device Type", &InputChannelNameFilteredByDeviceType::m_inputDeviceType)
->Field("Input Channel Name", &InputChannelNameFilteredByDeviceType::m_inputChannelName)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<InputChannelNameFilteredByDeviceType>("InputChannelNameFilteredByDeviceType",
"An input channel name (filtered by an input device type) to add to the input mapping.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
->Attribute(AZ::Edit::Attributes::NameLabelOverride, &InputChannelNameFilteredByDeviceType::GetNameLabelOverride)
->DataElement(AZ::Edit::UIHandlers::ComboBox, &InputChannelNameFilteredByDeviceType::m_inputDeviceType, "Input Device Type",
"The type of input device by which to filter input channel names.")
->Attribute(AZ::Edit::Attributes::StringList, &InputChannelNameFilteredByDeviceType::GetValidInputDeviceTypes)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &InputChannelNameFilteredByDeviceType::OnInputDeviceTypeSelected)
->DataElement(AZ::Edit::UIHandlers::ComboBox, &InputChannelNameFilteredByDeviceType::m_inputChannelName, "Input Channel Name",
"The input channel name to add to the input mapping.")
->Attribute(AZ::Edit::Attributes::StringList, &InputChannelNameFilteredByDeviceType::GetValidInputChannelNamesBySelectedDevice)
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
;
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputMapping::InputChannelNameFilteredByDeviceType::InputChannelNameFilteredByDeviceType()
{
// Try initialize the selected input device type and input channel name to something valid.
if (m_inputDeviceType.empty())
{
const AZStd::vector<AZStd::string> validInputDeviceTypes = GetValidInputDeviceTypes();
if (!validInputDeviceTypes.empty())
{
m_inputDeviceType = validInputDeviceTypes[0];
OnInputDeviceTypeSelected();
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
AZ::Crc32 InputMapping::InputChannelNameFilteredByDeviceType::OnInputDeviceTypeSelected()
{
const AZStd::vector<AZStd::string> validInputNames = GetValidInputChannelNamesBySelectedDevice();
if (!validInputNames.empty())
{
m_inputChannelName = validInputNames[0];
}
return AZ::Edit::PropertyRefreshLevels::AttributesAndValues;
}
////////////////////////////////////////////////////////////////////////////////////////////////
AZStd::string InputMapping::InputChannelNameFilteredByDeviceType::GetNameLabelOverride() const
{
return m_inputChannelName.empty() ? "<Select>" : m_inputChannelName;
}
////////////////////////////////////////////////////////////////////////////////////////////////
AZStd::vector<AZStd::string> InputMapping::InputChannelNameFilteredByDeviceType::GetValidInputDeviceTypes() const
{
AZStd::set<AZStd::string> uniqueInputDeviceTypes;
InputDeviceRequests::InputDeviceByIdMap availableInputDevicesById;
InputDeviceRequestBus::Broadcast(&InputDeviceRequests::GetInputDevicesById,
availableInputDevicesById);
for (const auto& inputDeviceById : availableInputDevicesById)
{
// Filter out input contexts so that mappings can only be created from 'raw' input events.
if (!azrtti_istypeof<InputContext*>(inputDeviceById.second))
{
uniqueInputDeviceTypes.insert(inputDeviceById.first.GetName());
}
}
return AZStd::vector<AZStd::string>(uniqueInputDeviceTypes.begin(), uniqueInputDeviceTypes.end());
}
////////////////////////////////////////////////////////////////////////////////////////////////
AZStd::vector<AZStd::string> InputMapping::InputChannelNameFilteredByDeviceType::GetValidInputChannelNamesBySelectedDevice() const
{
AZStd::vector<AZStd::string> validInputChannelNames;
InputDeviceId selectedDeviceId(m_inputDeviceType.c_str());
InputDeviceRequests::InputChannelIdSet validInputChannelIds;
InputDeviceRequestBus::Event(selectedDeviceId,
&InputDeviceRequests::GetInputChannelIds,
validInputChannelIds);
for (const InputChannelId& inputChannelId : validInputChannelIds)
{
validInputChannelNames.push_back(inputChannelId.GetName());
}
AZStd::sort(validInputChannelNames.begin(), validInputChannelNames.end());
return validInputChannelNames;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputMapping::ConfigBase::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<InputMapping::ConfigBase>()
->Version(0)
->Field("Output Input Channel Name", &InputMapping::ConfigBase::m_outputInputChannelName)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<InputMapping::ConfigBase>("Input Mapping: Base",
"Maps multiple different input sources to a single output input channel.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &ConfigBase::m_outputInputChannelName, "Output Input Channel Name",
"The unique input channel name (ie. input event name) output by the input mapping.\n"
"This will be truncated if its length exceeds that of InputChannelId::MAX_NAME_LENGTH = 64")
->Attribute(AZ::Edit::Attributes::Max, InputChannelId::MAX_NAME_LENGTH)
;
}
}
InputChannelNameFilteredByDeviceType::Reflect(context);
}
////////////////////////////////////////////////////////////////////////////////////////////////
AZStd::shared_ptr<InputMapping> InputMapping::ConfigBase::CreateInputMappingAndAddToContext(InputContext& inputContext) const
{
AZStd::shared_ptr<InputMapping> inputMapping = CreateInputMapping(inputContext);
if (inputMapping)
{
inputContext.AddInputMapping(inputMapping);
}
return inputMapping;
}
////////////////////////////////////////////////////////////////////////////////////////////////
AZStd::string InputMapping::ConfigBase::GetNameLabelOverride() const
{
return m_outputInputChannelName.empty() ? "<Input Mapping>" : m_outputInputChannelName;
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputMapping::InputMapping(const InputChannelId& inputChannelId, const InputContext& inputContext)
: InputChannel(inputChannelId, inputContext)
@@ -12,6 +12,7 @@
#include <AzFramework/Input/Devices/InputDevice.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
@@ -26,6 +27,111 @@ namespace AzFramework
class InputMapping : public InputChannel
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
//! Convenience class that allows for selection of an input channel name filtered by device.
struct InputChannelNameFilteredByDeviceType
{
public:
////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(InputChannelNameFilteredByDeviceType, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////
// Type Info
AZ_RTTI(InputChannelNameFilteredByDeviceType, "{68CC4865-1C0E-4E2E-BDAE-AF42EA30DBE8}");
////////////////////////////////////////////////////////////////////////////////////////
// Reflection
static void Reflect(AZ::ReflectContext* context);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
InputChannelNameFilteredByDeviceType();
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
virtual ~InputChannelNameFilteredByDeviceType() = default;
////////////////////////////////////////////////////////////////////////////////////////////
//! Get the currently selected input device type.
//! \return Currently selected input device type.
inline const AZStd::string& GetInputDeviceType() const { return m_inputDeviceType; }
////////////////////////////////////////////////////////////////////////////////////////////
//! Get the currently selected input channel name.
//! \return Currently selected input channel name.
inline const AZStd::string& GetInputChannelName() const { return m_inputChannelName; }
protected:
////////////////////////////////////////////////////////////////////////////////////////////
//! Called when an input device type is selected.
//! \return The AZ::Edit::PropertyRefreshLevels to apply to the property tree view.
virtual AZ::Crc32 OnInputDeviceTypeSelected();
////////////////////////////////////////////////////////////////////////////////////////////
//! Get the name label override to display.
//! \return Name label override to display.
virtual AZStd::string GetNameLabelOverride() const;
////////////////////////////////////////////////////////////////////////////////////////////
//! Get the valid input device types for this input mapping.
//! \return Valid input device types for this input mapping.
virtual AZStd::vector<AZStd::string> GetValidInputDeviceTypes() const;
////////////////////////////////////////////////////////////////////////////////////////////
//! Get the valid input channel names for this input mapping given the selected device type.
//! \return Valid input channel names for this input mapping given the selected device type.
virtual AZStd::vector<AZStd::string> GetValidInputChannelNamesBySelectedDevice() const;
private:
////////////////////////////////////////////////////////////////////////////////////////////
// Variables
AZStd::string m_inputDeviceType; //!< The currently selected input device type.
AZStd::string m_inputChannelName; //!< The currently selected input channel name.
};
////////////////////////////////////////////////////////////////////////////////////////////
//! Base class for input mapping configuration values that are exposed to the editor.
class ConfigBase
{
public:
////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(ConfigBase, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////
// Type Info
AZ_RTTI(ConfigBase, "{72EBBBCC-D57E-4085-AFD9-4910506010B6}");
////////////////////////////////////////////////////////////////////////////////////////
// Reflection
static void Reflect(AZ::ReflectContext* context);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
virtual ~ConfigBase() = default;
////////////////////////////////////////////////////////////////////////////////////////
//! Create an input mapping and add it to the input context.
//! \param[in] inputContext Input context that the input mapping will be added to.
AZStd::shared_ptr<InputMapping> CreateInputMappingAndAddToContext(InputContext& inputContext) const;
////////////////////////////////////////////////////////////////////////////////////////
//! Override to create the relevant input mapping.
//! \param[in] inputContext Input context that owns the input mapping.
virtual AZStd::shared_ptr<InputMapping> CreateInputMapping(const InputContext& inputContext) const = 0;
////////////////////////////////////////////////////////////////////////////////////////////
//! Get the name label override to display.
//! \return Name label override to display.
virtual AZStd::string GetNameLabelOverride() const;
protected:
////////////////////////////////////////////////////////////////////////////////////////
//! The unique input channel name (event) output by the input mapping.
AZStd::string m_outputInputChannelName;
};
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(InputMapping, AZ::SystemAllocator, 0);
@@ -8,9 +8,72 @@
#include <AzFramework/Input/Mappings/InputMappingAnd.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/smart_ptr/make_shared.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
void InputMappingAnd::Config::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<InputMappingAnd::Config, InputMapping::ConfigBase>()
->Version(0)
->Field("Source Input Channel Names", &Config::m_sourceInputChannelNames)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<InputMappingAnd::Config>("Input Mapping: And",
"Maps multiple different input sources to a single output using 'AND' logic.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
->Attribute(AZ::Edit::Attributes::NameLabelOverride, &InputMappingAnd::Config::GetNameLabelOverride)
->DataElement(AZ::Edit::UIHandlers::Default, &Config::m_sourceInputChannelNames, "Source Input Channel Names",
"The source input channel names that will be mapped to the output input channel name.")
;
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
AZStd::shared_ptr<InputMapping> InputMappingAnd::Config::CreateInputMapping(const InputContext& inputContext) const
{
if (m_outputInputChannelName.empty())
{
AZ_Error("InputMappingAnd::Config", false, "Cannot create input mapping with empty name.");
return nullptr;
}
if (InputChannelRequests::FindInputChannel(InputChannelId(m_outputInputChannelName.c_str())))
{
AZ_Error("InputMappingAnd::Config", false,
"Cannot create input mapping '%s' with non-unique name.", m_outputInputChannelName.c_str());
return nullptr;
}
if (m_sourceInputChannelNames.empty())
{
AZ_Error("InputMappingAnd::Config", false,
"Cannot create input mapping '%s' with no source inputs.", m_outputInputChannelName.c_str());
return nullptr;
}
const InputChannelId outputInputChannelId(m_outputInputChannelName.c_str());
AZStd::shared_ptr<InputMappingAnd> inputMapping = AZStd::make_shared<InputMappingAnd>(outputInputChannelId,
inputContext);
for (const InputChannelNameFilteredByDeviceType& sourceInputChannelName : m_sourceInputChannelNames)
{
const InputChannelId sourceInputChannelId(sourceInputChannelName.GetInputChannelName().c_str());
inputMapping->AddSourceInput(sourceInputChannelId);
}
return inputMapping;
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputMappingAnd::InputMappingAnd(const InputChannelId& inputChannelId, const InputContext& inputContext)
: InputMapping(inputChannelId, inputContext)
@@ -19,6 +19,38 @@ namespace AzFramework
class InputMappingAnd : public InputMapping
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
//! The input mapping configuration values that are exposed to the editor.
class Config : public InputMapping::ConfigBase
{
public:
////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(Config, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////
// Type Info
AZ_RTTI(Config, "{54E972F3-0477-4E2E-93F5-4E06ED755DF6}", InputMapping::ConfigBase);
////////////////////////////////////////////////////////////////////////////////////////
// Reflection
static void Reflect(AZ::ReflectContext* context);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
~Config() override = default;
protected:
////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputMapping::Type::CreateInputMapping
AZStd::shared_ptr<InputMapping> CreateInputMapping(const InputContext& inputContext) const override;
private:
////////////////////////////////////////////////////////////////////////////////////////
//! The source input channel names that will be mapped to the output input channel name.
AZStd::vector<InputChannelNameFilteredByDeviceType> m_sourceInputChannelNames;
};
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(InputMappingAnd, AZ::SystemAllocator, 0);
@@ -8,9 +8,72 @@
#include <AzFramework/Input/Mappings/InputMappingOr.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/smart_ptr/make_shared.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
void InputMappingOr::Config::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<InputMappingOr::Config, InputMapping::ConfigBase>()
->Version(0)
->Field("Source Input Channel Names", &Config::m_sourceInputChannelNames)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<InputMappingOr::Config>("Input Mapping: Or",
"Maps multiple different input sources to a single output using 'OR' logic.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
->Attribute(AZ::Edit::Attributes::NameLabelOverride, &InputMappingOr::Config::GetNameLabelOverride)
->DataElement(AZ::Edit::UIHandlers::Default, &Config::m_sourceInputChannelNames, "Source Input Channel Names",
"The source input channel names that will be mapped to the output input channel name.")
;
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
AZStd::shared_ptr<InputMapping> InputMappingOr::Config::CreateInputMapping(const InputContext& inputContext) const
{
if (m_outputInputChannelName.empty())
{
AZ_Error("InputMappingOr::Config", false, "Cannot create input mapping with empty name.");
return nullptr;
}
if (InputChannelRequests::FindInputChannel(InputChannelId(m_outputInputChannelName.c_str())))
{
AZ_Error("InputMappingOr::Config", false,
"Cannot create input mapping '%s' with non-unique name.", m_outputInputChannelName.c_str());
return nullptr;
}
if (m_sourceInputChannelNames.empty())
{
AZ_Error("InputMappingOr::Config", false,
"Cannot create input mapping '%s' with no source inputs.", m_outputInputChannelName.c_str());
return nullptr;
}
const InputChannelId outputInputChannelId(m_outputInputChannelName.c_str());
AZStd::shared_ptr<InputMappingOr> inputMapping = AZStd::make_shared<InputMappingOr>(outputInputChannelId,
inputContext);
for (const InputChannelNameFilteredByDeviceType& sourceInputChannelName : m_sourceInputChannelNames)
{
const InputChannelId sourceInputChannelId(sourceInputChannelName.GetInputChannelName().c_str());
inputMapping->AddSourceInput(sourceInputChannelId);
}
return inputMapping;
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputMappingOr::InputMappingOr(const InputChannelId& inputChannelId, const InputContext& inputContext)
: InputMapping(inputChannelId, inputContext)
@@ -19,6 +19,38 @@ namespace AzFramework
class InputMappingOr : public InputMapping
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
//! The input mapping configuration values that are exposed to the editor.
class Config : public InputMapping::ConfigBase
{
public:
////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(Config, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////
// Type Info
AZ_RTTI(Config, "{428AFDD4-D353-494A-BBAC-37E00F82CFFD}", InputMapping::ConfigBase);
////////////////////////////////////////////////////////////////////////////////////////
// Reflection
static void Reflect(AZ::ReflectContext* context);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
~Config() override = default;
protected:
////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputMapping::Type::CreateInputMapping
AZStd::shared_ptr<InputMapping> CreateInputMapping(const InputContext& inputContext) const override;
private:
////////////////////////////////////////////////////////////////////////////////////////
//! The source input channel names that will be mapped to the output input channel name.
AZStd::vector<InputChannelNameFilteredByDeviceType> m_sourceInputChannelNames;
};
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(InputMappingOr, AZ::SystemAllocator, 0);
@@ -343,6 +343,8 @@ set(FILES
Input/Channels/InputChannelQuaternion.h
Input/Contexts/InputContext.cpp
Input/Contexts/InputContext.h
Input/Contexts/InputContextComponent.cpp
Input/Contexts/InputContextComponent.h
Input/Devices/InputDevice.cpp
Input/Devices/InputDevice.h
Input/Devices/InputDeviceId.cpp