skip to Main Content

Recreating Sniper Elite’s killshot cameras

In this post we’re going to recreate Sniper Elite’s killshot cameras. This post is written using Unreal Engine 4.12.5 so your implementation might differ in case you’re working with a different version of the engine.

Before we start, here is the end result:

Before we start recreating this mechanic, let’s take a step back and analyze how everything works. The mechanic is triggered when the character that we’re shooting at is going to die. For the sake of simplicity, we’re going to assume that every enemy character dies with one shot only. For the rest of this post, a successful shot will be considered a shot that hits an enemy target.

Assuming that our character fired a successful shot, our system’s logic is divided into the following steps:

  1. We dilate the world’s time by a specified value
  2. We make a transition from a First Person Camera view to a Third Person Camera view in order to see the character that fired the shot.
  3. After a specified time, we transition from a Third Person Camera view to a camera that is attached to the fired projectile.
  4. When the projectile is within a specified range from the enemy character we transition from the projectile’s camera to a camera that is attached to the enemy character
  5. The moment that the enemy character hits the ground, we transition from the active camera to First Person Camera view again.

For this project, I’m using the First Person C++ Template that comes with UE4. Before we achieve the end result shown in the video above, we will modify some functions and properties that come with the c++ template. This means that we’re going to strip down some functionality and add our own.

Step 1. Configuring World’s Time dilation

In order to dilate the World’s Time, expose the following property in your charater’s header file:

protected:
	/*The time dilation multiplier*/
	UPROPERTY(EditAnywhere)
	float TimeDilationMultiplier = 0.05f;

Then, navigate on the OnFire() function in your source file and add the following line right after the Projectile’s spawn to dilate the time:

UGameplayStatics::SetGlobalTimeDilation(World, TimeDilationMultiplier);

To clarify, here is the whole OnFire function at this step:

void ASELethalShotCharacter::OnFire()
{
	// try and fire a projectile
	if (ProjectileClass != 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);

		UWorld* const World = GetWorld();
		if (World != NULL)
		{
			// spawn the projectile at the muzzle
			World->SpawnActor<ASELethalShotProjectile>(ProjectileClass, SpawnLocation, SpawnRotation);

			//Dilate the time
			UGameplayStatics::SetGlobalTimeDilation(World, TimeDilationMultiplier);
		}
	}

	// 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);
		}
	}
}

Step 2. Transition from First Person to Third Person View

In order to switch a perspective, we’re going to use a new camera that is attached to a spring arm component which is attached to our character’s root component.

Having said that, add the following components and properties to your character’s header file:

protected:

	/*The time dilation multiplier*/
	UPROPERTY(EditAnywhere)
	float TimeDilationMultiplier = 0.2f;

	UPROPERTY(VisibleAnywhere)
	USpringArmComponent* ThirdPersonSpringArmComp;

	UPROPERTY(VisibleAnywhere)
	UCameraComponent* ThirdPersonCameraComp;

Then, navigate in your character’s constructor and type in the following code (in order to create our components):

	//Create the spring arm comp
	ThirdPersonSpringArmComp = CreateDefaultSubobject<USpringArmComponent>(FName("ThirdPersonSpringArmComp"));

	//Attach it to the character's root component
	ThirdPersonSpringArmComp->SetupAttachment(GetRootComponent());

	//Create the third person camera comp
	ThirdPersonCameraComp = CreateDefaultSubobject<UCameraComponent>(FName("ThirdPersonCameraComp"));

	//Attach it our spring arm
	ThirdPersonCameraComp->SetupAttachment(ThirdPersonSpringArmComp);

Moreover, make sure to change the option SetOnlyOwnerSee from true to false on Mesh1P and FP_Gun. Here is my complete constructor at this stage:

