Barrel Roll Mechanic

Hello, I am currently working with the flying code template and am trying to get the aircraft to do a “barrel roll” (A single 360 degree turn on it’s roll axis). However I have run into a roadblock and can’t figure out what to do next. Basically I have a bool set up in the tick function that, when set to true, will add a rotation to the plane making it spin. I need a way to make it stop spinning after a full 360 degree turn. Right now it either keeps spinning or will stop about halfway no matter what I change. Any suggestions on how to fix this or if there is a better way of doing this (Like setting up a timer that stops the plane from spinning after a second).

Here is the header:

 // Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
#pragma once

#include "DroneProjectPawn.generated.h"

UCLASS(config=Game)
class ADroneProjectPawn : public APawn
{
public:
	GENERATED_UCLASS_BODY()

	/** StaticMesh component that will be the visuals for our flying pawn */
	UPROPERTY(Category=Mesh, VisibleDefaultsOnly, BlueprintReadOnly)
	TSubobjectPtr<class UStaticMeshComponent> PlaneMesh;

	/** Spring arm that will offset the camera */
	UPROPERTY(Category = Camera, VisibleDefaultsOnly, BlueprintReadOnly)
	TSubobjectPtr<class USpringArmComponent> SpringArm;

	/** Camera component that will be our viewpoint */
	UPROPERTY(Category=Camera, VisibleDefaultsOnly, BlueprintReadOnly)
	TSubobjectPtr<class UCameraComponent> Camera;

	// Begin AActor overrides
	virtual void Tick(float DeltaSeconds) OVERRIDE;
	virtual void ReceiveHit(class UPrimitiveComponent* MyComp, class AActor* Other, class UPrimitiveComponent* OtherComp, bool bSelfMoved, FVector HitLocation, FVector HitNormal, FVector NormalImpulse, const FHitResult& Hit) OVERRIDE;
	// End AActor overrides

	//Max Pitch that plane can turn
	UPROPERTY(Category = Movement, EditAnywhere, BlueprintReadWrite)
		float MaxPitch;
	//Min Pitch that plance can turn
	UPROPERTY(Category = Movement, EditAnywhere, BlueprintReadWrite)
		float MinPitch;

	//Max Roll that plane can turn
	UPROPERTY(Category = Movement, EditAnywhere, BlueprintReadWrite)
		float MaxRoll;
	//Min Roll that plance can turn
	UPROPERTY(Category = Movement, EditAnywhere, BlueprintReadWrite)
		float MinRoll;

protected:

	// Begin APawn overrides
	virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) OVERRIDE; // Allows binding actions/axes to functions
	// End APawn overrides

	/** Bound to the vertical axis */
	void ThrustInput(float Val);
	
	/** Bound to the horizontal axis */
	void MoveUpInput(float Val);

	/** */
	void MoveRightInput(float Val);

	//Function that deploys the barrel roll right mechanic
	void BarrelRollRight();

	//Function that deploys the barrel roll left mechanic
	void BarrelRollLeft();


private:

	bool bCanBarrelRollRight;
	bool bCanBarrelRollLeft;
	bool bIsRollingRight;
	bool bIsRollingLeft;

	FRotator RotationRate;

	/** How quickly forward speed changes */
	UPROPERTY(Category=Plane, EditAnywhere)
	float Acceleration;

	/** How quickly pawn can steer */
	UPROPERTY(Category=Plane, EditAnywhere)
	float TurnSpeed;

	/** MAx forward speed */
	UPROPERTY(Category = Pitch, EditAnywhere)
	float MaxSpeed;

	/** Min forward speed */
	UPROPERTY(Category=Yaw, EditAnywhere)
	float MinSpeed;

	/** Min forward speed */
	UPROPERTY(Category = Camera, EditAnywhere)
	float MinCameraDistance;

	/** Min forward speed */
	UPROPERTY(Category = Camera, EditAnywhere)
	float MaxCameraDistance;

	/** Current forward speed */
	float CurrentForwardSpeed;

	/** Current yaw speed */
	float CurrentYawSpeed;

	/** Current pitch speed */
	float CurrentPitchSpeed;

	/** Current roll speed */
	float CurrentRollSpeed;


};

