3 - Implementing Projectiles

How to implement projectiles for your First Person Shooter game.

Choose your operating system:

Windows

macOS

Linux

Prerequisite Topics

In order to understand and use the content on this page, make sure you are familiar with the following topics:

CrosshairsInGame.png

This is what you'll see at the end of this section.

Goals

The purpose of this section is to show you how to implement projectiles for your First Person Shooter game.

Objectives

By the end of this section of the tutorial, you'll be able to:

  • Add Projectiles to Your Game

  • Implement Shooting

  • Set Up Projectile Collision and Lifetime

  • Get Your Projectiles to Interact with the World

  • Add Crosshairs to Your Viewport

Steps

  • 3.1 - Adding Projectiles to Your Game

  • 3.2 - Implementing Shooting

  • 3.3 - Setting Up Projectile Collision and Lifetime

  • 3.4 - Getting Projectiles to Interact with the World

  • 3.5 - Adding Crosshairs to Your Viewport

3.1 - Adding Projectiles to your Game

Now that you have set up your character, it's time to implement a projectile weapon. You are going to program a simple grenade-like projectile to shoot from the center of the screen and fly until it collides with the world. During this step, you are going to add input and create a new code class for our projectile.

Adding Fire Action Mapping

  1. In the Edit menu, click Project Settings.

  2. Under the Engine heading on the left side of the Project Settings tab, click Input.

  3. Under Bindings, click the plus sign next to Action Mappings.

  4. Click the arrow to the left of Action Mappings.

  5. Type "Fire" into the text field that appears, then click the arrow to the left of the text box to expand the action binding options.

  6. In the dropdown menu, select Left Mouse Button from the Mouse dropdown list.

  7. Your input settings should now look like the following:

    ActionMapping.png

  8. Close the Project Settings menu.

Adding a Projectile Class

  1. In the File menu, select New C++ Class... to choose your new parent class.

  2. The Choose Parent Class menu will open. Scroll down, select Actor as the parent class, and click Next.

    AddProjectileClass.png

  3. Name the new class "FPSProjectile," then click Create Class.

    CreateProjectileClass.png

Adding a USphere Component

  1. Locate the FPSProjectile class header in the Solution Explorer and open FPSProjectile.h .

  2. Add the SphereComponent header file:

    #include "Components/SphereComponent.h"
  3. Add a reference to a USphereComponent in the FPSProjectile interface.

    // Sphere collision component.
    UPROPERTY(VisibleDefaultsOnly, Category = Projectile)
    USphereComponent* CollisionComponent;
  4. FPSProjectile.h should now look like the following:

    // Copyright Epic Games, Inc. All Rights Reserved.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "GameFramework/Actor.h"
    #include "Components/SphereComponent.h"
    #include "FPSProjectile.generated.h"
    
    UCLASS()
    class FPSPROJECT_API AFPSProjectile : public AActor
    {
        GENERATED_BODY()
    
    public: 
        // Sets default values for this actor's properties
        AFPSProjectile();
    
    protected:
        // Called when the game starts or when spawned
        virtual void BeginPlay() override;
    
    public:
        // Called every frame
        virtual void Tick( float DeltaTime ) override;
    
        // Sphere collision component
        UPROPERTY(VisibleDefaultsOnly, Category = Projectile)
        USphereComponent* CollisionComponent;
    };
  5. Locate the FPSProjectile class CPP file in the Solution Explorer and open FPSProjectile.cpp .

  6. Add the following code to the AFPSProjectile constructor (after PrimaryActorTick.bcanEverTick ) in FPSProjectile.cpp :

    if(!RootComponent)
    {
    RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent"));
    }
    if(!CollisionComponent)
    {
    // Use a sphere as a simple collision representation.
    CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
    // Set the sphere's collision radius.
    CollisionComponent->InitSphereRadius(15.0f);
    // Set the root component to be the collision component.
    RootComponent = CollisionComponent;
    }

    You are making CollisionComponent a RootComponent since the simulation will drive it.

  7. FPSProjectile.cpp should now look like the following:

    // Copyright Epic Games, Inc. All Rights Reserved.
    
    #include "FPSProjectile.h"
    
    // Sets default values
    AFPSProjectile::AFPSProjectile()
    {
        // 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;
    
        if(!RootComponent)
        {
            RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent"));
        }
    
        if(!CollisionComponent)
        {
            // Use a sphere as a simple collision representation.
            CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
            // Set the sphere's collision radius.
            CollisionComponent->InitSphereRadius(15.0f);
            // Set the root component to be the collision component.
            RootComponent = CollisionComponent;
        }
    }
    
    // Called when the game starts or when spawned
    void AFPSProjectile::BeginPlay()
    {
        Super::BeginPlay();
    
    }
    
    // Called every frame
    void AFPSProjectile::Tick( float DeltaTime )
    {
        Super::Tick( DeltaTime );
    
    }

Adding a Projectile Movement Component

  1. Locate the FPSProjectile class header file in the Solution Explorer and open FPSProjectile.h .

  2. Add the ProjectileMovementComponent header file.

    #include "GameFramework/ProjectileMovementComponent.h"
  3. Add the following code to FPSProjectile.h :

    // Projectile movement component.
    UPROPERTY(VisibleAnywhere, Category = Movement)
    UProjectileMovementComponent* ProjectileMovementComponent;
  4. FPSProjectile.h should now look like the following:

    // Copyright Epic Games, Inc. All Rights Reserved.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "GameFramework/Actor.h"
    #include "Components/SphereComponent.h"
    #include "GameFramework/ProjectileMovementComponent.h"
    #include "FPSProjectile.generated.h"
    
    UCLASS()
    class FPSPROJECT_API AFPSProjectile : public AActor
    {
        GENERATED_BODY()
    
    public: 
        // Sets default values for this actor's properties
        AFPSProjectile();
    
    protected:
        // Called when the game starts or when spawned
        virtual void BeginPlay() override;
    
    public:
        // Called every frame
        virtual void Tick( float DeltaTime ) override;
    
        // Sphere collision component.
        UPROPERTY(VisibleDefaultsOnly, Category = Projectile)
        USphereComponent* CollisionComponent;
    
        // Projectile movement component.
        UPROPERTY(VisibleAnywhere, Category = Movement)
        UProjectileMovementComponent* ProjectileMovementComponent;
    };
  5. Open FPSProjectile.cpp from the Solution Explorer.

  6. Add the following lines of code to the FPSProjectile constructor in FPSProjectile.cpp :

    if(!ProjectileMovementComponent)
    {
        // Use this component to drive this projectile's movement.
        ProjectileMovementComponent = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovementComponent"));
        ProjectileMovementComponent->SetUpdatedComponent(CollisionComponent);
        ProjectileMovementComponent->InitialSpeed = 3000.0f;
        ProjectileMovementComponent->MaxSpeed = 3000.0f;
        ProjectileMovementComponent->bRotationFollowsVelocity = true;
        ProjectileMovementComponent->bShouldBounce = true;
        ProjectileMovementComponent->Bounciness = 0.3f;
        ProjectileMovementComponent->ProjectileGravityScale = 0.0f;
    }
  7. FPSProjectile.cpp should now look like the following:

    // Copyright Epic Games, Inc. All Rights Reserved.
    
    #include "FPSProjectile.h"
    
    // Sets default values
    AFPSProjectile::AFPSProjectile()
    {
        // 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;
        if(!RootComponent)
        {
            RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent"));
        }
    
        if(!CollisionComponent)
        {
            // Use a sphere as a simple collision representation.
            CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
            // Set the sphere's collision radius.
            CollisionComponent->InitSphereRadius(15.0f);
            // Set the root component to be the collision component.
            RootComponent = CollisionComponent;
        }
    
        if(!ProjectileMovementComponent)
        {
            // Use this component to drive this projectile's movement.
            ProjectileMovementComponent = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovementComponent"));
            ProjectileMovementComponent->SetUpdatedComponent(CollisionComponent);
            ProjectileMovementComponent->InitialSpeed = 3000.0f;
            ProjectileMovementComponent->MaxSpeed = 3000.0f;
            ProjectileMovementComponent->bRotationFollowsVelocity = true;
            ProjectileMovementComponent->bShouldBounce = true;
            ProjectileMovementComponent->Bounciness = 0.3f;
            ProjectileMovementComponent->ProjectileGravityScale = 0.0f;
        }
    }
    
    // Called when the game starts or when spawned
    void AFPSProjectile::BeginPlay()
    {
        Super::BeginPlay();
    
    }
    
    // Called every frame
    void AFPSProjectile::Tick( float DeltaTime )
    {
        Super::Tick( DeltaTime );
    
    }

