组件和碰撞(2/2)

4.同时使用Pawn和组件

  1. 要使用自定义 Pawn移动组件,首先需将变量添加到 Pawn 类进行追踪。在CollidingPawn.h中的类定义底部,添加"OurParticleSystem"变量的位置旁,添加: UPROPERTY() class UCollidingPawnMovementComponent* OurMovementComponent; Copy full snippet UPROPERTY() class UCollidingPawnMovementComponent* OurMovementComponent;
  2. 拥有可用于追踪其的位置后,需创建存储在新变量中的 碰撞Pawn移动组件。因此打开 CollidingPawn.cpp ,在文件顶部 GameFramework/Pawn.h 所在一行的下方添加下列行,以便代码引用新类: #include "CollidingPawnMovementComponent.h" Copy full snippet #include "CollidingPawnMovementComponent.h" 请确保列最后一个头文件是 "generated.h",否则会造成编译错误。 创建Pawn移动组件并将其与Pawn关联的方法非常简单。在 ACollidingPawn::ACollidingPawn 底部,添加此代码: // 创建移动组件的实例,并要求其更新根。 OurMovementComponent = CreateDefaultSubobject(TEXT("CustomMovementComponent")); OurMovementComponent->UpdatedComponent = RootComponent; Copy full snippet // 创建移动组件的实例,并要求其更新根。 OurMovementComponent = CreateDefaultSubobject<UCollidingPawnMovementComponent>(TEXT("CustomMovementComponent")); OurMovementComponent->UpdatedComponent = RootComponent; 与目前为止所见其他 组件 不同,无需将此组件附加到自己的组件层级。这是因为其他组件均属于 场景组件 类型,本身需要物理位置。但 运动控制器 并非场景组件,不代表物理对象,因此物理位置上存在或者与另一组件物理连接的概念不适用于此类控制器。
  3. Pawn拥有名为 GetMovementComponent 的函数,用于提供引擎中其他类访问该Pawn当前所用Pawn移动组件的权限。需覆盖该函数,使其返回自定义Pawn移动组件。在 CollidingPawn.h 中的类定义中,需添加: virtual UPawnMovementComponent* GetMovementComponent() const override; Copy full snippet virtual UPawnMovementComponent GetMovementComponent() const override; 而在 CollidingPawn.cpp 中,需按以下所示,添加覆盖函数的定义: UPawnMovementComponent* ACollidingPawn::GetMovementComponent() const { return OurMovementComponent; } Copy full snippet UPawnMovementComponent ACollidingPawn::GetMovementComponent() const { return OurMovementComponent; }
  4. 设置新的Pawn移动组件后,可创建代码处理Pawn会接收的输入。首先在 CollidingPawn.h 中的类定义内声明以下函数: void MoveForward(float AxisValue); void MoveRight(float AxisValue); void Turn(float AxisValue); void ParticleToggle(); Copy full snippet void MoveForward(float AxisValue); void MoveRight(float AxisValue); void Turn(float AxisValue); void ParticleToggle(); 在 CollidingPawn.cpp 中,按以下所示,添加此类函数的定义: void ACollidingPawn::MoveForward(float AxisValue) { if (OurMovementComponent && (OurMovementComponent->UpdatedComponent == RootComponent)) { OurMovementComponent->AddInputVector(GetActorForwardVector() * AxisValue); } } void ACollidingPawn::MoveRight(float AxisValue) { if (OurMovementComponent && (OurMovementComponent->UpdatedComponent == RootComponent)) { OurMovementComponent->AddInputVector(GetActorRightVector() * AxisValue); } } void ACollidingPawn::Turn(float AxisValue) { FRotator NewRotation = GetActorRotation(); NewRotation.Yaw += AxisValue; SetActorRotation(NewRotation); } void ACollidingPawn::ParticleToggle() { if (OurParticleSystem && OurParticleSystem->Template) { OurParticleSystem->ToggleActive(); } } Copy full snippet void ACollidingPawn::MoveForward(float AxisValue) { if (OurMovementComponent && (OurMovementComponent->UpdatedComponent == RootComponent)) { OurMovementComponent->AddInputVector(GetActorForwardVector() * AxisValue); } } void ACollidingPawn::MoveRight(float AxisValue) { if (OurMovementComponent && (OurMovementComponent->UpdatedComponent == RootComponent)) { OurMovementComponent->AddInputVector(GetActorRightVector() * AxisValue); } } void ACollidingPawn::Turn(float AxisValue) { FRotator NewRotation = GetActorRotation(); NewRotation.Yaw += AxisValue; SetActorRotation(NewRotation); } void ACollidingPawn::ParticleToggle() { if (OurParticleSystem && OurParticleSystem->Template) { OurParticleSystem->ToggleActive(); } }
  5. 现在只需将函数绑定到输入事件。将下列代码添加到 ACollidingPawn::SetupPlayerInputComponent: InInputComponent->BindAction("ParticleToggle", IE_Pressed, this, &ACollidingPawn::ParticleToggle); InInputComponent->BindAxis("MoveForward", this, &ACollidingPawn::MoveForward); InInputComponent->BindAxis("MoveRight", this, &ACollidingPawn::MoveRight); InInputComponent->BindAxis("Turn", this, &ACollidingPawn::Turn); Copy full snippet InInputComponent->BindAction("ParticleToggle", IE_Pressed, this, &ACollidingPawn::ParticleToggle); InInputComponent->BindAxis("MoveForward", this, &ACollidingPawn::MoveForward); InInputComponent->BindAxis("MoveRight", this, &ACollidingPawn::MoveRight); InInputComponent->BindAxis("Turn", this, &ACollidingPawn::Turn);
  6. 现已完成编程,可返回 虚幻编辑器 并按 编译 按钮加载更改。