and the source:

// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.

#include "DroneProject.h"
#include "DroneProjectPawn.h"

ADroneProjectPawn::ADroneProjectPawn(const class FPostConstructInitializeProperties& PCIP) 
	: Super(PCIP)
{
	// Structure to hold one-time initialization
	struct FConstructorStatics
	{
		ConstructorHelpers::FObjectFinderOptional<UStaticMesh> PlaneMesh;
		FConstructorStatics()
			: PlaneMesh(TEXT("/Game/Meshes/Predator.Predator"))
		{
		}
	};
	static FConstructorStatics ConstructorStatics;

	// Create static mesh component
	PlaneMesh = PCIP.CreateDefaultSubobject<UStaticMeshComponent>(this, TEXT("PlaneMesh0"));
	PlaneMesh->SetStaticMesh(ConstructorStatics.PlaneMesh.Get());
	RootComponent = PlaneMesh;

	// Create a spring arm component
	SpringArm = PCIP.CreateDefaultSubobject<USpringArmComponent>(this, TEXT("SpringArm0"));
	SpringArm->AttachTo(RootComponent);
	SpringArm->TargetArmLength = MinCameraDistance; // The camera follows at this distance behind the character	
	SpringArm->SocketOffset = FVector(0.f,0.f,60.f);
	SpringArm->bEnableCameraLag = false;
	SpringArm->CameraLagSpeed = 15.f;

	// Create camera component 
	Camera = PCIP.CreateDefaultSubobject<UCameraComponent>(this, TEXT("Camera0"));
	Camera->AttachTo(SpringArm, USpringArmComponent::SocketName);
	Camera->bUseControllerViewRotation = false; // Don't rotate camera with controller

	// Set handling parameters
	Acceleration = 500.f;
	TurnSpeed = 50.f;
	MaxSpeed = 8000.f;
	MinSpeed = 3000.f;
	CurrentForwardSpeed = 3000.f;

	MinCameraDistance = 160.f;
	MaxCameraDistance = 250.f;

	MaxPitch = 60.f;
	MinPitch = -60.f;

	MaxRoll = 45.f;
	MinRoll = -45.f;

	bCanBarrelRollRight = true;
	bCanBarrelRollLeft = true;
	bIsRollingRight = false;
	bIsRollingLeft = false;
}

void ADroneProjectPawn::Tick(float DeltaSeconds)
{
	const FVector LocalMove = FVector(CurrentForwardSpeed * DeltaSeconds, 0.f, 0.f);

	const float OldPitch = GetActorRotation().Pitch;
	const float MinDeltaPitch = MinPitch - OldPitch;
	const float MaxDeltaPitch = MaxPitch - OldPitch;

	const float OldRoll = GetActorRotation().Roll;
	const float MinDeltaRoll = MinRoll - OldRoll;
	const float MaxDeltaRoll = MaxRoll - OldRoll;

	// Move plane forwards (with sweep so we stop when we collide with things)
	AddActorLocalOffset(LocalMove, true);

	// Calculate change in rotation this frame
	FRotator DeltaRotation(0,0,0);

	if (!bIsRollingLeft && !bIsRollingRight)
	{
		DeltaRotation.Pitch = FMath::ClampAngle(CurrentPitchSpeed * DeltaSeconds, MinDeltaPitch, MaxDeltaPitch);
		DeltaRotation.Yaw = CurrentYawSpeed * DeltaSeconds;
		DeltaRotation.Roll = FMath::ClampAngle(CurrentRollSpeed * DeltaSeconds, MinDeltaRoll, MaxDeltaRoll);
	}
	// Rotate plane
	AddActorLocalRotation(DeltaRotation);


	//Barrel Roll Right
	if (bCanBarrelRollRight && bIsRollingRight)
	{
		MaxRoll = 350.f;
		
		if (DeltaRotation.Roll <= MaxDeltaRoll)
		{
			this->RotationRate = FRotator(0.0f, 0.0f, 180);
			this->AddActorLocalRotation(this->RotationRate * DeltaSeconds, true);
		}
		else
		{
			GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("This is an on screen message!"));
			bIsRollingRight = false;
			bCanBarrelRollLeft = true;
		
			MaxRoll = 45.f;
			MinRoll = -45.f;
		}

	}

	/*
	//Barrel Roll Left
	if (bCanBarrelRollLeft && bIsRollingLeft)
	{
		MaxRoll = 359;
		MinRoll = -359;
		this->RotationRate = FRotator(0.0f, 0.0f, -180.0f);
		this->AddActorLocalRotation(this->RotationRate * DeltaSeconds, false);
	}
	*/

	// Call any parent class Tick implementation
	Super::Tick(DeltaSeconds);
}

