Open Door With Timeline and Curve Float

February 20, 2018

UE4 Version: 4.18.3

Github Link: https://github.com/Harrison1/unrealcpp/tree/master/OpenDoorTimelineCurve

For this tutorial we are using the standard first person C++ template with the starter content.

In this Unreal Engine 4 C++ tutorial we will learn how to open a door using a timeline and curve float when the player presses a key. The door will open depending on which way the player is facing.

First, inside the editor add an Action key by going to Edit > Project Settins > Input > Action Mappings. Add an an action and call it Action and bind it to to the E key.

Next, in your content folder, right click, go to Miscellaneous > Curve and create new Curve Float. Open the curve float and create a new key by right clicking and selecting new key. In the top left set the values of the first key to have the values of 0 for Time and Value. Create a second key and set its Time value to 1.0 and its Value property to 90.0. Hold SHIFT, select both keys and then click Auto in the top bar to give the curve a smooth beginning and end.

Next, in your content folder, right click, go to User Interface and select Widget Blueprint. Inside the Widget Blueprint drag and drop an image property into the viewport. For the source you can add the image below or any image you want. At the top of the details panel for the image name the property helpimage.

feel free to use this image you want

press e to open

Before we create a new actor, let's add to out build file so we can use UMG widgets. In your Build.cs file add UMG, Slate, and SlateCore;

UnrealCpp.Build.cs

using UnrealBuildTool;

public class UnrealCPP : ModuleRules
{
	public UnrealCPP(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay", "UMG", "Slate", "SlateCore" });
	}
}

Next, create a new actor class and call it whatever you want, in this tutorial I will call it OpenDoorTimelineCurve.

First, in the .h file let's #include the TimelineComponent.h" at the top of the file. Make sure it comes before your Actor's generated.h file.

include TimelineComponent

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"

// include before generated file
#include "Components/TimelineComponent.h"

#include "OpenDoorWithLerp.generated.h"

Next, we'll setup our variables. We'll create some variables and functions to use and track throughout our .cpp file.

Our Header Properties and Functions

...
	UPROPERTY(EditAnywhere)
	UStaticMeshComponent* Door;

	UPROPERTY(EditAnywhere)
	UStaticMeshComponent* DoorFrame;

	UPROPERTY(EditAnywhere)
	UCurveFloat *DoorCurve;

	UFUNCTION()
    void ControlDoor();

	UFUNCTION()
    void ToggleDoor();

	UFUNCTION()
    void SetState();

	bool Open;
	bool ReadyState;
	float RotateValue;
	float CurveFloatValue;
	float TimelineValue;
	FRotator DoorRotation;
	FTimeline MyTimeline;

Next, we'll move into the Actor's .cpp file. We'll first want to #include the the KismetMathLibrary and GameplayStatics" header file. We'll use Kismet functions in the toggle function.

include files

#include "Kismet/KismetMathLibrary.h"
#include "Kismet/GameplayStatics.h"

In the constructor function we will set our default variables. First, we'll set our Open bool to false and our ReadyState to true. Then we'll setup our DoorFrame as the RootComponent and then attach our Door mesh to it.

constructor function

AOpenDoorTimelineCurve::AOpenDoorTimelineCurve()
{
	PrimaryActorTick.bCanEverTick = true;

    Open = false;
    ReadyState = true;

    DoorFrame = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DoorFrame"));
    RootComponent = DoorFrame;

    Door = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("My Mesh"));
    Door->SetupAttachment(RootComponent);
}

Next, in the BeginPlay function we will setup our Timeline component. In the editor we'll set our DoorCurve to be the float curve we created earlier. So, if the DoorCurve is not NULL, we'll setup our callback functions and connect them to our timeline. When the timeline is moving through the DoorCurve we will call the ControlDoor function and when the timeline is finished we will call the SetState function.

BeginPlay function

void AOpenDoorTimelineCurve::BeginPlay()
{
	Super::BeginPlay();

    RotateValue = 1.0f;

    if (DoorCurve)
    {
        FOnTimelineFloat TimelineCallback;
        FOnTimelineEventStatic TimelineFinishedCallback;

        TimelineCallback.BindUFunction(this, FName("ControlDoor"));
        TimelineFinishedCallback.BindUFunction(this, FName{ TEXT("SetState") });
        MyTimeline.AddInterpFloat(DoorCurve, TimelineCallback);
        MyTimeline.SetTimelineFinishedFunc(TimelineFinishedCallback);
    }

}