编程工作已结束,现可将自定义Pawn放入场景进行移动。

最终代码

CollidingPawn.h

    // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.

    #pragma once

    #include "CoreMinimal.h"
    #include "GameFramework/Pawn.h"
    #include "CollidingPawn.generated.h"

    UCLASS()
    class HOWTO_COMPONENTS_API ACollidingPawn : public APawn
    {
        GENERATED_BODY()

    public:
        // 设置此Pawn属性的默认值
        ACollidingPawn();

    protected:
        // 游戏开始或生成时调用
        virtual void BeginPlay() override;

    public:
        // 逐帧调用
        virtual void Tick( float DeltaSeconds ) override;

        // 调用以将功能与输入绑定
        virtual void SetupPlayerInputComponent(class UInputComponent* InInputComponent) override;

        UPROPERTY()
        class UParticleSystemComponent* OurParticleSystem;

        UPROPERTY()
        class UCollidingPawnMovementComponent* OurMovementComponent;

        virtual UPawnMovementComponent* GetMovementComponent() const override;

        void MoveForward(float AxisValue);
        void MoveRight(float AxisValue);
        void Turn(float AxisValue);
        void ParticleToggle();
    };

Copy full snippet

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "CollidingPawn.generated.h"

UCLASS()
class HOWTO_COMPONENTS_API ACollidingPawn : public APawn
{
    GENERATED_BODY()

public:
    // 设置此Pawn属性的默认值
    ACollidingPawn();

protected:
    // 游戏开始或生成时调用
    virtual void BeginPlay() override;

public:
    // 逐帧调用
    virtual void Tick( float DeltaSeconds ) override;

    // 调用以将功能与输入绑定
    virtual void SetupPlayerInputComponent(class UInputComponent* InInputComponent) override;

    UPROPERTY()
    class UParticleSystemComponent* OurParticleSystem;

    UPROPERTY()
    class UCollidingPawnMovementComponent* OurMovementComponent;

    virtual UPawnMovementComponent* GetMovementComponent() const override;

    void MoveForward(float AxisValue);
    void MoveRight(float AxisValue);
    void Turn(float AxisValue);
    void ParticleToggle();
};