Setting the Projectile's Initial Velocity

  1. Open FPSProjectile.h in the Solution Explorer.

  2. Add the following function declaration in FPSProjectile.h:

    // Function that initializes the projectile's velocity in the shoot direction.
    void FireInDirection(const FVector& ShootDirection);

    This function will be responsible for launching the projectile.

  3. FPSProjectile.h should now look like the following:

    // Copyright Epic Games, Inc. All Rights Reserved.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "GameFramework/Actor.h"
    #include "Components/SphereComponent.h"
    #include "GameFramework/ProjectileMovementComponent.h"
    #include "FPSProjectile.generated.h"
    
    UCLASS()
    class FPSPROJECT_API AFPSProjectile : public AActor
    {
        GENERATED_BODY()
    
    public: 
        // Sets default values for this actor's properties
        AFPSProjectile();
    
    protected:
        // Called when the game starts or when spawned
        virtual void BeginPlay() override;
    
    public:
        // Called every frame
        virtual void Tick( float DeltaTime ) override;
    
        // Sphere collision component.
        UPROPERTY(VisibleDefaultsOnly, Category = Projectile)
        USphereComponent* CollisionComponent;
    
        // Projectile movement component.
        UPROPERTY(VisibleAnywhere, Category = Movement)
        UProjectileMovementComponent* ProjectileMovementComponent;
    
        // Function that initializes the projectile's velocity in the shoot direction.
        void FireInDirection(const FVector& ShootDirection);
    };
  4. Open FPSProjectile.cpp in the Solution Explorer.

  5. Add the following function definition to FPSProjectile.cpp :

    // Function that initializes the projectile's velocity in the shoot direction.
    void AFPSProjectile::FireInDirection(const FVector& ShootDirection)
    {
        ProjectileMovementComponent->Velocity = ShootDirection * ProjectileMovementComponent->InitialSpeed;
    }

    You only needed to supply a launch direction because the projectile's speed is defined by ProjectileMovementComponent .

  6. FPSProjectile.cpp should now look like the following:

    // Copyright Epic Games, Inc. All Rights Reserved.
    
    #include "FPSProjectile.h"
    
    // Sets default values
    AFPSProjectile::AFPSProjectile()
    {
    // 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;
    
    if(!RootComponent)
    {
        // Use a sphere as a simple collision representation.
        CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
        // Set the sphere's collision radius.
        CollisionComponent->InitSphereRadius(15.0f);
        // Set the root component to be the collision component.
        RootComponent = CollisionComponent;
    }
    
    if(!ProjectileMovementComponent)
    {
        // Use this component to drive this projectile's movement.
        ProjectileMovementComponent = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovementComponent"));
        ProjectileMovementComponent->SetUpdatedComponent(CollisionComponent);
        ProjectileMovementComponent->InitialSpeed = 3000.0f;
        ProjectileMovementComponent->MaxSpeed = 3000.0f;
        ProjectileMovementComponent->bRotationFollowsVelocity = true;
        ProjectileMovementComponent->bShouldBounce = true;
        ProjectileMovementComponent->Bounciness = 0.3f;
        ProjectileMovementComponent->ProjectileGravityScale = 0.0f;
    }
    }
    
    // Called when the game starts or when spawned
    void AFPSProjectile::BeginPlay()
    {
    Super::BeginPlay();
    
    }
    
    // Called every frame
    void AFPSProjectile::Tick( float DeltaTime )
    {
    Super::Tick( DeltaTime );
    
    }
    
    // Function that initializes the projectile's velocity in the shoot direction.
    void AFPSProjectile::FireInDirection(const FVector& ShootDirection)
    {
    ProjectileMovementComponent->Velocity = ShootDirection * ProjectileMovementComponent->InitialSpeed;
    }

Binding the Fire Input Action

  1. Open FPSCharacter.h in the Solution Explorer.

  2. Add the following function declaration in FPSCharacter.h :

    // Function that handles firing projectiles.
    UFUNCTION()
    void Fire();
  3. FPSCharacter.h should now look like the following:

    // Copyright Epic Games, Inc. All Rights Reserved.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "GameFramework/Character.h"
    #include "Camera/CameraComponent.h"
    #include "Components/CapsuleComponent.h"
    #include "FPSCharacter.generated.h"
    
    UCLASS()
    class FPSPROJECT_API AFPSCharacter : public ACharacter
    {
        GENERATED_BODY()
    
    public:
        // Sets default values for this character's properties
        AFPSCharacter();
    
    protected:
        // Called when the game starts or when spawned
        virtual void BeginPlay() override;
    
    public:
        // Called every frame
        virtual void Tick( float DeltaTime ) override;
    
        // Called to bind functionality to input
        virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
    
        // Handles input for moving forward and backward.
        UFUNCTION()
        void MoveForward(float Value);
    
        // Handles input for moving right and left.
        UFUNCTION()
        void MoveRight(float Value);
    
        // Sets jump flag when key is pressed.
        UFUNCTION()
        void StartJump();
    
        // Clears jump flag when key is released.
        UFUNCTION()
        void StopJump();
    
        // Function that handles firing projectiles.
        UFUNCTION()
        void Fire();
    
        // FPS camera
        UPROPERTY(VisibleAnywhere)
        UCameraComponent* FPSCameraComponent;
    
        // First-person mesh (arms), visible only to the owning player.
        UPROPERTY(VisibleDefaultsOnly, Category = Mesh)
        USkeletalMeshComponent* FPSMesh;
    };
  4. Locate the FPSCharacter CPP file in the Solution Explorer and open FPSCharacter.cpp .

  5. To bind the Fire function, add the following code to the SetupPlayerInputComponent function in FPSCharacter.cpp:

    PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &AFPSCharacter::Fire);
  6. Now, add the following function definition to FPSCharacter.cpp :

    void AFPSCharacter::Fire()
    {
    }
  7. FPSCharacter.cpp should now look like the following:

    // Copyright Epic Games, Inc. All Rights Reserved.
    
    #include "FPSCharacter.h"
    
    // Sets default values
    AFPSCharacter::AFPSCharacter()
    {
        // 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;
    
        // Create a first person camera component.
        FPSCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("FirstPersonCamera"));
        check(FPSCameraComponent != nullptr);
    
        // Attach the camera component to our capsule component.
        FPSCameraComponent->SetupAttachment(CastChecked<USceneComponent, UCapsuleComponent>(GetCapsuleComponent()));
    
        // Position the camera slightly above the eyes.
        FPSCameraComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 50.0f + BaseEyeHeight));
    
        // Enable the pawn to control camera rotation.
        FPSCameraComponent->bUsePawnControlRotation = true;
    
        // Create a first person mesh component for the owning player.
        FPSMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("FirstPersonMesh"));
        check(FPSMesh != nullptr);
    
        // Only the owning player sees this mesh.
        FPSMesh->SetOnlyOwnerSee(true);
    
        // Attach the FPS mesh to the FPS camera.
        FPSMesh->SetupAttachment(FPSCameraComponent);
    
        // Disable some environmental shadowing to preserve the illusion of having a single mesh.
        FPSMesh->bCastDynamicShadow = false;
        FPSMesh->CastShadow = false;
    
        // The owning player doesn't see the regular (third-person) body mesh.
        GetMesh()->SetOwnerNoSee(true);
    }
    
    // Called when the game starts or when spawned
    void AFPSCharacter::BeginPlay()
    {
        Super::BeginPlay();
    
        if (GEngine)
        {
            // Display a debug message for five seconds. 
            // The -1 "Key" value argument prevents the message from being updated or refreshed.
            GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("We are using FPSCharacter."));
        }
    }
    
    // Called every frame
    void AFPSCharacter::Tick( float DeltaTime )
    {
        Super::Tick( DeltaTime );
    
    }
    
    // Called to bind functionality to input
    void AFPSCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
    {
        Super::SetupPlayerInputComponent(PlayerInputComponent);
    
        // Set up "movement" bindings.
        PlayerInputComponent->BindAxis("MoveForward", this, &AFPSCharacter::MoveForward);
        PlayerInputComponent->BindAxis("MoveRight", this, &AFPSCharacter::MoveRight);
    
        // Set up "look" bindings.
        PlayerInputComponent->BindAxis("Turn", this, &AFPSCharacter::AddControllerYawInput);
        PlayerInputComponent->BindAxis("LookUp", this, &AFPSCharacter::AddControllerPitchInput);
    
        // Set up "action" bindings.
        PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &AFPSCharacter::StartJump);
        PlayerInputComponent->BindAction("Jump", IE_Released, this, &AFPSCharacter::StopJump);
        PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &AFPSCharacter::Fire);
    }
    
    void AFPSCharacter::MoveForward(float Value)
    {
        // Find out which way is "forward" and record that the player wants to move that way.
        FVector Direction = FRotationMatrix(Controller->GetControlRotation()).GetScaledAxis(EAxis::X);
        AddMovementInput(Direction, Value);
    }
    
    void AFPSCharacter::MoveRight(float Value)
    {
        // Find out which way is "right" and record that the player wants to move that way.
        FVector Direction = FRotationMatrix(Controller->GetControlRotation()).GetScaledAxis(EAxis::Y);
        AddMovementInput(Direction, Value);
    }
    
    void AFPSCharacter::StartJump()
    {
        bPressedJump = true;
    }
    
    void AFPSCharacter::StopJump()
    {
        bPressedJump = false;
    }
    
    void AFPSCharacter::Fire()
    {
    }

