Kaynağa Gözat

[Simulation Interfaces] Add keyboard activated state transition (#928)

* Add keyboard activated state transition

This commit allows User to change simulation state with keyboard.
There is possibility to set three transitions with registry:
```
{
    "SimulationInterfaces": {
        "PrintStateNameInGui": true,
        "StartInStoppedState": true,
        "KeyboardTransitions":
        {
            "StoppedToPlaying": "keyboard_key_alphanumeric_R",
            "PausedToPlaying": "keyboard_key_alphanumeric_R",
            "PlayingToPaused": "keyboard_key_alphanumeric_P"
        }
    }
}
```

It will:
show hint in UI, and will listen to keyboard inputs.
Names if keyboard inputs are available `o3de/Code/Framework/AzFramework/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h`

Signed-off-by: Michał Pełka <[email protected]>
Co-authored-by: Copilot <[email protected]>
Michał Pełka 1 hafta önce
ebeveyn
işleme
10254f9144

+ 140 - 1
Gems/SimulationInterfaces/Code/Source/Clients/SimulationManager.cpp

@@ -24,6 +24,39 @@ namespace SimulationInterfaces
     namespace
     {
 
+        //! Convert string like : keyboard_key_alphanumeric_O to a pretty name like "Key 'O'"
+        AZStd::string MakePrettyKeyboardName(const AzFramework::InputChannelId& inputChannelId)
+        {
+            // Convert the input channel name to a pretty format for display
+
+            // split the name by underscores
+            AZStd::vector<AZStd::string> parts;
+            AZ::StringFunc::Tokenize(inputChannelId.GetName(), parts, "_");
+            if (parts.size() < 4)
+            {
+                return inputChannelId.GetName(); // return original if no parts found
+            }
+
+            const AZStd::string& deviceType = parts[0];
+            const AZStd::string& keyType = parts[1];
+            const AZStd::string& keyRegion = parts[2];
+            const AZStd::string& keyName = parts[3];
+
+            if (deviceType != "keyboard" || keyType != "key")
+            {
+                return inputChannelId.GetName(); // return original if not a keyboard alphanumeric key
+            }
+
+            // Create a pretty name based on the key region and key name
+            if (keyRegion == "alphanumeric" || keyRegion == "function")
+            {
+                // For alphanumeric keys, we can return the key name directly
+                return AZStd::string::format("Key '%s'", keyName.c_str());
+            }
+
+            return AZStd::string::format("Key '%s_%s'", keyRegion.c_str(), keyName.c_str());
+        }
+
         const AZStd::unordered_map<SimulationState, AZStd::string> SimulationStateToString = {
             { simulation_interfaces::msg::SimulationState::STATE_PAUSED, "STATE_PAUSED" },
             { simulation_interfaces::msg::SimulationState::STATE_PLAYING, "STATE_PLAYING" },
@@ -33,6 +66,9 @@ namespace SimulationInterfaces
 
         constexpr AZStd::string_view PrintStateName = "/SimulationInterfaces/PrintStateNameInGui";
         constexpr AZStd::string_view StartInStoppedStateKey = "/SimulationInterfaces/StartInStoppedState";
+        constexpr AZStd::string_view KeyboardTransitionStoppedToPlaying = "/SimulationInterfaces/KeyboardTransitions/StoppedToPlaying";
+        constexpr AZStd::string_view KeyboardTransitionPausedToPlaying = "/SimulationInterfaces/KeyboardTransitions/PausedToPlaying";
+        constexpr AZStd::string_view KeyboardTransitionPlayingToPaused = "/SimulationInterfaces/KeyboardTransitions/PlayingToPaused";
 
         AZStd::string GetStateName(SimulationState state)
         {
@@ -61,6 +97,32 @@ namespace SimulationInterfaces
             settingsRegistry->Get(output, PrintStateName);
             return output;
         }
+
+        AZStd::optional<AzFramework::InputChannelId> GetKeyboardTransitionKey(const AZStd::string& registryKeyName)
+        {
+            AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get();
+            AZ_Assert(settingsRegistry, "Settings Registry is not available");
+            AZStd::string channelIdName;
+            settingsRegistry->Get(channelIdName, registryKeyName);
+
+            if (channelIdName.empty())
+            {
+                AZ_Error("SimulationManager", false, "Failed to get keyboard transition key from registry: %s", registryKeyName.c_str());
+                return AZStd::nullopt;
+            }
+            AZ::Crc32 channelIdCrc32 = AZ::Crc32(channelIdName.c_str());
+
+            for (const auto& inputChannel : AzFramework::InputDeviceKeyboard::Key::All)
+            {
+                if (inputChannel.GetNameCrc32() == channelIdCrc32)
+                {
+                    return inputChannel;
+                }
+            }
+            AZ_Error("SimulationManager", false, "Failed to find input channel with name: %s", channelIdName.c_str());
+            return AZStd::nullopt;
+        }
+
     } // namespace
 
     AZ_COMPONENT_IMPL(SimulationManager, "SimulationManager", SimulationManagerTypeId);
@@ -101,10 +163,12 @@ namespace SimulationInterfaces
         {
             SimulationManagerRequestBusInterface::Register(this);
         }
+        InputChannelEventListener::BusConnect();
     }
 
     SimulationManager::~SimulationManager()
     {
+        InputChannelEventListener::BusDisconnect();
         if (SimulationManagerRequestBusInterface::Get() == this)
         {
             SimulationManagerRequestBusInterface::Unregister(this);
@@ -142,15 +206,42 @@ namespace SimulationInterfaces
                                                          simulation_interfaces::msg::SimulatorFeatures::STEP_SIMULATION_ACTION,
                                                          simulation_interfaces::msg::SimulatorFeatures::SIMULATION_STATE_SETTING,
                                                          simulation_interfaces::msg::SimulatorFeatures::SIMULATION_STATE_GETTING });
+
         if (PrintStateNameInGui())
         {
             AZ::TickBus::Handler::BusConnect();
         }
+
         AZ::SystemTickBus::QueueFunction(
             [this]()
             {
                 InitializeSimulationState();
             });
+
+        // Query registry for keyboard transition keys
+        if (const auto stoppedToPlayingKey = GetKeyboardTransitionKey(KeyboardTransitionStoppedToPlaying))
+        {
+            RegisterTransitionsKey(
+                *stoppedToPlayingKey,
+                simulation_interfaces::msg::SimulationState::STATE_STOPPED,
+                simulation_interfaces::msg::SimulationState::STATE_PLAYING);
+        }
+
+        if (const auto pausedToPlayingKey = GetKeyboardTransitionKey(KeyboardTransitionPausedToPlaying))
+        {
+            RegisterTransitionsKey(
+                *pausedToPlayingKey,
+                simulation_interfaces::msg::SimulationState::STATE_PAUSED,
+                simulation_interfaces::msg::SimulationState::STATE_PLAYING);
+        }
+
+        if (const auto playingToPausedKey = GetKeyboardTransitionKey(KeyboardTransitionPlayingToPaused))
+        {
+            RegisterTransitionsKey(
+                *playingToPausedKey,
+                simulation_interfaces::msg::SimulationState::STATE_PLAYING,
+                simulation_interfaces::msg::SimulationState::STATE_PAUSED);
+        }
     }
 
     void SimulationManager::Deactivate()
