As stated in the part 1 of this tutorial, this tutorial will be focused on moving around the camera with the mouse.
This tutorial will not go into the input constructs that Lumberyard offers, but will use them; however, if you really want to know how the concepts behind the input system work, you should read through my first input tutorial for an FPS type game. There I go into more detail about InputChannels, InputDevices, and all the other constructs.
If you want to know more about different constructs, check out here, here, and here. (Totally not a nerd, though.)
Enabling the Cursor
You may have noticed that we don’t have a cursor. That’s not very RTS of us. How are we supposed to feed South Korea’s carpal tunnel epidemic if we don’t have a cursor to allow over 9000!! actions per minute (sorry for the mixed reference.)
So we could throw this into System/GameStartup.cpp in the init function. However, this is not very Lumberyard’ish of us, and not the right place (and I totally didn’t do that at first!)
Lumberyard gives us a nice Bus called GameEntityContextEventBus, and a starting system component that we haven’t used yet (in my case, Source/BirdEyeSystemComponent.h).
Lets include the header and inherit the bus.
BirdEyeSystemComponent.h
:
#include "AzFramework/Entity/GameEntityContextBus.h" namespace BirdEye { class BirdEyeSystemComponent : public AZ::Component , protected EditorGameRequestBus::Handler , protected BirdEyeRequestBus::Handler , protected AzFramework::GameEntityContextEventBus::Handler { public:
And then add the function we need to the use from that bus.
BirdEyeSystemComponent.h
:
protected: void OnGameEntitiesStarted() override;
Now lets implement the function in the source file
BirdEyeSystemComponent.cpp
:
#include "AzFramework/Input/Devices/Mouse/InputDeviceMouse.h" ... void BirdEyeSystemComponent::Activate() { BirdEyeRequestBus::Handler::BusConnect(); EditorGameRequestBus::Handler::BusConnect(); AzFramework::GameEntityContextEventBus::Handler::BusConnect(); } void BirdEyeSystemComponent::Deactivate() { EditorGameRequestBus::Handler::BusConnect(); BirdEyeRequestBus::Handler::BusDisconnect(); AzFramework::GameEntityContextEventBus::Handler::BusDisconnect(); } void BirdEyeSystemComponent::OnGameEntitiesStarted() { EBUS_EVENT(AzFramework::InputSystemCursorRequestBus, SetSystemCursorState, AzFramework::SystemCursorState::ConstrainedAndVisible); }
Now if you compile and launch the game, you will see a mouse cursor, and it will be constrained by the window. Sorta like a rat in a cage. Ok, I feel bad about that one… I should really hit the backspace now…
Capturing when the Mouse is on the Edge
Now we need to detect when the mouse is on the edge. We had an empty function on the BECameraComponent called OnMouseEvent. Time to fill it in.
BECameraComponent.cpp
:
void BECameraComponent::OnMouseEvent(const InputChannel& inputChannel) { auto input_type = inputChannel.GetInputChannelId(); if (input_type == InputDeviceMouse::Button::Left || input_type == InputDeviceMouse::Button::Right) { } else if (input_type == InputDeviceMouse::SystemCursorPosition) { mouseEdgeDown = mouseEdgeLeft = mouseEdgeRight = mouseEdgeUp = false; if (auto position_data = inputChannel.GetCustomData<InputChannel::PositionData2D>()) { auto position = position_data->m_normalizedPosition; auto x = position.GetX(); auto y = position.GetY(); mouseEdgeLeft = x <= .01; mouseEdgeRight = x >= .99; mouseEdgeUp = y <= .01; mouseEdgeDown = y >= .99; } } }
You can see we are handling the SystemCursorPosition input channel type. We grab the position, which is normalized between 0 and 1. 0,0 starts on the top left, and 1,1 is the bottom right.
Be sure to add these mouseEdge* bools to the header file.
Compile to make sure everything is kosher.
Moving the Camera
Now lets use dem variables to actually move the camera. In our OnTick function, we use our current movingUp/Down/Right/Left variables. We can tack on our new variables with an ||
.
BECameraComponent.cpp
:
if (movingUp || mouseEdgeUp) y += movementScale * deltaTime; if (movingDown || mouseEdgeDown) y -= movementScale * deltaTime; if (movingRight || mouseEdgeRight) x += movementScale * deltaTime; if (movingLeft || mouseEdgeLeft) x -= movementScale * deltaTime;
BOOM. That should be it. Compile and run the launcher. Now when you move the cursor to the edge of the window, it should move the camera. Yaaaay.
DON’T RUN THIS IN THE EDITOR. I’M PREJUDICE AGAINST IT
Ok, I’m not against the editor, but if you run it, you will notice that the editor does not show the cursor. Sad face. I mean 😦
This is because the editor actually loads your game and level, but then puts the engine in an editor mode, that can be switched to a game mode (by press control + g). However, right now it seems hard-coded to set the state of the mouse to ConstrainedAndHidden whenever you enter game mode.
Preferably, when switching game mode, it would be good to use the in-editor setting:
For now, in the HardwareMouse.cpp file, you can hardcode it to be ConstrainedAndVisible, but even then, the constrained part doesn’t seem to work in the editor. I’ll have to put a forum post about this.
If I find some way to deal with this better, it looks like we will be limited to the launcher for trying out our game. I honestly don’t mind, I ❤ c++ for life, and generally run the game with the launcher anyhoo.
This is the End, my only Friend, the End (of part 2)
If you made it this far without getting some song stuck in your head, then you are a miracle.
In the next part, we are going to handle rotating the camera. There are a lot of choices here, and we’ll go through them. Until then, stay frosty.