Movement & Rotation in Unreal Engine

C++ focused guide explaining how to move and rotate with actors, pawns, characters (just any movable object) in Unreal Engine.

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 in APawn class (and so it's available in all classes inherited from APawn.

  • AI Movement

    Functions MoveToLocation() and SimpleMoveToLocation() 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 to ACharacter instead of to APawn as we do in a case of the looking based on mouse input. That's because Jump function is defined by AController and thus it is not available for APawn 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 of ACharacter.

    #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();
    };
    #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++)
    }
    void 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

UMovementComponentA 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.