In the Tick function we have to connect our Timeline to DeltaTime;

Tick function

void AOpenDoorTimelineCurve::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

    MyTimeline.TickTimeline(DeltaTime);
}

Next, let's create the ControlDoor function. This function will get the playback position of the timeline, then grab the value that number has on the float curve, and then set the relative rotation for the door. The RotateValue is set in ToggleDoor function that we'll create later, but the RotateValue will determine the direction in which the door opens.

ControlDoor function

void AOpenDoorTimelineCurve::ControlDoor()
{
    TimelineValue = MyTimeline.GetPlaybackPosition();
    CurveFloatValue = RotateValue*DoorCurve->GetFloatValue(TimelineValue);

    FQuat NewRotation = FQuat(FRotator(0.f, CurveFloatValue, 0.f));

    Door->SetRelativeRotation(NewRotation);
}

Next, we'll create the SetState function to simply set ReadyState to true. We will only be able to open or close the door when ReadyState is true.

SetState function

void AOpenDoorTimelineCurve::SetState()
{
    ReadyState = true;
}

Next, we'll create the ToggleDoor function. First, we'll check if ReadyState is true. If ReadyState is true we will toggle Open and get the Door's current rotation. Next, we will get the direction the player is facing, determining if they are in front or behind the door. We will use the KismetMathLibrary's function LessLess_VectorRotator to get an X coordinate that is either positive or negative. If Open is true we will set RotateValue to 1.0f or -1.0f depending on the direction and then set ReadyState to false and then play the timeline from the start. If Open is false we will set ReadyState to false and reverse the timeline.

ToggleDoor function

void AOpenDoorTimelineCurve::ToggleDoor() 
{
    if(ReadyState) 
    {
        Open = !Open;
        DoorRotation = Door->RelativeRotation;

        APawn* OurPawn = UGameplayStatics::GetPlayerPawn(this, 0);
        FVector PawnLocation = OurPawn->GetActorLocation();
        FVector Direction = GetActorLocation() - PawnLocation;
        Direction = UKismetMathLibrary::LessLess_VectorRotator(Direction, GetActorRotation());

        if(Open)
        {
                
            if(Direction.X > 0.0f)
            {
                RotateValue = 1.0f;
            }
            else
            {
                RotateValue = -1.0f;
            }

            ReadyState = false;
            MyTimeline.PlayFromStart();
        }
        else 
        {
            ReadyState = false;
            MyTimeline.Reverse();
        }
    }

}

Next, let's move into the character header file. at the tope include the new door class the UserWidget header file.

include in the character header file. The path to your door class might be different, so use your path.

#include "CoreMinimal.h"
#include "GameFramework/Character.h"

// include these two
#include "OpenDoorTimelineCurve/OpenDoorTimelineCurve.h"
#include "Blueprint/UserWidget.h"

#include "UnrealCPPCharacter.generated.h"

Then, we will be using the Tick function so we need to override it and then add our properties and add our OnAction function.

character setup

...
protected:
	virtual void BeginPlay();

    // add tick override
	virtual void Tick(float DeltaSeconds) override;

public:

	/** Base turn rate, in deg/sec. Other scaling may affect final turn rate. */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
	float BaseTurnRate;

	/** Base look up/down rate, in deg/sec. Other scaling may affect final rate. */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
	float BaseLookUpRate;

	/** Gun muzzle's offset from the characters location */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Gameplay)
	FVector GunOffset;

	/** Projectile class to spawn */
	UPROPERTY(EditDefaultsOnly, Category=Projectile)
	TSubclassOf<class AUnrealCPPProjectile> ProjectileClass;

	/** Sound to play each time we fire */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Gameplay)
	class USoundBase* FireSound;

	/** AnimMontage to play each time we fire */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay)
	class UAnimMontage* FireAnimation;

    // declare float curve
	UPROPERTY(EditAnywhere)
	class AOpenDoorTimelineCurve* CurrentDoor;

	// Reference UMG Asset in the Editor
	UPROPERTY(EditAnywhere)
	TSubclassOf<class UUserWidget> HelpWidgetClass;

    // declare widget
	class UUserWidget* InfoWidget;

