学习利用组件将Pawn与物理交互、使用粒子效果等方法。
创建 Pawn 子类
为了能对 Actor 进行控制, 创建 Pawn 子类并命名为 CollidingPawn
添加粒子系统组件
创建组件
为了实现碰撞, 需要创建具有物理碰撞性质的组件, 球体组件是一种可与游戏场景交互并碰撞的物理实体, 本例子使用球体组件。同时本例子还需要负责显示形态的静态网格体组件, 可附加的摄像机组件, 控制游戏视角的弹簧臂组件以及可控制的粒子系统组件. 有些组件需要包含头文件才能编译成功:
#include "UObject/ConstructorHelpers.h"
#include "Particles/ParticleSystemComponent.h"
#include "Components/SphereComponent.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"
同时在类中加入粒子系统组件类型的成员变量:
UPROPERTY()
class UParticleSystemComponent* OurParticleSystem;
- 构造函数
// 创建球体组件并将其设置为根组件 USphereComponent* SphereComponent = CreateDefaultSubobject<USphereComponent>(TEXT("RootComponent")); RootComponent = SphereComponent; SphereComponent->InitSphereRadius(40.0f); SphereComponent->SetCollisionProfileName(TEXT("Pawn")); // 创建并放置网格体组件,以便查看球体位置 UStaticMeshComponent* SphereVisual = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("VisualRepresentation")); SphereVisual->SetupAttachment(RootComponent); static ConstructorHelpers::FObjectFinder<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<UParticleSystemComponent>(TEXT("MovementParticles")); OurParticleSystem->SetupAttachment(SphereVisual); OurParticleSystem->bAutoActivate = false; OurParticleSystem->SetRelativeLocation(FVector(-20.0f, 0.0f, 20.0f)); static ConstructorHelpers::FObjectFinder<UParticleSystem> ParticleAsset(TEXT("/Game/StarterContent/Particles/P_Fire.P_Fire")); if (ParticleAsset.Succeeded()) { OurParticleSystem->SetTemplate(ParticleAsset.Object); } // 使用弹簧臂给予摄像机平滑自然的运动感。 USpringArmComponent* SpringArm = CreateDefaultSubobject<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<UCameraComponent>(TEXT("ActualCamera")); Camera->SetupAttachment(SpringArm, USpringArmComponent::SocketName); // 控制默认玩家 AutoPossessPlayer = EAutoReceiveInput::Player0;
配置游戏输入
在编辑器中,编辑->项目设置->输入, 在绑定中添加:
操作映射 | ||
---|---|---|
ParticleToggle | 空格 | |
轴映射 | ||
MoveForward | W | 1.0 |
S | -1.0 | |
MoveRight | A | -1.0 |
D | 1.0 | |
Turn | Mouse X | 1.0 |
创建PawnMovementComponent
子类
PawnMovementComponent (Pawn移动组件) 拥有部分强大内置功能,以便使用常见物理功能,同时便于在大量Pawn类型间共享移动代码。使用组件分隔不同功能是上佳方法,可在项目增大、Pawn越加复杂时减少杂乱。创建PawnMovementComponent
子类并将其命名为 CollidingPawnMovementComponent
-
编写Pawn移动组件代码
为自定义Pawn移动组件编写代码。只需编写 TickComponent 函数(类似Actor的 Tick 函数)告知逐帧移动方式。在 CollidingPawnMovementComponent.h 中,需在类定义中覆盖 TickComponent:public: virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override;
在 CollidingPawnMovementComponent.cpp 中定义此函数:
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); } } };
此 TickComponent 函数使用 UPawnMovementComponent 类提供的几种强大功能ConsumeInputVector 报告并清空用于存储移动输入的内置变量值。
SafeMoveUpdatedComponent 利用虚幻引擎物理移动Pawn移动组件,同时考虑固体障碍。移动导致碰撞时, SlideAlongSurface 会处理沿墙壁和斜坡等碰撞表面平滑滑动所涉及的计算和物理,而非直接停留原地,粘在墙壁或斜坡上。
Pawn移动组件中还包含众多值得探究的功能,但本例子范围中暂时无需使用。
同时使用Pawn和组件
在 CollidingPawn.h中添加我们自定义的Pawn移动组件
UPROPERTY()
class UCollidingPawnMovementComponent* OurMovementComponent;
添加头文件
#include "CollidingPawnMovementComponent.h"
须确保列最后一个头文件是 "generated.h",否则会造成编译错误。
-
在构造函数中创建移动组件的实例
// 创建移动组件的实例,并要求其更新根。 OurMovementComponent = CreateDefaultSubobject<UCollidingPawnMovementComponent>(TEXT("CustomMovementComponent")); OurMovementComponent->UpdatedComponent = RootComponent;
PawnMovementComponent 组件和场景组件的后代不同, 只有场景组件(及其子类)才需要和可以彼此附加, 相反的是他需要设置 UpdatedComponent, 用来告知物理效果应该更新到哪个组件.
-
重写
GetMovementComponent
函数
Pawn拥有名为 GetMovementComponent 的函数,用于提供引擎中其他类访问该Pawn当前所用Pawn移动组件的权限. 因此在类中添加virtual UPawnMovementComponent* GetMovementComponent() const override;
并实现该方法
UPawnMovementComponent* ACollidingPawn::GetMovementComponent() const { return OurMovementComponent; }
-
最后设置输入事件的响应
类中添加声明响应事件的成员函数void MoveForward(float AxisValue); void MoveRight(float AxisValue); void Turn(float AxisValue); void ParticleToggle();
实现这些函数
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(); } }
最后绑定事件
PlayerInputComponent->BindAction("ParticleToggle", IE_Pressed, this, &ACollidingPawn::ParticleToggle); PlayerInputComponent->BindAxis("MoveForward", this, & ACollidingPawn::MoveForward); PlayerInputComponent->BindAxis("MoveRight", this, & ACollidingPawn::MoveRight); PlayerInputComponent->BindAxis("Turn", this, &ACollidingPawn::Turn);