Starting with Lumberyard Input – Part 3

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

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s