protected:
	
	/** Fires a projectile. */
	void OnFire();

	/** Action Function */
	void OnAction();

Next, in the character .cpp file #include DrawDebugHelpers.h to so we can visualize the line trace.

include DrawDebugHelpers.h

#include "DrawDebugHelpers.h"

In the constructor function set CurrentDoor to NULL.

constructor function

AUnrealCPPCharacter::AUnrealCPPCharacter()
{
	// Set size for collision capsule
	GetCapsuleComponent()->InitCapsuleSize(55.f, 96.0f);

	// set our turn rates for input
	BaseTurnRate = 45.f;
	BaseLookUpRate = 45.f;

	// Create a CameraComponent	
	FirstPersonCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("FirstPersonCamera"));
	FirstPersonCameraComponent->SetupAttachment(GetCapsuleComponent());
	FirstPersonCameraComponent->RelativeLocation = FVector(-39.56f, 1.75f, 64.f); // Position the camera
	FirstPersonCameraComponent->bUsePawnControlRotation = true;

	// Create a mesh component that will be used when being viewed from a '1st person' view (when controlling this pawn)
	Mesh1P = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("CharacterMesh1P"));
	Mesh1P->SetOnlyOwnerSee(true);
	Mesh1P->SetupAttachment(FirstPersonCameraComponent);
	Mesh1P->bCastDynamicShadow = false;
	Mesh1P->CastShadow = false;
	Mesh1P->RelativeRotation = FRotator(1.9f, -19.19f, 5.2f);
	Mesh1P->RelativeLocation = FVector(-0.5f, -4.4f, -155.7f);

	// Create a gun mesh component
	FP_Gun = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("FP_Gun"));
	FP_Gun->SetOnlyOwnerSee(true);			// only the owning player will see this mesh
	FP_Gun->bCastDynamicShadow = false;
	FP_Gun->CastShadow = false;
	// FP_Gun->SetupAttachment(Mesh1P, TEXT("GripPoint"));
	FP_Gun->SetupAttachment(RootComponent);

	FP_MuzzleLocation = CreateDefaultSubobject<USceneComponent>(TEXT("MuzzleLocation"));
	FP_MuzzleLocation->SetupAttachment(FP_Gun);
	FP_MuzzleLocation->SetRelativeLocation(FVector(0.2f, 48.4f, -10.6f));

	// Default offset from the character location for projectiles to spawn
	GunOffset = FVector(100.0f, 0.0f, 10.0f);

	// Note: The ProjectileClass and the skeletal mesh/anim blueprints for Mesh1P, FP_Gun, and VR_Gun 
	// are set in the derived blueprint asset named MyCharacter to avoid direct content references in C++.

	CurrentDoor = NULL;

}

In the BeginPlay function, we will setup the UserWidget and add it to the viewport.

BeginPlay function

void AUnrealCPPCharacter::BeginPlay()
{
	// Call the base class  
	Super::BeginPlay();

	//Attach gun mesh component to Skeleton, doing it here because the skeleton is not yet created in the constructor
	FP_Gun->AttachToComponent(Mesh1P, FAttachmentTransformRules(EAttachmentRule::SnapToTarget, true), TEXT("GripPoint"));

	Mesh1P->SetHiddenInGame(false, true);

	if (HelpWidgetClass)
	{
		InfoWidget = CreateWidget<UUserWidget>(GetWorld(), HelpWidgetClass);
 
		if (InfoWidget)
		{
			InfoWidget->AddToViewport();
		}
 
	}	

}

In the Tick function we will draw a line trace every frame to see if we are returning a door. If the Hit actor is a door then we will set CurrentDoor to the current actor and display our help image.

Tick function

void AUnrealCPPCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	FHitResult Hit;
	FVector Start = FirstPersonCameraComponent->GetComponentLocation();

	FVector ForwardVector = FirstPersonCameraComponent->GetForwardVector();
	FVector End = ((ForwardVector * 200.f) + Start);
	FCollisionQueryParams CollisionParams;

	DrawDebugLine(GetWorld(), Start, End, FColor::Green, false, 1, 0, 1);

	if(GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECC_Visibility, CollisionParams)) 
	{
		if(Hit.bBlockingHit)
		{
			if(Hit.GetActor()->GetClass()->IsChildOf(AOpenDoorTimelineCurve::StaticClass())) 
			{
				InfoWidget->GetWidgetFromName("helpimage")->SetVisibility(ESlateVisibility::Visible);
					
				CurrentDoor = Cast<AOpenDoorTimelineCurve>(Hit.GetActor());
				
			}
		}
	}
	else
	{
		InfoWidget->GetWidgetFromName("helpimage")->SetVisibility(ESlateVisibility::Hidden);
		CurrentDoor = NULL;
	}
}

Next, bind the OnAction event in the SetupPlayerInputComponent function.

...
	// Bind action event
	PlayerInputComponent->BindAction("Action", IE_Pressed, this, &AUnrealCPPCharacter::OnAction);

Next, create the OnAction function to run the door's toggle function;

OnAction

void AUnrealCPPCharacter::OnAction()
{
	if(CurrentDoor)
	{
		CurrentDoor->ToggleDoor();
	}

}

Save and compile. Go into the editor, select the character and add the user widget class to them in the details panel. Drag and drop the door actor into the game world. Add the float curve to the actor in the details panel. Add the door frame and and door meshes. Now when you push play the door will open and close the player pushes the Action key while in range.

Below is the final code.

UnrealCpp.Build.cs

using UnrealBuildTool;

public class UnrealCPP : ModuleRules
{
	public UnrealCPP(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay", "UMG", "Slate", "SlateCore" });
	}
}

OpenDoorTimelineCurve.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/TimelineComponent.h"
#include "Components/BoxComponent.h"
#include "OpenDoorTimelineCurve.generated.h"

UCLASS()
class UNREALCPP_API AOpenDoorTimelineCurve : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AOpenDoorTimelineCurve();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	UPROPERTY(EditAnywhere)
	UStaticMeshComponent* Door;

	UPROPERTY(EditAnywhere)
	UStaticMeshComponent* DoorFrame;

	UPROPERTY(EditAnywhere)
	UCurveFloat *DoorCurve;

	UFUNCTION()
    void ControlDoor();

	UFUNCTION()
    void ToggleDoor();

	UFUNCTION()
    void SetState();

	bool Open;
	bool ReadyState;
	float RotateValue;
	float CurveFloatValue;
	float TimelineValue;
	FRotator DoorRotation;
	FTimeline MyTimeline;

};

OpenDoorTimelineCurve.cpp

#include "OpenDoorTimelineCurve.h"
#include "Kismet/KismetMathLibrary.h"

// Sets default values
AOpenDoorTimelineCurve::AOpenDoorTimelineCurve()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

    Open = false;
    ReadyState = true;

    DoorFrame = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DoorFrame"));
    RootComponent = DoorFrame;

    Door = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("My Mesh"));
    Door->SetupAttachment(RootComponent);

}

// Called when the game starts or when spawned
void AOpenDoorTimelineCurve::BeginPlay()
{
	Super::BeginPlay();

    RotateValue = 1.0f;

    if (DoorCurve)
    {
        FOnTimelineFloat TimelineCallback;
        FOnTimelineEventStatic TimelineFinishedCallback;

        TimelineCallback.BindUFunction(this, FName("ControlDoor"));
        TimelineFinishedCallback.BindUFunction(this, FName{ TEXT("SetState") });
        MyTimeline.AddInterpFloat(DoorCurve, TimelineCallback);
        MyTimeline.SetTimelineFinishedFunc(TimelineFinishedCallback);
    }

}

// Called every frame
void AOpenDoorTimelineCurve::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

    MyTimeline.TickTimeline(DeltaTime);
}

void AOpenDoorTimelineCurve::ControlDoor()
{
    TimelineValue = MyTimeline.GetPlaybackPosition();
    CurveFloatValue = RotateValue*DoorCurve->GetFloatValue(TimelineValue);

    FQuat NewRotation = FQuat(FRotator(0.f, CurveFloatValue, 0.f));

    Door->SetRelativeRotation(NewRotation);
}

void AOpenDoorTimelineCurve::SetState()
{
    ReadyState = true;
}

