Movement in Unreal Engine
Movement is usually processed in each frame, and as the frame time is not constant over time, movement is being used together with float DeltaTime
value in Unreal Engine.
DeltaTime
or also referred to as elapsed time is the time difference between the previous frame that was drawn and the current frame. Why is it so important? Shortly, by multiplying by DeltaTime
we remove the dependence of the number of FPS (frames per second) on the speed of movement.
In Unreal Engine, DeltaTime
is in seconds, then fps (frames per second) is calculate as fps = 1 / DeltaTime.
Deltatime
value can be obtained by:
- It's a parameter of
Tick
function - By asking
UGameplayStatics::GetWorldDeltaSeconds(this);
Movement initializators
-
Movement based on user's axes and actions input
SetupPlayerInputComponent
function is defined inAPawn
class (and so it's available in all classes inherited fromAPawn
. -
AI Movement
Functions
MoveToLocation()
andSimpleMoveToLocation()
processes AI Movement using a NavMesh from current Location to a defined location. -
Movement with predefined trajectory and timing
Trajectory can be processed through AI MoveToLocation movement between points from the trajectory table or by converting the defined trajectory into inputs.
Actors Movement
Following examples presents a movement processed through functions defined in AActor
class. As so, they are available in all classes inherited from AActor
.
Take into notice, e.g. a Character animation is not processed automatically based on the movement processed through the function.
-
Example 1 -
AActor::AddActorLocalOffset()
AExample::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); PlayerInputComponent->BindAxis(TEXT("MoveForward"), this, &AExample::MoveForward); PlayerInputComponent->BindAxis(TEXT("TurnToSides"), this, &AExample::TurnRight); } void AExample::MoveForward(float value) { FVector DeltaLocation = FVector::ZeroVector; DeltaLocation.X = value * UGameplayStatics::GetWorldDeltaSeconds(this) * optionalSpeedCoefficient; bool bSweep = true; AddActorLocalOffset(DeltaLocation, bSweep); // If we work with a world space: //AddActorWorldOffset(DeltaLocation, bSweep); } void AExample::TurnRight(float value) { FRotator DeltaRotation = FRotator::ZeroRotator; DeltaRotation.Yaw = value * UGameplayStatics::GetWorldDeltaSeconds(this) * optionalTurnRateCoefficient; bool bSweep = true; AddActorLocalRotation(DeltaRotation, bSweep); }
Example 2 -
AActor::SetActorLocation()
void AExampleClass::Tick(float DeltaTime) { FVector NewLocation = FMath::Lerp(StartLocation, TargetLocation, CurrentTime / MovementTime); SetActorLocation(NewLocation, bSweep); }
Pawn Movement
-
Example 1 -
APawn::AddMovementInput()
ASimulationCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { // proces parents' classes functionality Super::SetupPlayerInputComponent(PlayerInputComponent); // keyboard - axis - deltaTime included automatically by the class function PlayerInputComponent->BindAxis(TEXT("MoveForward"), this, &AExample::MoveForward); PlayerInputComponent->BindAxis(TEXT("MoveRight"), this, &AExample::MoveRight); // keyboard - actions PlayerInputComponent->BindAction(TEXT("Jump"), EInputEvent::IE_Pressed, this, &Character::Jump) // mouse PlayerInputComponent->BindAxis(TEXT("LookUp"), this, &APawn::AddControllerPitchInput); PlayerInputComponent->BindAxis(TEXT("LookRight"), this, &APawn::AddControllerYawInput); // controller PlayerInputComponent->BindAxis(TEXT("LookUpRate"), this, &ASimulationCharacter::LookUpRate); } void ASimulationCharacter::MoveForward(float AxisValue) { AddMovementInput(GetActorForwardVector() * AxisValue); } void ASimulationCharacter::MoveRight(float AxisValue) { AddMovementInput(GetActorRightVector() * AxisValue); } void ASimulationCharacter::LookUpRate(float AxisValue) { AddControllerPitchInput(AxisValue * RotationRate * GetWorld()->GetDeltaSeconds()); }
You may notice in the example above, that in a case of
Jump
we refer toACharacter
instead of toAPawn
as we do in a case of the looking based on mouse input. That's becauseJump
function is defined byAController
and thus it is not available forAPawn
characters in default. -
Example 2 -
UPawnMovementComponent::AddInputVector()
Following example is used in the default Third Person Project Template in Unreal Engine. The pack features a playable character controlled by keyboard and mouse. Characters animations are included, processed through
Mopvement Component
that is native part ofACharacter
..h#pragma once #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "InputActionValue.h" #include "MyProjectTmpCharacter.generated.h" UCLASS(config=Game) class AMyProjectTmpCharacter : public ACharacter { GENERATED_BODY() /** MappingContext */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true")) class UInputMappingContext* DefaultMappingContext; /** Jump Input Action */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true")) class UInputAction* JumpAction; /** Move Input Action */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true")) class UInputAction* MoveAction; /** Look Input Action */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true")) class UInputAction* LookAction; public: AMyProjectTmpCharacter(); protected: /** Called for movement input */ void Move(const FInputActionValue& Value); /** Called for looking input */ void Look(const FInputActionValue& Value); protected: // APawn interface virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; // To add mapping context virtual void BeginPlay(); };
.cpp constructor#include "MyProjectTmpCharacter.h" #include "Components/CapsuleComponent.h" #include "Components/InputComponent.h" #include "GameFramework/CharacterMovementComponent.h" #include "GameFramework/Controller.h" #include "EnhancedInputComponent.h" AMyProjectTmpCharacter::AMyProjectTmpCharacter() { // Set size for collision capsule GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f); // Don't rotate when the controller rotates. Let that just affect the camera. bUseControllerRotationPitch = false; bUseControllerRotationYaw = false; bUseControllerRotationRoll = false; // Configure character movement GetCharacterMovement()->bOrientRotationToMovement = true; // Character moves in the direction of input... GetCharacterMovement()->RotationRate = FRotator(0.0f, 500.0f, 0.0f); // ...at this rotation rate // Note: For faster iteration times these variables, and many more, can be tweaked in the Character Blueprint // instead of recompiling to adjust them GetCharacterMovement()->JumpZVelocity = 700.f; GetCharacterMovement()->AirControl = 0.35f; GetCharacterMovement()->MaxWalkSpeed = 500.f; GetCharacterMovement()->MinAnalogWalkSpeed = 20.f; GetCharacterMovement()->BrakingDecelerationWalking = 2000.f; // Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character) // are set in the derived blueprint asset named ThirdPersonCharacter (to avoid direct content references in C++) }
.cpp Movementvoid AMyProjectTmpCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) { // Set up action bindings if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent)) { //Jumping EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Triggered, this, &ACharacter::Jump); EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping); //Moving EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AMyProjectTmpCharacter::Move); //Looking EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AMyProjectTmpCharacter::Look); } } void AMyProjectTmpCharacter::Move(const FInputActionValue& Value) { // input is a Vector2D FVector2D MovementVector = Value.Get<FVector2D>(); if (Controller != nullptr) { // find out which way is forward const FRotator Rotation = Controller->GetControlRotation(); const FRotator YawRotation(0, Rotation.Yaw, 0); // get forward vector const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X); // get right vector const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y); // add movement //AddMovementInput(ForwardDirection, MovementVector.Y); AddMovementInput(RightDirection, MovementVector.X); UPawnMovementComponent* MovementComponent = GetMovementComponent(); MovementComponent->AddInputVector(ForwardDirection * MovementVector.Y); //AIController* ai = Cast<AIController>(GetController()); //GetController()->MoveToLocation //AddActorLocalOffset(FVector(1.f, 0.f, 0.f), false); } } void AMyProjectTmpCharacter::Look(const FInputActionValue& Value) { // input is a Vector2D FVector2D LookAxisVector = Value.Get<FVector2D>(); if (Controller != nullptr) { // add yaw and pitch input to controller AddControllerYawInput(LookAxisVector.X); AddControllerPitchInput(LookAxisVector.Y); } }
Movement by Impulse
It works based on initially provided impulse (force with direction) and then letting the physics (gravity and so on) take over
Movement component
UMovementComponent
A special type of component that can be inculded into a class. After setting some parameters on the UMovementComponent
, this component handles the movement for the actor. Unreal Engine has built-in types of movement components made for specifical objects, such as:
UProjectileMovementComponent
An Unreal Engine movement component for projectiles. It updates the position of another component during its tick.
// .h file UPROPERTY(VisibleAnywhere, Category = "Movement") class UProjectileMovementComponent* ProjectileMC; UFUNCTION() void OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit); // .cpp file #include "GameFramework/ProjectileMovementComponent.h" AProjectile::AProjectile() { ProjectileMP = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("Projectile Movement Component")); ProjectileMP->MaxSpeed = 1200.f; ProjectileMP->InitialSpeed = 2100.f; } AProjectile::BeginPlay() { ProjectileMesh->OnComponentHit.AddDynamic(this, &AProjectile::OnHit); //Collision Presets must be Enabled (Query and Physics) for handling Hit events } AProjectile::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit) { // ... }
Casting
Anytime there's need a connection or access between two classes, when one is derived from the another, there's possible to Cast
from child class to parent class and each other. This can be done with code Cast<type>(Pointer)
, see examples Getting APlayerController and APawn derived class.
Example 1 - Getting APlayerController
refference
APlayerController
is necessary if we need for example GetHitResultUnderCursor
information. To get it, there may be used APawn::GetController()
for getting controller which is a constant function of APawn
class. The return type is a pointer to AController
.
As our APlayerController
is a derived function of AController
, there's possible to Cast<>()
from the parent AController
to its child APlayerController
and each other. With casting, there's possible to access properties of the required class.
void AExample::BeginPlay()
{
Super::BeginPlay();
// APlayerController* PlayerController; defined in .h file
PlayerController = Cast<APlayerController>(GetController());
}
void AExample::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if(!PlayerController) return;
FHitResult HitResult;
PlayerController->GetHitResultUnderCursor(ECollisionChannel::ECC_Visibility, bTraceComplex, HitResult); // fills HitResult variable
// Debug Visualization of pointer location target considering collisions
DrawDebugSpeher(GetWorld(), HitResult.ImpactPoint, 20.f, 12, FColor::Red, false);
RottateTo(HitResult.ImpactPoint);
}
void AExample::RottateTo(FVector LookAtTarget)
{
// Rotate from current rotation to aim to pointer location
// ToTarget = TraceHitLocation - CurrentObjectLocation
FVector ToTarget = LookAtTarget - ObjectMesh->GetComponentLocation();
FRotator LookAtRotation = ToTarget.Rotation();
ObjectMesh->SetWorldTotation(FMath::RInterpTo(ObjectMesh->GetComponentRotation(), LookAtRotation, UGameplayStatics::GetWorldDeltaSeconds(this), 10.f));
}
Example 2 - Getting APawn
derived class refference
AExamplePlayerPawn
is derived from APawn
that includes construction of function GetPlayerPawn()
that we need. As our AExamplePlayerPawn
is derived from APawn
, there's possible to cast.
// .h file
class AExamplePlayerPawn* PlayerPawn;
// .cpp file
PlayerPawn = Cast<AExamplePlayerPawn>(UGameplayStatics::GetPlayerPawn(this,0));
Local / World Transforms (Location, Rotation, Scale properties)
Each Actor-type of asset has an own Transform
while within the Actor
, there may be many components in a hierarchical structure, that have own Transform
.
As clear, a Transform
or each Component
in the hierarchy has a parent with an own Transform
. How do these "hierarchical" Transform work?
Relative possition of each actor is the position to its parent actor. In the case of the image above, the BP_Character has a position relative to the world and holding a gun within a component attached to the class that has relative position to the BP_Character.
Local Coordinate System
Looking at the coordinate system, when a parent actor (BP_Character) moves or rotates, all its child actors (Gun) moves automatically as well as they have relative position to their parent (BP_Character).
Transforming between Global and Local space
- Global space has the base at the center of the world and the Actor is relative
- Local space has the base at the Self actor and all other is relative
It is important to distinguish between the Global and Local space especially when deal with a rotation - e.g. an angle of aiming. If a character turn around for any angle, the directions changes. Example below shows a state after turning around 180 degrees - if we would not reflect local world transform, there would be played right animation for moving left and left animation for moving right according to the position change in the Global Space.
Vector Transform
There's need to take care about the Space in which BP nodes returns the data. Usually we would get Transform in the Global space while e.g. animations work with local space. To achieve a proper functionality, there's need to convert returned Global Space to Local Space. Unreal Engine has 2 nodes for that. We use Inverted value as we transform from global to the local space.
Inverse Transform Direction
for Inverted Direction Vector Transform. Position Vector is basically a Vector that provides a difference between the current location (or zero point in the coordination system) and an arrow that points to where something should be - let's say gun. If transformed to the Global World, the arrow would go from 0,0,0 Vector of The World to the Gun again. As we need the opposite, we use the inverted value. Shortly, it's full transform.Inverse Transform Location
for Inverted Position Vector Transform that tells us where and how fast the character should be going in the direction. Shortly, it informs only about rotation and scale.
See example of use at the Character Animation page.