ASELethalShotCharacter::ASELethalShotCharacter()
{
	// 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(false);
	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(false);			// only the owning player will see this mesh
	FP_Gun->bCastDynamicShadow = false;
	FP_Gun->CastShadow = false;
	// FP_Gun->SetupAttachment(Mesh1P, TEXT("GripPoint"));

	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, 30.0f, 10.0f);

	//Create the spring arm comp
	ThirdPersonSpringArmComp = CreateDefaultSubobject<USpringArmComponent>(FName("ThirdPersonSpringArmComp"));

	//Attach it to the character's root component
	ThirdPersonSpringArmComp->SetupAttachment(GetRootComponent());

	//Create the third person camera comp
	ThirdPersonCameraComp = CreateDefaultSubobject<UCameraComponent>(FName("ThirdPersonCameraComp"));

	//Attach it our spring arm
	ThirdPersonCameraComp->SetupAttachment(ThirdPersonSpringArmComp);

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

Then in order to switch from the first person to third person camera, declare the following function in the header file of your character:

private:
	/*De-activates the default camera and activates the third person camera*/
	void ActivateThirdPersonCamera();

In your character’s source file type in the following implementation of the above function:

void ASELethalShotCharacter::ActivateThirdPersonCamera()
{
	//Deactivates the first person camera
	FirstPersonCameraComponent->Deactivate();

	//Activates the third person camera
	ThirdPersonCameraComp->Activate();
}

When you’re done with that, right after the Time Dilation on the OnFire() function, call the above function. Here is the complete OnFire function at this step:

void ASELethalShotCharacter::OnFire()
{
	// try and fire a projectile
	if (ProjectileClass != 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);

		UWorld* const World = GetWorld();
		if (World != NULL)
		{
			// spawn the projectile at the muzzle
			World->SpawnActor<ASELethalShotProjectile>(ProjectileClass, SpawnLocation, SpawnRotation);

			//Dilate the time
			UGameplayStatics::SetGlobalTimeDilation(World, TimeDilationMultiplier);

			//Change the activate camera
			ActivateThirdPersonCamera();
		}
	}

	// 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);
		}
	}
}

Save and compile your code. Then, open up the Character’s Blueprint and assign the following options in your SpringArmComponent:

mainchar_thirdpersonspringarmoptions

In case you’re wondering how I came up with these values it was just by trial and error.

At this point, the active camera will change when the character fires his weapon. However you won’t be able to see the fired projectile just yet. This is because the projectile that comes with the First Person C++ Template contains an initial speed. This means that by the time you switch the active camera the projectile will have moved “a bit” forward.

Step 3. Transition from a Third Person Camera to the camera that is attached to the fired projectile

Before we modify the projectile class that comes with the demo to suits our needs, we need to inform the character’s controller that in case we attempt to set a new view target, the controller will search the new target’s hierarchy until it finds a valid camera. Then, it automatically activates that camera.

To do that, inside the BeginPlay function in your character’s source file type in the following code:

GetController()->bFindCameraComponentWhenViewTarget = true;

Here is my complete BeginPlay function at this point:

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

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

	//Tells the controller to search for an owned camera componenet to view through when used as a view target
	GetController()->bFindCameraComponentWhenViewTarget = true;
}

We’re now ready to modify the projectile class. Specifically we need to:

  1. Create a Spring Arm Component attached to the root component.
  2. Create a Camera Component attached to the aforementioned component.
  3. Since the World’s time is dilated we need to apply a velocity multiplier in order to adjust the movement speed of our projectile. This is done because we need to find a “sweetspot”, meaning that the projectiles should not travel too fast or too slow either.

Let’s get started then. In your projectile’s header file, type in the following declarations:

protected:

	/*The velocity multiplier that is applied when we slow the world's time*/
	UPROPERTY(EditAnywhere)
	float VelocityMultiplier = 5.f;

	UPROPERTY(VisibleAnywhere)
	UCameraComponent* ProjectileCameraComp;

	UPROPERTY(VisibleAnywhere)
	USpringArmComponent* ProjectileSpringArmComp;

public:

	/** Applies the velocity multiplier to the existing velocity of the projectile */
	void ApplyVelocityMultiplier();

Then, open up the source file of your projectile’s class and inside the constructor:

  1. Adjust the projectile’s max speed to 0.f
  2. Disable it’s bounce effect
  3. Adjust the initial life span to 0.f. This is done because we want the projectile to exist until it hits somethings. Please note that we’re not going to modify the OnHit function just yet!