void AOpenDoorTimelineCurve::ToggleDoor() 
{
    if(ReadyState) 
    {
        Open = !Open;

        // alternative way to get pawn position
        // GetWorld()->GetFirstPlayerController()->GetPawn()->GetActorLocation()
        APawn* OurPawn = UGameplayStatics::GetPlayerPawn(this, 0);
        FVector PawnLocation = OurPawn->GetActorLocation();
        FVector Direction = GetActorLocation() - PawnLocation;
        Direction = UKismetMathLibrary::LessLess_VectorRotator(Direction, GetActorRotation());

        DoorRotation = Door->RelativeRotation;

        if(Open)
        {
                
            if(Direction.X > 0.0f)
            {
                RotateValue = 1.0f;
            }
            else
            {
                RotateValue = -1.0f;
            }

            ReadyState = false;
            MyTimeline.PlayFromStart();
        }
        else 
        {
            ReadyState = false;
            MyTimeline.Reverse();
        }
    }

}

UnrealCPPCharacter.h

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "OpenDoorTimelineCurve/OpenDoorTimelineCurve.h"
#include "Blueprint/UserWidget.h"
#include "UnrealCPPCharacter.generated.h"

class UInputComponent;

UCLASS(config=Game)
class AUnrealCPPCharacter : public ACharacter
{
	GENERATED_BODY()

	/** Pawn mesh: 1st person view (arms; seen only by self) */
	UPROPERTY(VisibleDefaultsOnly, Category=Mesh)
	class USkeletalMeshComponent* Mesh1P;

	/** Gun mesh: 1st person view (seen only by self) */
	UPROPERTY(VisibleDefaultsOnly, Category = Mesh)
	class USkeletalMeshComponent* FP_Gun;

	/** Location on gun mesh where projectiles should spawn. */
	UPROPERTY(VisibleDefaultsOnly, Category = Mesh)
	class USceneComponent* FP_MuzzleLocation;

	/** First person camera */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
	class UCameraComponent* FirstPersonCameraComponent;


public:
	AUnrealCPPCharacter();

protected:
	virtual void BeginPlay();

	virtual void Tick(float DeltaSeconds) override;

public:

	/** Base turn rate, in deg/sec. Other scaling may affect final turn rate. */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
	float BaseTurnRate;

	/** Base look up/down rate, in deg/sec. Other scaling may affect final rate. */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
	float BaseLookUpRate;

	/** Gun muzzle's offset from the characters location */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Gameplay)
	FVector GunOffset;

	/** Projectile class to spawn */
	UPROPERTY(EditDefaultsOnly, Category=Projectile)
	TSubclassOf<class AUnrealCPPProjectile> ProjectileClass;

	/** Sound to play each time we fire */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Gameplay)
	class USoundBase* FireSound;

	/** AnimMontage to play each time we fire */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay)
	class UAnimMontage* FireAnimation;

	UPROPERTY(EditAnywhere)
	class AOpenDoorTimelineCurve* CurrentDoor;

	// Reference UMG Asset in the Editor
	UPROPERTY(EditAnywhere)
	TSubclassOf<class UUserWidget> HelpWidgetClass;

	// UPROPERTY(EditAnywhere)
	class UUserWidget* InfoWidget;

protected:
	
	/** Fires a projectile. */
	void OnFire();

	/** Action Function */
	void OnAction();

	/** Handles moving forward/backward */
	void MoveForward(float Val);

	/** Handles stafing movement, left and right */
	void MoveRight(float Val);

	/**
	 * Called via input to turn at a given rate.
	 * @param Rate	This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
	 */
	void TurnAtRate(float Rate);

	/**
	 * Called via input to turn look up/down at a given rate.
	 * @param Rate	This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
	 */
	void LookUpAtRate(float Rate);
	
protected:
	// APawn interface
	virtual void SetupPlayerInputComponent(UInputComponent* InputComponent) override;
	// End of APawn interface
	FORCEINLINE class USkeletalMeshComponent* GetMesh1P() const { return Mesh1P; }
	/** Returns FirstPersonCameraComponent subobject **/
	FORCEINLINE class UCameraComponent* GetFirstPersonCameraComponent() const { return FirstPersonCameraComponent; }

};

