Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,194 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "StartingPointInput_precompiled.h"
#include "InputConfigurationComponent.h"
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Serialization/ObjectStream.h>
#include <AzCore/Serialization/DataPatch.h>
#include <AzCore/IO/ByteContainerStream.h>
#include <AzCore/Serialization/Utils.h>
namespace StartingPointInput
{
static AZ::s32 Uint32ToInt32(const AZ::u32& value)
{
return static_cast<AZ::s32>(value);
};
InputConfigurationComponent::~InputConfigurationComponent()
{
m_inputEventBindings.Cleanup();
}
void InputConfigurationComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("InputConfigurationService"));
}
void InputConfigurationComponent::Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<InputConfigurationComponent, AZ::Component>()
->Version(4)
->Field("Input Event Bindings", &InputConfigurationComponent::m_inputEventBindingsAsset)
->Field("Local Player Index", &InputConfigurationComponent::m_localPlayerIndex)
->NameChange(2, 3, "Local User Id", "Local Player Index")
->TypeChange("Local Player Index", 3, 4, AZStd::function<AZ::s32(const AZ::u32&)>(&Uint32ToInt32))
;
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<InputConfigurationComponent>("Input",
"The Input component allows an entity to bind a set of inputs to an event by referencing a .inputbindings file")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Gameplay")
->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/InputConfig.svg")
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/InputConfig.png")
->Attribute(AZ::Edit::Attributes::PrimaryAssetType, AZ::AzTypeInfo<InputEventBindingsAsset>::Uuid())
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game"))
->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://docs.aws.amazon.com/lumberyard/latest/userguide/component-input.html")
->DataElement(AZ::Edit::UIHandlers::Default, &InputConfigurationComponent::m_inputEventBindingsAsset, "Input to event bindings",
"Asset containing input to event binding information.")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, true)
->Attribute("BrowseIcon", ":/stylesheet/img/UI20/browse-edit-select-files.svg")
->Attribute("EditButton", "")
->Attribute("EditDescription", "Open in Input Bindings Editor")
->DataElement(AZ::Edit::UIHandlers::SpinBox, &InputConfigurationComponent::m_localPlayerIndex, "Local player index",
"The player index that this component 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)
;
}
}
}
void InputConfigurationComponent::Init()
{
// The player index that this component will receive input from (0 based, -1 means all controllers)
// can be set from data, but will only work on platforms such as PC where the local user id corresponds
// to the local player index. For other platforms, SetLocalUserId must be called at runtime with the id
// of a logged in user, which will overwrite anything set here from data.
if (m_localPlayerIndex == -1)
{
m_localUserId = AzFramework::LocalUserIdAny;
}
else
{
// we have to cast to u32 here even if LocalUserId is not a u32 type because some platforms use
// an aggregate type for m_localUserId and only have the pertinent constructors/operators for u32
m_localUserId = aznumeric_cast<AZ::u32>(m_localPlayerIndex);
}
}
void InputConfigurationComponent::Activate()
{
InputConfigurationComponentRequestBus::Handler::BusConnect(GetEntityId());
AZ::Data::AssetBus::Handler::BusConnect(m_inputEventBindingsAsset.GetId());
}
void InputConfigurationComponent::Deactivate()
{
InputConfigurationComponentRequestBus::Handler::BusDisconnect();
AZ::Data::AssetBus::Handler::BusDisconnect();
if (m_localUserId != AzFramework::LocalUserIdNone)
{
m_inputEventBindings.Deactivate(m_localUserId);
}
}
void InputConfigurationComponent::SetLocalUserId(AzFramework::LocalUserId localUserId)
{
if (m_localUserId != localUserId)
{
if (m_localUserId != AzFramework::LocalUserIdNone)
{
m_inputEventBindings.Deactivate(m_localUserId);
}
m_localUserId = localUserId;
if (m_localUserId != AzFramework::LocalUserIdNone)
{
m_inputEventBindings.Activate(m_localUserId);
}
}
}
void InputConfigurationComponent::OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
if (asset.GetId() == m_inputEventBindingsAsset.GetId())
{
// before we reload and reapply, disable any existing old ones, or else they'd double up
// and you'd end up with both being active.
if (m_localUserId != AzFramework::LocalUserIdNone)
{
m_inputEventBindings.Deactivate(m_localUserId);
}
m_inputEventBindingsAsset = asset;
if (asset.IsReady())
{
OnAssetReady(asset);
}
}
}
void InputConfigurationComponent::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
if (InputEventBindingsAsset* inputAsset = asset.GetAs<InputEventBindingsAsset>())
{
// the input asset actually requires us to do additional cloning and copying of the data
// mainly because we retrieve the player profile data and apply it as a bindings patch on top of the data.
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
if (serializeContext)
{
// we swap with a fresh empty one here just to make sure that if this happens repeatedly, we don't have anything left over.
InputEventBindings freshBindings;
serializeContext->CloneObjectInplace<InputEventBindings>(freshBindings, &inputAsset->m_bindings);
m_inputEventBindings.Cleanup();
m_inputEventBindings.Swap(&freshBindings);
}
m_isAssetPrepared = true;
ActivateBindingsIfAppropriate();
}
else
{
AZ_Error("Input Configuration", false, "Input bindings asset is not the correct type.");
}
}
void InputConfigurationComponent::ActivateBindingsIfAppropriate()
{
if (m_isAssetPrepared)
{
if (m_localUserId != AzFramework::LocalUserIdNone)
{
m_inputEventBindings.Activate(m_localUserId);
}
}
}
void InputConfigurationComponent::EditorSetPrimaryAsset(const AZ::Data::AssetId& assetId)
{
m_inputEventBindingsAsset.Create(assetId);
}
}
@@ -0,0 +1,84 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzFramework/Components/EditorEntityEvents.h>
#include <StartingPointInput/InputEventRequestBus.h>
#include <InputEventBindings.h>
namespace AZ
{
class SerializeContext;
}
namespace StartingPointInput
{
class InputConfigurationComponent
: public AZ::Component
, private AZ::Data::AssetBus::Handler
, private InputConfigurationComponentRequestBus::Handler
, public AzFramework::EditorEntityEvents
{
public:
AZ_COMPONENT(InputConfigurationComponent, "{3106EE2A-4816-433E-B855-D17A6484D5EC}", AzFramework::EditorEntityEvents);
virtual ~InputConfigurationComponent();
InputConfigurationComponent() = default;
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void Reflect(AZ::ReflectContext* reflection);
//////////////////////////////////////////////////////////////////////////
// AZ::Component
void Init() override;
void Activate() override;
void Deactivate() override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/// AzFramework::EditorEntityEvents
void EditorSetPrimaryAsset(const AZ::Data::AssetId& assetId) override;
//////////////////////////////////////////////////////////////////////////
private:
InputConfigurationComponent(const InputConfigurationComponent&) = delete;
//////////////////////////////////////////////////////////////////////////
// AZ::InputConfigurationComponentRequestBus::Handler
void SetLocalUserId(AzFramework::LocalUserId localUserId) override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/// AZ::Data::AssetBus::Handler
void OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
void OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
//////////////////////////////////////////////////////////////////////////
void ActivateBindingsIfAppropriate();
//////////////////////////////////////////////////////////////////////////
// Reflected Data
InputEventBindings m_inputEventBindings;
AZStd::vector<AZStd::string> m_inputContexts;
AZ::Data::Asset<InputEventBindingsAsset> m_inputEventBindingsAsset;
AZ::s32 m_localPlayerIndex = -1;
AzFramework::LocalUserId m_localUserId = AzFramework::LocalUserIdAny;
bool m_isContextActive = false;
// Unlike the definition of most assets, the input asset requires additional preparation after its loaded
// in order to actually be prepared to be used.
bool m_isAssetPrepared = false;
};
} // namespace Input
@@ -0,0 +1,128 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/RTTI/TypeInfo.h>
#include <AzFramework/Asset/SimpleAsset.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Memory/Memory.h>
#include "InputEventGroup.h"
namespace StartingPointInput
{
/*!
* InputEventBinding asset type configuration.
* Reflect as: AzFramework::SimpleAssetReference<InputEventBindings>
* This base class holds a list of InputEventGroups which organizes raw input processors by the
* gameplay events they generate, Ex. Held(eKI_Space) -> "Jump"
*/
class InputEventBindings
{
public:
virtual ~InputEventBindings()
{
}
AZ_CLASS_ALLOCATOR(InputEventBindings, AZ::SystemAllocator, 0);
AZ_RTTI(InputEventBindings, "{14FFD4A8-AE46-4E23-B45B-6A7C4F787A91}")
static void Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<InputEventBindings>()
->Version(1)
->Field("Input Event Groups", &InputEventBindings::m_inputEventGroups);
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<InputEventBindings>("Input Event Bindings", "Holds InputEventBindings")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->DataElement(0, &InputEventBindings::m_inputEventGroups, "Input Event Groups", "Input Event Groups");
}
}
}
void Activate(const AzFramework::LocalUserId& localUserId)
{
for (InputEventGroup& inputEventGroup : m_inputEventGroups)
{
inputEventGroup.Activate(localUserId);
}
}
void Deactivate(const AzFramework::LocalUserId& localUserId)
{
for (InputEventGroup& inputEventGroup : m_inputEventGroups)
{
inputEventGroup.Deactivate(localUserId);
}
}
void Cleanup()
{
for (InputEventGroup& inputEventGroup : m_inputEventGroups)
{
inputEventGroup.Cleanup();
}
}
void Swap(InputEventBindings* other)
{
m_inputEventGroups.swap(other->m_inputEventGroups);
}
protected:
AZStd::vector<InputEventGroup> m_inputEventGroups;
};
class InputEventBindingsAsset
: public AZ::Data::AssetData
{
public:
AZ_CLASS_ALLOCATOR(InputEventBindingsAsset, AZ::SystemAllocator, 0);
AZ_RTTI(InputEventBindingsAsset, "{25971C7A-26E2-4D08-A146-2EFCC1C36B0C}", AZ::Data::AssetData);
InputEventBindingsAsset() = default;
virtual ~InputEventBindingsAsset()
{
m_bindings.Cleanup();
}
InputEventBindings m_bindings;
static void Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<InputEventBindingsAsset>()
->Attribute(AZ::Edit::Attributes::EnableForAssetEditor, true)
->Field("Bindings", &InputEventBindingsAsset::m_bindings)
;
AZ::EditContext* edit = serialize->GetEditContext();
if (edit)
{
edit->Class<InputEventBindingsAsset>("Input to Event Bindings Asset", "")
->DataElement(0, &InputEventBindingsAsset::m_bindings, "Bindings", "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ_CRC("PropertyVisibility_ShowChildrenOnly", 0xef428f20))
;
}
}
}
private:
InputEventBindingsAsset(const InputEventBindingsAsset&) = delete;
};
} // namespace Input
@@ -0,0 +1,109 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/RTTI/RTTI.h>
#include <InputEventMap.h>
namespace StartingPointInput
{
//////////////////////////////////////////////////////////////////////////
/// This class holds all of the raw input handlers that generate events.
//////////////////////////////////////////////////////////////////////////
class InputEventGroup
{
public:
AZ_RTTI(InputEventGroup, "{25143B7E-2FEC-4CC5-92FE-270B67E79734}");
virtual ~InputEventGroup() = default;
static void Reflect(AZ::ReflectContext* reflection)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection))
{
serializeContext->Class<InputEventGroup>()
->Version(1)
->Field("Event Name", &InputEventGroup::m_eventName)
->Field("Event Generators", &InputEventGroup::m_inputHandlers)
->Field("Exclude From Release", &InputEventGroup::m_excludeFromRelease);
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<InputEventGroup>("InputEventGroup", "Groups input bindings by the event they generate")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::NameLabelOverride, &InputEventGroup::GetEditorText)
->DataElement(0, &InputEventGroup::m_eventName, "Event Name", "The event generated by the collection of Input Bindings")
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshAttributesAndValues"))
->DataElement(0, &InputEventGroup::m_inputHandlers, "Event Generators", "Handlers that generate named events")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &InputEventGroup::m_excludeFromRelease, "Exclude from release", "This input binding will not activate in release builds");
}
}
}
virtual void Activate(const AzFramework::LocalUserId& localUserId)
{
#if defined(RELEASE)
if (m_excludeFromRelease)
{
return;
}
#endif // defined(RELEASE)
InputEventNotificationId busId(localUserId, m_eventName.c_str());
for (InputSubComponent* inputHandler : m_inputHandlers)
{
inputHandler->Activate(busId);
}
}
virtual void Deactivate(const AzFramework::LocalUserId& localUserId)
{
#if defined(RELEASE)
if (m_excludeFromRelease)
{
return;
}
#endif // defined(RELEASE)
InputEventNotificationId busId(localUserId, m_eventName.c_str());
for (InputSubComponent* inputHandler : m_inputHandlers)
{
inputHandler->Deactivate(busId);
}
}
// Explicitly release our array of input handlers here. There is no system that is currently cleaning up the Input objects we have in this array
// We cannot do this in the destructor because of the allocation patterns of this object in the serializer that causes us to end up releasing invalid data during serialization load
// I did not have success changing the raw pointer array of input handlers to a shared pointer of InputSubComponents.
// The most straight forward resolution for now is to be explicit about when we release this data in order to prevent large memory leaks
void Cleanup()
{
// Release the InputSubComponents opposite of how they were allocated during serialization in InstanceFactory::Create
for (InputSubComponent* inputHandler : m_inputHandlers)
{
inputHandler->~InputSubComponent();
azfree(static_cast<void*>(inputHandler), AZ::SystemAllocator);
}
m_inputHandlers.clear();
}
protected:
virtual AZStd::string GetEditorText() const
{
return m_eventName.empty() ? "<Unspecified Event>" : m_eventName;
}
AZStd::vector<InputSubComponent*> m_inputHandlers;
AZStd::string m_eventName;
bool m_excludeFromRelease = false;
};
} // namespace Input
@@ -0,0 +1,381 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "StartingPointInput_precompiled.h"
#include "InputEventMap.h"
#include <AzCore/EBus/EBus.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/std/containers/set.h>
#include <AzCore/std/sort.h>
#include <AzFramework/Input/Buses/Requests/InputDeviceRequestBus.h>
#include <AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.h>
using namespace AzFramework;
namespace StartingPointInput
{
void InputEventMap::Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<InputEventMap>()
->Version(2)
->Field("Input Device Type", &InputEventMap::m_inputDeviceType)
->Field("Input Name", &InputEventMap::m_inputName)
->Field("Event Value Multiplier", &InputEventMap::m_eventValueMultiplier)
->Field("Dead Zone", &InputEventMap::m_deadZone);
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<InputEventMap>("InputEventMap", "Maps raw input to a game specific input event")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::NameLabelOverride, &InputEventMap::GetEditorText)
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
->DataElement(AZ::Edit::UIHandlers::ComboBox, &InputEventMap::m_inputDeviceType, "Input Device Type", "The type of input device, ex keyboard")
->Attribute(AZ::Edit::Attributes::ChangeNotify, &InputEventMap::OnDeviceSelected)
->Attribute(AZ::Edit::Attributes::StringList, &InputEventMap::GetInputDeviceTypes)
->DataElement(AZ::Edit::UIHandlers::ComboBox, &InputEventMap::m_inputName, "Input Name", "The name of the input you want to hold ex. space")
->Attribute(AZ::Edit::Attributes::StringList, &InputEventMap::GetInputNamesBySelectedDevice)
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
->DataElement(0, &InputEventMap::m_eventValueMultiplier, "Event value multiplier", "When the event fires, the value will be scaled by this multiplier")
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
->DataElement(0, &InputEventMap::m_deadZone, "Dead zone", "An event will only be sent out if the value is above this threshold")
->Attribute(AZ::Edit::Attributes::Min, 0.0f);
}
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(reflection))
{
behaviorContext->EBus<InputEventNotificationBus>("InputEventNotificationBus")
->Event("OnPressed", &InputEventNotificationBus::Events::OnPressed)
->Event("OnHeld", &InputEventNotificationBus::Events::OnHeld)
->Event("OnReleased", &InputEventNotificationBus::Events::OnReleased);
}
}
}
InputEventMap::InputEventMap()
{
if (m_inputDeviceType.empty())
{
auto&& deviceTypes = GetInputDeviceTypes();
if (!deviceTypes.empty())
{
m_inputDeviceType = deviceTypes[0];
OnDeviceSelected();
}
}
}
bool InputEventMap::OnInputChannelEventFiltered(const AzFramework::InputChannel& inputChannel)
{
const LocalUserId localUserIdOfEvent = static_cast<LocalUserId>(inputChannel.GetInputDevice().GetAssignedLocalUserId());
const float value = CalculateEventValue(inputChannel);
const bool isPressed = fabs(value) > m_deadZone;
if (!m_wasPressed && isPressed)
{
SendEventsInternal(value, localUserIdOfEvent, m_outgoingBusId, &InputEventNotificationBus::Events::OnPressed);
}
else if (m_wasPressed && isPressed)
{
SendEventsInternal(value, localUserIdOfEvent, m_outgoingBusId, &InputEventNotificationBus::Events::OnHeld);
}
else if (m_wasPressed && !isPressed)
{
SendEventsInternal(value, localUserIdOfEvent, m_outgoingBusId, &InputEventNotificationBus::Events::OnReleased);
}
m_wasPressed = isPressed;
// Return false so we don't consume the event. This should perhaps be a configurable option?
return false;
}
float InputEventMap::CalculateEventValue(const AzFramework::InputChannel& inputChannel) const
{
return inputChannel.GetValue();
}
void InputEventMap::SendEventsInternal(float value, const AzFramework::LocalUserId& localUserIdOfEvent, const InputEventNotificationId busId, InputEventType eventType)
{
value *= m_eventValueMultiplier;
InputEventNotificationId localUserBusId = InputEventNotificationId(localUserIdOfEvent, busId.m_actionNameCrc);
InputEventNotificationBus::Event(localUserBusId, eventType, value);
InputEventNotificationId wildCardBusId = InputEventNotificationId(AzFramework::LocalUserIdAny, busId.m_actionNameCrc);
InputEventNotificationBus::Event(wildCardBusId, eventType, value);
}
void InputEventMap::Activate(const InputEventNotificationId& eventNotificationId)
{
const AzFramework::InputDevice* inputDevice = AzFramework::InputDeviceRequests::FindInputDevice(AzFramework::InputDeviceId(m_inputDeviceType.c_str()));
if (!inputDevice || !inputDevice->IsSupported())
{
// The input device that this input binding would be listening for input from
// is not supported on the current platform, so don't bother even activating.
// Please note distinction between InputDevice::IsSupported and IsConnected.
return;
}
const AZ::Crc32 channelNameFilter(m_inputName.c_str());
const AZ::Crc32 deviceNameFilter(m_inputDeviceType.c_str());
const AzFramework::LocalUserId localUserIdFilter(eventNotificationId.m_localUserId);
AZStd::shared_ptr<InputChannelEventFilterInclusionList> filter = AZStd::make_shared<InputChannelEventFilterInclusionList>(channelNameFilter, deviceNameFilter, localUserIdFilter);
InputChannelEventListener::SetFilter(filter);
InputChannelEventListener::Connect();
m_wasPressed = false;
m_outgoingBusId = eventNotificationId;
}
void InputEventMap::Deactivate([[maybe_unused]] const InputEventNotificationId& eventNotificationId)
{
if (m_wasPressed)
{
InputEventNotificationBus::Event(m_outgoingBusId, &InputEventNotifications::OnReleased, 0.0f);
}
InputChannelEventListener::Disconnect();
}
AZStd::string InputEventMap::GetEditorText() const
{
return m_inputName.empty() ? "<Select input>" : m_inputName;
}
const AZStd::vector<AZStd::string> InputEventMap::GetInputDeviceTypes() const
{
AZStd::set<AZStd::string> uniqueInputDeviceTypes;
AzFramework::InputDeviceRequests::InputDeviceIdSet availableInputDeviceIds;
AzFramework::InputDeviceRequestBus::Broadcast(&AzFramework::InputDeviceRequests::GetInputDeviceIds,
availableInputDeviceIds);
for (const AzFramework::InputDeviceId& inputDeviceId : availableInputDeviceIds)
{
uniqueInputDeviceTypes.insert(inputDeviceId.GetName());
}
return AZStd::vector<AZStd::string>(uniqueInputDeviceTypes.begin(), uniqueInputDeviceTypes.end());
}
const AZStd::vector<AZStd::string> InputEventMap::GetInputNamesBySelectedDevice() const
{
AZStd::vector<AZStd::string> retval;
AzFramework::InputDeviceId selectedDeviceId(m_inputDeviceType.c_str());
AzFramework::InputDeviceRequests::InputChannelIdSet availableInputChannelIds;
AzFramework::InputDeviceRequestBus::Event(selectedDeviceId,
&AzFramework::InputDeviceRequests::GetInputChannelIds,
availableInputChannelIds);
for (const AzFramework::InputChannelId& inputChannelId : availableInputChannelIds)
{
retval.push_back(inputChannelId.GetName());
}
AZStd::sort(retval.begin(), retval.end());
return retval;
}
AZ::Crc32 InputEventMap::OnDeviceSelected()
{
auto&& inputList = GetInputNamesBySelectedDevice();
if (!inputList.empty())
{
m_inputName = inputList[0];
}
return AZ::Edit::PropertyRefreshLevels::AttributesAndValues;
}
ThumbstickInputEventMap::ThumbstickInputEventMap()
{
m_inputDeviceType = AzFramework::InputDeviceGamepad::Name;
OnDeviceSelected();
}
void ThumbstickInputEventMap::Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<ThumbstickInputEventMap, InputEventMap>()
->Version(1, nullptr)
->Field("Inner Dead Zone Radius", &ThumbstickInputEventMap::m_innerDeadZoneRadius)
->Field("Outer Dead Zone Radius", &ThumbstickInputEventMap::m_outerDeadZoneRadius)
->Field("Axis Dead Zone Value", &ThumbstickInputEventMap::m_axisDeadZoneValue)
->Field("Sensitivity Exponent", &ThumbstickInputEventMap::m_sensitivityExponent)
->Field("Output Axis", &ThumbstickInputEventMap::m_outputAxis)
;
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<ThumbstickInputEventMap>("ThumbstickInputEventMap", "Generate events from thumbstick input")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::NameLabelOverride, &ThumbstickInputEventMap::GetEditorText)
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
->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")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->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")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->DataElement(0, &ThumbstickInputEventMap::m_axisDeadZoneValue, "Axis Dead Zone Value", "The individual axis values will be normalized between this and 1.0f")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->DataElement(0, &ThumbstickInputEventMap::m_sensitivityExponent, "Sensitivity Exponent", "The sensitivity exponent to apply to the normalized thumbstick components")
->DataElement(AZ::Edit::UIHandlers::ComboBox, &ThumbstickInputEventMap::m_outputAxis, "Output Axis", "The axis value to output after peforming the dead-zone and sensitivity calculations")
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
->Attribute(AZ::Edit::Attributes::EnumValues, AZStd::vector<AZ::Edit::EnumConstant<OutputAxis>>
{
AZ::Edit::EnumConstant<OutputAxis>(OutputAxis::X, "x"),
AZ::Edit::EnumConstant<OutputAxis>(OutputAxis::Y, "y")
})
;
}
}
}
AZStd::string ThumbstickInputEventMap::GetEditorText() const
{
return m_inputName.empty() ?
"<Select input>" :
m_inputName + (m_outputAxis == OutputAxis::X ? " (x-axis)" : " (y-axis)");
}
const AZStd::vector<AZStd::string> ThumbstickInputEventMap::GetInputDeviceTypes() const
{
// Gamepads are currently the only device type that support thumbstick input.
// We could (should) be more robust here by iterating over all input devices,
// looking for any with associated input channels of type InputChannelAxis2D.
AZStd::vector<AZStd::string> retval;
retval.push_back(AzFramework::InputDeviceGamepad::Name);
return retval;
}
const AZStd::vector<AZStd::string> ThumbstickInputEventMap::GetInputNamesBySelectedDevice() const
{
// Gamepads are currently the only device type that support thumbstick input.
// We could (should) be more robust here by iterating over all input devices,
// looking for any with associated input channels of type InputChannelAxis2D.
AZStd::vector<AZStd::string> retval;
retval.push_back(AzFramework::InputDeviceGamepad::ThumbStickAxis2D::L.GetName());
retval.push_back(AzFramework::InputDeviceGamepad::ThumbStickAxis2D::R.GetName());
return retval;
}
bool ThumbstickInputEventMap::OnInputChannelEventFiltered(const AzFramework::InputChannel& inputChannel)
{
// Because we are sending all thumbstick events regardless of if they are inside the dead-zone
// (see InputChannelAxis2D::ProcessRawInputEvent)
// ThumbstickInputEventMap components can effectively cancel themselves out if they happen to be setup
// to receive input from a local user id that is signed into multiple controllers at the same
// time. If the controller not being used is updated last, the (~0, ~0) events it sends every
// frame cause the base InputEventMap::OnInputChannelEventFiltered function to determine that we need
// to send an InputEventNotificationBus::Events::OnReleased event because m_wasPressed is set
// to true by the other controller that is actually in use (and that is being updated first).
//
// To combat this, anytime we enter the m_wasPressed == true state we'll store a reference to
// the input device id that sent the event (see below). Each time we receive an event we will
// then check whether it's originating from the same input device id, and if not we will just
// ignore it. Please note that while taking the address of the device id is a little sketchy,
// we can do this because it's lifecycle is guaranteed to be longer than that of this object
// UNLESS we ever start calling InputSystemComponent::RecreateEnabledInputDevices somewhere.
//
// Now in this case, the old InputDevice that owns the InputChannelId will be destroyed, but
// not before the InputChannels it owns are destroyed first meaning InputChannel::ResetState
// will be called, the internal state of the input channels will be reset, and an event will
// be broadcast that will ultimately result in m_wasPressed being set to false and therefore
// m_wasLastPressedByInputDeviceId being set to nullptr below instead of becoming a dangling
// pointer. I definitely don't like this much, as it is risky behavior entirely dependent on
// the internal workings of the AzFramework input system, but it's the fastest way to perform
// this additional check. The alternative is to store the last pressed InputDeviceId by value,
// but this would involve a (slightly) more expensive check (see InputDeviceId::operator==),
// along with a string copy each time we set/reset the value (see InputDeviceId::operator=).
//
// At some point it may be worth looking at doing this check (or a safer version of it) in
// the base InputEventMap::OnInputChannelEventFiltered function, because the 'one user logged into
// multiple controllers' situation could conceivably cause strange behaviour for all types
// of input bindings (albeit only if the user were to actively use both controllers at the
// same time, in which case who can even really say what the correct behaviour should be?).
// But that would be a far riskier change, and because it's only a problem for thumb-stick
// input we're sending even when the controller is completely idle this fix will do for now.
const InputDeviceId* inputDeviceId = &(inputChannel.GetInputDevice().GetInputDeviceId());
if (m_wasLastPressedByInputDeviceId && m_wasLastPressedByInputDeviceId != inputDeviceId)
{
return false;
}
const bool shouldBeConsumed = InputEventMap::OnInputChannelEventFiltered(inputChannel);
m_wasLastPressedByInputDeviceId = m_wasPressed ? inputDeviceId : nullptr;
return shouldBeConsumed;
}
float ThumbstickInputEventMap::CalculateEventValue(const AzFramework::InputChannel& inputChannel) const
{
const AzFramework::InputChannelAxis2D::AxisData2D* axisData2D = inputChannel.GetCustomData<AzFramework::InputChannelAxis2D::AxisData2D>();
if (axisData2D == nullptr)
{
AZ_Warning("ThumbstickInputEventMap", false, "InputChannel with id '%s' has no axis data 2D", inputChannel.GetInputChannelId().GetName());
return 0.0f;
}
const AZ::Vector2 outputValues = ApplyDeadZonesAndSensitivity(axisData2D->m_preDeadZoneValues,
m_innerDeadZoneRadius,
m_outerDeadZoneRadius,
m_axisDeadZoneValue,
m_sensitivityExponent);
// Ideally we would return both values here and allow each to be mapped to a different output
// event, but that would require a greater re-factor of the StartingPointInput Gem, and there
// is nothing preventing anyone from setting up one ThumbstickInputEventMap component for each
// axis so it would only be a simplification/optimization.
const float axisValueToReturn = (m_outputAxis == OutputAxis::X) ? outputValues.GetX() : outputValues.GetY();
return axisValueToReturn;
}
AZ::Vector2 ThumbstickInputEventMap::ApplyDeadZonesAndSensitivity(const AZ::Vector2& inputValues, float innerDeadZone, float outerDeadZone, float axisDeadZone, float sensitivityExponent)
{
static const AZ::Vector2 zeroVector = AZ::Vector2::CreateZero();
const AZ::Vector2 rawAbsValues(fabsf(inputValues.GetX()), fabsf(inputValues.GetY()));
const float rawLength = rawAbsValues.GetLength();
if (rawLength == 0.0f)
{
return zeroVector;
}
// Apply the circular dead zones
const AZ::Vector2 normalizedValues = rawAbsValues / rawLength;
const float postCircularDeadZoneLength = AZ::GetClamp((rawLength - innerDeadZone) / (outerDeadZone - innerDeadZone), 0.0f, 1.0f);
AZ::Vector2 absValues = normalizedValues * postCircularDeadZoneLength;
// Apply the per-axis dead zone
const AZ::Vector2 absAxisValues = zeroVector.GetMax(rawAbsValues - AZ::Vector2(axisDeadZone, axisDeadZone)) / (outerDeadZone - axisDeadZone);
// Merge the circular and per-axis dead zones. The resulting values are the smallest ones (dead zone takes priority). And restore the components sign.
const AZ::Vector2 signValues(AZ::GetSign(inputValues.GetX()), AZ::GetSign(inputValues.GetY()));
AZ::Vector2 values = absValues.GetMin(absAxisValues) * signValues;
// Rescale the vector using the post circular dead zone length, which is the real stick vector length,
// to avoid any jump in values when the stick is fully pushed along an axis and slowly getting out of the axis dead zone
// Additionally, apply the sensitivity curve to the final stick vector length
const float postAxisDeadZoneLength = values.GetLength();
if (postAxisDeadZoneLength > 0.0f)
{
values /= postAxisDeadZoneLength;
const float postSensitivityLength = powf(postCircularDeadZoneLength, sensitivityExponent);
values *= postSensitivityLength;
}
return values;
}
} // namespace Input
@@ -0,0 +1,134 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/Component/TickBus.h>
#include <AzFramework/Input/Buses/Notifications/InputDeviceNotificationBus.h>
#include <AzFramework/Input/Events/InputChannelEventListener.h>
#include <StartingPointInput/InputEventNotificationBus.h>
namespace AZ
{
class ReflectContext;
}
namespace StartingPointInput
{
//////////////////////////////////////////////////////////////////////////
/// Classes that inherit from this one will share the life-cycle API's
/// with components. Components that contain the subclasses are expected
/// to call these methods in their Init/Activate/Deactivate methods
//////////////////////////////////////////////////////////////////////////
class InputSubComponent
{
public:
AZ_RTTI(InputSubComponent, "{3D0F14F8-AE29-4ECC-BC88-26B8F8168398}");
virtual ~InputSubComponent() = default;
//////////////////////////////////////////////////////////////////////////
/// InputSubComponents will share the life-cycle API's of components.
/// Any Component that contains an InputSubComponent is expected to call
/// these methods in their Activate/Deactivate methods
virtual void Activate(const InputEventNotificationId& channel) = 0;
virtual void Deactivate(const InputEventNotificationId& channel) = 0;
};
//////////////////////////////////////////////////////////////////////////
/// Maps raw input from any raw input source and outputs Pressed, Held, and Released input events
class InputEventMap
: public InputSubComponent
, protected AzFramework::InputChannelEventListener
{
public:
InputEventMap();
~InputEventMap() override = default;
AZ_RTTI(InputEventMap, "{A14EA0A3-F053-469D-840E-A70002F51384}", InputSubComponent);
static void Reflect(AZ::ReflectContext* reflection);
//////////////////////////////////////////////////////////////////////////
// InputSubComponent
void Activate(const InputEventNotificationId& eventNotificationId) override;
void Deactivate(const InputEventNotificationId& eventNotificationId) override;
protected:
AZStd::string GetEditorText() const;
virtual const AZStd::vector<AZStd::string> GetInputDeviceTypes() const;
virtual const AZStd::vector<AZStd::string> GetInputNamesBySelectedDevice() const;
AZ::Crc32 OnDeviceSelected();
//////////////////////////////////////////////////////////////////////////
// AzFramework::InputChannelEventListener
bool OnInputChannelEventFiltered(const AzFramework::InputChannel& inputChannel) override;
using InputEventType = void(InputEventNotificationBus::Events::*)(float);
virtual float CalculateEventValue(const AzFramework::InputChannel& inputChannel) const;
void SendEventsInternal(float value, const AzFramework::LocalUserId& localUserIdOfEvent, const InputEventNotificationId busId, InputEventType eventType);
//////////////////////////////////////////////////////////////////////////
// Non Reflected Data
InputEventNotificationId m_outgoingBusId;
bool m_wasPressed = false;
//////////////////////////////////////////////////////////////////////////
// Reflected Data
float m_eventValueMultiplier = 1.f;
AZStd::string m_inputName = "";
AZStd::string m_inputDeviceType = "";
float m_deadZone = 0.0f;
};
//////////////////////////////////////////////////////////////////////////
/// ThumbstickInput handles raw input from thumbstick sources, applies any
/// custom dead-zone or sensitivity curve calculations, and then outputs
/// Pressed, Held, and Released input events for the specified axis
class ThumbstickInputEventMap : public InputEventMap
{
public:
ThumbstickInputEventMap();
~ThumbstickInputEventMap() override = default;
AZ_RTTI(ThumbstickInputEventMap, "{4881FA7C-0667-476C-8C77-4DBB6C69F646}", InputEventMap);
static void Reflect(AZ::ReflectContext* reflection);
protected:
AZStd::string GetEditorText() const;
const AZStd::vector<AZStd::string> GetInputDeviceTypes() const override;
const AZStd::vector<AZStd::string> GetInputNamesBySelectedDevice() const override;
bool OnInputChannelEventFiltered(const AzFramework::InputChannel& inputChannel) override;
float CalculateEventValue(const AzFramework::InputChannel& inputChannel) const override;
static AZ::Vector2 ApplyDeadZonesAndSensitivity(const AZ::Vector2& inputValues,
float innerDeadZone,
float outerDeadZone,
float axisDeadZone,
float sensitivityExponent);
enum class OutputAxis
{
X,
Y
};
//////////////////////////////////////////////////////////////////////////
// Non Reflected Data
const AzFramework::InputDeviceId* m_wasLastPressedByInputDeviceId = nullptr;
//////////////////////////////////////////////////////////////////////////
// Reflected Data
float m_innerDeadZoneRadius = 0.0f;
float m_outerDeadZoneRadius = 1.0f;
float m_axisDeadZoneValue = 0.0f;
float m_sensitivityExponent = 1.0f;
OutputAxis m_outputAxis = OutputAxis::X;
};
} // namespace Input
@@ -0,0 +1,57 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <StartingPointInput_precompiled.h>
#include <InputLibrary.h>
#include <AzCore/Serialization/EditContext.h>
// script canvas
#include <ScriptCanvas/Libraries/Libraries.h>
#include <InputNode.h>
namespace StartingPointInput
{
void InputLibrary::Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<InputLibrary, LibraryDefinition>()
->Version(1)
;
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<InputLibrary>("Input", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/InputConfig.png")
;
}
}
}
void InputLibrary::InitNodeRegistry(ScriptCanvas::NodeRegistry& nodeRegistry)
{
ScriptCanvas::Library::AddNodeToRegistry<InputLibrary, InputNode>(nodeRegistry);
}
AZStd::vector<AZ::ComponentDescriptor*> InputLibrary::GetComponentDescriptors()
{
return AZStd::vector<AZ::ComponentDescriptor*>({
InputNode::CreateDescriptor(),
});
}
} // namespace Input
@@ -0,0 +1,38 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
// script canvas
#include <ScriptCanvas/Libraries/Libraries.h>
namespace AZ
{
class ReflectContext;
class ComponentDescriptor;
} // namespace AZ
namespace StartingPointInput
{
//////////////////////////////////////////////////////////////////////////
/// This defines the Library for Input.
/// Add your custom nodes like this:
/// ScriptCanvas::Library::AddNodeToRegistry<InputLibrary, InputNode>(nodeRegistry);
//////////////////////////////////////////////////////////////////////////
struct InputLibrary : public ScriptCanvas::Library::LibraryDefinition
{
AZ_RTTI(InputLibrary, "{0F7E1590-C2D1-4979-9B51-21576667A514}", ScriptCanvas::Library::LibraryDefinition);
static void Reflect(AZ::ReflectContext*);
static void InitNodeRegistry(ScriptCanvas::NodeRegistry& nodeRegistry);
static AZStd::vector<AZ::ComponentDescriptor*> GetComponentDescriptors();
};
} // namespace Input
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<ScriptCanvas Include="Source/InputNode.h" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Class Name="InputNode"
QualifiedName="StartingPointInput::InputNode"
PreferredClassName="Input Handler"
Uuid="{0B0AC61B-4BBA-42BF-BDCD-DAF2D3CA41A8}"
Base="ScriptCanvas::Node"
Icon="Editor/Icons/ScriptCanvas/Bus.png"
EditAttributes="AZ::Edit::Attributes::Category@Gameplay/Input"
GraphEntryPoint="True"
GeneratePropertyFriend="True"
Description="Handle processed input events found in input binding assets">
<Out Name="Pressed" Description="Signaled when the input event begins."/>
<Out Name="Held" Description="Signaled while the input event is active."/>
<Out Name="Released" Description="Signaled when the input event ends."/>
<Property Name="Event Name"
Description="The input event name as defined in an inputbinding asset. Example 'Fireball'"
Type="AZStd::string"
IsInput="True"
IsOutput="False" />
<Property Name="Value"
Description="The current value from the input."
Type="float"
IsInput="False"
IsOutput="True" />
</Class>
</ScriptCanvas>
@@ -0,0 +1,97 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <StartingPointInput_precompiled.h>
#include <InputNode.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <ScriptCanvas/Libraries/Libraries.h>
namespace StartingPointInput
{
void InputNode::OnPostActivate()
{
if (GetExecutionType() == ScriptCanvas::ExecutionType::Runtime)
{
const ScriptCanvas::SlotId eventNameSlotId = InputNodeProperty::GetEventNameSlotId(this);
m_eventName = (*(FindDatum(eventNameSlotId)->GetAs<ScriptCanvas::Data::StringType>()));
InputEventNotificationBus::Handler::BusConnect(InputEventNotificationId(m_eventName.c_str()));
}
}
void InputNode::OnDeactivate()
{
InputEventNotificationBus::Handler::BusDisconnect();
}
void InputNode::OnInputChanged(const ScriptCanvas::Datum& input, const ScriptCanvas::SlotId& slotId)
{
// we got a new event name, we need to drop our connection to the old and connect to the new event
const ScriptCanvas::SlotId eventNameSlotId = InputNodeProperty::GetEventNameSlotId(this);
if (slotId == eventNameSlotId)
{
InputEventNotificationBus::Handler::BusDisconnect();
m_eventName = (*input.GetAs<ScriptCanvas::Data::StringType>());
InputEventNotificationBus::Handler::BusConnect(InputEventNotificationId(m_eventName.c_str()));
}
}
void InputNode::OnPressed(float value)
{
m_value = value;
const ScriptCanvas::Datum output = ScriptCanvas::Datum(m_value);
const ScriptCanvas::SlotId pressedSlotId = InputNodeProperty::GetPressedSlotId(this);
const ScriptCanvas::SlotId valueId = InputNodeProperty::GetValueSlotId(this);
if (auto* slot = GetSlot(valueId))
{
PushOutput(output, *slot);
}
SignalOutput(pressedSlotId);
}
void InputNode::OnHeld(float value)
{
m_value = value;
const ScriptCanvas::Datum output = ScriptCanvas::Datum(m_value);
const ScriptCanvas::SlotId heldSlotId = InputNodeProperty::GetHeldSlotId(this);
const ScriptCanvas::SlotId valueId = InputNodeProperty::GetValueSlotId(this);
if (auto* slot = GetSlot(valueId))
{
PushOutput(output, *slot);
}
SignalOutput(heldSlotId);
}
void InputNode::OnReleased(float value)
{
m_value = value;
const ScriptCanvas::Datum output = ScriptCanvas::Datum(m_value);
const ScriptCanvas::SlotId releasedSlotId = InputNodeProperty::GetReleasedSlotId(this);
const ScriptCanvas::SlotId valueId = InputNodeProperty::GetValueSlotId(this);
if (auto* slot = GetSlot(valueId))
{
PushOutput(output, *slot);
}
SignalOutput(releasedSlotId);
}
} // namespace StartingPointInput
@@ -0,0 +1,75 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
// code gen
#include <Source/InputNode.generated.h>
// script canvas
#include <ScriptCanvas/Core/Node.h>
#include <ScriptCanvas/Core/Graph.h>
#include <ScriptCanvas/CodeGen/CodeGen.h>
#include <StartingPointInput/InputEventNotificationBus.h>
#include <AzCore/RTTI/TypeInfo.h>
namespace StartingPointInput
{
//////////////////////////////////////////////////////////////////////////
/// Input handles raw input from any source and outputs Pressed, Held, and Released input events
class InputNode
: public ScriptCanvas::Node
, protected InputEventNotificationBus::Handler
{
ScriptCanvas_Node(InputNode,
ScriptCanvas_Node::Uuid("{0B0AC61B-4BBA-42BF-BDCD-DAF2D3CA41A8}")
ScriptCanvas_Node::Name("Input Handler")
ScriptCanvas_Node::Description("Handle processed input events found in input binding assets")
ScriptCanvas_Node::Icon("Editor/Icons/Components/InputConfig.png")
ScriptCanvas_Node::Category("Gameplay/Input")
ScriptCanvas_Node::GraphEntryPoint(true)
);
public:
InputNode() = default;
~InputNode() override = default;
InputNode(const InputNode&) = default;
InputNode& operator=(const InputNode&) = default;
// Outputs
ScriptCanvas_Out(ScriptCanvas_Out::Name("Pressed", "Signaled when the input event begins."));
ScriptCanvas_Out(ScriptCanvas_Out::Name("Held", "Signaled while the input event is active."));
ScriptCanvas_Out(ScriptCanvas_Out::Name("Released", "Signaled when the input event ends."));
// Data
ScriptCanvas_Property(AZStd::string, ScriptCanvas_Property::Name("Event Name", "The input event name as defined in an inputbinding asset. Example 'Fireball'") ScriptCanvas_Property::Input);
AZStd::string m_eventName;
ScriptCanvas_Property(float, ScriptCanvas_Property::Name("Value", "The current value from the input.") ScriptCanvas_Property::Visibility(true) ScriptCanvas_Property::Output ScriptCanvas_Property::OutputStorageSpec);
float m_value;
//////////////////////////////////////////////////////////////////////////
/// ScriptCanvas_Node
void OnInputChanged(const ScriptCanvas::Datum& input, const ScriptCanvas::SlotId& slotId) override;
protected:
//////////////////////////////////////////////////////////////////////////
/// Node
void OnPostActivate() override;
void OnDeactivate() override;
//////////////////////////////////////////////////////////////////////////
/// InputEventNotificationBus::Handler
void OnPressed(float value) override;
void OnHeld(float value) override;
void OnReleased(float value) override;
};
} // namespace StartingPointInput
@@ -0,0 +1,235 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "StartingPointInput_precompiled.h"
#include "InputConfigurationComponent.h"
#include "InputEventBindings.h"
#include "InputEventMap.h"
#include "InputLibrary.h"
#include "InputNode.h"
#include <AzCore/Module/Module.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Module/Environment.h>
#include <AzCore/Component/Component.h>
namespace StartingPointInput
{
static bool ConvertToInputEventMap(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement)
{
// Capture the old values
AZStd::string deviceType;
classElement.GetChildData(AZ::Crc32("Input Device Type"), deviceType);
AZStd::string inputName;
classElement.GetChildData(AZ::Crc32("Input Name"), inputName);
float eventValueMultiplier;
classElement.GetChildData(AZ::Crc32("Event Value Multiplier"), eventValueMultiplier);
float deadZone;
classElement.GetChildData(AZ::Crc32("Dead Zone"), deadZone);
// Convert to the new class
classElement.Convert(context, AZ::AzTypeInfo<InputEventMap>::Uuid());
// Add the old values to the new class
classElement.AddElementWithData(context, "Input Device Type", deviceType);
classElement.AddElementWithData(context, "Input Name", inputName);
classElement.AddElementWithData(context, "Event Value Multiplier", eventValueMultiplier);
classElement.AddElementWithData(context, "Dead Zone", deadZone);
return true;
}
class BehaviorInputEventNotificationBusHandler : public InputEventNotificationBus::Handler, public AZ::BehaviorEBusHandler
{
public:
AZ_EBUS_BEHAVIOR_BINDER(BehaviorInputEventNotificationBusHandler, "{8AAEEB1A-21E2-4D2E-A719-73552D41F506}", AZ::SystemAllocator,
OnPressed, OnHeld, OnReleased);
void OnPressed(float value) override
{
Call(FN_OnPressed, value);
}
void OnHeld(float value) override
{
Call(FN_OnHeld, value);
}
void OnReleased(float value) override
{
Call(FN_OnReleased, value);
}
};
void InputEventNonIntrusiveConstructor(InputEventNotificationId* thisOutPtr, AZ::ScriptDataContext& dc)
{
if (dc.GetNumArguments() == 0)
{
// Use defaults.
}
else if (dc.GetNumArguments() == 1 && dc.IsString(0))
{
thisOutPtr->m_localUserId = AzFramework::LocalUserIdAny;
const char* actionName = nullptr;
dc.ReadArg(0, actionName);
thisOutPtr->m_actionNameCrc = AZ::Crc32(actionName);
}
else if (dc.GetNumArguments() == 2 && dc.IsClass<AZ::Crc32>(0) && dc.IsString(1))
{
AzFramework::LocalUserId localUserId = 0;
dc.ReadArg(0, localUserId);
thisOutPtr->m_localUserId = localUserId;
const char* actionName = nullptr;
dc.ReadArg(1, actionName);
thisOutPtr->m_actionNameCrc = AZ::Crc32(actionName);
}
else
{
AZ_Error("InputEventNotificationId", false, "The InputEventNotificationId takes one or two args. 1 argument: a string representing the input events name (determined by the event group). 2 arguments: a Crc of the profile channel, and a string representing the input event's name");
}
}
class StartingPointInputSystemComponent : public AZ::Component
{
public:
AZ_COMPONENT(StartingPointInputSystemComponent, "{95DE3485-5E51-42A9-899D-433EC3448AA3}");
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC("AssetDatabaseService"));
required.push_back(AZ_CRC("AssetCatalogService"));
}
static void Reflect(AZ::ReflectContext* context)
{
InputEventBindingsAsset::Reflect(context);
InputEventBindings::Reflect(context);
InputEventGroup::Reflect(context);
InputEventMap::Reflect(context);
InputLibrary::Reflect(context);
ThumbstickInputEventMap::Reflect(context);
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<StartingPointInputSystemComponent, AZ::Component>()
->Version(1)
;
serializeContext->ClassDeprecate("Input", "{546C9EBC-90EF-4F03-891A-0736BE2A487E}", &ConvertToInputEventMap);
serializeContext->Class<InputEventNotificationId>()
->Version(1)
->Field("LocalUserId", &InputEventNotificationId::m_localUserId)
->Field("ActionName", &InputEventNotificationId::m_actionNameCrc)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<StartingPointInputSystemComponent>(
"Starting point input", "Manages input bindings and events")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Editor")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System"))
;
}
}
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<InputEventNotificationId>("InputEventNotificationId")
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::Preview)
->Constructor<const char*>()
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)
->Attribute(AZ::Script::Attributes::ConstructorOverride, &InputEventNonIntrusiveConstructor)
->Property("actionNameCrc", BehaviorValueProperty(&InputEventNotificationId::m_actionNameCrc))
->Property("localUserId", BehaviorValueProperty(&InputEventNotificationId::m_localUserId))
->Method("ToString", &InputEventNotificationId::ToString)
->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::ToString)
->Method("Equal", &InputEventNotificationId::operator==)
->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::Equal)
->Method("Clone", &InputEventNotificationId::Clone)
->Property("actionName", nullptr, [](InputEventNotificationId* thisPtr, AZStd::string_view value) { *thisPtr = InputEventNotificationId(value.data()); })
->Method("CreateInputEventNotificationId", [](AzFramework::LocalUserId localUserId, AZStd::string_view value) -> InputEventNotificationId { return InputEventNotificationId(localUserId, value.data()); },
{ { { "localUserId", "Local User ID" },
{ "actionName", "The name of the Input event action used to create an InputEventNotificationId" } } });
behaviorContext->EBus<InputEventNotificationBus>("InputEventNotificationBus")
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::Preview)
->Handler<BehaviorInputEventNotificationBusHandler>()
->Event("OnPressed", &InputEventNotificationBus::Events::OnPressed)
->Event("OnHeld", &InputEventNotificationBus::Events::OnHeld)
->Event("OnReleased", &InputEventNotificationBus::Events::OnReleased);
}
}
void Init() override
{
AZ::EnvironmentVariable<ScriptCanvas::NodeRegistry> nodeRegistryVariable = AZ::Environment::FindVariable<ScriptCanvas::NodeRegistry>(ScriptCanvas::s_nodeRegistryName);
if (nodeRegistryVariable)
{
ScriptCanvas::NodeRegistry& nodeRegistry = nodeRegistryVariable.Get();
InputLibrary::InitNodeRegistry(nodeRegistry);
}
}
void Activate() override
{
// Register asset handlers. Requires "AssetDatabaseService"
AZ_Assert(AZ::Data::AssetManager::IsReady(), "Asset manager isn't ready!");
m_inputEventBindingsAssetHandler = aznew AzFramework::GenericAssetHandler<InputEventBindingsAsset>("Input Bindings", "Other", "inputbindings", AZ::AzTypeInfo<InputConfigurationComponent>::Uuid());
m_inputEventBindingsAssetHandler->Register();
}
void Deactivate() override
{
delete m_inputEventBindingsAssetHandler;
m_inputEventBindingsAssetHandler = nullptr;
}
private:
AzFramework::GenericAssetHandler<InputEventBindingsAsset>* m_inputEventBindingsAssetHandler = nullptr;
};
class StartingPointInputModule
: public AZ::Module
{
public:
AZ_RTTI(StartingPointInputModule, "{B30D421E-127D-4C46-90B1-AC3DDF3EC1D9}", AZ::Module);
StartingPointInputModule()
: AZ::Module()
{
m_descriptors.insert(m_descriptors.end(), {
InputConfigurationComponent::CreateDescriptor(),
StartingPointInputSystemComponent::CreateDescriptor(),
});
AZStd::vector<AZ::ComponentDescriptor*> componentDescriptors(InputLibrary::GetComponentDescriptors());
m_descriptors.insert(m_descriptors.end(), componentDescriptors.begin(), componentDescriptors.end());
}
AZ::ComponentTypeList GetRequiredSystemComponents() const override
{
return AZ::ComponentTypeList({ StartingPointInputSystemComponent::RTTI_Type() });
}
};
}
// DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM
// The first parameter should be GemName_GemIdLower
// The second should be the fully qualified name of the class above
AZ_DECLARE_MODULE_CLASS(Gem_StartingPointInput, StartingPointInput::StartingPointInputModule)
@@ -0,0 +1,12 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "StartingPointInput_precompiled.h"
@@ -0,0 +1,13 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once