Defining the Projectile's Spawn Location

  1. When spawning the FPSProjectile actor, there are two points to consider when implementing the OnFire function, namely:

    • Where to spawn the projectile.

    • The projectile class (so that FPSCharacter and its derived Blueprint know what projectile to spawn).

    You are going to use a camera-space offset vector to determine the projectile's spawn location. You will make this parameter editable so that you can set and tweak it in your BP_FPSCharacter Blueprint. Ultimately, you'll be able to calculate an initial location for the projectile based on this data.

  2. Open FPSCharacter.h in the Solution Explorer.

  3. Add the following code to FPSCharacter.h :

    // Gun muzzle offset from the camera location.
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay)
    FVector MuzzleOffset;

    EditAnywhere enables you to change the value of the muzzle offset within the Defaults mode of the Blueprint Editor or within the Details tab for any instance of the character. The BlueprintReadWrite specifier enables you to get and set the value of the muzzle offset within a Blueprint.

  4. Add the following code to FPSCharacter.h under the protected access specifier:

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

    EditDefaultsOnly means that you will only be able to set the projectile class as a default on the Blueprint, not on each instance of the Blueprint.

  5. FPSCharacter.h should look like the following:

    // Copyright Epic Games, Inc. All Rights Reserved.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "GameFramework/Character.h"
    #include "Camera/CameraComponent.h"
    #include "Components/CapsuleComponent.h"
    #include "FPSCharacter.generated.h"
    
    UCLASS()
    class FPSPROJECT_API AFPSCharacter : public ACharacter
    {
    GENERATED_BODY()
    
    public:
    // Sets default values for this character's properties
    AFPSCharacter();
    
    protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;
    
    // Projectile class to spawn.
    UPROPERTY(EditDefaultsOnly, Category = Projectile)
    TSubclassOf<class AFPSProjectile> ProjectileClass;
    
    public:
    // Called every frame
    virtual void Tick( float DeltaTime ) override;
    
    // Called to bind functionality to input
    virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
    
    // Handles input for moving forward and backward.
    UFUNCTION()
    void MoveForward(float Value);
    
    // Handles input for moving right and left.
    UFUNCTION()
    void MoveRight(float Value);
    
    // Sets jump flag when key is pressed.
    UFUNCTION()
    void StartJump();
    
    // Clears jump flag when key is released.
    UFUNCTION()
    void StopJump();
    
    // Function that fires projectiles.
    UFUNCTION()
    void Fire();
    
    // FPS camera
    UPROPERTY(VisibleAnywhere)
    UCameraComponent* FPSCameraComponent;
    
    // First-person mesh (arms), visible only to the owning player.
    UPROPERTY(VisibleDefaultsOnly, Category = Mesh)
    USkeletalMeshComponent* FPSMesh;
    
    // Gun muzzle offset from the camera location.
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay)
    FVector MuzzleOffset;
    
    };

Compiling and Checking Your Code

It is now time to compile and check your newly implemented projectile code.

  1. Save all of your header and implementation files in Visual Studio.

  2. Locate FPSProject in the Solution Explorer .

  3. Right-click on FPSProject and select Build to compile your project.

    BuildFPSProject.png

    The purpose of this step is to catch any build errors before moving onto the next step. If you encounter any build errors or warnings outside the scope of this tutorial, refer to our Coding Standard and the Unreal Engine API Reference .

3.2 - Implementing Shooting

Learn how to implement shooting for your First Person Shooter character.

Implementing the Fire Function

  1. Add the following line to FPSCharacter.h.

    #include "FPSProjectile.h"
  2. Add the following Fire function definition to FPSCharacter.cpp:

    void AFPSCharacter::Fire()
    {
    // Attempt to fire a projectile.
    if (ProjectileClass)
    {
    // Get the camera transform.
    FVector CameraLocation;
    FRotator CameraRotation;
    GetActorEyesViewPoint(CameraLocation, CameraRotation);
    
    // Set MuzzleOffset to spawn projectiles slightly in front of the camera.
    MuzzleOffset.Set(100.0f, 0.0f, 0.0f);
    
    // Transform MuzzleOffset from camera space to world space.
    FVector MuzzleLocation = CameraLocation + FTransform(CameraRotation).TransformVector(MuzzleOffset);
    
    // Skew the aim to be slightly upwards.
    FRotator MuzzleRotation = CameraRotation;
    MuzzleRotation.Pitch += 10.0f;
    
    UWorld* World = GetWorld();
    if (World)
    {
        FActorSpawnParameters SpawnParams;
        SpawnParams.Owner = this;
        SpawnParams.Instigator = GetInstigator();
    
        // Spawn the projectile at the muzzle.
        AFPSProjectile* Projectile = World->SpawnActor<AFPSProjectile>(ProjectileClass, MuzzleLocation, MuzzleRotation, SpawnParams);
        if (Projectile)
        {
            // Set the projectile's initial trajectory.
            FVector LaunchDirection = MuzzleRotation.Vector();
            Projectile->FireInDirection(LaunchDirection);
        }
    }
    }
    }
  3. FPSCharacter.h should now look like the following:

    // Copyright Epic Games, Inc. All Rights Reserved.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "GameFramework/Character.h"
    #include "Camera/CameraComponent.h"
    #include "Components/CapsuleComponent.h"
    #include "FPSProjectile.h"
    #include "FPSCharacter.generated.h"
    
    UCLASS()
    class FPSPROJECT_API AFPSCharacter : public ACharacter
    {
        GENERATED_BODY()
    
    public:
        // Sets default values for this character's properties
        AFPSCharacter();
    
    protected:
        // Called when the game starts or when spawned
        virtual void BeginPlay() override;
    
       // Projectile class to spawn.
       UPROPERTY(EditAnywhere, Category = Projectile)
       TSubclassOf<class AFPSProjectile> ProjectileClass;
    
    public:
        // Called every frame
        virtual void Tick(float DeltaTime) override;
    
        // Called to bind functionality to input
       virtual void SetupPlayerInputComponent(class UIComponent* PlayerInputComponent) override;
    
        // Handles input for moving forward and backward.
        UFUNCTION()
        void MoveForward(float Value);
    
        // Handles input for moving right and left.
        UFUNCTION()
        void MoveRight(float Value);
    
        // Sets jump flag when key is pressed.
        UFUNCTION()
        void StartJump();
    
        // Clears jump flag when key is released.
        UFUNCTION()
        void StopJump();
    
        // Function that fires projectiles.
        UFUNCTION()
        void Fire();
    
        // FPS camera
        UPROPERTY(VisibleAnywhere)
        UCameraComponent* FPSCameraComponent;
    
        // First-person mesh (arms), visible only to the owning player.
        UPROPERTY(VisibleDefaultsOnly, Category = Mesh)
        USkeletalMeshComponent* FPSMesh;
    
        // Gun muzzle offset from the camera location.
        UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay)
        FVector MuzzleOffset;
    
    };
  4. FPSCharacter.cpp should now look like the following:

    // Copyright Epic Games, Inc. All Rights Reserved.
    
    #include "FPSCharacter.h"
    
    // Sets default values
    AFPSCharacter::AFPSCharacter()
    {
        // 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;
    
        // Create a first person camera component.
        FPSCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("FirstPersonCamera"));
        check(FPSCameraComponent != nullptr);
    
        // Attach the camera component to our capsule component.
        FPSCameraComponent->SetupAttachment(CastChecked<USceneComponent, UCapsuleComponent>(GetCapsuleComponent()));
    
        // Position the camera slightly above the eyes.
        FPSCameraComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 50.0f + BaseEyeHeight));
    
        // Enable the Pawn to control camera rotation.
        FPSCameraComponent->bUsePawnControlRotation = true;
    
        // Create a first person mesh component for the owning player.
        FPSMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("FirstPersonMesh"));
        check(FPSMesh != nullptr);
    
        // Only the owning player sees this mesh.
        FPSMesh->SetOnlyOwnerSee(true);
    
        // Attach the FPS mesh to the FPS camera.
        FPSMesh->SetupAttachment(FPSCameraComponent);
    
        // Disable some environmental shadowing to preserve the illusion of having a single mesh.
        FPSMesh->bCastDynamicShadow = false;
        FPSMesh->CastShadow = false;
    
        // The owning player doesn't see the regular (third-person) body mesh.
        GetMesh()->SetOwnerNoSee(true);
    }
    
    // Called when the game starts or when spawned
    void AFPSCharacter::BeginPlay()
    {
        Super::BeginPlay();
    
        if (GEngine)
        {
            // Display a debug message for five seconds. 
            // The -1 "Key" value argument prevents the message from being updated or refreshed.
            GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("We are using FPSCharacter."));
        }
    }
    
    // Called every frame
    void AFPSCharacter::Tick( float DeltaTime )
    {
        Super::Tick( DeltaTime );
    
    }
    
    // Called to bind functionality to input
    void AFPSCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
    {
        Super::SetupPlayerInputComponent(PlayerInputComponent);
    
        // Set up "movement" bindings.
        PlayerInputComponent->BindAxis("MoveForward", this, &AFPSCharacter::MoveForward);
        PlayerInputComponent->BindAxis("MoveRight", this, &AFPSCharacter::MoveRight);
    
        // Set up "look" bindings.
        PlayerInputComponent->BindAxis("Turn", this, &AFPSCharacter::AddControllerYawInput);
        PlayerInputComponent->BindAxis("LookUp", this, &AFPSCharacter::AddControllerPitchInput);
    
        // Set up "action" bindings.
        PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &AFPSCharacter::StartJump);
        PlayerInputComponent->BindAction("Jump", IE_Released, this, &AFPSCharacter::StopJump);
        PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &AFPSCharacter::Fire);
    }
    
    void AFPSCharacter::MoveForward(float Value)
    {
        // Find out which way is "forward" and record that the player wants to move that way.
        FVector Direction = FRotationMatrix(Controller->GetControlRotation()).GetScaledAxis(EAxis::X);
        AddMovementInput(Direction, Value);
    }
    
    void AFPSCharacter::MoveRight(float Value)
    {
        // Find out which way is "right" and record that the player wants to move that way.
        FVector Direction = FRotationMatrix(Controller->GetControlRotation()).GetScaledAxis(EAxis::Y);
        AddMovementInput(Direction, Value);
    }
    
    void AFPSCharacter::StartJump()
    {
        bPressedJump = true;
    }
    
    void AFPSCharacter::StopJump()
    {
        bPressedJump = false;
    }
    
    void AFPSCharacter::Fire()
    {
        // Attempt to fire a projectile.
        if (ProjectileClass)
        {
            // Get the camera transform.
            FVector CameraLocation;
            FRotator CameraRotation;
            GetActorEyesViewPoint(CameraLocation, CameraRotation);
    
            // Set MuzzleOffset to spawn projectiles slightly in front of the camera.
            MuzzleOffset.Set(100.0f, 0.0f, 0.0f);
    
            // Transform MuzzleOffset from camera space to world space.
            FVector MuzzleLocation = CameraLocation + FTransform(CameraRotation).TransformVector(MuzzleOffset);
    
            // Skew the aim to be slightly upwards.
            FRotator MuzzleRotation = CameraRotation;
            MuzzleRotation.Pitch += 10.0f;
    
            UWorld* World = GetWorld();
            if (World)
            {
                FActorSpawnParameters SpawnParams;
                SpawnParams.Owner = this;
                SpawnParams.Instigator = GetInstigator();
    
                // Spawn the projectile at the muzzle.
                AFPSProjectile* Projectile = World->SpawnActor<AFPSProjectile>(ProjectileClass, MuzzleLocation, MuzzleRotation, SpawnParams);
                if (Projectile)
                {
                    // Set the projectile's initial trajectory.
                    FVector LaunchDirection = MuzzleRotation.Vector();
                    Projectile->FireInDirection(LaunchDirection);
                }
            }
        }
    }
  5. Save FPSCharacter.h and FPSCharacter.cpp in Visual Studio.

  6. Locate FPSProject in the Solution Explorer .

  7. Right-click on FPSProject and select Build to compile your project.

    BuildFPSProject.png