Here is the needed code to apply all the menionted changes, plus the implementation of the ApplyVelocityMultiplier function:

void ASELethalShotProjectile::ApplyVelocityMultiplier()
{
	ProjectileMovement->Velocity *= VelocityMultiplier;

	//We need to explicitely tell the projectile movement to update it's velocity after we adjust it's value
	ProjectileMovement->UpdateComponentVelocity();
}

ASELethalShotProjectile::ASELethalShotProjectile() 
{
	// Use a sphere as a simple collision representation
	CollisionComp = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComp"));
	CollisionComp->InitSphereRadius(5.0f);
	CollisionComp->BodyInstance.SetCollisionProfileName("Projectile");
	CollisionComp->OnComponentHit.AddDynamic(this, &ASELethalShotProjectile::OnHit);		// set up a notification for when this component hits something blocking

	// Players can't walk on it
	CollisionComp->SetWalkableSlopeOverride(FWalkableSlopeOverride(WalkableSlope_Unwalkable, 0.f));
	CollisionComp->CanCharacterStepUpOn = ECB_No;

	// Set as root component
	RootComponent = CollisionComp;

	// Use a ProjectileMovementComponent to govern this projectile's movement
	ProjectileMovement = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileComp"));
	ProjectileMovement->UpdatedComponent = CollisionComp;
	ProjectileMovement->InitialSpeed = 5000.f;

	//Don't clamp the max speed of the projectile
	ProjectileMovement->MaxSpeed = 0.f;

	ProjectileMovement->bRotationFollowsVelocity = true;

	//Disable the bounce effect on projectiles
	ProjectileMovement->bShouldBounce = false;

	//We want this projectile to exist until it hits something so don't set an initial life span
	InitialLifeSpan = 0.f;

	//Create the spring arm component
	ProjectileSpringArmComp = CreateDefaultSubobject<USpringArmComponent>(FName("ProjectileSpringArmComp"));

	//Attach it to our root component
	ProjectileSpringArmComp->SetupAttachment(GetRootComponent());

	//Create the camera component
	ProjectileCameraComp = CreateDefaultSubobject<UCameraComponent>(FName("ProjectileCameraComp"));

	//Attach it to our spring arm component
	ProjectileCameraComp->SetupAttachment(ProjectileSpringArmComp);

}

Save and compile your code. Then, assign the following values to the projectile’s spring arm component:

projectile_springarm_comp_details

At this point, we need to modify our character’s class again in order to transition from it’s third person camera to the camera that is attached to the fired projectile. Before we do that, make sure to include the projectile’s header file right before the .generated.h file. Then, type in the following declarations:

private:

	/*De-activates the third person camera and activates the camera on the given projectile*/
	UFUNCTION()
	void ActivateProjectileCamera(ASELethalShotProjectile* Projectile);

protected:

	/*The time in seconds that we will transition from the third person camera to the projectile's camera*/
	UPROPERTY(EditAnywhere)
	float ThirdPersonCameraToProjectileCameraBlendTime = 0.05f;

	/*The delay in seconds that we will activate the projectile's camera*/
	UPROPERTY(EditAnywhere)
	float ThirdPersonToProjectileTransitionDelay = 0.005f;

Then, open up your character’s source file and type in the following implementation for the ActivateProjectileCamera function:

void ASELethalShotCharacter::ActivateProjectileCamera(ASELethalShotProjectile* Projectile)
{
	//Change the active camera based on the assigned blend time from within our editor
	Cast<APlayerController>(GetController())->SetViewTargetWithBlend(Projectile, ThirdPersonCameraToProjectileCameraBlendTime);
}

When you’re done with that, navigate to the OnFire function which resides inside the character’s source file and:

  1. Grab a reference of the spawned projectile
  2. Execute the ActivateProjectileCamera function after the ThirdPersonToProjectileTransitionDelay seconds.

To execute a function after a specified time in UE4 we’re going to use a timer. Here is my OnFire function at this point (which implements the mentioned steps):

