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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user