Importing the Projectile Mesh

Before continuing, download and extract the sample mesh from the following link: "Projectile Mesh"

  1. Right-click inside the Content Browser's file box to open the Import Asset dialog box

    Although we cover right-click import, there are three methods to import content. Read these documents showing how to import content using:

  2. Click 'Import to /Game...' to open the Import dialog box.

    RightClickImport.png

  3. Locate and select the Sphere.fbx mesh file.

  4. Click Open to begin importing the mesh to your project.

  5. In the Content Browser , the FBX Import Options dialog box appears. Clicking Import All adds your mesh to the Project.

    Disregard the following error regarding smoothing groups:

    FBXWarning.png

    This mesh still illustrates the first person mesh setup and it will work with the animations you'll set-up in a later section.

  6. Click the Save button to save your imported static mesh.

Adding the Projectile's Mesh

  1. Add the following code to FPSProjectile.h:

    // Projectile mesh
    UPROPERTY(VisibleDefaultsOnly, Category = Projectile)
    UStaticMeshComponent* ProjectileMeshComponent;
  2. Add the following code to the constructor in FPSProjectile.cpp:

    if(!ProjectileMeshComponent)
    {
        ProjectileMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMeshComponent"));
        static ConstructorHelpers::FObjectFinder<UStaticMesh>Mesh(TEXT("[ADD STATIC MESH ASSET REFERENCE]"));
        if(Mesh.Succeeded())
        {
            ProjectileMeshComponent->SetStaticMesh(Mesh.Object);
        }
    }
  3. Open the Content Browser, right-click the Sphere static mesh, and select Copy Reference :

    SphereCopyReference.png

  4. Go back to the ProjectileMeshComponent code in FPSProjectile.cpp and replace [ADD STATIC MESH ASSET REFERENCE] with the copied reference. Your code should look similar to the following:

    if(!ProjectileMeshComponent)
    {
        ProjectileMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMeshComponent"));
        static ConstructorHelpers::FObjectFinder<UStaticMesh>Mesh(TEXT("'/Game/Sphere.Sphere'"));
        if(Mesh.Succeeded())
        {
            ProjectileMeshComponent->SetStaticMesh(Mesh.Object);
        }
    }

    Your asset reference path may vary depending on where you saved the Sphere mesh in the Content Browser. Also, when you paste in the copied asset reference, the reference contains the asset's type name before the asset's reference path. In our case, you will observe StaticMesh'/Game/Sphere.Sphere'. Make sure to remove the asset's type name (for example, StaticMesh) from the reference path.

  5. FPSProjectile.h should look like the following:

    // Copyright Epic Games, Inc. All Rights Reserved.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "GameFramework/Actor.h"
    #include "Components/SphereComponent.h"
    #include "GameFramework/ProjectileMovementComponent.h"
    #include "FPSProjectile.generated.h"
    
    UCLASS()
    class FPSPROJECT_API AFPSProjectile : public AActor
    {
        GENERATED_BODY()
    
    public: 
        // Sets default values for this actor's properties
        AFPSProjectile();
    
    protected:
        // Called when the game starts or when spawned
        virtual void BeginPlay() override;
    
    public: 
        // Called every frame
        virtual void Tick(float DeltaTime) override;
    
        // Sphere collision component
        UPROPERTY(VisibleDefaultsOnly, Category = Projectile)
        USphereComponent* CollisionComponent;
    
        // Projectile movement component
        UPROPERTY(VisibleAnywhere, Category = Movement)
        UProjectileMovementComponent* ProjectileMovementComponent;
    
        // Projectile mesh
        UPROPERTY(VisibleDefaultsOnly, Category = Projectile)
        UStaticMeshComponent* ProjectileMeshComponent;
    
        // Function that initializes the projectile's velocity in the shoot direction.
        void FireInDirection(const FVector& ShootDirection);
    
    };
  6. FPSProjectile.cpp should look like the following:

    // Copyright Epic Games, Inc. All Rights Reserved.         
    
    #include "FPSProjectile.h"
    
    // Sets default values
    AFPSProjectile::AFPSProjectile()
    {
        // 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;
    
        if (!RootComponent)
        {
            RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent"));
        }
    
        if (!CollisionComponent)
        {
            // Use a sphere as a simple collision representation.
            CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
            // Set the sphere's collision radius.
            CollisionComponent->InitSphereRadius(15.0f);
            // Set the root component to be the collision component.
            RootComponent = CollisionComponent;
        }
    
        if (!ProjectileMovementComponent)
        {
            // Use this component to drive this projectile's movement.
            ProjectileMovementComponent = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovementComponent"));
            ProjectileMovementComponent->SetUpdatedComponent(CollisionComponent);
            ProjectileMovementComponent->InitialSpeed = 3000.0f;
            ProjectileMovementComponent->MaxSpeed = 3000.0f;
            ProjectileMovementComponent->bRotationFollowsVelocity = true;
            ProjectileMovementComponent->bShouldBounce = true;
            ProjectileMovementComponent->Bounciness = 0.3f;
            ProjectileMovementComponent->ProjectileGravityScale = 0.0f;
        }
    
        if (!ProjectileMeshComponent)
        {
            ProjectileMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMeshComponent"));
            static ConstructorHelpers::FObjectFinder<UStaticMesh>Mesh(TEXT("'/Game/Sphere.Sphere'"));
            if (Mesh.Succeeded())
            {
                ProjectileMeshComponent->SetStaticMesh(Mesh.Object);
            }
        }
    }
    
    // Called when the game starts or when spawned
    void AFPSProjectile::BeginPlay()
    {
        Super::BeginPlay();
    
    }
    
    // Called every frame
    void AFPSProjectile::Tick(float DeltaTime)
    {
        Super::Tick(DeltaTime);
    
    }
    
    // Function that initializes the projectile's velocity in the shoot direction.
    void AFPSProjectile::FireInDirection(const FVector& ShootDirection)
    {
        ProjectileMovementComponent->Velocity = ShootDirection * ProjectileMovementComponent->InitialSpeed;
    }
  7. Save and build your code before continuing to the next step where you will set the mesh's material and scale.