void ASELethalShotCharacter::OnFire()
{
	// try and fire a projectile
	if (ProjectileClass != 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);

		UWorld* const World = GetWorld();
		if (World != NULL)
		{
			// spawn the projectile at the muzzle
			ASELethalShotProjectile* SpawnedProjectile = World->SpawnActor<ASELethalShotProjectile>(ProjectileClass, SpawnLocation, SpawnRotation);

			//Dilate the time
			UGameplayStatics::SetGlobalTimeDilation(World, TimeDilationMultiplier);

			//Change the activate camera
			ActivateThirdPersonCamera();

			//Create a timer handle and a timer delegate
			FTimerHandle TimerHandle;
			FTimerDelegate TimerDel;

			//Assign the corresponding UFUNCTION to the timer delegate
			TimerDel.BindUFunction(this, FName("ActivateProjectileCamera"), SpawnedProjectile);

			//Fire the delegate after she specified delay
			World->GetTimerManager().SetTimer(TimerHandle, TimerDel, ThirdPersonToProjectileTransitionDelay, false);
		}
	}

	// 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);
		}
	}
}

At this point, if we fire a projectile, the cameras will get activated with the following order:

  1. Third Person Camera
  2. Projectile Camera

Let’s continue on Step 4.

When the projectile is within a specified range from the enemy character we transition from the projectile’s camera to a camera that is attached to the enemy character

Step 4. Transition from the projectile’s camera to a camera attached to an enemy character

In order to complete this step we’re going to divide it into two sub steps:

  1. Create an Enemy Character
  2. Create the camera transition (this step includes 2 more sub-steps).

Step 4.1. Creating an Enemy Character

In this post, I’m going to use the skeletal mesh with the animations that reside in the Third Person Template Project. So, before we dive into our code, create the mentioned template project, and migrate those assets to your project. When you’re done with that, create a new C++ class which inherits from the Character class (in my case this class is named DummyEnemyCharacter). Then, we’re going to create a SpringArmComponent and a CameraComponent (just like in our projectile’s class) in order to achieve a camera transition.

Open up the header file of your DummyEnemyCharacter and add the following declarations:

protected:

	/*The blend time in seconds, from the projectile's camera to corresponding death camera*/
	UPROPERTY(EditAnywhere)
	float DeathCameraBlendTime = 0.05f;

	UPROPERTY(VisibleAnywhere)
	USpringArmComponent* DeathSpringArmComp;

	UPROPERTY(VisibleAnywhere)
	UCameraComponent* DeathCameraComp;

public:

	/*Transitions the active camera to the corresponding death camera*/
	void EnableCameraTransition();

Then, type in the following implementations in the source file of the character:

void ADummyEnemyCharacter::EnableCameraTransition()
{
	UGameplayStatics::GetPlayerController(GetWorld(), 0)->SetViewTargetWithBlend(this, DeathCameraBlendTime);
}

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

	DeathSpringArmComp = CreateDefaultSubobject<USpringArmComponent>(FName("DeathSpringArmComp"));

	DeathSpringArmComp->SetupAttachment(GetRootComponent());

	DeathCameraComp = CreateDefaultSubobject<UCameraComponent>(FName("DeathCameraComp"));

	DeathCameraComp->SetupAttachment(DeathSpringArmComp);
}

Save and compile your code. Then:

  1. Create a Blueprint based on the above class
  2. Assign the ThirdPerson Static Mesh from the Third Person Template project to the Mesh component
  3. Assign the Pre-Built Anim_BP to the above mesh

Steps 2 and 3 can be seen below:

dummychar_bp

Moreover, configure the following options for the enemy character’s spring arm component:

dummychar_bp_springarm

Step 4.2. Creating the transition from the projectile’s camera to the enemy’s camera

In order to create this transition, we need to know two things:

  1. The distance between the projectile and the enemy in order to create a smooth transition.
  2. That the projectile will hit an enemy. Up until now, we’re transition from the character’s camera to the projectile’s camera even if we don’t hit an enemy.

Step 4.2.1 Determining the distance between the projectile and the enemy