UnrealCPPCharacter.cpp

#include "UnrealCPPCharacter.h"
#include "UnrealCPPProjectile.h"
#include "Animation/AnimInstance.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/SphereComponent.h"
#include "Components/InputComponent.h"
#include "GameFramework/InputSettings.h"
#include "HeadMountedDisplayFunctionLibrary.h"
#include "Kismet/GameplayStatics.h"
#include "MotionControllerComponent.h"
#include "DrawDebugHelpers.h"

DEFINE_LOG_CATEGORY_STATIC(LogFPChar, Warning, All);

//////////////////////////////////////////////////////////////////////////
// AUnrealCPPCharacter

AUnrealCPPCharacter::AUnrealCPPCharacter()
{
	// Set size for collision capsule
	GetCapsuleComponent()->InitCapsuleSize(55.f, 96.0f);

	// set our turn rates for input
	BaseTurnRate = 45.f;
	BaseLookUpRate = 45.f;

	// Create a CameraComponent	
	FirstPersonCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("FirstPersonCamera"));
	FirstPersonCameraComponent->SetupAttachment(GetCapsuleComponent());
	FirstPersonCameraComponent->RelativeLocation = FVector(-39.56f, 1.75f, 64.f); // Position the camera
	FirstPersonCameraComponent->bUsePawnControlRotation = true;

	// Create a mesh component that will be used when being viewed from a '1st person' view (when controlling this pawn)
	Mesh1P = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("CharacterMesh1P"));
	Mesh1P->SetOnlyOwnerSee(true);
	Mesh1P->SetupAttachment(FirstPersonCameraComponent);
	Mesh1P->bCastDynamicShadow = false;
	Mesh1P->CastShadow = false;
	Mesh1P->RelativeRotation = FRotator(1.9f, -19.19f, 5.2f);
	Mesh1P->RelativeLocation = FVector(-0.5f, -4.4f, -155.7f);

	// Create a gun mesh component
	FP_Gun = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("FP_Gun"));
	FP_Gun->SetOnlyOwnerSee(true);			// only the owning player will see this mesh
	FP_Gun->bCastDynamicShadow = false;
	FP_Gun->CastShadow = false;
	// FP_Gun->SetupAttachment(Mesh1P, TEXT("GripPoint"));
	FP_Gun->SetupAttachment(RootComponent);

	FP_MuzzleLocation = CreateDefaultSubobject<USceneComponent>(TEXT("MuzzleLocation"));
	FP_MuzzleLocation->SetupAttachment(FP_Gun);
	FP_MuzzleLocation->SetRelativeLocation(FVector(0.2f, 48.4f, -10.6f));

	// Default offset from the character location for projectiles to spawn
	GunOffset = FVector(100.0f, 0.0f, 10.0f);

	// Note: The ProjectileClass and the skeletal mesh/anim blueprints for Mesh1P, FP_Gun, and VR_Gun 
	// are set in the derived blueprint asset named MyCharacter to avoid direct content references in C++.

	CurrentDoor = NULL;

}

void AUnrealCPPCharacter::BeginPlay()
{
	// Call the base class  
	Super::BeginPlay();

	//Attach gun mesh component to Skeleton, doing it here because the skeleton is not yet created in the constructor
	FP_Gun->AttachToComponent(Mesh1P, FAttachmentTransformRules(EAttachmentRule::SnapToTarget, true), TEXT("GripPoint"));

	Mesh1P->SetHiddenInGame(false, true);

	if (HelpWidgetClass)
	{
		InfoWidget = CreateWidget<UUserWidget>(GetWorld(), HelpWidgetClass);
 
		if (InfoWidget)
		{
			InfoWidget->AddToViewport();
		}
 
	}	

}