Adding the Projectile's Material

  1. Add the following code to FPSProjectile.h:

    // Projectile material
    UPROPERTY(VisibleDefaultsOnly, Category = Movement)
    UMaterialInstanceDynamic* ProjectileMaterialInstance;
  2. Add the following code to the constructor in FPSProjectile.cpp:

    static ConstructorHelpers::FObjectFinder<UMaterial>Material(TEXT("[ADD MATERIAL ASSET REFERENCE]"));
    if (Material.Succeeded())
    {
    ProjectileMaterialInstance = UMaterialInstanceDynamic::Create(Material.Object, ProjectileMeshComponent);
    }
    ProjectileMeshComponent->SetMaterial(0, ProjectileMaterialInstance);
    ProjectileMeshComponent->SetRelativeScale3D(FVector(0.09f, 0.09f, 0.09f));
    ProjectileMeshComponent->SetupAttachment(RootComponent);
  3. Right-click in the Content Browser and select Material .

    CreateBasicMaterial.png

  4. Name the new material "SphereMaterial".

  5. Set up the new material's node graph with properties similar to the following:

    SetSphereNodes.png

    • Base Color: Constant2VectorNode set to (1,0,0)

    • Specular: Constant Node set to 0.5 Emissive Color: Constant Node set to 0.05

    • Emissive Color: Constant Node set to 0.05

    During this step, you are creating a basic Material asset. If you want to learn how to make more complex materials, read how to use and make Materials.

  6. After setting up the new material's node graph, click Save, and open the Content Browser.

  7. Right-click the Sphere material and select Copy Reference.

    SphereCopyReference.png

  8. Go back to the ProjectileMeshComponent code in FPSProjectile.cpp and replace [ADD MATERIAL ASSET REFERENCE] with the copied reference. Your code should look similar to the following:

    static ConstructorHelpers::FObjectFinder<UMaterial>Material(TEXT("'/Game/SphereMaterial.SphereMaterial'"));
    if (Material.Succeeded())
    {
        ProjectileMaterialInstance = UMaterialInstanceDynamic::Create(Material.Object, ProjectileMeshComponent);
    }
    ProjectileMeshComponent->SetMaterial(0, ProjectileMaterialInstance);
    ProjectileMeshComponent->SetRelativeScale3D(FVector(0.09f, 0.09f, 0.09f));
    ProjectileMeshComponent->SetupAttachment(RootComponent);

    Your asset reference path may vary depending on where you saved the Sphere material in the Content Browser. Also, when you paste in the copied asset reference, the reference contains the asset's type name before the asset's reference path. In our case, you will observe Material'/Game/Sphere.Sphere'. Make sure to remove the asset's type name (for example, Material) from the reference path.

  9. FPSProjectile.h should look like the following:

    // Copyright Epic Games, Inc. All Rights Reserved.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "GameFramework/Actor.h"
    #include "Components/SphereComponent.h"
    #include "GameFramework/ProjectileMovementComponent.h"
    #include "FPSProjectile.generated.h"
    
    UCLASS()
    class FPSPROJECT_API AFPSProjectile : public AActor
    {
        GENERATED_BODY()
    
    public: 
        // Sets default values for this actor's properties
        AFPSProjectile();
    
    protected:
        // Called when the game starts or when spawned
        virtual void BeginPlay() override;
    
    public: 
        // Called every frame
        virtual void Tick(float DeltaTime) override;
    
        // Sphere collision component
        UPROPERTY(VisibleDefaultsOnly, Category = Projectile)
        USphereComponent* CollisionComponent;
    
        // Projectile movement component
        UPROPERTY(VisibleAnywhere, Category = Movement)
        UProjectileMovementComponent* ProjectileMovementComponent;
    
        // Projectile mesh
        UPROPERTY(VisibleDefaultsOnly, Category = Projectile)
        UStaticMeshComponent* ProjectileMeshComponent;
    
        // Projectile material
        UPROPERTY(VisibleDefaultsOnly, Category = Movement)
        UMaterialInstanceDynamic* ProjectileMaterialInstance;
    
        // Function that initializes the projectile's velocity in the shoot direction.
        void FireInDirection(const FVector& ShootDirection);
    
    };
  10. FPSProjectile.cpp should look like the following:

    // Copyright Epic Games, Inc. All Rights Reserved.     
    
    #include "FPSProjectile.h"
    
    // Sets default values
    AFPSProjectile::AFPSProjectile()
    {
    // 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;
    
    if (!RootComponent)
    {
        RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent"));
    }
    
    if (!CollisionComponent)
    {
        // Use a sphere as a simple collision representation.
        CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
        // Set the sphere's collision radius.
        CollisionComponent->InitSphereRadius(15.0f);
        // Set the root component to be the collision component.
        RootComponent = CollisionComponent;
    }
    
    if (!ProjectileMovementComponent)
    {
        // Use this component to drive this projectile's movement.
        ProjectileMovementComponent = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovementComponent"));
        ProjectileMovementComponent->SetUpdatedComponent(CollisionComponent);
        ProjectileMovementComponent->InitialSpeed = 3000.0f;
        ProjectileMovementComponent->MaxSpeed = 3000.0f;
        ProjectileMovementComponent->bRotationFollowsVelocity = true;
        ProjectileMovementComponent->bShouldBounce = true;
        ProjectileMovementComponent->Bounciness = 0.3f;
        ProjectileMovementComponent->ProjectileGravityScale = 0.0f;
    }
    
    if (!ProjectileMeshComponent)
    {
        ProjectileMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMeshComponent"));
        static ConstructorHelpers::FObjectFinder<UStaticMesh>Mesh(TEXT("'/Game/Sphere.Sphere'"));
        if (Mesh.Succeeded())
        {
            ProjectileMeshComponent->SetStaticMesh(Mesh.Object);
        }
    
        static ConstructorHelpers::FObjectFinder<UMaterial>Material(TEXT("'/Game/SphereMaterial.SphereMaterial'"));
        if (Material.Succeeded())
        {
            ProjectileMaterialInstance = UMaterialInstanceDynamic::Create(Material.Object, ProjectileMeshComponent);
        }
        ProjectileMeshComponent->SetMaterial(0, ProjectileMaterialInstance);
        ProjectileMeshComponent->SetRelativeScale3D(FVector(0.09f, 0.09f, 0.09f));
        ProjectileMeshComponent->SetupAttachment(RootComponent);
    }
        }
    
        // Called when the game starts or when spawned
        void AFPSProjectile::BeginPlay()
        {
    Super::BeginPlay();
    
        }
    
        // Called every frame
        void AFPSProjectile::Tick(float DeltaTime)
        {
    Super::Tick(DeltaTime);
    
        }
    
        // Function that initializes the projectile's velocity in the shoot direction.
        void AFPSProjectile::FireInDirection(const FVector& ShootDirection)
        {
    ProjectileMovementComponent->Velocity = ShootDirection * ProjectileMovementComponent->InitialSpeed;
        }
  11. Head to the Blueprints folder in the Content Browser, and open the BP_FPSCharacter file.

    ContentBrowserBPFPSCharacter.png

  12. Open the full editor (if necessary) then navigate to the Detail panel.

  13. Locate the Projectile header, and in the dropdown next to Projectile Class, select FPSProjectile.

    DetailsSelectProjectile.png

  14. Build FPSProject, and run the game in PIE mode to verify that the Static Mesh and Material are being spawned in the scene.

    PIEModeDemo.gif

    While firing the projectiles, you will observe in the World Outliner that the number of projectiles keeps increasing because they have no defined lifespan.

    ProjectileTTLDemo.gif

    In the next section, we will show you how to define the projectile's initial lifespan.

3.3 - Setting up Projectile Collision and Lifetime

Currently, our projectiles:

  • Live forever (they never disappear from the Scene Outliner)

  • Don't collide with other objects in the world

During this step, we're going to set up projectile collision and lifetime.

Limiting the Projectile's Life Span

  1. Open FPSProjectile.cpp.

  2. Add the following code to the FPSProjectile constructor to set the projectile's lifespan:

    // Delete the projectile after 3 seconds.
    InitialLifeSpan = 3.0f;
  3. FPSProjectile.cpp should look like the following:

    // Copyright Epic Games, Inc. All Rights Reserved.
    
    #include "FPSProjectile.h"
    
    // Sets default values
    AFPSProjectile::AFPSProjectile()
    {
        // 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;
    
        if (!RootComponent)
        {
            RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent"));
        }
    
        if (!CollisionComponent)
        {
            // Use a sphere as a simple collision representation.
            CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
            // Set the sphere's collision radius.
            CollisionComponent->InitSphereRadius(15.0f);
            // Set the root component to be the collision component.
            RootComponent = CollisionComponent;
        }
    
        if (!ProjectileMovementComponent)
        {
            // Use this component to drive this projectile's movement.
            ProjectileMovementComponent = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovementComponent"));
            ProjectileMovementComponent->SetUpdatedComponent(CollisionComponent);
            ProjectileMovementComponent->InitialSpeed = 3000.0f;
            ProjectileMovementComponent->MaxSpeed = 3000.0f;
            ProjectileMovementComponent->bRotationFollowsVelocity = true;
            ProjectileMovementComponent->bShouldBounce = true;
            ProjectileMovementComponent->Bounciness = 0.3f;
            ProjectileMovementComponent->ProjectileGravityScale = 0.0f;
        }
    
        if (!ProjectileMeshComponent)
        {
            ProjectileMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMeshComponent"));
            static ConstructorHelpers::FObjectFinder<UStaticMesh>Mesh(TEXT("'/Game/Sphere.Sphere'"));
            if (Mesh.Succeeded())
            {
                ProjectileMeshComponent->SetStaticMesh(Mesh.Object);
            }
    
            static ConstructorHelpers::FObjectFinder<UMaterial>Material(TEXT("'/Game/SphereMaterial.SphereMaterial'"));
            if (Material.Succeeded())
            {
                ProjectileMaterialInstance = UMaterialInstanceDynamic::Create(Material.Object, ProjectileMeshComponent);
            }
            ProjectileMeshComponent->SetMaterial(0, ProjectileMaterialInstance);
            ProjectileMeshComponent->SetRelativeScale3D(FVector(0.09f, 0.09f, 0.09f));
            ProjectileMeshComponent->SetupAttachment(RootComponent);
        }
    
        // Delete the projectile after 3 seconds.
        InitialLifeSpan = 3.0f;
    }
    
    // Called when the game starts or when spawned
    void AFPSProjectile::BeginPlay()
    {
        Super::BeginPlay();
    
    }
    
    // Called every frame
    void AFPSProjectile::Tick(float DeltaTime)
    {
        Super::Tick(DeltaTime);
    
    }
    
    // Function that initializes the projectile's velocity in the shoot direction.
    void AFPSProjectile::FireInDirection(const FVector& ShootDirection)
    {
        ProjectileMovementComponent->Velocity = ShootDirection * ProjectileMovementComponent->InitialSpeed;
    }
  4. Save and build FPSProject.

  5. To verify that the projectiles are being destroyed after three seconds, run the game in PIE mode.

    ProjectileWorldOutlinerDemo.gif

    As verified in the World Outliner, every spawned projectile will disappear from the scene after three seconds.

Editing the Projectile's Collision Settings