In case our spawned projectile hits an enemy, we’re going to store a reference to that enemy before the actual hit, so we can transition to his camera and see the hit result from that point of view. This reference will come from the Step 4.2.2 which is written below. For now we’re going to assume that we have a valid reference and focus on calling the camera transition.

To do that, go to your projectile’s header file and when you include the DummyEnemyCharacter.h (right before the .generated.h file), type in the following code:

private:

	/*The enemy that this projectile is going to kill*/
	ADummyEnemyCharacter* EnemyToKill = nullptr;

	/*True when the transition from the projectile's camera to the enemy's camera has been activated*/
	bool bActivatedTransition = false;

protected:

	/*The distance threshold that the death camera transition will occur*/
	UPROPERTY(EditAnywhere)
	float DeathCameraTransitionDistance = 300.f;

public:

	virtual void Tick(float DeltaSeconds) override;

	/*Sets the enemy that is going to be killed*/
	FORCEINLINE void SetEnemyToKill(ADummyEnemyCharacter* Enemy) { EnemyToKill = Enemy; }

As you might notice, we need to override the Tick function (which isn’t overriden by default). This is happening because we need to check the distance between the EnemyToKill and the projectile in order to activate our camera transition. Please note that the Projectile provided in the First Person Template project does not tick by default. With that said, we need to tell the engine that we want our projectiles to call the tick function. To do that, open up the constructor of your projectile class and add the following line:

PrimaryActorTick.bCanEverTick = true;

Once you have included the above line of code, here is the implementation of the tick function:

void ASELethalShotProjectile::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);

	if (!bActivatedTransition && EnemyToKill && (EnemyToKill->GetActorLocation() - GetActorLocation()).Size() <= DeathCameraTransitionDistance)
	{
		//We're near the enemy, enable the camera transition
		EnemyToKill->EnableCameraTransition();
		bActivatedTransition = true;
	}
}

Save and compile your code. Then, let’s move to the next step.

Step 4.2.2 Making sure that the spawned projectile hits an enemy

To make sure that the spawned projectile hits an enemy, we’re going to implement a line raycast. If we have a hit, we’re going to store a reference to our projectile. Before we implement the actual raycast, make sure to include the DummyEnemyCharacter.h file in your Character’s header files right before the .generated.h file. Then, type the following declarations:

private:

	/*Returns true if the projectile hits an enemy - false otherwise*/
	bool HitsAnEnemy(ASELethalShotProjectile* Projectile, ADummyEnemyCharacter*& HitEnemy);

	/*Performs a raycast and returns the hit actor - if any*/
	AActor* GetHitActor(ASELethalShotProjectile* Projectile);

protected:

	/*The raycast length*/
	UPROPERTY(EditAnywhere)
	float RaycastLength = 2000.f;

Open up your character’s source file and type in the following implementations:

bool ASELethalShotCharacter::HitsAnEnemy(ASELethalShotProjectile* Projectile, ADummyEnemyCharacter*& HitEnemy)
{
	HitEnemy = Cast<ADummyEnemyCharacter>(GetHitActor(Projectile));

	return (HitEnemy) ? true : false;
}

AActor* ASELethalShotCharacter::GetHitActor(ASELethalShotProjectile* Projectile)
{
	FHitResult HitResult;

	FVector StartLocation = Projectile->GetActorLocation();

	FVector EndLocation = StartLocation + (Projectile->GetActorForwardVector() * RaycastLength);

	FCollisionQueryParams CollisionParams;

	//Ignore the character and the projectile
	CollisionParams.AddIgnoredActor(this);
	CollisionParams.AddIgnoredActor(Projectile);

	//Perform a raycast - search for pawns only
	GetWorld()->LineTraceSingleByChannel(HitResult, StartLocation, EndLocation, ECollisionChannel::ECC_Pawn, CollisionParams);

	return HitResult.GetActor();
}

When you’re done with that, navigate to the OnFire function and make the following changes:

