rendering
The original tutorial is based on UE 4.18, I am based on UE 4.25.
English original Address
In this tutorial, create a new c + + class, inheritance UPawnMovementComponent parent class, and named it CollidingPawnMovementComponent. We will use this component in the pawn we create later.
CollidingPawnMovementComponent.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PawnMovementComponent.h"
#include "CollidingPawnMovementComponent.generated.h"
UCLASS(a)class UNREALCPP_API UCollidingPawnMovementComponent : public UPawnMovementComponent
{
GENERATED_BODY(a)public:
virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override;
};
Copy the code
The CPP file will put all the logic into the Tick function to check if we hit anything. First, make sure it is still moving efficiently. Add the following if statement to the Tick function.
void UCollidingPawnMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
// Make sure that everything is still valid, and that we are allowed to move.
if(! PawnOwner || ! UpdatedComponent ||ShouldSkipUpdate(DeltaTime))
{
return; }};Copy the code
Next, we want to get the vector that Pawn generated. We’ll create our Pawn Actor later, but the Pawn will generate a position vector for each frame. First, we want to get the vector of the frame by using the ConsumeInputVector function. Then truncate the vector to 1.0f (no more than 1.f) and multiply by DeltaTime and 150.f. This ensures that Pawn moves smoothly around the world at a maximum speed of 150 units per second. Then we check if the amount of movement is close to zero, and if it is not close to zero (condition: yes! False to enter the branch), we move the pawn safely first, and if it hits a blocking component, move it along the edge of the object it hits.
CollidingPawnMovementComponent.cpp
void UCollidingPawnMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
// Make sure that everything is still valid, and that we are allowed to move.
if(! PawnOwner || ! UpdatedComponent ||ShouldSkipUpdate(DeltaTime))
{
return;
}
// Get (and then clear) the movement vector that we set in ACollidingPawn::Tick
FVector DesiredMovementThisFrame = ConsumeInputVector().GetClampedToMaxSize(1.0 f) * DeltaTime * 150.0 f;
if(! DesiredMovementThisFrame.IsNearlyZero())
{
FHitResult Hit;
SafeMoveUpdatedComponent(DesiredMovementThisFrame, UpdatedComponent->GetComponentRotation(), true, Hit);
// If we bumped into something, try to slide along it
if (Hit.IsValidBlockingHit())
{
SlideAlongSurface(DesiredMovementThisFrame, 1.f- Hit.Time, Hit.Normal, Hit); }}};Copy the code
Now, go ahead and create a new C++ class that inherits from parent Pawn and names it CollidingPawn.
In the header file, we declare variables that will be used in the.cpp file. We will declare our mobile function as well as our particle system and the newly created UCollidingPawnMovementComponent.
We’re going to trigger the particle with a button. We will set the input options later.
Here is the final collidingPawn.h file.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "CollidingPawn.generated.h"
UCLASS(a)class UNREALCPP_API ACollidingPawn : public APawn
{
GENERATED_BODY(a)public:
// Sets default values for this pawn's properties
ACollidingPawn(a);protected:
// Called when the game starts or when spawned
virtual void BeginPlay(a) override;
public:
// Called every frame
virtual void Tick( float DeltaSeconds ) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;
UParticleSystemComponent* OurParticleSystem;
class UCollidingPawnMovementComponent* OurMovementComponent;
virtual UPawnMovementComponent* GetMovementComponent(a) const override;
void MoveForward(float AxisValue);
void MoveRight(float AxisValue);
void Turn(float AxisValue);
void ParticleToggle(a);
};
Copy the code
In the.cpp file, we first make sure the #include includes all the necessary scripts (header files) that will be used throughout the code. Below you’ll see the components and helper scripts we added to the top of the.cpp file, under Pawn’s header file.
#include "CollidingPawn.h"
// include these in your file
#include "Camera/CameraComponent.h"
#include "CollidingPawnMovementComponent.h"
#include "Components/InputComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Components/SphereComponent.h"
#include "ConstructorHelpers.h"
#include "GameFramework/SpringArmComponent.h"
#include "Particles/ParticleSystemComponent.h"
Copy the code
In the constructor of our Pawn, we will set up all the necessary components. First, add a USphereComponent as RootComponent.
ACollidingPawn::ACollidingPawn() {...// Our root component will be a sphere that reacts to physics
USphereComponent* SphereComponent = CreateDefaultSubobject<USphereComponent>(TEXT("RootComponent"));
RootComponent = SphereComponent;
SphereComponent->InitSphereRadius(40.0 f);
SphereComponent->SetCollisionProfileName(TEXT("Pawn"));
}
Copy the code
Next, we’ll add a mesh of spheres to represent and visualize our Pawns. In the code, we use ConstructorHelper to find the mesh of spheres by providing paths.
ACollidingPawn::ACollidingPawn() {...// Create and position a mesh component so we can see where our sphere is
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.0 f.0.0 f.40.0 f));
SphereVisual->SetWorldScale3D(FVector(0.8 f)); }}Copy the code
Next, we add the particle system to pawn. By default, we’ll set its bAutoActivate to false so it won’t start the flame when we start playing. We’re going to control it with a button. Also, notice that we attach this particle system to the SphereVisual grid, not the RootComponent.
ACollidingPawn::ACollidingPawn() {...// Create a particle system that we can activate or deactivate
OurParticleSystem = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("MovementParticles"));
OurParticleSystem->SetupAttachment(SphereVisual);
OurParticleSystem->bAutoActivate = false;
OurParticleSystem->SetRelativeLocation(FVector(20.0 f.0.0 f.20.0 f));
static ConstructorHelpers::FObjectFinder<UParticleSystem> ParticleAsset(TEXT("/Game/StarterContent/Particles/P_Fire.P_Fire"));
if (ParticleAsset.Succeeded())
{
OurParticleSystem->SetTemplate(ParticleAsset.Object); }}Copy the code
Next, add a USpringArmComponent (spring arm component) for camera motion control and attach it to the RootComponent
ACollidingPawn::ACollidingPawn() {...// Use a spring arm to give the camera smooth, natural-feeling motion.
USpringArmComponent* SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraAttachmentArm"));
SpringArm->SetupAttachment(RootComponent);
SpringArm->RelativeRotation = FRotator(-45.f.0.f.0.f);
SpringArm->TargetArmLength = 400.0 f;
SpringArm->bEnableCameraLag = true;
SpringArm->CameraLagSpeed = 3.0 f;
}
Copy the code
Next we create a UCameraComponent and attach it to our SpringArm
ACollidingPawn::ACollidingPawn() {...// Create a camera and attach to our spring arm
UCameraComponent* Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("ActualCamera"));
Camera->SetupAttachment(SpringArm, USpringArmComponent::SocketName);
}
Copy the code
Next, let the pawn control the player immediately (become player 0).
ACollidingPawn::ACollidingPawn() {...// Take control of the default player
AutoPossessPlayer = EAutoReceiveInput::Player0;
}
Copy the code
Then add our custom collision move component to the Pawn
ACollidingPawn::ACollidingPawn() {...// Create an instance of our movement component, and tell it to update our root component.
OurMovementComponent = CreateDefaultSubobject<UCollidingPawnMovementComponent>(TEXT("CustomMovementComponent"));
OurMovementComponent->UpdatedComponent = RootComponent;
}
Copy the code
Here is the final ACollidingPawn() function.
// 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;
// Our root component will be a sphere that reacts to physics
USphereComponent* SphereComponent = CreateDefaultSubobject<USphereComponent>(TEXT("RootComponent"));
RootComponent = SphereComponent;
SphereComponent->InitSphereRadius(40.0 f);
SphereComponent->SetCollisionProfileName(TEXT("Pawn"));
// Create and position a mesh component so we can see where our sphere is
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.0 f.0.0 f.40.0 f));
SphereVisual->SetWorldScale3D(FVector(0.8 f));
}
// Create a particle system that we can activate or deactivate
OurParticleSystem = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("MovementParticles"));
OurParticleSystem->SetupAttachment(SphereVisual);
OurParticleSystem->bAutoActivate = false;
OurParticleSystem->SetRelativeLocation(FVector(20.0 f.0.0 f.20.0 f));
static ConstructorHelpers::FObjectFinder<UParticleSystem> ParticleAsset(TEXT("/Game/StarterContent/Particles/P_Fire.P_Fire"));
if (ParticleAsset.Succeeded())
{
OurParticleSystem->SetTemplate(ParticleAsset.Object);
}
// Use a spring arm to give the camera smooth, natural-feeling motion.
USpringArmComponent* SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraAttachmentArm"));
SpringArm->SetupAttachment(RootComponent);
SpringArm->RelativeRotation = FRotator(-45.f.0.f.0.f);
SpringArm->TargetArmLength = 400.0 f;
SpringArm->bEnableCameraLag = true;
SpringArm->CameraLagSpeed = 3.0 f;
// Create a camera and attach to our spring arm
UCameraComponent* Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("ActualCamera"));
Camera->SetupAttachment(SpringArm, USpringArmComponent::SocketName);
// Take control of the default player
AutoPossessPlayer = EAutoReceiveInput::Player0;
// Create an instance of our movement component, and tell it to update our root component.
OurMovementComponent = CreateDefaultSubobject<UCollidingPawnMovementComponent>(TEXT("CustomMovementComponent"));
OurMovementComponent->UpdatedComponent = RootComponent;
}
Copy the code
With all the necessary components after setting the pawn, let’s continue to input binding to pawn in SetupPlayerInputComponent function. But first, let’s go into the editor and create Action and Axis inputs. In Action Mappings, add a mapping named ParticleToggle and bind it to the P key. Create MoveForward, MoveRight, and Turn inputs in the axis map. Here are their corresponding values.
# MoveForward
W: Scale 1.0
S: Scale 1.0
Up: Scale 1.0
Down: Scale 1.0
Gamepad Left Thumbstick Y-Axis: Scale 1.0
# MoveRight
A: Scale 1.0
D: Scale 1.0
Left: Scale 1.0
Right: Scale 1.0
Gamepad Left Thumbstick X-Axis: Scale 1.0
# Turn
Mouse X: Scale 1.0
Copy the code
Let’s continue to input binding in SetupPlayerInputComponent function to our pawn. Our input will be bound to a function defined later in the code.
void ACollidingPawn::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
Super::SetupPlayerInputComponent(InputComponent);
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);
}
Copy the code
Now we need to set up the Action and Axis functions.
The MoveForward function moves the Pawn forward based on its forward vector.
The MoveRight function moves our pawn forward according to its RightVector.
The Turn function rotates the camera around the Pawn.
The ParticleToggle function is a simple function that toggles the active state of the fire particle between true and false.
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(a); NewRotation.Yaw += AxisValue;SetActorRotation(NewRotation);
}
void ACollidingPawn::ParticleToggle(a)
{
if (OurParticleSystem && OurParticleSystem->Template)
{
OurParticleSystem->ToggleActive();
}
}
Copy the code
Finally, drag and drop your Pawn into the game scene, and you should be able to move the sphere around the world and release fire particles as you did at the beginning of this article.