Unreal Engine comes packaged with several preset collision channels; however, the engine also provides customizable channels that game projects can use.

  1. To create a custom collision channel, open Project Settings, and under Engine - Collision, expand Preset.

    SettingCollisionChannels.png

  2. Select New Object Channel... under Object Channels to create a new collision channel. Name your new collision channel "Projectile" and verify that the Default Response is set to Block before clicking Accept.

    NewChannel.png

  3. Select New... under Preset and name your new profile "Projectile". Reference the following image to set your collision presets. Then click Accept.

    NewProfile.png

This collision profile specifies that the projectile will be blocked by Static Actors, Dynamic Actors, Actors simulating Physics, Vehicles, and Destructible Actors. Also, this collision profile specifies that the projectile overlaps Pawns.

Using the New Collision Channel's Settings

  1. Open FPSProjectile.cpp.

  2. In the FPSProjectile constructor, add the following line below CreateDefaultSubobject

    // Set the sphere's collision profile name to "Projectile".
    CollisionComponent->BodyInstance.SetCollisionProfileName(TEXT("Projectile"));
  3. FPSProjectile.cpp should now look like the following:

    // Copyright Epic Games, Inc. All Rights Reserved.
    
    #include "FPSProjectile.h"
    
    // Sets default values
    AFPSProjectile::AFPSProjectile()
    {
    // 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;
    
    if (!RootComponent)
    {
        RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent"));
    }
    
    if (!CollisionComponent)
    {
        // Use a sphere as a simple collision representation.
        CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
        // Set the sphere's collision profile name to "Projectile".
        CollisionComponent->BodyInstance.SetCollisionProfileName(TEXT("Projectile"));
        // Set the sphere's collision radius.
        CollisionComponent->InitSphereRadius(15.0f);
        // Set the root component to be the collision component.
        RootComponent = CollisionComponent;
    }
    
    if (!ProjectileMovementComponent)
    {
        // Use this component to drive this projectile's movement.
        ProjectileMovementComponent = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovementComponent"));
        ProjectileMovementComponent->SetUpdatedComponent(CollisionComponent);
        ProjectileMovementComponent->InitialSpeed = 3000.0f;
        ProjectileMovementComponent->MaxSpeed = 3000.0f;
        ProjectileMovementComponent->bRotationFollowsVelocity = true;
        ProjectileMovementComponent->bShouldBounce = true;
        ProjectileMovementComponent->Bounciness = 0.3f;
        ProjectileMovementComponent->ProjectileGravityScale = 0.0f;
    }
    
    if (!ProjectileMeshComponent)
    {
        ProjectileMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMeshComponent"));
        static ConstructorHelpers::FObjectFinder<UStaticMesh>Mesh(TEXT("'/Game/Sphere.Sphere'"));
        if (Mesh.Succeeded())
        {
            ProjectileMeshComponent->SetStaticMesh(Mesh.Object);
        }
    
        static ConstructorHelpers::FObjectFinder<UMaterial>Material(TEXT("'/Game/SphereMaterial.SphereMaterial'"));
        if (Material.Succeeded())
        {
            ProjectileMaterialInstance = UMaterialInstanceDynamic::Create(Material.Object, ProjectileMeshComponent);
        }
        ProjectileMeshComponent->SetMaterial(0, ProjectileMaterialInstance);
        ProjectileMeshComponent->SetRelativeScale3D(FVector(0.09f, 0.09f, 0.09f));
        ProjectileMeshComponent->SetupAttachment(RootComponent);
    }
    
    // Delete the projectile after 3 seconds.
    InitialLifeSpan = 3.0f;
    }
    
    // Called when the game starts or when spawned
    void AFPSProjectile::BeginPlay()
    {
    Super::BeginPlay();
    
    }
    
    // Called every frame
    void AFPSProjectile::Tick(float DeltaTime)
    {
    Super::Tick(DeltaTime);
    
    }
    
    // Function that initializes the projectile's velocity in the shoot direction.
    void AFPSProjectile::FireInDirection(const FVector& ShootDirection)
    {
    ProjectileMovementComponent->Velocity = ShootDirection * ProjectileMovementComponent->InitialSpeed;
    }
  4. Save and build FPSProject.

3.4 - Getting Projectiles to Interact with the World

Now that we can detect the projectile's collision interactions, we can determine how to respond to those collisions. During this step, we will add an OnHit function to FPSProjectile that'll respond to collision events.

Getting Projectiles to React to Collisions

  1. Open FPSProjectile.h.

  2. Add the following code to FPSProjectile.h:

    // Function that is called when the projectile hits something.
    UFUNCTION()
    void OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit);
  3. FPSProjectile.h should now look like the following:

    // Copyright Epic Games, Inc. All Rights Reserved.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "GameFramework/Actor.h"
    #include "Components/SphereComponent.h"
    #include "GameFramework/ProjectileMovementComponent.h"
    #include "FPSProjectile.generated.h"
    
    UCLASS()
    class FPSPROJECT_API AFPSProjectile : public AActor
    {
    GENERATED_BODY()
    
    public: 
    // Sets default values for this actor's properties
    AFPSProjectile();
    
    protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;
    
    public: 
    // Called every frame
    virtual void Tick(float DeltaTime) override;
    
    // Function that is called when the projectile hits something.
    UFUNCTION()
    void OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit);
    
    // Sphere collision component
    UPROPERTY(VisibleDefaultsOnly, Category = Projectile)
    USphereComponent* CollisionComponent;
    
    // Projectile movement component
    UPROPERTY(VisibleAnywhere, Category = Movement)
    UProjectileMovementComponent* ProjectileMovementComponent;
    
    // Projectile mesh
    UPROPERTY(VisibleDefaultsOnly, Category = Projectile)
    UStaticMeshComponent* ProjectileMeshComponent;
    
    // Projectile material
    UPROPERTY(VisibleDefaultsOnly, Category = Movement)
    UMaterialInstanceDynamic* ProjectileMaterialInstance;
    
    // Function that initializes the projectile's velocity in the shoot direction.
    void FireInDirection(const FVector& ShootDirection);
    
    };
  4. Locate the FPSProjectile class CPP file in the Solution Explorer and open FPSProjectile.cpp to add the following code:

    // Function that is called when the projectile hits something.
    void AFPSProjectile::OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit)
    {
        if (OtherActor != this && OtherComponent->IsSimulatingPhysics())
        {
            OtherComponent->AddImpulseAtLocation(ProjectileMovementComponent->Velocity * 100.0f, Hit.ImpactPoint);
        }
    
        Destroy();
    }
  5. In the FPSProjectile constructor, add the following line after BodyInstance.SetCollisionProfileName:

    // Event called when component hits something.
    CollisionComponent->OnComponentHit.AddDynamic(this, &AFPSProjectile::OnHit);
  6. FPSProjectile.cpp should now look like the following:

    // Copyright Epic Games, Inc. All Rights Reserved.
    
    #include "FPSProjectile.h"
    
    // Sets default values
    AFPSProjectile::AFPSProjectile()
    {
    // 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;
    
    if (!RootComponent)
    {
        RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent"));
    }
    
    if (!CollisionComponent)
    {
        // Use a sphere as a simple collision representation.
        CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
        // Set the sphere's collision profile name to "Projectile".
        CollisionComponent->BodyInstance.SetCollisionProfileName(TEXT("Projectile"));
        // Event called when component hits something.
        CollisionComponent->OnComponentHit.AddDynamic(this, &AFPSProjectile::OnHit);
        // Set the sphere's collision radius.
        CollisionComponent->InitSphereRadius(15.0f);
        // Set the root component to be the collision component.
        RootComponent = CollisionComponent;
    }
    
    if (!ProjectileMovementComponent)
    {
        // Use this component to drive this projectile's movement.
        ProjectileMovementComponent = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovementComponent"));
        ProjectileMovementComponent->SetUpdatedComponent(CollisionComponent);
        ProjectileMovementComponent->InitialSpeed = 3000.0f;
        ProjectileMovementComponent->MaxSpeed = 3000.0f;
        ProjectileMovementComponent->bRotationFollowsVelocity = true;
        ProjectileMovementComponent->bShouldBounce = true;
        ProjectileMovementComponent->Bounciness = 0.3f;
        ProjectileMovementComponent->ProjectileGravityScale = 0.0f;
    }
    
    if (!ProjectileMeshComponent)
    {
        ProjectileMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMeshComponent"));
        static ConstructorHelpers::FObjectFinder<UStaticMesh>Mesh(TEXT("'/Game/Sphere.Sphere'"));
        if (Mesh.Succeeded())
        {
            ProjectileMeshComponent->SetStaticMesh(Mesh.Object);
        }
    
        static ConstructorHelpers::FObjectFinder<UMaterial>Material(TEXT("'/Game/SphereMaterial.SphereMaterial'"));
        if (Material.Succeeded())
        {
            ProjectileMaterialInstance = UMaterialInstanceDynamic::Create(Material.Object, ProjectileMeshComponent);
        }
        ProjectileMeshComponent->SetMaterial(0, ProjectileMaterialInstance);
        ProjectileMeshComponent->SetRelativeScale3D(FVector(0.09f, 0.09f, 0.09f));
        ProjectileMeshComponent->SetupAttachment(RootComponent);
    }
    
    // Delete the projectile after 3 seconds.
    InitialLifeSpan = 3.0f;
    }
    
    // Called when the game starts or when spawned
    void AFPSProjectile::BeginPlay()
    {
    Super::BeginPlay();
    
    }
    
    // Called every frame
    void AFPSProjectile::Tick(float DeltaTime)
    {
    Super::Tick(DeltaTime);
    
    }
    // Function that is called when the projectile hits something.
        void AFPSProjectile::OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit)
    {
    if (OtherActor != this && OtherComponent->IsSimulatingPhysics())
    {
        OtherComponent->AddImpulseAtLocation(ProjectileMovementComponent->Velocity * 100.0f, Hit.ImpactPoint);
    }
    
          Destroy();
    }
    
    // Function that initializes the projectile's velocity in the shoot direction.
    void AFPSProjectile::FireInDirection(const FVector& ShootDirection)
    {
    ProjectileMovementComponent->Velocity = ShootDirection * ProjectileMovementComponent->InitialSpeed;
    }