@@ -373,11 +464,59 @@ namespace SimulationInterfaces
 
     void SimulationManager::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
     {
+        // get if we have available keyboard transition
+
+        AZStd::string keyboardHint;
+        const auto maybeKeyboardTransition = m_keyboardTransitions.find(m_simulationState);
+
+        if (maybeKeyboardTransition != m_keyboardTransitions.end())
+        {
+            keyboardHint = maybeKeyboardTransition->second.m_uiDescription;
+        }
+
         DebugDraw::DebugDrawRequestBus::Broadcast(
             &DebugDraw::DebugDrawRequests::DrawTextOnScreen,
-            AZStd::string::format("Simulation state: %s", GetStateName(m_simulationState).c_str()),
+            AZStd::string::format("Simulation state: %s %s", GetStateName(m_simulationState).c_str(), keyboardHint.c_str()),
             AZ::Color(1.0f, 1.0f, 1.0f, 1.0f),
             0.f);
     }
 
+    void SimulationManager::RegisterTransitionsKey(
+        const AzFramework::InputChannelId& key, SimulationState sourceState, SimulationState desiredState)
+    {
+        const auto uiKeyName = MakePrettyKeyboardName(key);
+        const auto uiHint =
+            AZStd::string::format("Press %s to change simulation state to %s", uiKeyName.c_str(), GetStateName(desiredState).c_str());
+        m_keyboardTransitions[sourceState] = { key, desiredState, uiHint };
+    }
+
+    bool SimulationManager::OnInputChannelEventFiltered(const AzFramework::InputChannel& inputChannel)
+    {
+        const AzFramework::InputDeviceId& deviceId = inputChannel.GetInputDevice().GetInputDeviceId();
+
+        if (AzFramework::InputDeviceKeyboard::IsKeyboardDevice(deviceId) && inputChannel.IsStateBegan())
+        {
+            const auto maybeKeyboardTransition = m_keyboardTransitions.find(m_simulationState);
+            if (maybeKeyboardTransition == m_keyboardTransitions.end())
+            {
+                return false;
+            }
+            if (maybeKeyboardTransition->second.m_inputChannelId == inputChannel.GetInputChannelId())
+            {
+                // if we have transition, set the state
+                auto result = SetSimulationState(maybeKeyboardTransition->second.m_desiredState);
+
+                AZ_Error(
+                    "SimulationManager",
+                    result.IsSuccess(),
+                    "Failed to change simulation state: %d %s",
+                    result.GetError().m_errorCode,
+                    result.GetError().m_errorString.c_str());
+
+                return true;
+            }
+        }
+        return false;
+    }
+
 } // namespace SimulationInterfaces