void ADroneProjectPawn::ReceiveHit(class UPrimitiveComponent* MyComp, class AActor* Other, class UPrimitiveComponent* OtherComp, bool bSelfMoved, FVector HitLocation, FVector HitNormal, FVector NormalImpulse, const FHitResult& Hit)
{
	Super::ReceiveHit(MyComp, Other, OtherComp, bSelfMoved, HitLocation, HitNormal, NormalImpulse, Hit);

	// Set velocity to zero upon collision
	CurrentForwardSpeed = 0.f;
}


void ADroneProjectPawn::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
	check(InputComponent);

	// Fine up and right axes
	InputComponent->BindAxis("Thrust", this, &ADroneProjectPawn::ThrustInput);
	InputComponent->BindAxis("MoveUp", this, &ADroneProjectPawn::MoveUpInput);
	InputComponent->BindAxis("MoveRight", this, &ADroneProjectPawn::MoveRightInput);
	InputComponent->BindAction("BarrelRollRight", IE_Pressed, this, &ADroneProjectPawn::BarrelRollRight);
	InputComponent->BindAction("BarrelRollLeft", IE_Pressed, this, &ADroneProjectPawn::BarrelRollLeft);

}

void ADroneProjectPawn::ThrustInput(float Val)
{
	// Is there no input?
	bool bHasInput = !FMath::IsNearlyEqual(Val, 0.f);
	// If input is not held down, reduce speed
	float CurrentAcc = bHasInput ? (Val * Acceleration) : (-0.5f * Acceleration);

	//If input is not held down, reduce camera distance
	float CurrentCamDistance = bHasInput ? (Val * 500.f) : (-.5 * 500.f);

	//Calculate new Camera Distance
	float NewCameraDistance = SpringArm->TargetArmLength + (GetWorld()->GetDeltaSeconds() * CurrentCamDistance * .5f);

	// Calculate new speed
	float NewForwardSpeed = CurrentForwardSpeed + (GetWorld()->GetDeltaSeconds() * CurrentAcc * 20.f);
	
	// Clamp between MinSpeed and MaxSpeed
	CurrentForwardSpeed = FMath::Clamp(NewForwardSpeed, MinSpeed, MaxSpeed);

	//Clamp between minCameraDistance and maxCameraDistance
	SpringArm->TargetArmLength = FMath::Clamp(NewCameraDistance, MinCameraDistance, MaxCameraDistance);
}

void ADroneProjectPawn::MoveUpInput(float Val)
{
	// Target pitch speed is based in input
	float TargetPitchSpeed = (Val * TurnSpeed * -1.f);

	// When steering, we decrease pitch slightly
	TargetPitchSpeed += (FMath::Abs(CurrentYawSpeed) * -0.2f);

	// Smoothly inerpolate to target pitch speed
	CurrentPitchSpeed = FMath::FInterpTo(CurrentPitchSpeed, TargetPitchSpeed, GetWorld()->GetDeltaSeconds(), 2.f);
}

