Someone asked about getting a tutorial going on a basic RTS type camera with inputs. I thought it was a good idear, so here it goes!
I pull a lot from my other input tutorial series, so you may want to check that out first.
Setting up our new project
I’m calling this project “BirdEye” as in bird’s eye view. You can call your whatever you want, but any reference to BirdEye or “BE”, just change it to reflect yours.
See my tutorial on starting a new project with Lumberyard 1.10.
Once you have that going, we can start coding.
Adding our custom Camera component
Lets first set up a component that we will add to our Camera entity. We need to create a component that can handle the tick event, and any input events. That means inheriting and implementing the InputChannelEventLister and TickBus::Handler classes.
BECameraComponent.h
:
#pragma once #include "AzCore/Component/Component.h" #include "AzCore/Component/TickBus.h" #include "AzFramework/Input/Events/InputChannelEventListener.h" namespace BirdEye { class BECameraComponent : public AZ::Component, public AzFramework::InputChannelEventListener, public AZ::TickBus::Handler { public: AZ_COMPONENT(BECameraComponent, "{49a33c8d-4df0-4f27-9cd2-65c2ccd91355}") ~BECameraComponent() override{}; static void Reflect(AZ::ReflectContext* reflection); void Init() override; void Activate() override; void Deactivate() override; protected: bool OnInputChannelEventFiltered(const AzFramework::InputChannel& inputChannel) override; void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; void OnKeyboardEvent(const AzFramework::InputChannel& inputChannel); void OnMouseEvent(const AzFramework::InputChannel& inputChannel); private: bool movingUp = false; bool movingDown = false; bool movingLeft = false; bool movingRight = false; float movementScale = 5.f; }; }
OnKeyboardEvent and OnMouseEvent are merely helper methods that we are borrowing from the other input tutorial. We also having some “moving” member variables for when the player is holding down those input keys. Silly players, amirite, amirite?
Next we are going to handle the input and track when they are holding down certain keys for moving the camera (WASD).
BECameraComponent.cpp
:
#include "StdAfx.h" #include "BECameraComponent.h" #include "AzCore/Component/TransformBus.h" #include "AzCore/Math/Transform.h" #include "AzCore/Serialization/EditContext.h" #include "AzFramework/Input/Channels/InputChannel.h" #include "AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h" #include "AzFramework/Input/Devices/Mouse/InputDeviceMouse.h" using namespace BirdEye; using namespace AzFramework; void BECameraComponent::Reflect(AZ::ReflectContext* reflection) { if (auto serializationContext = azrtti_cast<AZ::SerializeContext*>(reflection)) { serializationContext->Class<BECameraComponent>() ->Version(1) ->Field("Movement scale", &BECameraComponent::movementScale); if (auto editContext = serializationContext->GetEditContext()) { editContext->Class<BECameraComponent>("BECameraComponent", "Camera component from bird's eye view") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::Category, "BirdEye") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game")) ->DataElement(nullptr, &BECameraComponent::movementScale, "Movement scale", "How fast the camera moves"); } } } void BECameraComponent::Init() { } void BECameraComponent::Activate() { AZ::TickBus::Handler::BusConnect(); InputChannelEventListener::Connect(); } void BECameraComponent::Deactivate() { AZ::TickBus::Handler::BusDisconnect(); InputChannelEventListener::Disconnect(); } bool BECameraComponent::OnInputChannelEventFiltered(const AzFramework::InputChannel& inputChannel) { OnMouseEvent(inputChannel); OnKeyboardEvent(inputChannel); return false; } void BECameraComponent::OnKeyboardEvent(const InputChannel& inputChannel) { auto input_type = inputChannel.GetInputChannelId(); if (input_type == InputDeviceKeyboard::Key::AlphanumericW) { movingUp = !!inputChannel.GetValue(); } else if (input_type == InputDeviceKeyboard::Key::AlphanumericS) { movingDown = !!inputChannel.GetValue(); } else if (input_type == InputDeviceKeyboard::Key::AlphanumericA) { movingLeft = !!inputChannel.GetValue(); } else if (input_type == InputDeviceKeyboard::Key::AlphanumericD) { movingRight = !!inputChannel.GetValue(); } } void BECameraComponent::OnMouseEvent(const InputChannel& inputChannel) { auto input_type = inputChannel.GetInputChannelId(); if (input_type == InputDeviceMouse::Button::Left || input_type == InputDeviceMouse::Button::Right) { } } void BECameraComponent::OnTick(float deltaTime, AZ::ScriptTimePoint time) { }
This is mostly straight forward and similar to the first input tutorial.
When we fill out the tick event, however, it will be quite a bit different. Instead of using physics to move the character, we are going to move them with Josh Groban. Oops, I mean move them by changing their translation::position with the ebus.
void BECameraComponent::OnTick(float deltaTime, AZ::ScriptTimePoint time) { AZ::Transform entityTransform; EBUS_EVENT_ID_RESULT(entityTransform, GetEntityId(), AZ::TransformBus, GetWorldTM); float x = 0.f; float y = 0.f; if (movingUp) y += movementScale * deltaTime; if (movingDown) y -= movementScale * deltaTime; if (movingRight) x += movementScale * deltaTime; if (movingLeft) x -= movementScale * deltaTime; if (y != 0.f || x != 0.f) { auto new_position = entityTransform.GetPosition() + AZ::Vector3{x, y, 0.f}; entityTransform.SetPosition(new_position); EBUS_EVENT_ID(GetEntityId(), AZ::TransformBus, SetWorldTM, entityTransform); } }
So if you think of the camera floating in the air, we don’t need to change the rotation for now, and we don’t need to increase or decrease the height. We merely need to change the x and y position of the camera. Sorta like a Ouija board
Okay, one last thing, we need to register our component in our BirdEyeModule.cpp file.
BirdEyeModule.cpp
:
BirdEyeModule() : CryHooksModule() { // Push results of [MyComponent]::CreateDescriptor() into m_descriptors here. m_descriptors.insert(m_descriptors.end(), { BirdEyeSystemComponent::CreateDescriptor(), BECameraComponent::CreateDescriptor(), }); }
See line 7.
And be sure to add our new BECameraComponent.* files to our waf_file:
birdeye.waf_files
:
... "Source/Compnonets": [ "Source/Components/BECameraComponent.h", "Source/Components/BECameraComponent.cpp" ], ...
Ooookay. Lets compile to make sure everything is kosher.
Setting up the level
Lets set up a new level for this project. I call it TutorialLevel because I’m awesomely creative with names.
Anyhoo, before we go into the editor, lets do one last thing in c++. Lets go to our System/GameStartup.cpp file and auto load our level on startup.
GameStartup.cpp
:
int GameStartup::Run(const char* autoStartLevelName) { gEnv->pConsole->ExecuteString("exec autoexec.cfg"); gEnv->pConsole->ExecuteString("map TutorialLevel"); #ifdef WIN32
Open the editor and select New Level to create our TutorialLevel. The defaults are fine.
Next create a new entity called Camera and give it the following components.
This has the camera looking down on our level at a slight angle. If you don’t see our custom component we created in the add component list, then follow the following post to the T. Including watching the screaming goats. It’s needed. Like using power mode when your editor supports it.
Save and export the level.
Now you can either exit and run the launcher, or run the game direction from the editor. We should now be able to move the camera around using the WASD keys.
Dude, Beer… duh.
The next part of the tutorial will be around using the mouse to control scrolling when hitting the edges of the screen. You’ll notice that the mouse isn’t even visible right now.
We can fix that with the InputSystemCurosorRequestBus
but that is for the next tutorial.
Until then, continue fumbling around. It’s what we all do.
2 thoughts on “RTS C++ Setup – The Inputs Part 1”