UE4组件和碰撞
简介
学习利用组件将 Pawn与物理交互、使用粒子效果等效果。官方文档地址
例子实现内容:在场景中创建一个可以交互的球体,可以通过键盘输入控制球体移动,并且使用到粒子系统组件,可以由键盘输入控制开启或者关闭粒子特效。
实践
实现思路(步骤)简述:
- ACollidingPawn 类继承自 Pawn;
- 需要有一个可交互的球体组件(SphereComponent),然后给球体组件附上可见的材质;
- 粒子系统组件,附上材质之后,直接附加在静态网格组件上面;
- 使用弹簧臂组件,同时需要创建摄像机组件,并将弹簧臂组件附加在摄像机组件上;
- 将此 Pawn(ACollidingPawn)设置为玩家默认控制;
- 创建用户自定义移动类(UCollidingPawnMovementComponent),并给 UpdatedComponent赋初值 RootComponent
代码
CollidingPawn.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "../HowTo_Components/CollidingPawnMovementComponent.h"
// 确保列最后一个头文件是 "generated.h",否则会造成编译错误
#include "CollidingPawn.generated.h"
UCLASS()
class MYFIRSTCPPDEMO_API ACollidingPawn : public APawn
{
GENERATED_BODY()
public:
// Sets default values for this pawn's properties
ACollidingPawn();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
// 定义粒子系统组件
UPROPERTY()
class UParticleSystemComponent* MyParticleSystem;
// 添加自定义的pawn移动组件
class UCollidingPawnMovementComponent* MyMovementComponent;
// 提供引擎中其他类访问该Pawn当前所用Pawn移动组件的权限
virtual UPawnMovementComponent* GetMovementComponent() const override;
// 处理用户输入的指令移动pawn方法
void MoveForward(float AxisValue);
void MoveRight(float AxisValue);
void Turn(float AxisValue);
void ParticleToggle();
};
ACollidingPawn.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "../HowTo_Components/CollidingPawn.h"
#include "./UObject/ConstructorHelpers.h"
#include "./Particles/ParticleSystemComponent.h"
#include "./Components/SphereComponent.h"
#include "./Camera/CameraComponent.h"
#include "./Gameframework/SpringArmComponent.h"
// Sets default values
ACollidingPawn::ACollidingPawn()
{
// Set this pawn to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
/** 可交互的球体组件 */
USphereComponent* SphereComponent = CreateDefaultSubobject<USphereComponent>(TEXT("RootComponent"));
// 根组件成为对物理反应的球体
RootComponent = SphereComponent;
SphereComponent->InitSphereRadius(40.0f);
SphereComponent->SetCollisionProfileName(TEXT("Pawn"));
/** 静态网格组件 附加半径为50的可见球 */
UStaticMeshComponent* SphereVisual = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("VisualRepresentation"));
SphereVisual->SetupAttachment(SphereComponent);
// 查找材质
const ConstructorHelpers::FObjectFinder<UStaticMesh> SphereVisualAsset(TEXT("StaticMesh'/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));
}
// 创建可激活或停止的粒子系统
MyParticleSystem = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("MovementParticles"));
MyParticleSystem->SetupAttachment(SphereVisual);
// 粒子系统在场景中是否自动启动粒子特效
MyParticleSystem->bAutoActivate = false;
MyParticleSystem->SetRelativeLocation(FVector(-20.0f, 0.0f, 20.0f));
// 查找粒子材质
const ConstructorHelpers::FObjectFinder<UParticleSystem> ParticleAsset(TEXT("ParticleSystem'/Game/StarterContent/Particles/P_Fire.P_Fire'"));
if (ParticleAsset.Succeeded())
{
MyParticleSystem->SetTemplate(ParticleAsset.Object);
}
// SphereComponent->SetupAttachment(MyParticleSystem);
/** 弹簧臂组件 控制视角的弹簧臂组件 使用弹簧臂给予摄像机平滑自然的感觉 */
USpringArmComponent* SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraAttachmentArm"));
SpringArm->SetupAttachment(RootComponent);
// 设置相对位置
SpringArm->SetRelativeLocation(FVector(-45.0f, 0.0f, 0.0f));
// 设置臂长
SpringArm->TargetArmLength = 150.0f;
// 为true,摄像机会有一个滞后效果(摄像机跟随物体移动,有种很漂的感觉)
SpringArm->bEnableCameraLag = false;
SpringArm->CameraLagSpeed = 3.0f;
// 创建摄像机 并附加上弹簧臂
UCameraComponent* Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("ActualCamera"));
Camera->SetupAttachment(SpringArm, USpringArmComponent::SocketName);
/** 将此pawn设置为默认玩家控制 */
AutoPossessPlayer = EAutoReceiveInput::Player0;
/** 创建pawn移动组件 并且与pawn关联 */
MyMovementComponent = CreateDefaultSubobject<UCollidingPawnMovementComponent>(TEXT("CustomMovementComponent"));
MyMovementComponent->UpdatedComponent = RootComponent;
}
// Called when the game starts or when spawned
void ACollidingPawn::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void ACollidingPawn::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// Called to bind functionality to input
void ACollidingPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
/** 绑定pawn移动事件 */
InputComponent->BindAction("ParticleToggle", IE_Pressed, this, &ACollidingPawn::ParticleToggle);
InputComponent->BindAxis("MoveForward", this, &ACollidingPawn::MoveForward);
InputComponent->BindAxis("MoveRight", this, &ACollidingPawn::MoveRight);
InputComponent->BindAxis("Turn", this, &ACollidingPawn::Turn);
}
/** 返回pawn移动组件对象实例 */
UPawnMovementComponent* ACollidingPawn::GetMovementComponent() const
{
return MyMovementComponent;
}
/** 前后移动 */
void ACollidingPawn::MoveForward(float AxisValue)
{
if (MyMovementComponent && (MyMovementComponent->UpdatedComponent == RootComponent))
{
MyMovementComponent->AddInputVector(GetActorForwardVector() * AxisValue);
}
}
/** 左右移动 */
void ACollidingPawn::MoveRight(float AxisValue)
{
if (MyMovementComponent && (MyMovementComponent->UpdatedComponent == RootComponent))
{
MyMovementComponent->AddInputVector(GetActorRightVector() * AxisValue);
}
}
/** 旋转 */
void ACollidingPawn::Turn(float AxisValue)
{
FRotator NewRotation = GetActorRotation();
NewRotation.Yaw += AxisValue;
SetActorRotation(NewRotation);
}
/** 开启或关闭粒子系统 */
void ACollidingPawn::ParticleToggle()
{
if (MyParticleSystem && MyParticleSystem->Template)
{
MyParticleSystem->ToggleActive();
}
}
CollidingPawnMovementComponent.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PawnMovementComponent.h"
#include "CollidingPawnMovementComponent.generated.h"
/**
*
*/
UCLASS()
class MYFIRSTCPPDEMO_API UCollidingPawnMovementComponent : public UPawnMovementComponent
{
GENERATED_BODY()
public:
// pawn移动组件行为
virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
};
UCollidingPawnMovementComponent.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "../HowTo_Components/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中设置移动向量 最大速度被硬编码为每秒150
// ConsumeInputVector 报告并清空用于存储移动输入的内置变量值
FVector DesiredMovenmentThisFrame = ConsumeInputVector().GetClampedToMaxSize(1.0f) * DeltaTime * 150.0f;
if (!DesiredMovenmentThisFrame.IsNearlyZero())
{
FHitResult Hit;
// SafeMoveUpdatedComponent 利用虚幻引擎物理移动Pawn移动组件,同时考虑固体障碍
SafeMoveUpdatedComponent(DesiredMovenmentThisFrame, UpdatedComponent->GetComponentRotation(), true, Hit);
// 如果发生碰撞,尝试滑过去
if (Hit.IsValidBlockingHit())
{
// 移动导致碰撞时, SlideAlongSurface 会处理沿墙壁和斜坡等碰撞表面平滑滑动所涉及的计算和物理,而非直接停留原地,粘在墙壁或斜坡上
SlideAlongSurface(DesiredMovenmentThisFrame, 1.f - Hit.Time, Hit.Normal, Hit);
}
}
}
- 编译完成之后回到 ue4编辑器,做对应的设置 Edit - Project Settings…
- 完成按键的绑定,将
拖拽到地图场景合适位置即可运行测试
后记
- 对于
A->SetupAttachment()
方法,有点迷惑,按照命名的意思就是A对象
给自己设置一个附件;上面的例子,弹簧臂设置一个附件是RootComponent,Camera组件设置的附件是弹簧臂,这些都可以理解,但是粒子系统组件
MyParticleSystem->SetupAttachment(SphereVisual);
,这意思就是粒子系统组件设置一个附件,而这个附件就是附上材质的一个静态网格组件? - 按照自己的想法,应该是静态网格组件上附加一个附件
SphereComponent->SetupAttachment(MyParticleSystem);
,当然,结果是行不通的
开启之后,火焰粒子的生成位置是在一个初始化的位置,并不会依附在玩家球体上。
经过思考和查阅资料后知道,场景中的组件(USceneComponent 以及其子类),可以彼此附加,且父项只有一个;
附加组件的方法有SetupAttachment
和AttachToComponent
; - 综上,弹簧臂组件是在 Camera组件下面,粒子系统组件是在静态网格组件下面的,它们父项都是 RootComponent
笔记中如有理解错误的地方,希望看到的大佬不吝指正,十分感谢。继续加油!