void ADroneProjectPawn::MoveRightInput(float Val)
{
	// Target yaw speed is based on input
	float TargetYawSpeed = (Val * TurnSpeed * .75f);

	// Smoothly interpolate to target yaw speed
	CurrentYawSpeed = FMath::FInterpTo(CurrentYawSpeed, TargetYawSpeed, GetWorld()->GetDeltaSeconds(), 2.f);

	// Is there any left/right input?
	const bool bIsTurning = FMath::Abs(Val) > 0.2f;

	// If turning, yaw value is used to influence roll
	// If not turning, roll to reverse current roll value
	float TargetRollSpeed = bIsTurning ? (CurrentYawSpeed * 0.5f) : (GetActorRotation().Roll * -.25f);

	// Smoothly interpolate roll speed
	CurrentRollSpeed = FMath::FInterpTo(CurrentRollSpeed, TargetRollSpeed, GetWorld()->GetDeltaSeconds(), 10.f);
}


void ADroneProjectPawn::BarrelRollRight()
{
	if (bCanBarrelRollRight && !bIsRollingRight)
	{
		bIsRollingRight = true;
		bCanBarrelRollLeft = false;
	}
}

void ADroneProjectPawn::BarrelRollLeft()
{
	if (bCanBarrelRollLeft && !bIsRollingLeft)
	{
		bIsRollingLeft = true;
		bCanBarrelRollRight = false;
	}
}

I ended up creating a timer and it seems to work fine.

I still want to know if there is another way though! I’m always looking to learn :slight_smile:

Here is the updated source file:

// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.

#include "DroneProject.h"
#include "DroneProjectPawn.h"

ADroneProjectPawn::ADroneProjectPawn(const class FPostConstructInitializeProperties& PCIP) 
	: Super(PCIP)
{
	// Structure to hold one-time initialization
	struct FConstructorStatics
	{
		ConstructorHelpers::FObjectFinderOptional<UStaticMesh> PlaneMesh;
		FConstructorStatics()
			: PlaneMesh(TEXT("/Game/Meshes/Predator.Predator"))
		{
		}
	};
	static FConstructorStatics ConstructorStatics;

	// Create static mesh component
	PlaneMesh = PCIP.CreateDefaultSubobject<UStaticMeshComponent>(this, TEXT("PlaneMesh0"));
	PlaneMesh->SetStaticMesh(ConstructorStatics.PlaneMesh.Get());
	RootComponent = PlaneMesh;

	// Create a spring arm component
	SpringArm = PCIP.CreateDefaultSubobject<USpringArmComponent>(this, TEXT("SpringArm0"));
	SpringArm->AttachTo(RootComponent);
	SpringArm->TargetArmLength = MinCameraDistance; // The camera follows at this distance behind the character	
	SpringArm->SocketOffset = FVector(0.f,0.f,60.f);
	SpringArm->bEnableCameraLag = false;
	SpringArm->CameraLagSpeed = 15.f;

	// Create camera component 
	Camera = PCIP.CreateDefaultSubobject<UCameraComponent>(this, TEXT("Camera0"));
	Camera->AttachTo(SpringArm, USpringArmComponent::SocketName);
	Camera->bUseControllerViewRotation = false; // Don't rotate camera with controller

	// Set handling parameters
	Acceleration = 500.f;
	TurnSpeed = 50.f;
	MaxSpeed = 8000.f;
	MinSpeed = 3000.f;
	CurrentForwardSpeed = 3000.f;

	MinCameraDistance = 160.f;
	MaxCameraDistance = 250.f;

	MaxPitch = 60.f;
	MinPitch = -60.f;

	MaxRoll = 45.f;
	MinRoll = -45.f;

	bCanBarrelRollRight = true;
	bCanBarrelRollLeft = true;
	bIsRollingRight = false;
	bIsRollingLeft = false;
}

