Version:

第18章 角色移动

第18章 角色移动

介绍

注意:

上一章捕获了输入。本章将通过更新 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
)
  1. 将鸡实体与预制件分离。我们将构建一个新的 chicken 预制件。

右键单击→分离预制件

  1. 这会将实体从预制件中直接放到关卡中。
注意:
当您分离预制件时,Editor 将创建一个具有该预制件名称的新实体,并将所有预制件实体放置在该实体下。然后,将删除预制件实例,留下实体。

从 Chicken_Actor.prefab 分离的实体

  1. 将 PhysX Character Controller (PhysX 角色控制器) 组件添加到Chicken_Actor实体。

PhysX Character Controller component

  1. 对于 Shape 属性上的控制器形状,您有几个选项。我使用了一个胶囊,并使用胶囊部分中的 Height 和 Radius 属性修改了胶囊形状以匹配鸡。

  2. 从 Atom Environment 实体下删除摄像机实体

  3. 在实体下创建带有摄像机Chicken_Action实体。这样,当鸡在关卡中移动时,摄像机将跟随它。

带相机的鸡

  1. 将子实体 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.移动鸡的设计

以下是实现前进和后退运动的步骤。

  1. ChickenControllerComponent 将注册游戏 tick 事件。
class ChickenControllerComponent
: public AZ::Component
, public AZ::TickBus::Handler
  1. 在每个 tick 上,我们将收集 input 并对其进行处理。
void ChickenControllerComponent::OnTick(float, AZ::ScriptTimePoint)
{
  const ChickenInput input = CreateInput(); 
  ProcessInput(input);
}
  1. 输入最初保存在 OnPressed 和 OnReleased 方法中。
void ChickenControllerComponent::OnPressed(float value)
{
  const InputEventNotificationId* inputId =
  InputEventNotificationBus::GetCurrentBusId();
  if (inputId == nullptr)
  {
    return;
  }
  if (*inputId == MoveFwdEventId)
  {
    m_forward = value;
  }
}
  1. 在刻度时,输入值将复制到结构 ChickenInput 中。
ChickenInput ChickenControllerComponent::CreateInput()
{
  ChickenInput input;
  input.m_forwardAxis = m_forward;
  return input;
}
  1. ChickenInput 使用 PhysX Character Controller 组件 API 进行处理并应用于角色。
void ChickenControllerComponent::ProcessInput(const ChickenInput& input)
{
  UpdateVelocity(input);
  Physics::CharacterRequestBus::Event(GetEntityId(),
  &Physics::CharacterRequestBus::Events::AddVelocity,
  m_velocity);
}
  1. 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 章 为世界添加物理特性 中为鸡添加引力。

源代码

注意:

这是 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