//Called every frame
void AUnrealCPPCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	FHitResult Hit;
	FVector Start = FirstPersonCameraComponent->GetComponentLocation();

	FVector ForwardVector = FirstPersonCameraComponent->GetForwardVector();
	FVector End = ((ForwardVector * 200.f) + Start);
	FCollisionQueryParams CollisionParams;

	DrawDebugLine(GetWorld(), Start, End, FColor::Green, false, 1, 0, 1);

	if(GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECC_Visibility, CollisionParams)) 
	{
		if(Hit.bBlockingHit)
		{
			if(Hit.GetActor()->GetClass()->IsChildOf(AOpenDoorTimelineCurve::StaticClass())) 
			{
				InfoWidget->GetWidgetFromName("helpimage")->SetVisibility(ESlateVisibility::Visible);
					
				CurrentDoor = Cast<AOpenDoorTimelineCurve>(Hit.GetActor());
				
			}
		}
	}
	else
	{
		InfoWidget->GetWidgetFromName("helpimage")->SetVisibility(ESlateVisibility::Hidden);
		CurrentDoor = NULL;
	}
}

//////////////////////////////////////////////////////////////////////////
// Input

void AUnrealCPPCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
	// set up gameplay key bindings
	check(PlayerInputComponent);

	// Bind jump events
	PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
	PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);

	// Bind fire event
	PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &AUnrealCPPCharacter::OnFire);

	// Bind action event
	PlayerInputComponent->BindAction("Action", IE_Pressed, this, &AUnrealCPPCharacter::OnAction);

	// Bind movement events
	PlayerInputComponent->BindAxis("MoveForward", this, &AUnrealCPPCharacter::MoveForward);
	PlayerInputComponent->BindAxis("MoveRight", this, &AUnrealCPPCharacter::MoveRight);

	// We have 2 versions of the rotation bindings to handle different kinds of devices differently
	// "turn" handles devices that provide an absolute delta, such as a mouse.
	// "turnrate" is for devices that we choose to treat as a rate of change, such as an analog joystick
	PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
	PlayerInputComponent->BindAxis("TurnRate", this, &AUnrealCPPCharacter::TurnAtRate);
	PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
	PlayerInputComponent->BindAxis("LookUpRate", this, &AUnrealCPPCharacter::LookUpAtRate);
}

void AUnrealCPPCharacter::OnFire()
{

	// try and fire a projectile
	if (ProjectileClass != NULL)
	{
		UWorld* const World = GetWorld();
		if (World != NULL)
		{
			const FRotator SpawnRotation = GetControlRotation();
			// MuzzleOffset is in camera space, so transform it to world space before offsetting from the character location to find the final muzzle position
			const FVector SpawnLocation = ((FP_MuzzleLocation != nullptr) ? FP_MuzzleLocation->GetComponentLocation() : GetActorLocation()) + SpawnRotation.RotateVector(GunOffset);

			//Set Spawn Collision Handling Override
			FActorSpawnParameters ActorSpawnParams;
			ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding;

			// spawn the projectile at the muzzle
			World->SpawnActor<AUnrealCPPProjectile>(ProjectileClass, SpawnLocation, SpawnRotation, ActorSpawnParams);
		}
	}

	// try and play the sound if specified
	if (FireSound != NULL)
	{
		UGameplayStatics::PlaySoundAtLocation(this, FireSound, GetActorLocation());
	}

	// try and play a firing animation if specified
	if (FireAnimation != NULL)
	{
		// Get the animation object for the arms mesh
		UAnimInstance* AnimInstance = Mesh1P->GetAnimInstance();
		if (AnimInstance != NULL)
		{
			AnimInstance->Montage_Play(FireAnimation, 1.f);
		}
	}
}

void AUnrealCPPCharacter::MoveForward(float Value)
{
	if (Value != 0.0f)
	{
		// add movement in that direction
		AddMovementInput(GetActorForwardVector(), Value);
	}
}

void AUnrealCPPCharacter::MoveRight(float Value)
{
	if (Value != 0.0f)
	{
		// add movement in that direction
		AddMovementInput(GetActorRightVector(), Value);
	}
}

void AUnrealCPPCharacter::TurnAtRate(float Rate)
{
	// calculate delta for this frame from the rate information
	AddControllerYawInput(Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds());
}

void AUnrealCPPCharacter::LookUpAtRate(float Rate)
{
	// calculate delta for this frame from the rate information
	AddControllerPitchInput(Rate * BaseLookUpRate * GetWorld()->GetDeltaSeconds());
}

void AUnrealCPPCharacter::OnAction()
{
	if(CurrentDoor)
	{
		CurrentDoor->ToggleDoor();
	}

}

Author

Harrison McGuire

Harrison McGuire