本文内容
第19章 使用鼠标输入转向
第19章 使用鼠标输入转向
介绍
在第 18 章 角色移动 中,我们使用 W 和 S 实现角色移动,用于向前和向后移动。在本章中,我将介绍如何使用鼠标运动来转动角色实体,这也会转动摄像机。
注意:本章的源代码可以在 GitHub 上找到: https://github.com/AMZN-Olex/O3DEBookCode2111/tree/ch19_character_turning
自定义输入事件组
在第 18 章 角色移动 中,我们使用了 O3DE 安装的输入绑定文件,网址为 C:\O3DE\21.11.2\Gems\StartingPointInput\Assets\thirdpersonmovement.inputbindings。最好复制它,使其成为我们自己的,并将其置于我们的源代码控制之下。
- 将 thirdpersonmovement.inputbindings 从 C:\O3DE\21.11.2\Gems \StartingPointInput\Assets\ 复制到 C:\git\book\Gems\MyGem\Assets\ 作为 mythirdpersonmovement.inputbindings。
- 将 Input (输入) 组件更改为指向 mythirdpersonmovement.inputbindings。
- 打开 mythirdpersonmovement.inputbindings 的 Asset Editor。
- 删除除 move forward、move right 和 rotate yaw 之外的所有绑定
鼠标输入
Input Event Groups 允许您为每个事件指定设备和设备输入。在这里,我们分配了鼠标设备和mouse_delta_x输入来旋转偏航事件。这意味着鼠标的横向运动将被发送到分配的事件。以下是捕获此鼠标输入并应用它的方法。
图 19.1.使用鼠标转动
- 创建一个变量来存储 mouse_delta_x 的当前状态。
class ChickenControllerComponent
{
...
float m_yaw = 0.f;
};
- 为 “rotate yaw” 创建 InputEventNotificationId。
const StartingPointInput::InputEventNotificationId RotateYawEventId("rotate yaw");
- 使用 InputEventNotificationBus 注册活动。
void ChickenControllerComponent::Activate()
{
...
InputEventNotificationBus::MultiHandler::BusConnect(
RotateYawEventId);
}
注意:此步骤至关重要。否则,您将根本不会收到 “rotate yaw” 的鼠标事件。
- 重写虚拟方法 OnHeld 以捕获鼠标输入。
void ChickenControllerComponent::OnHeld(float value)
{
const InputEventNotificationId* inputId =
InputEventNotificationBus::GetCurrentBusId();
if (inputId == nullptr)
{
return;
}
if (*inputId == RotateYawEventId)
{
m_yaw = value;
}
}
- 更新 CreateInput 以复制m_yaw值进行处理。
ChickenInput ChickenControllerComponent::CreateInput()
{
ChickenInput input;
...
input.m_viewYaw = m_yaw;
return input;
}
- 更新 ProcessInput 以更新鸡的旋转。
void ChickenControllerComponent::UpdateRotation(
const ChickenInput& input)
{
AZ::TransformInterface* t = GetEntity()->GetTransform();
float currentHeading = t->GetWorldRotationQuaternion().
GetEulerRadians().GetZ();
currentHeading += input.m_viewYaw * m_turnSpeed;
AZ::Quaternion q =
AZ::Quaternion::CreateRotationZ(currentHeading);
t->SetWorldRotationQuaternion(q);
}
通过这些更改,鸡将根据鼠标的左右移动而转动。相机将始终保持在鸡的后面,因为它被锁定到该方向
源代码
注意:本章的源代码可以在 GitHub 上找到: https://github.com/AMZN-Olex/O3DEBookCode2111/tree/ch19_character_turning
作为奖励,此源代码还实现了 strafing。它的实现与前进和向后移动非常相似。
例 19.1.ChickenControllerComponent.h
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Math/Vector3.h>
#include <StartingPointInput/InputEventNotificationBus.h>
namespace MyGem
{
const StartingPointInput::InputEventNotificationId
MoveFwdEventId("move forward");
const StartingPointInput::InputEventNotificationId
MoveRightEventId("move right");
const StartingPointInput::InputEventNotificationId
RotateYawEventId("rotate yaw");
class ChickenInput
{
public:
float m_forwardAxis = 0;
float m_strafeAxis = 0;
float m_viewYaw = 0;
};
class ChickenControllerComponent
:
public AZ::Component
,
,
public AZ::TickBus::Handler
public StartingPointInput::
InputEventNotificationBus::MultiHandler
{
public:
AZ_COMPONENT(ChickenControllerComponent,
"{fe639d60-75c0-4e16-aa1a-0d44dbe6d339}");
static void Reflect(AZ::ReflectContext* rc);
// AZ::Component interface implementation
void Activate() override;
void Deactivate() override;
// AZ::InputEventNotificationBus interface
void OnPressed(float value) override;
void OnReleased(float value) override;
void OnHeld(float value) override;
// TickBus interface
void OnTick(float deltaTime, AZ::ScriptTimePoint) override;
private:
ChickenInput CreateInput();
void ProcessInput(const ChickenInput& input);
void UpdateRotation(const ChickenInput& input);
void UpdateVelocity(const ChickenInput& input);
AZ::Vector3 m_velocity = AZ::Vector3::CreateZero();
float m_speed = 10.f;
float m_turnSpeed = 1.f;
float m_forward = 0.f;
float m_strafe = 0.f;
float m_yaw = 0.f;
};
} // namespace MyGem
例 19.2. ChickenControllerComponent.cpp
#include <ChickenControllerComponent.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzFramework/Physics/CharacterBus.h>
namespace MyGem
{
using namespace StartingPointInput;
void ChickenControllerComponent::Reflect(AZ::ReflectContext* rc)
{
if (auto sc = azrtti_cast<AZ::SerializeContext*>(rc))
{
sc->Class<ChickenControllerComponent, AZ::Component>()
->Field("Speed", &ChickenControllerComponent::m_speed)
->Field("Turn Speed",
&ChickenControllerComponent::m_turnSpeed)
->Version(2);
if (AZ::EditContext* ec = sc->GetEditContext())
{
using namespace AZ::Edit;
ec->Class<ChickenControllerComponent>(
"Chicken Controller",
"[Player controlled chicken]")
->ClassElement(ClassElements::EditorData, "")
->Attribute(
Attributes::AppearsInAddComponentMenu,
AZ_CRC_CE("Game"))
->DataElement(nullptr,
&ChickenControllerComponent::m_turnSpeed,
"Turn Speed", "Chicken's turning speed")
->DataElement(nullptr,
&ChickenControllerComponent::m_speed,
"Speed", "Chicken's speed");
}
}
}
void ChickenControllerComponent::Activate()
{
InputEventNotificationBus::MultiHandler::BusConnect(
MoveFwdEventId);
InputEventNotificationBus::MultiHandler::BusConnect(
MoveRightEventId);
InputEventNotificationBus::MultiHandler::BusConnect(
RotateYawEventId);
AZ::TickBus::Handler::BusConnect();
}
void ChickenControllerComponent::Deactivate()
{
AZ::TickBus::Handler::BusDisconnect();
InputEventNotificationBus::MultiHandler::BusDisconnect();
}
void ChickenControllerComponent::OnPressed(float value)
{
const InputEventNotificationId* inputId =
InputEventNotificationBus::GetCurrentBusId();
if (inputId == nullptr)
{
return;
}
if (*inputId == MoveFwdEventId)
{
m_forward = value;
}
else if (*inputId == MoveRightEventId)
{
m_strafe = value;
}
}
void ChickenControllerComponent::OnReleased(float value)
{
const InputEventNotificationId* inputId =
InputEventNotificationBus::GetCurrentBusId();
if (inputId == nullptr)
{
return;
}
if (*inputId == MoveFwdEventId)
{
m_forward = value;
}
else if (*inputId == MoveRightEventId)
{
m_strafe = value;
}
}
void ChickenControllerComponent::OnHeld(float value)
{
const InputEventNotificationId* inputId =
InputEventNotificationBus::GetCurrentBusId();
if (inputId == nullptr)
{
return;
}
if (*inputId == RotateYawEventId)
{
m_yaw = value;
}
}
void ChickenControllerComponent::OnTick(float,
AZ::ScriptTimePoint)
{
const ChickenInput input = CreateInput();
ProcessInput(input);
}
ChickenInput ChickenControllerComponent::CreateInput()
{
ChickenInput input;
input.m_forwardAxis = m_forward;
input.m_strafeAxis = m_strafe;
input.m_viewYaw = m_yaw;
return input;
}
void ChickenControllerComponent::UpdateRotation(
const ChickenInput& input)
{
AZ::TransformInterface* t = GetEntity()->GetTransform();
float currentHeading = t->GetWorldRotationQuaternion().
GetEulerRadians().GetZ();
currentHeading += input.m_viewYaw * m_turnSpeed;
AZ::Quaternion q =
AZ::Quaternion::CreateRotationZ(currentHeading);
t->SetWorldRotationQuaternion(q);
}
void ChickenControllerComponent::UpdateVelocity(
const ChickenInput& input)
{
const float currentHeading = GetEntity()->GetTransform()->
GetWorldRotationQuaternion().GetEulerRadians().GetZ();
const AZ::Vector3 fwd = AZ::Vector3::CreateAxisY(
input.m_forwardAxis);
const AZ::Vector3 strafe = AZ::Vector3::CreateAxisX(
input.m_strafeAxis);
const AZ::Vector3 combined = (fwd + strafe).GetNormalized();
m_velocity = AZ::Quaternion::CreateRotationZ(currentHeading).
TransformVector(combined) * m_speed;
}
void ChickenControllerComponent::ProcessInput(
const ChickenInput& input)
{
UpdateRotation(input);
UpdateVelocity(input);
Physics::CharacterRequestBus::Event(GetEntityId(),
&Physics::CharacterRequestBus::Events::AddVelocity,
m_velocity);
}
} // namespace MyGem