CollidingPawn.cpp

    // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.

    #include "CollidingPawn.h"
    #include "CollidingPawnMovementComponent.h"
    #include "UObject/ConstructorHelpers.h"
    #include "Particles/ParticleSystemComponent.h"
    #include "Components/SphereComponent.h"
    #include "Camera/CameraComponent.h"
    #include "GameFramework/SpringArmComponent.h"

    // 设置默认值
    ACollidingPawn::ACollidingPawn()
    {
        // 设置该Pawn以逐帧调用Tick()。如无需此功能,可关闭以提高性能。
        PrimaryActorTick.bCanEverTick = true;

        // 根组件将成为对物理反应的球体
        USphereComponent* SphereComponent = CreateDefaultSubobject(TEXT("RootComponent"));
        RootComponent = SphereComponent;
        SphereComponent->InitSphereRadius(40.0f);
        SphereComponent->SetCollisionProfileName(TEXT("Pawn"));

        // 创建并放置网格体组件,以便查看球体位置
        UStaticMeshComponent* SphereVisual = CreateDefaultSubobject(TEXT("VisualRepresentation"));
        SphereVisual->SetupAttachment(RootComponent);
        static ConstructorHelpers::FObjectFinder SphereVisualAsset(TEXT("/Game/StarterContent/Shapes/Shape_Sphere.Shape_Sphere"));
        if (SphereVisualAsset.Succeeded())
        {
            SphereVisual->SetStaticMesh(SphereVisualAsset.Object);
            SphereVisual->SetRelativeLocation(FVector(0.0f, 0.0f, -40.0f));
            SphereVisual->SetWorldScale3D(FVector(0.8f));
        }

        // 创建可激活或停止的粒子系统
        OurParticleSystem = CreateDefaultSubobject(TEXT("MovementParticles"));
        OurParticleSystem->SetupAttachment(SphereVisual);
        OurParticleSystem->bAutoActivate = false;
        OurParticleSystem->SetRelativeLocation(FVector(-20.0f, 0.0f, 20.0f));
        static ConstructorHelpers::FObjectFinder ParticleAsset(TEXT("/Game/StarterContent/Particles/P_Fire.P_Fire"));
        if (ParticleAsset.Succeeded())
        {
            OurParticleSystem->SetTemplate(ParticleAsset.Object);
        }

        // 使用弹簧臂给予摄像机平滑自然的运动感。
        USpringArmComponent* SpringArm = CreateDefaultSubobject(TEXT("CameraAttachmentArm"));
        SpringArm->SetupAttachment(RootComponent);
        SpringArm->SetRelativeRotation(FRotator(-45.f, 0.f, 0.f));
        SpringArm->TargetArmLength = 400.0f;
        SpringArm->bEnableCameraLag = true;
        SpringArm->CameraLagSpeed = 3.0f;

        // 创建摄像机并附加到弹簧臂
        UCameraComponent* Camera = CreateDefaultSubobject(TEXT("ActualCamera"));
        Camera->SetupAttachment(SpringArm, USpringArmComponent::SocketName);

        // 控制默认玩家
        AutoPossessPlayer = EAutoReceiveInput::Player0;

        // 创建移动组件的实例,并要求其更新根组件。
        OurMovementComponent = CreateDefaultSubobject(TEXT("CustomMovementComponent"));
        OurMovementComponent->UpdatedComponent = RootComponent;
    }

    // 游戏开始或生成时调用
    void ACollidingPawn::BeginPlay()
    {
        Super::BeginPlay();

    }

    // 逐帧调用
    void ACollidingPawn::Tick( float DeltaTime )
    {
        Super::Tick( DeltaTime );

    }

    // 调用以将功能与输入绑定
    void ACollidingPawn::SetupPlayerInputComponent(class UInputComponent* InInputComponent)
    {
        Super::SetupPlayerInputComponent(InInputComponent);

        InInputComponent->BindAction("ParticleToggle", IE_Pressed, this, &ACollidingPawn::ParticleToggle);

        InInputComponent->BindAxis("MoveForward", this, &ACollidingPawn::MoveForward);
        InInputComponent->BindAxis("MoveRight", this, &ACollidingPawn::MoveRight);
        InInputComponent->BindAxis("Turn", this, &ACollidingPawn::Turn);
    }

    UPawnMovementComponent* ACollidingPawn::GetMovementComponent() const
    {
        return OurMovementComponent;
    }

    void ACollidingPawn::MoveForward(float AxisValue)
    {
        if (OurMovementComponent && (OurMovementComponent->UpdatedComponent == RootComponent))
        {
            OurMovementComponent->AddInputVector(GetActorForwardVector() * AxisValue);
        }
    }

    void ACollidingPawn::MoveRight(float AxisValue)
    {
        if (OurMovementComponent && (OurMovementComponent->UpdatedComponent == RootComponent))
        {
            OurMovementComponent->AddInputVector(GetActorRightVector() * AxisValue);
        }
    }

    void ACollidingPawn::Turn(float AxisValue)
    {
        FRotator NewRotation = GetActorRotation();
        NewRotation.Yaw += AxisValue;
        SetActorRotation(NewRotation);
    }

    void ACollidingPawn::ParticleToggle()
    {
        if (OurParticleSystem && OurParticleSystem->Template)
        {
            OurParticleSystem->ToggleActive();
        }
    }

Copy full snippet

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.

#include "CollidingPawn.h"
#include "CollidingPawnMovementComponent.h"
#include "UObject/ConstructorHelpers.h"
#include "Particles/ParticleSystemComponent.h"
#include "Components/SphereComponent.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"