Testing Projectile Collision

  1. After the build finishes, go back to Unreal Editor and open FPSProject.

  2. Select the Floor StaticMesh.

  3. Copy-and-paste the floor mesh.

  4. Making sure that the ratio lock is unlocked (the lock icon next to the Scale row), scale the floor mesh copy (Floor2) to {0.2, 0.2, 3.0}.

  5. Position the floor mesh copy at {320, 0, 170}.

  6. Scroll down to the Physics section and check the Simulate Physics box.

    Click on the image to zoom in.

  7. Save the map.

  8. Click Play In in the Level Editor Toolbar.

  9. To verify that projectiles are colliding with the cube, left-click your mouse button to fire projectiles and move the cube around your level.

    [ CollisionTest.gif

    Congratulations, your projectiles are complete!

  10. Press the Escape key or click Stop in the Level Editor to exit Play in Editor (PIE) mode.

3.5 - Adding Crosshairs to your Viewport

During this step, we will add a crosshair HUD element to our game so that we can aim our projectiles.

Importing a Crosshair Asset

Before getting started, download and extract the sample image from the following link:

  1. Right-click inside the Content Browser's file box to open the Import Asset dialog box

  2. Click 'Import to / Game...' to open the Import dialog box.

    RightClickImport.png

  3. Locate and select the crosshair.TGA image file.

  4. Click Open to begin importing the image file to your project.

  5. Click the Save button to save your imported image.

Adding a New HUD Class

  1. In the File menu, select New C++ Class... to choose your new parent class.

  2. The Choose Parent Class menu will open. Scroll down, select HUD as the parent class and click Next .

    ChooseParentHUDClass.png

  3. Name the new class "FPSHUD," then click Create Class .

    NameHUDClass.png

  4. Locate the FPSHUD class header file in the Solution Explorer , open FPSHUD.h and add the following protected variable:

    protected:
        // This will be drawn at the center of the screen.
        UPROPERTY(EditDefaultsOnly)
        UTexture2D* CrosshairTexture;
  5. Add the following function declaration in FPSHUD.h :

    public:
        // Primary draw call for the HUD.
        virtual void DrawHUD() override;
  6. Add the following header file to FPSHUD.h:

    #include "Engine/Canvas.h" 
  7. FPSHUD.h should now look like this:

    // Copyright Epic Games, Inc. All Rights Reserved.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "GameFramework/HUD.h"
    #include "Engine/Canvas.h"
    #include "FPSHUD.generated.h"
    
    /**
     * 
     */
    UCLASS()
    class FPSPROJECT_API AFPSHUD : public AHUD
    {
        GENERATED_BODY()
    
    public:
        // Primary draw call for the HUD.
        virtual void DrawHUD() override;
    
    protected:
        // This will be drawn at the center of the screen.
        UPROPERTY(EditDefaultsOnly)
        UTexture2D* CrosshairTexture;
    };
  8. Now implement the DrawHUD function in FPSHUD.cpp :

    void AFPSHUD::DrawHUD()
    {
        Super::DrawHUD();
    
        if (CrosshairTexture)
        {
            // Find the center of our canvas.
            FVector2D Center(Canvas->ClipX * 0.5f, Canvas->ClipY * 0.5f);
    
            // Offset by half of the texture's dimensions so that the center of the texture aligns with the center of the Canvas.
            FVector2D CrossHairDrawPosition(Center.X - (CrosshairTexture->GetSurfaceWidth() * 0.5f), Center.Y - (CrosshairTexture->GetSurfaceHeight() * 0.5f));
    
            // Draw the crosshair at the centerpoint.
            FCanvasTileItem TileItem(CrossHairDrawPosition, CrosshairTexture->Resource, FLinearColor::White);
            TileItem.BlendMode = SE_BLEND_Translucent;
            Canvas->DrawItem(TileItem);
        }
    }
  9. Save FPSHUD.h and FPSHUD.cpp in Visual Studio.

  10. Locate FPSProject in the Solution Explorer .

  11. Right-click on FPSProject and select Build to compile your project.

Extending your CPP HUD Class to Blueprints

Now is a good time to extend the CPP HUD class to Blueprints. If you need a refresher, go to our C++ and Blueprints reference page to learn more about extending C++ classes to Blueprints.

  1. Right-click the FPSHUD class to open the C++ Class Actions menu.

  2. Click Create Blueprint class based on FPSHUD to open the Add Blueprint Class dialog menu.

    CPPClassActionsMenu.png

  3. Name your new Blueprint Class "BP_FPSHUD" and choose the Blueprints folder before clicking the Create Blueprint Class button.

    AddBPClass.png

  4. By now, you should have a newly created BP_FPSHUD Blueprint Class located inside of the Blueprints folder.

    AddedBPClass.png

  5. Make sure to save your BP_FPSHUD Blueprint before closing the Blueprint Editor.

Setting the Default HUD Class

  1. In the Edit menu, click on Project Settings .

  2. Under the Project heading on the left side of the Project Settings tab, click on Maps & Modes .

  3. Select BP_FPSHUD in the Default HUD dropdown menu.

    ChooseHUDClass.png

  4. Close the Project Settings menu.

  5. Go back and open the BP_FPSHUD Blueprint editor.

  6. Now, click on the dropdown menu located in the FPSHUD section of the Blueprint editor to select your crosshair texture.

    SelectCrosshairTexture.png

  7. Finally, save the BP_FPSHUD Blueprint before closing the Blueprint editor.

Verifying your HUD

  1. Click the Play button in the Level Editor Toolbar. You should now be able to aim the projectiles with your newly added crosshair.

    CrosshairsInGame.png

  2. Click the Stop button in the Level Editor to exit Play in Editor (PIE) mode.

Finished Section Code

FPSProjectile.h

    // Copyright Epic Games, Inc. All Rights Reserved.

    #pragma once

    #include "CoreMinimal.h"
    #include "GameFramework/Actor.h"
    #include "Components/SphereComponent.h"
    #include "GameFramework/ProjectileMovementComponent.h"
    #include "FPSProjectile.generated.h"

    UCLASS()
    class FPSPROJECT_API AFPSProjectile : public AActor
    {
        GENERATED_BODY()

    public: 
        // Sets default values for this actor's properties
        AFPSProjectile();

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

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

        // Function that is called when the projectile hits something.
        UFUNCTION()
        void OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit);

        // Sphere collision component
        UPROPERTY(VisibleDefaultsOnly, Category = Projectile)
        USphereComponent* CollisionComponent;

        // Projectile movement component
        UPROPERTY(VisibleAnywhere, Category = Movement)
        UProjectileMovementComponent* ProjectileMovementComponent;

        // Projectile mesh
        UPROPERTY(VisibleDefaultsOnly, Category = Projectile)
        UStaticMeshComponent* ProjectileMeshComponent;

        // Projectile material
        UPROPERTY(VisibleDefaultsOnly, Category = Movement)
        UMaterialInstanceDynamic* ProjectileMaterialInstance;

        // Function that initializes the projectile's velocity in the shoot direction.
        void FireInDirection(const FVector& ShootDirection);

    };

FPSProjectile.cpp

    // Copyright Epic Games, Inc. All Rights Reserved.         

    #include "FPSProjectile.h"

    // Sets default values
    AFPSProjectile::AFPSProjectile()
    {
        // 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;

        if (!RootComponent)
        {
            RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent"));
        }

        if (!CollisionComponent)
        {
            // Use a sphere as a simple collision representation.
            CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
            // Set the sphere's collision profile name to "Projectile".
            CollisionComponent->BodyInstance.SetCollisionProfileName(TEXT("Projectile"));
            // Event called when component hits something.
            CollisionComponent->OnComponentHit.AddDynamic(this, &AFPSProjectile::OnHit);
            // Set the sphere's collision radius.
            CollisionComponent->InitSphereRadius(15.0f);
            // Set the root component to be the collision component.
            RootComponent = CollisionComponent;
        }

        if (!ProjectileMovementComponent)
        {
            // Use this component to drive this projectile's movement.
            ProjectileMovementComponent = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovementComponent"));
            ProjectileMovementComponent->SetUpdatedComponent(CollisionComponent);
            ProjectileMovementComponent->InitialSpeed = 3000.0f;
            ProjectileMovementComponent->MaxSpeed = 3000.0f;
            ProjectileMovementComponent->bRotationFollowsVelocity = true;
            ProjectileMovementComponent->bShouldBounce = true;
            ProjectileMovementComponent->Bounciness = 0.3f;
            ProjectileMovementComponent->ProjectileGravityScale = 0.0f;
        }

        if (!ProjectileMeshComponent)
        {
            ProjectileMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMeshComponent"));
            static ConstructorHelpers::FObjectFinder<UStaticMesh>Mesh(TEXT("'/Game/Sphere.Sphere'"));
            if (Mesh.Succeeded())
            {
                ProjectileMeshComponent->SetStaticMesh(Mesh.Object);
            }

            static ConstructorHelpers::FObjectFinder<UMaterial>Material(TEXT("'/Game/SphereMaterial.SphereMaterial'"));
            if (Material.Succeeded())
            {
                ProjectileMaterialInstance = UMaterialInstanceDynamic::Create(Material.Object, ProjectileMeshComponent);
            }
            ProjectileMeshComponent->SetMaterial(0, ProjectileMaterialInstance);
            ProjectileMeshComponent->SetRelativeScale3D(FVector(0.09f, 0.09f, 0.09f));
            ProjectileMeshComponent->SetupAttachment(RootComponent);
        }

        // Delete the projectile after 3 seconds.
        InitialLifeSpan = 3.0f;
    }

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

    }

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

    }

    void AFPSProjectile::OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit)
    {
        if (OtherActor != nullptr && OtherActor != this && OtherComponent != nullptr && OtherComponent->IsSimulatingPhysics())
        {
            OtherComponent->AddImpulseAtLocation(ProjectileMovementComponent->Velocity * 100.0f, Hit.ImpactPoint);
        }

        Destroy();
    }

    // Function that initializes the projectile's velocity in the shoot direction.
    void AFPSProjectile::FireInDirection(const FVector& ShootDirection)
    {
        ProjectileMovementComponent->Velocity = ShootDirection * ProjectileMovementComponent->InitialSpeed;
    }

