本文内容
第18章 角色移动
第18章 角色移动
介绍
注意:本章的源代码可以在 GitHub 上找到: https://github.com/AMZN-Olex/O3DEBookCode2111/tree/ch18_character_movement
上一章捕获了输入。本章将通过更新 Chicken Controller 组件的代码,根据该输入移动 Chicken。按 W 或 S 将向前和向后移动鸡肉,而按 A 或 D 将使其向左和向右移动。
PhysX Character Controller
为了移动鸡,我们将使用 PhysX Character Controller (PhysX 角色控制器) 组件。我们的项目已经启用了 PhysX,因此将显示组件。如果没有,请检查 PhysX Gem 是否列在 MyProject\Code\enabled_gems.cmake 中:
set(ENABLED_GEMS
...
PhysX
)
- 将鸡实体与预制件分离。我们将构建一个新的 chicken 预制件。
右键单击→分离预制件
- 这会将实体从预制件中直接放到关卡中。
注意:当您分离预制件时,Editor 将创建一个具有该预制件名称的新实体,并将所有预制件实体放置在该实体下。然后,将删除预制件实例,留下实体。
从 Chicken_Actor.prefab 分离的实体
- 将 PhysX Character Controller (PhysX 角色控制器) 组件添加到Chicken_Actor实体。
PhysX Character Controller component
对于 Shape 属性上的控制器形状,您有几个选项。我使用了一个胶囊,并使用胶囊部分中的 Height 和 Radius 属性修改了胶囊形状以匹配鸡。
从 Atom Environment 实体下删除摄像机实体
在实体下创建带有摄像机Chicken_Action实体。这样,当鸡在关卡中移动时,摄像机将跟随它。
带相机的鸡
- 将子实体 Camera 移动到离鸡有一定距离的地方,以创建舒适的第三人称视角
带有 Character 组件的鸡
现在,鸡角色已经设置了实体,我们可以改进 ChickenControllerComponent 来响应玩家的输入
移动角色
您可以通过调用 PhysX Character Controller (PhysX 角色控制器) 组件的公共 API 来移动组件。其公共接口可在 AzFramework\Physics\CharacterBus.h 中找到。在本章中,我们将使用 AddVelocity:
virtual void AddVelocity(const AZ::Vector3& velocity) = 0;
这将为 PhysX 要模拟的下一帧增加一定量的速度,从而在物理世界中移动角色。
提示:您可以在每帧中多次调用 AddVelocity。角色会将它们一起累积,例如分别添加玩家移动和重力。
图 18.1.移动鸡的设计
以下是实现前进和后退运动的步骤。
- ChickenControllerComponent 将注册游戏 tick 事件。
class ChickenControllerComponent
: public AZ::Component
, public AZ::TickBus::Handler
- 在每个 tick 上,我们将收集 input 并对其进行处理。
void ChickenControllerComponent::OnTick(float, AZ::ScriptTimePoint)
{
const ChickenInput input = CreateInput();
ProcessInput(input);
}
- 输入最初保存在 OnPressed 和 OnReleased 方法中。
void ChickenControllerComponent::OnPressed(float value)
{
const InputEventNotificationId* inputId =
InputEventNotificationBus::GetCurrentBusId();
if (inputId == nullptr)
{
return;
}
if (*inputId == MoveFwdEventId)
{
m_forward = value;
}
}
- 在刻度时,输入值将复制到结构 ChickenInput 中。
ChickenInput ChickenControllerComponent::CreateInput()
{
ChickenInput input;
input.m_forwardAxis = m_forward;
return input;
}
- ChickenInput 使用 PhysX Character Controller 组件 API 进行处理并应用于角色。
void ChickenControllerComponent::ProcessInput(const ChickenInput& input)
{
UpdateVelocity(input);
Physics::CharacterRequestBus::Event(GetEntityId(),
&Physics::CharacterRequestBus::Events::AddVelocity,
m_velocity);
}
- UpdateVelocity 计算角色的移动方向,并将结果保存到 AZ::Vector3 类型的m_velocity中。
void ChickenControllerComponent::UpdateVelocity(const ChickenInput& input)
{
float currentHeading = GetEntity()->GetTransform()->
GetWorldRotationQuaternion().GetEulerRadians().GetZ();
AZ::Vector3 fwd = AZ::Vector3::CreateAxisY(
input.m_forwardAxis);
m_velocity = AZ::Quaternion::CreateRotationZ(currentHeading).
TransformVector(fwd) * m_speed;
}
注意:此处的数学运算获取角色面对的方向,并使用玩家输入值和鸡的速度在该方向上创建一个向量。
通过这些更改,当您按 W 时,鸡将向前移动,当您按 S 时,鸡将向后移动。
相机将跟随,因为相机组件已附加到鸡的子实体。
图 18.2.在游戏模式下使用 CTRL+G 的鸡
我们将在第 20 章 为世界添加物理特性 中为鸡添加引力。
源代码
注意:本章的源代码可以在 GitHub 上找到: https://github.com/AMZN-Olex/O3DEBookCode2111/tree/ch18_character_movement
这是 ChickenControllerComponent 的更新代码。您可以通过增强 CreateInput 和 ProcessInput 来使用相同的方法添加扫射和其他移动。
例 18.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");
class ChickenInput
{
public:
float m_forwardAxis = 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* context);
// AZ::Component interface implementation
void Activate() override;
void Deactivate() override;
// AZ::InputEventNotificationBus interface
void OnPressed(float value) override;
void OnReleased(float value) override;
// TickBus interface
void OnTick(float deltaTime, AZ::ScriptTimePoint) override;
private:
ChickenInput CreateInput();
void ProcessInput(const ChickenInput& input);
void UpdateVelocity(const ChickenInput& input);
AZ::Vector3 m_velocity = AZ::Vector3::CreateZero();
float m_speed = 6.f;
float m_forward = 0.f;
};
} // namespace MyGem
例 18.2.ChickenControllerComponent.cpp with motion
#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)
->Version(1);
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_speed,
"Speed", "Chicken's speed");
}
void ChickenControllerComponent::Activate()
{
InputEventNotificationBus::MultiHandler::BusConnect(
MoveFwdEventId);
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;
}
}
void ChickenControllerComponent::OnReleased(float)
{
const InputEventNotificationId* inputId =
InputEventNotificationBus::GetCurrentBusId();
if (inputId == nullptr)
{
return;
}
if (*inputId == MoveFwdEventId)
{
m_forward = 0.f;
}
}
void ChickenControllerComponent::OnTick(float,
AZ::ScriptTimePoint)
{
const ChickenInput input = CreateInput();
ProcessInput(input);
}
ChickenInput ChickenControllerComponent::CreateInput()
{
ChickenInput input;
input.m_forwardAxis = m_forward;
return input;
}
void ChickenControllerComponent::UpdateVelocity(
const ChickenInput& input)
{
const float currentHeading = GetEntity()->GetTransform()->
GetWorldRotationQuaternion().GetEulerRadians().GetZ();
const AZ::Vector3 fwd = AZ::Vector3::CreateAxisY(
input.m_forwardAxis);
m_velocity = AZ::Quaternion::CreateRotationZ(currentHeading).
TransformVector(fwd) * m_speed;
}
void ChickenControllerComponent::ProcessInput(
const ChickenInput& input)
{
UpdateVelocity(input);
Physics::CharacterRequestBus::Event(GetEntityId(),
&Physics::CharacterRequestBus::Events::AddVelocity,
m_velocity);
}
} // namespace MyGem