// 设置默认值
ACollidingPawn::ACollidingPawn()
{
    // 设置该Pawn以逐帧调用Tick()。如无需此功能,可关闭以提高性能。
    PrimaryActorTick.bCanEverTick = true;

    // 根组件将成为对物理反应的球体
    USphereComponent* SphereComponent = CreateDefaultSubobject&lt;USphereComponent>(TEXT("RootComponent"));
    RootComponent = SphereComponent;
    SphereComponent->InitSphereRadius(40.0f);
    SphereComponent->SetCollisionProfileName(TEXT("Pawn"));

    // 创建并放置网格体组件,以便查看球体位置
    UStaticMeshComponent* SphereVisual = CreateDefaultSubobject&lt;UStaticMeshComponent>(TEXT("VisualRepresentation"));
    SphereVisual->SetupAttachment(RootComponent);
    static ConstructorHelpers::FObjectFinder&lt;UStaticMesh> SphereVisualAsset(TEXT("/Game/StarterContent/Shapes/Shape_Sphere.Shape_Sphere"));
    if (SphereVisualAsset.Succeeded())
    {
        SphereVisual->SetStaticMesh(SphereVisualAsset.Object);
        SphereVisual->SetRelativeLocation(FVector(0.0f, 0.0f, -40.0f));
        SphereVisual->SetWorldScale3D(FVector(0.8f));
    }

    // 创建可激活或停止的粒子系统
    OurParticleSystem = CreateDefaultSubobject&lt;UParticleSystemComponent>(TEXT("MovementParticles"));
    OurParticleSystem->SetupAttachment(SphereVisual);
    OurParticleSystem->bAutoActivate = false;
    OurParticleSystem->SetRelativeLocation(FVector(-20.0f, 0.0f, 20.0f));
    static ConstructorHelpers::FObjectFinder&lt;UParticleSystem> ParticleAsset(TEXT("/Game/StarterContent/Particles/P_Fire.P_Fire"));
    if (ParticleAsset.Succeeded())
    {
        OurParticleSystem->SetTemplate(ParticleAsset.Object);
    }

    // 使用弹簧臂给予摄像机平滑自然的运动感。
    USpringArmComponent* SpringArm = CreateDefaultSubobject&lt;USpringArmComponent>(TEXT("CameraAttachmentArm"));
    SpringArm->SetupAttachment(RootComponent);
    SpringArm->SetRelativeRotation(FRotator(-45.f, 0.f, 0.f));
    SpringArm->TargetArmLength = 400.0f;
    SpringArm->bEnableCameraLag = true;
    SpringArm->CameraLagSpeed = 3.0f;

    // 创建摄像机并附加到弹簧臂
    UCameraComponent* Camera = CreateDefaultSubobject&lt;UCameraComponent>(TEXT("ActualCamera"));
    Camera->SetupAttachment(SpringArm, USpringArmComponent::SocketName);

    // 控制默认玩家
    AutoPossessPlayer = EAutoReceiveInput::Player0;

    // 创建移动组件的实例,并要求其更新根组件。
    OurMovementComponent = CreateDefaultSubobject&lt;UCollidingPawnMovementComponent>(TEXT("CustomMovementComponent"));
    OurMovementComponent->UpdatedComponent = RootComponent;
}

// 游戏开始或生成时调用
void ACollidingPawn::BeginPlay()
{
    Super::BeginPlay();

}

// 逐帧调用
void ACollidingPawn::Tick( float DeltaTime )
{
    Super::Tick( DeltaTime );

}

// 调用以将功能与输入绑定
void ACollidingPawn::SetupPlayerInputComponent(class UInputComponent* InInputComponent)
{
    Super::SetupPlayerInputComponent(InInputComponent);

    InInputComponent->BindAction("ParticleToggle", IE_Pressed, this, &ACollidingPawn::ParticleToggle);

    InInputComponent->BindAxis("MoveForward", this, &ACollidingPawn::MoveForward);
    InInputComponent->BindAxis("MoveRight", this, &ACollidingPawn::MoveRight);
    InInputComponent->BindAxis("Turn", this, &ACollidingPawn::Turn);
}

UPawnMovementComponent* ACollidingPawn::GetMovementComponent() const
{
    return OurMovementComponent;
}

void ACollidingPawn::MoveForward(float AxisValue)
{
    if (OurMovementComponent && (OurMovementComponent->UpdatedComponent == RootComponent))
    {
        OurMovementComponent->AddInputVector(GetActorForwardVector() * AxisValue);
    }
}

void ACollidingPawn::MoveRight(float AxisValue)
{
    if (OurMovementComponent && (OurMovementComponent->UpdatedComponent == RootComponent))
    {
        OurMovementComponent->AddInputVector(GetActorRightVector() * AxisValue);
    }
}