FPSCharacter.h

    // Copyright Epic Games, Inc. All Rights Reserved.

    #pragma once

    #include "CoreMinimal.h"
    #include "GameFramework/Character.h"
    #include "Camera/CameraComponent.h"
    #include "Components/CapsuleComponent.h"
    #include "FPSProjectile.h"
    #include "FPSCharacter.generated.h"

    UCLASS()
    class FPSPROJECT_API AFPSCharacter : public ACharacter
    {
        GENERATED_BODY()

    public:
        // Sets default values for this character's properties
        AFPSCharacter();

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

        // Projectile class to spawn.
        UPROPERTY(EditAnywhere, Category = Projectile)
        TSubclassOf<class AFPSProjectile> ProjectileClass;

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

        // Called to bind functionality to input
        virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

        // Handles input for moving forward and backward.
        UFUNCTION()
        void MoveForward(float Value);

        // Handles input for moving right and left.
        UFUNCTION()
        void MoveRight(float Value);

        // Sets jump flag when key is pressed.
        UFUNCTION()
        void StartJump();

        // Clears jump flag when key is released.
        UFUNCTION()
        void StopJump();

        // Function that fires projectiles.
        UFUNCTION()
        void Fire();

        // FPS camera
        UPROPERTY(VisibleAnywhere)
        UCameraComponent* FPSCameraComponent;

        // First-person mesh (arms), visible only to the owning player.
        UPROPERTY(VisibleDefaultsOnly, Category = Mesh)
        USkeletalMeshComponent* FPSMesh;

        // Gun muzzle offset from the camera location.
        UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay)
        FVector MuzzleOffset;
    };

FPSCharacter.cpp

    // Copyright Epic Games, Inc. All Rights Reserved.         

    #include "FPSCharacter.h"

    // Sets default values
    AFPSCharacter::AFPSCharacter()
    {
        // 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;

        // Create a first person camera component.
        FPSCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("FirstPersonCamera"));
        check(FPSCameraComponent != nullptr);

        // Attach the camera component to our capsule component.
        FPSCameraComponent->SetupAttachment(CastChecked<USceneComponent, UCapsuleComponent>(GetCapsuleComponent()));

        // Position the camera slightly above the eyes.
        FPSCameraComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 50.0f + BaseEyeHeight));

        // Enable the Pawn to control camera rotation.
        FPSCameraComponent->bUsePawnControlRotation = true;

        // Create a first person mesh component for the owning player.
        FPSMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("FirstPersonMesh"));
        check(FPSMesh != nullptr);

        // Only the owning player sees this mesh.
        FPSMesh->SetOnlyOwnerSee(true);

        //Attach the FPS mesh to the FPS Camera.
        FPSMesh->SetupAttachment(FPSCameraComponent);

        //Disable some environmental shadows to preserve the illusion of having a single mesh.
        FPSMesh->bCastDynamicShadow = false;
        FPSMesh->CastShadow = false;

        // The owning player doesn't see the regular (third-person) body mesh.
        GetMesh()->SetOwnerNoSee(true);
    }

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

        if (GEngine)
        {
            // Display a debug message for five seconds.
            // The -1 "Key" value argument prevents the message from being updated or refreshed.
            GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("We are using FPSCharacter."));
        }
    }

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

    }

    // Called to bind functionality to input
    void AFPSCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
    {
        Super::SetupPlayerInputComponent(PlayerInputComponent);

        // Set up "movement" bindings.
        PlayerInputComponent->BindAxis("MoveForward", this, &AFPSCharacter::MoveForward);
        PlayerInputComponent->BindAxis("MoveRight", this, &AFPSCharacter::MoveRight);

        // Set up "look" bindings.
        PlayerInputComponent->BindAxis("Turn", this, &AFPSCharacter::AddControllerYawInput);
        PlayerInputComponent->BindAxis("LookUp", this, &AFPSCharacter::AddControllerPitchInput);

        // Set up "action" bindings.
        PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &AFPSCharacter::StartJump);
        PlayerInputComponent->BindAction("Jump", IE_Released, this, &AFPSCharacter::StopJump);
        PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &AFPSCharacter::Fire);
    }

    void AFPSCharacter::MoveForward(float Value)
    {
        // Find out which way is "forward" and record that the player wants to move that way.
        FVector Direction = FRotationMatrix(Controller->GetControlRotation()).GetScaledAxis(EAxis::X);
        AddMovementInput(Direction, Value);
    }

    void AFPSCharacter::MoveRight(float Value)
    {
        // Find out which way is "right" and record that the player wants to move that way.
        FVector Direction = FRotationMatrix(Controller->GetControlRotation()).GetScaledAxis(EAxis::Y);
        AddMovementInput(Direction, Value);
    }

    void AFPSCharacter::StartJump()
    {
        bPressedJump = true;
    }

    void AFPSCharacter::StopJump()
    {
        bPressedJump = false;
    }

    void AFPSCharacter::Fire()
    {
        // Attempt to fire a projectile.
        if (ProjectileClass)
        {
            // Get the camera transform.
            FVector CameraLocation;
            FRotator CameraRotation;
            GetActorEyesViewPoint(CameraLocation, CameraRotation);

            // Set MuzzleOffset to spawn projectiles slightly in front of the camera.
            MuzzleOffset.Set(100.0f, 0.0f, 0.0f);

            // Transform MuzzleOffset from camera space to world space.
            FVector MuzzleLocation = CameraLocation + FTransform(CameraRotation).TransformVector(MuzzleOffset);

            // Skew the aim to be slightly upwards. 
            FRotator MuzzleRotation = CameraRotation;
            MuzzleRotation.Pitch += 10.0f;

            UWorld* World = GetWorld();
            if (World)
            {
                FActorSpawnParameters SpawnParams;
                SpawnParams.Owner = this;
                SpawnParams.Instigator = GetInstigator();

                // Spawn the projectile at the muzzle.
                AFPSProjectile* Projectile = World->SpawnActor<AFPSProjectile>(ProjectileClass, MuzzleLocation, MuzzleRotation, SpawnParams);
                if (Projectile)
                {
                    // Set the projectile's initial trajectory.
                    FVector LaunchDirection = MuzzleRotation.Vector();
                    Projectile->FireInDirection(LaunchDirection);
                }

            }
        }
    }

FPSHUD.h

    // Copyright Epic Games, Inc. All Rights Reserved.

    #pragma once

    #include "CoreMinimal.h"
    #include "GameFramework/HUD.h"
    #include "Engine/Canvas.h"
    #include "FPSHUD.generated.h"

    /**
     * 
     */
    UCLASS()
    class FPSPROJECT_API AFPSHUD : public AHUD
    {
        GENERATED_BODY()

    public:
        // Primary draw call for the HUD.
        virtual void DrawHUD() override;

    protected:
        // This will be drawn at the center of the screen.
        UPROPERTY(EditDefaultsOnly)
        UTexture2D* CrosshairTexture;
    };

FPSHUD.cpp

    // Copyright Epic Games, Inc. All Rights Reserved.         

    #include "FPSHUD.h"

    void AFPSHUD::DrawHUD()
    {
        Super::DrawHUD();

        if (CrosshairTexture)
        {
            // Find the center of our canvas.
            FVector2D Center(Canvas->ClipX * 0.5f, Canvas->ClipY * 0.5f);

            // Offset by half of the texture's dimensions so that the center of the texture aligns with the center of the Canvas.
            FVector2D CrossHairDrawPosition(Center.X - (CrosshairTexture->GetSurfaceWidth() * 0.5f), Center.Y - (CrosshairTexture->GetSurfaceHeight() * 0.5f));

            // Draw the crosshair at the centerpoint.
            FCanvasTileItem TileItem(CrossHairDrawPosition, CrosshairTexture->Resource, FLinearColor::White);
            TileItem.BlendMode = SE_BLEND_Translucent;
            Canvas->DrawItem(TileItem);
        }
    }

Congratulations! You've learned how to:

✓ Add Projectiles to your Game
✓ Implement Shooting
✓ Set-up Projectile Collision and Lifetime
✓ Get your Projectiles to Interact with the World
✓ Add Crosshairs to your Viewport

You're now ready to learn how to animate your character in the next section.

Help shape the future of Unreal Engine documentation! Tell us how we're doing so we can serve you better.
Take our survey
Dismiss