In this tutorial we are going to solve two out of the 3 problems we encountered at the end of part2.
Don’t Let the Mouse Leave the Window
Before 1.10, you had to do this by using CryEngine’s hardwareMouse object on the gEnv. But post 1.10, we can now use the input system’s ebus to reset the mouse position to the center of the screen.
TutCharacterComp.cpp
void TutorialSeriesCharacterComponent::CenterCursorPosition() { EBUS_EVENT(InputSystemCursorRequestBus, SetSystemCursorPositionNormalized, AZ::Vector2{.5f, .5f}); m_lastMousePosition = AZ::Vector2{.5f, .5f}; } void TutorialSeriesCharacterComponent::PerformRotation(const InputChannel &inputChannel) { auto position_data = inputChannel.GetCustomData<InputChannel::PositionData2D>(); TrackMouseMovement(position_data); CenterCursorPosition(); }
We added the helper function CenterCursorPosition
so be sure to add that to our header. Also we call that helper on line 11.
InputSystemCursorRequestBus
is an eBus that allows us to get the normalized location of the cursor or set the normalized position (0 to 1). It also allows us to see whether or not the mouse is constrained to the window and visible or not (and set that value as well.)
Compile and run. We should be able to rotate all we want now.
Move the Direction we are Looking
This may sound complex but it’s really quite simple, especially with the operator overloads that Lumberyard has.
We are going to modify our OnTick function
TutCharacterComp.cpp
void TutorialSeriesCharacterComponent::OnTick(float deltaTime, AZ::ScriptTimePoint time) { AZ::Transform entityTransform; EBUS_EVENT_ID_RESULT(entityTransform, GetEntityId(), AZ::TransformBus, GetWorldTM); auto rotation = GetCurrentOrientation(); entityTransform.SetRotationPartFromQuaternion(rotation); EBUS_EVENT_ID(GetEntityId(), AZ::TransformBus, SetWorldTM, entityTransform); auto desiredVelocity = AZ::Vector3::CreateZero(); if (movingForward || movingBack || strafingLeft || strafingRight) { HandleForwardBackwardMovement(desiredVelocity); HandleStrafing(desiredVelocity); desiredVelocity = rotation * desiredVelocity; } // Apply relative translation to the character via physics. EBUS_EVENT_ID(GetEntityId(), LmbrCentral::CryCharacterPhysicsRequestBus, RequestVelocity, desiredVelocity, 0); }
Our changes are on 5, 6, and 15. Basically, we just have to “times” our rotation by our transform and Lumberyard does the math appropriately. (This is actually true for most game engines, I believe.)
Now if we compile and run, you should be able to move in any direction based on where you are facing.
Catch Up
I thought it would be useful to show the state of my code at this point in case you are coming late or just want to see how it all looks together
TutCharacterComp.h
#pragma once #include "AzCore/Component/TransformBus.h" #include "AzCore/Component/Component.h" #include "AzFramework/Input/Events/InputChannelEventListener.h" #include "AzCore/Component/TickBus.h" namespace TutorialSeries { struct Vector3; class TutorialSeriesCharacterComponent : public AZ::Component, public AzFramework::InputChannelEventListener, public AZ::TickBus::Handler, public AZ::TransformNotificationBus::Handler { public: AZ_COMPONENT(TutorialSeriesCharacterComponent, "{F52E6197-C72B-4BEF-99CB-FE41C36CF882}"); ~TutorialSeriesCharacterComponent() override = default; static void Reflect(AZ::ReflectContext* reflection); void Init() override; void Activate() override; void Deactivate() override; void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; bool OnInputChannelEventFiltered(const AzFramework::InputChannel& inputChannel) override; private: void OnKeyboardEvent(const AzFramework::InputChannel& inputChannel); void OnMouseEvent(const AzFramework::InputChannel& inputChannel); void HandleForwardBackwardMovement(AZ::Vector3& desiredVelocity); void HandleStrafing(AZ::Vector3& desiredVelocity); void PerformRotation(const AzFramework::InputChannel& inputChannel); void TrackMouseMovement(const AzFramework::InputChannel::PositionData2D* position_data); void CenterCursorPosition(); const AZ::Quaternion GetCurrentOrientation(); AZ::Vector2 m_lastMousePosition{.5f, .5f}; AZ::Vector2 m_mouseChangeAggregate{0, 0}; float RotationSpeed = 5.f; float MovementScale = 5.0f; bool movingForward = false; bool movingBack = false; bool strafingLeft = false; bool strafingRight = false; }; }
TutCharacterComp.cpp
#include "StdAfx.h" #include "Components/TutCharacterComp.h" #include "AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h" #include "AzFramework/Input/Devices/Mouse/InputDeviceMouse.h" #include "AzCore/Component/ComponentApplicationBus.h" #include "AzCore/EBus/EBus.h" #include "AzCore/Math/Transform.h" #include "AzCore/Serialization/EditContext.h" #include "AzFramework/Entity/GameEntityContextBus.h" #include "AzFramework/Input/Channels/InputChannel.h" #include "LmbrCentral/Physics/CryCharacterPhysicsBus.h" using namespace TutorialSeries; using namespace AzFramework; void TutorialSeriesCharacterComponent::Reflect(AZ::ReflectContext *reflection) { if (auto serializationContext = azrtti_cast<AZ::SerializeContext *>(reflection)) { serializationContext->Class<TutorialSeriesCharacterComponent>() ->Version(1) ->Field("Movement scale", &TutorialSeriesCharacterComponent::MovementScale) ->Field("Rotation Speed", &TutorialSeriesCharacterComponent::RotationSpeed); if (auto editContext = serializationContext->GetEditContext()) { editContext->Class<TutorialSeriesCharacterComponent>("TutorialSeriesCharacterComponent", "Main controller component") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::Category, "TutorialSeries") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game")) ->DataElement(nullptr, &TutorialSeriesCharacterComponent::MovementScale, "Movement scale", "How fast the character moves") ->DataElement(nullptr, &TutorialSeriesCharacterComponent::RotationSpeed, "Rotation Speed", "The speed multiplier to apply to mouse rotation"); } } } void TutorialSeriesCharacterComponent::Init() { } void TutorialSeriesCharacterComponent::Activate() { AZ::TickBus::Handler::BusConnect(); InputChannelEventListener::Connect(); AZ::TransformNotificationBus::Handler::BusConnect(GetEntityId()); } void TutorialSeriesCharacterComponent::Deactivate() { AZ::TickBus::Handler::BusDisconnect(); InputChannelEventListener::Disconnect(); AZ::TransformNotificationBus::Handler::BusDisconnect(); } bool TutorialSeriesCharacterComponent::OnInputChannelEventFiltered(const AzFramework::InputChannel &inputChannel) { auto device_id = inputChannel.GetInputDevice().GetInputDeviceId(); if (device_id == InputDeviceMouse::Id) { OnMouseEvent(inputChannel); } if (device_id == InputDeviceKeyboard::Id) { OnKeyboardEvent(inputChannel); } return false; } void TutorialSeriesCharacterComponent::OnKeyboardEvent(const InputChannel &inputChannel) { auto input_type = inputChannel.GetInputChannelId(); if (input_type == InputDeviceKeyboard::Key::AlphanumericW) { movingForward = !!inputChannel.GetValue(); } else if (input_type == InputDeviceKeyboard::Key::AlphanumericS) { movingBack = !!inputChannel.GetValue(); } else if (input_type == InputDeviceKeyboard::Key::AlphanumericA) { strafingLeft = !!inputChannel.GetValue(); } else if (input_type == InputDeviceKeyboard::Key::AlphanumericD) { strafingRight = !!inputChannel.GetValue(); } } void TutorialSeriesCharacterComponent::OnTick(float deltaTime, AZ::ScriptTimePoint time) { AZ::Transform entityTransform; EBUS_EVENT_ID_RESULT(entityTransform, GetEntityId(), AZ::TransformBus, GetWorldTM); auto rotation = GetCurrentOrientation(); entityTransform.SetRotationPartFromQuaternion(rotation); EBUS_EVENT_ID(GetEntityId(), AZ::TransformBus, SetWorldTM, entityTransform); auto desiredVelocity = AZ::Vector3::CreateZero(); if (movingForward || movingBack || strafingLeft || strafingRight) { HandleForwardBackwardMovement(desiredVelocity); HandleStrafing(desiredVelocity); desiredVelocity = rotation * desiredVelocity; } // Apply relative translation to the character via physics. EBUS_EVENT_ID(GetEntityId(), LmbrCentral::CryCharacterPhysicsRequestBus, RequestVelocity, desiredVelocity, 0); } const AZ::Quaternion TutorialSeriesCharacterComponent::GetCurrentOrientation() { auto z_rotation = AZ::Quaternion::CreateRotationZ(m_mouseChangeAggregate.GetX() * RotationSpeed); return z_rotation; } void TutorialSeriesCharacterComponent::HandleForwardBackwardMovement(AZ::Vector3 &desiredVelocity) { if (movingBack || movingForward) { float forward_back_vel = 0; if (movingForward) { forward_back_vel += MovementScale; } if (movingBack) { forward_back_vel -= (MovementScale / 2.f); } desiredVelocity.SetY(forward_back_vel); } } void TutorialSeriesCharacterComponent::HandleStrafing(AZ::Vector3 &desiredVelocity) { if (strafingLeft || strafingRight) { float left_right_vel = 0; if (strafingRight) { left_right_vel += MovementScale * .75f; } if (strafingLeft) { left_right_vel -= MovementScale * .75f; } desiredVelocity.SetX(left_right_vel); } } void TutorialSeriesCharacterComponent::OnMouseEvent(const InputChannel &inputChannel) { auto input_type = inputChannel.GetInputChannelId(); if (input_type == InputDeviceMouse::SystemCursorPosition) { PerformRotation(inputChannel); } } void TutorialSeriesCharacterComponent::CenterCursorPosition() { EBUS_EVENT(InputSystemCursorRequestBus, SetSystemCursorPositionNormalized, AZ::Vector2{.5f, .5f}); m_lastMousePosition = AZ::Vector2{.5f, .5f}; } void TutorialSeriesCharacterComponent::PerformRotation(const InputChannel &inputChannel) { auto position_data = inputChannel.GetCustomData<InputChannel::PositionData2D>(); TrackMouseMovement(position_data); CenterCursorPosition(); } void TutorialSeriesCharacterComponent::TrackMouseMovement(const InputChannel::PositionData2D *position_data) { auto deltaMousePosition = m_lastMousePosition - position_data->m_normalizedPosition; m_lastMousePosition = position_data->m_normalizedPosition; m_mouseChangeAggregate += deltaMousePosition; }
I hope the series has been helpful so far! The next part of this series will handle looking up and down.
2 thoughts on “Starting with Lumberyard Input – Part 3”