void ACollidingPawn::Turn(float AxisValue)
{
    FRotator NewRotation = GetActorRotation();
    NewRotation.Yaw += AxisValue;
    SetActorRotation(NewRotation);
}

void ACollidingPawn::ParticleToggle()
{
    if (OurParticleSystem && OurParticleSystem->Template)
    {
        OurParticleSystem->ToggleActive();
    }
}

CollidingPawnMovementComponent.h

    // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.

    #pragma once

    #include "CoreMinimal.h"
    #include "GameFramework/PawnMovementComponent.h"
    #include "CollidingPawnMovementComponent.generated.h"

    /**
        *
        */
    UCLASS()
    class HOWTO_COMPONENTS_API UCollidingPawnMovementComponent : public UPawnMovementComponent
    {
        GENERATED_BODY()

    public:
        virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override;
    };

Copy full snippet

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PawnMovementComponent.h"
#include "CollidingPawnMovementComponent.generated.h"

/**
    *
    */
UCLASS()
class HOWTO_COMPONENTS_API UCollidingPawnMovementComponent : public UPawnMovementComponent
{
    GENERATED_BODY()

public:
    virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override;
};

CollidingPawnMovementComponent.cpp

    // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.

    #include "CollidingPawnMovementComponent.h"

    void UCollidingPawnMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
    {
        Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

        // 确保所有事物持续有效,以便进行移动。
        if (!PawnOwner || !UpdatedComponent || ShouldSkipUpdate(DeltaTime))
        {
            return;
        }

        // 获取(然后清除)ACollidingPawn::Tick中设置的移动向量
        FVector DesiredMovementThisFrame = ConsumeInputVector().GetClampedToMaxSize(1.0f) * DeltaTime * 150.0f;
        if (!DesiredMovementThisFrame.IsNearlyZero())
        {
            FHitResult Hit;
            SafeMoveUpdatedComponent(DesiredMovementThisFrame, UpdatedComponent->GetComponentRotation(), true, Hit);

            // 若发生碰撞,尝试滑过去
            if (Hit.IsValidBlockingHit())
            {
                SlideAlongSurface(DesiredMovementThisFrame, 1.f - Hit.Time, Hit.Normal, Hit);
            }
        }
    };

Copy full snippet

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.

#include "CollidingPawnMovementComponent.h"

void UCollidingPawnMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
{
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

    // 确保所有事物持续有效,以便进行移动。
    if (!PawnOwner || !UpdatedComponent || ShouldSkipUpdate(DeltaTime))
    {
        return;
    }

    // 获取(然后清除)ACollidingPawn::Tick中设置的移动向量
    FVector DesiredMovementThisFrame = ConsumeInputVector().GetClampedToMaxSize(1.0f) * DeltaTime * 150.0f;
    if (!DesiredMovementThisFrame.IsNearlyZero())
    {
        FHitResult Hit;
        SafeMoveUpdatedComponent(DesiredMovementThisFrame, UpdatedComponent->GetComponentRotation(), true, Hit);

        // 若发生碰撞,尝试滑过去
        if (Hit.IsValidBlockingHit())
        {
            SlideAlongSurface(DesiredMovementThisFrame, 1.f - Hit.Time, Hit.Normal, Hit);
        }
    }
};

5.在编辑器中运行

  1. 在 虚幻编辑器 中,可按 编译 按钮加载代码更改。 组件和碰撞(2/2) - 图1
  2. 需将 CollidingPawn 实例放入场景。可在 内容浏览器 的"C++ Classes/HowTo_Components/CollidingPawn"下找到该类。 组件和碰撞(2/2) - 图2 组件和碰撞(2/2) - 图3
  3. 按 运行,球体便会随WASD移动,随鼠标转动,并在场景中的物体(如桌子和椅子)上碰撞和滑动,或需要放入场景的任何物理对象。还可使用空格键向自己点火(或扑灭)! 组件和碰撞(2/2) - 图4

虚幻引擎 提供各式 组件 - 目前仅简略介绍了部分常用组件。请尝试探究内置组件,或自己编写组件!组件灵活且功能强大,可协助项目代码保持整洁和可重用性。

6.自行尝试!

利用所学内容,尝试以下操作:

  • 创建在轨道上自动围绕父项运行的 组件。
  • 编译最多生成三个子项的组件,此类子项一段时间后会自行消失。
  • 了解通过组件将完整 Actor 附加到其他Actor的方法。