void ASELethalShotCharacter::OnFire()
{
	// try and fire a projectile
	if (ProjectileClass != 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);

		UWorld* const World = GetWorld();
		if (World != NULL)
		{
			// spawn the projectile at the muzzle
			ASELethalShotProjectile* SpawnedProjectile = World->SpawnActor<ASELethalShotProjectile>(ProjectileClass, SpawnLocation, SpawnRotation);

			ADummyEnemyCharacter* EnemyToBeKilled = nullptr;

			//If the projectile is going to hit an enemy - dilate the time and assign the enemy that we're going to kill
			if (HitsAnEnemy(SpawnedProjectile, EnemyToBeKilled))
			{
				SpawnedProjectile->SetEnemyToKill(EnemyToBeKilled);

				//Dilate the time
				UGameplayStatics::SetGlobalTimeDilation(World, TimeDilationMultiplier);

				//Change the activate camera
				ActivateThirdPersonCamera();

				//Create a timer handle and a timer delegate
				FTimerHandle TimerHandle;
				FTimerDelegate TimerDel;

				//Assign the corresponding UFUNCTION to the timer delegate
				TimerDel.BindUFunction(this, FName("ActivateProjectileCamera"), SpawnedProjectile);

				//Fire the delegate after she specified delay
				World->GetTimerManager().SetTimer(TimerHandle, TimerDel, ThirdPersonToProjectileTransitionDelay, false);
			}
		}
	}

	// 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);
		}
	}
}

At this point, if you place a DummyEnemyCharacter blueprint in your level and shoot him every camera transition will work. However, the projectile will stop the moment it hits the enemy. Don’t worry about that, we’re going to fix this in Step 5 which is written below. Moreover, at this point, only the projectiles that are lethal slow the world’s time.

Step 5. Configuring a Death for the DummyEnemyCharacter and resetting the cameras

Open up the header file of your main character and declare the following function:

public:

	/*Enables the FirstPersonCamera again*/
	void ResetActiveCamera();

Then, type in the following function:

void ASELethalShotCharacter::ResetActiveCamera()
{
	//Activate the first person camera
	FirstPersonCameraComponent->Activate();
	ThirdPersonCameraComp->Deactivate();

	//Transition to the first person camera
	Cast<APlayerController>(GetController())->SetViewTarget(this);
}

One you’re done with that, open up the header file of your DummyEnemyCharacter add declare the following function and property:

protected:

	/*The delay the camera will be reset right after the death of the enemy*/
	UPROPERTY(EditAnywhere)
	float CameraResetDelay = 1.f;

public:

	/*Kills the character and activates the FirstPerson camera again*/
	void Die();

Then, open up the source file of the DummyEnemyCharacter and when you include the main character’s header file, provide the following logic for the Die function:

void ADummyEnemyCharacter::Die()
{
	USkeletalMeshComponent* CharSM = GetMesh();
	//Enable ragdoll physics for our dummy enemy (to simulate a dying effect)
	CharSM->SetSimulatePhysics(true);
	CharSM->SetAllBodiesSimulatePhysics(true);
	CharSM->WakeAllRigidBodies();
	CharSM->SetCollisionEnabled(ECollisionEnabled::PhysicsOnly);

	//Reset the global time to default
	UGameplayStatics::SetGlobalTimeDilation(GetWorld(), 1.f);

	FTimerHandle TimerHandle;
	FTimerDelegate TimerDel;

	TimerDel.BindLambda([&]()
	{
		//Deactive the death camera
		DeathCameraComp->Deactivate();

		ASELethalShotCharacter* MainChar = Cast<ASELethalShotCharacter>(UGameplayStatics::GetPlayerCharacter(GetWorld(), 0));
		MainChar->ResetActiveCamera();
	});
	
	GetWorld()->GetTimerManager().SetTimer(TimerHandle, TimerDel, CameraResetDelay, false);
}

The last step, is to modify the OnHit function inside the projectile’s class in order to match our needs. To do that, locate that function and type in the following logic:

void ASELethalShotProjectile::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
	if ((OtherActor != NULL) && (OtherActor != this))
	{
		if (OtherActor->IsA<ADummyEnemyCharacter>())
		{
			Cast<ADummyEnemyCharacter>(OtherActor)->Die();
		}
		Destroy();
	}
}

Save and compile your code. You have re-created the cameras transition successfully!

Avatar photo

This Post Has 0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Back To Top