+ 22 - 0
Gems/SimulationInterfaces/Code/Source/Clients/SimulationManager.h

@@ -15,6 +15,8 @@
 #include <AzCore/std/utility/pair.h>
 #include <AzFramework/API/ApplicationAPI.h>
 #include <AzFramework/Entity/EntityContextBus.h>
+#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
+#include <AzFramework/Input/Events/InputChannelEventListener.h>
 #include <AzFramework/Physics/PhysicsScene.h>
 #include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
 #include <SimulationInterfaces/SimulationEntityManagerRequestBus.h>
@@ -23,11 +25,18 @@
 
 namespace SimulationInterfaces
 {
+    struct KeyboardTransition
+    {
+        AzFramework::InputChannelId m_inputChannelId; //! Input channel ID for the keyboard key that triggers the transition
+        SimulationState m_desiredState; //! Desired state to transition to when the key is pressed
+        AZStd::string m_uiDescription; //! Description of the transition, used in UI
+    };
     class SimulationManager
         : public AZ::Component
         , protected SimulationManagerRequestBus::Handler
         , protected AzFramework::LevelSystemLifecycleNotificationBus::Handler
         , protected AZ::TickBus::Handler
+        , protected AzFramework::InputChannelEventListener
     {
     public:
         AZ_COMPONENT_DECL(SimulationManager);
@@ -47,6 +56,7 @@ namespace SimulationInterfaces
         void Deactivate() override;
 
     private:
+
         // SimulationManagerRequestBus interface implementation
         void SetSimulationPaused(bool paused) override;
         void StepSimulation(AZ::u64 steps) override;
@@ -63,6 +73,15 @@ namespace SimulationInterfaces
         // AZ::TickBus::Handler
         void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
 
+        // InputChannelEventListener
+        bool OnInputChannelEventFiltered(const AzFramework::InputChannel& inputChannel) override;
+
+        //! Register a keyboard transition key in m_keyboardTransitions. Generate UI hints
+        //! \param key The input channel ID of the keyboard key
+        //! \param sourceState The current simulation state that the key is associated with
+        //! \param desiredState The desired simulation state to transition to when the key is pressed
+        void RegisterTransitionsKey(const AzFramework::InputChannelId& key, SimulationState sourceState, SimulationState desiredState);
+
         bool m_isSimulationPaused = false;
 
         uint64_t m_numberOfPhysicsSteps = 0;
@@ -83,5 +102,8 @@ namespace SimulationInterfaces
             { simulation_interfaces::msg::SimulationState::STATE_QUITTING, simulation_interfaces::msg::SimulationState::STATE_PLAYING },
             { simulation_interfaces::msg::SimulationState::STATE_QUITTING, simulation_interfaces::msg::SimulationState::STATE_PAUSED },
         } };
+
+        //! Map of keyboard transitions - defined in registry key
+        AZStd::unordered_map<SimulationState, KeyboardTransition> m_keyboardTransitions;
     };
 } // namespace SimulationInterfaces

+ 7 - 1
Gems/SimulationInterfaces/Registry/simulationinterface_settings.setreg

@@ -1,6 +1,12 @@
 {
     "SimulationInterfaces": {
         "PrintStateNameInGui": true,
-        "StartInStoppedState": true
+        "StartInStoppedState": true,
+        "KeyboardTransitions":
+        {
+            "StoppedToPlaying": "keyboard_key_alphanumeric_R",
+            "PausedToPlaying": "keyboard_key_alphanumeric_R",
+            "PlayingToPaused": "keyboard_key_alphanumeric_P"
+        }
     }
 }