void ADroneProjectPawn::Tick(float DeltaSeconds)
{
	const FVector LocalMove = FVector(CurrentForwardSpeed * DeltaSeconds, 0.f, 0.f);

	const float OldPitch = GetActorRotation().Pitch;
	const float MinDeltaPitch = MinPitch - OldPitch;
	const float MaxDeltaPitch = MaxPitch - OldPitch;

	const float OldRoll = GetActorRotation().Roll;
	const float MinDeltaRoll = MinRoll - OldRoll;
	const float MaxDeltaRoll = MaxRoll - OldRoll;

	// Move plane forwards (with sweep so we stop when we collide with things)
	AddActorLocalOffset(LocalMove, true);

	// Calculate change in rotation this frame
	FRotator DeltaRotation(0,0,0);

	if (!bIsRollingLeft && !bIsRollingRight)
	{
		DeltaRotation.Pitch = FMath::ClampAngle(CurrentPitchSpeed * DeltaSeconds, MinDeltaPitch, MaxDeltaPitch);
		DeltaRotation.Yaw = CurrentYawSpeed * DeltaSeconds;
		DeltaRotation.Roll = FMath::ClampAngle(CurrentRollSpeed * DeltaSeconds, MinDeltaRoll, MaxDeltaRoll);
	}
	// Rotate plane
	AddActorLocalRotation(DeltaRotation);


	//Barrel Roll Right
	if (bCanBarrelRollRight && bIsRollingRight)
	{
		MaxRoll = 350.f;
		this->RotationRate = FRotator(0.0f, 0.0f, 180);
		this->AddActorLocalRotation(this->RotationRate * DeltaSeconds, true);
		

	}

	
	//Barrel Roll Left
	if (bCanBarrelRollLeft && bIsRollingLeft)
	{
		MinRoll = -350.f;
		
		this->RotationRate = FRotator(0.0f, 0.0f, -180.0f);
		this->AddActorLocalRotation(this->RotationRate * DeltaSeconds, true);
	}
	

	// Call any parent class Tick implementation
	Super::Tick(DeltaSeconds);
}

void ADroneProjectPawn::ReceiveHit(class UPrimitiveComponent* MyComp, class AActor* Other, class UPrimitiveComponent* OtherComp, bool bSelfMoved, FVector HitLocation, FVector HitNormal, FVector NormalImpulse, const FHitResult& Hit)
{
	Super::ReceiveHit(MyComp, Other, OtherComp, bSelfMoved, HitLocation, HitNormal, NormalImpulse, Hit);

	// Set velocity to zero upon collision
	CurrentForwardSpeed = 0.f;
}


void ADroneProjectPawn::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
	check(InputComponent);

	// Fine up and right axes
	InputComponent->BindAxis("Thrust", this, &ADroneProjectPawn::ThrustInput);
	InputComponent->BindAxis("MoveUp", this, &ADroneProjectPawn::MoveUpInput);
	InputComponent->BindAxis("MoveRight", this, &ADroneProjectPawn::MoveRightInput);
	InputComponent->BindAction("BarrelRollRight", IE_Pressed, this, &ADroneProjectPawn::BarrelRollRight);
	InputComponent->BindAction("BarrelRollLeft", IE_Pressed, this, &ADroneProjectPawn::BarrelRollLeft);
	InputComponent->BindAction("Fire", IE_Pressed, this, &ADroneProjectPawn::OnFire);

}

void ADroneProjectPawn::ThrustInput(float Val)
{
	// Is there no input?
	bool bHasInput = !FMath::IsNearlyEqual(Val, 0.f);
	// If input is not held down, reduce speed
	float CurrentAcc = bHasInput ? (Val * Acceleration) : (-0.5f * Acceleration);

	//If input is not held down, reduce camera distance
	float CurrentCamDistance = bHasInput ? (Val * 500.f) : (-.5 * 500.f);

	//Calculate new Camera Distance
	float NewCameraDistance = SpringArm->TargetArmLength + (GetWorld()->GetDeltaSeconds() * CurrentCamDistance * .5f);

	// Calculate new speed
	float NewForwardSpeed = CurrentForwardSpeed + (GetWorld()->GetDeltaSeconds() * CurrentAcc * 20.f);
	
	// Clamp between MinSpeed and MaxSpeed
	CurrentForwardSpeed = FMath::Clamp(NewForwardSpeed, MinSpeed, MaxSpeed);

	//Clamp between minCameraDistance and maxCameraDistance
	SpringArm->TargetArmLength = FMath::Clamp(NewCameraDistance, MinCameraDistance, MaxCameraDistance);
}

void ADroneProjectPawn::MoveUpInput(float Val)
{
	// Target pitch speed is based in input
	float TargetPitchSpeed = (Val * TurnSpeed * -1.f);

	// When steering, we decrease pitch slightly
	TargetPitchSpeed += (FMath::Abs(CurrentYawSpeed) * -0.2f);

	// Smoothly inerpolate to target pitch speed
	CurrentPitchSpeed = FMath::FInterpTo(CurrentPitchSpeed, TargetPitchSpeed, GetWorld()->GetDeltaSeconds(), 2.f);
}

void ADroneProjectPawn::MoveRightInput(float Val)
{
	// Target yaw speed is based on input
	float TargetYawSpeed = (Val * TurnSpeed * .75f);

	// Smoothly interpolate to target yaw speed
	CurrentYawSpeed = FMath::FInterpTo(CurrentYawSpeed, TargetYawSpeed, GetWorld()->GetDeltaSeconds(), 2.f);

	// Is there any left/right input?
	const bool bIsTurning = FMath::Abs(Val) > 0.2f;

	// If turning, yaw value is used to influence roll
	// If not turning, roll to reverse current roll value
	float TargetRollSpeed = bIsTurning ? (CurrentYawSpeed * 0.5f) : (GetActorRotation().Roll * -.25f);

	// Smoothly interpolate roll speed
	CurrentRollSpeed = FMath::FInterpTo(CurrentRollSpeed, TargetRollSpeed, GetWorld()->GetDeltaSeconds(), 10.f);
}


void ADroneProjectPawn::BarrelRollRight()
{
	if (bCanBarrelRollRight && !bIsRollingRight)
	{
		bIsRollingRight = true;
		bCanBarrelRollLeft = false;
		GetWorldTimerManager().SetTimer(this, &ADroneProjectPawn::StopRoll, 2.0f, true);
	}
}

void ADroneProjectPawn::BarrelRollLeft()
{
	if (bCanBarrelRollLeft && !bIsRollingLeft)
	{
		bIsRollingLeft = true;
		bCanBarrelRollRight = false;
		GetWorldTimerManager().SetTimer(this, &ADroneProjectPawn::StopRoll, 2.0f, true);
	}
}

void ADroneProjectPawn::OnFire()
{
	//Try and fire a projectile
	if (ProjectileClass != NULL)
	{
		//Get the Drone's transform
		FVector DroneLoc;
		FRotator DroneRot;

	}
}

//Stops the barrel roll and resets the timer
void ADroneProjectPawn::StopRoll()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("This is an on screen message!"));
	bIsRollingRight = false;
	bIsRollingLeft = false;
	MaxRoll = 35.0f;
	MinRoll = -35.0f;
	bCanBarrelRollLeft = true;
	bCanBarrelRollRight = true;
	GetWorldTimerManager().ClearTimer(this, &ADroneProjectPawn::StopRoll);
}

Thanks for answering yourself, really helped me out a ton!