Language:
Page Info
Skill Level:
Engine Version:

3.1 - Adding Projectiles to your Game

Now that we've set-up our character, it's time to implement a projectile weapon so that when we fire, a simple grenade-like projectile will shoot from the center of the screen and fly until it collides with the world. During this step, we're going to add input and create a new code class for our projectile.

Adding Fire Action Mapping

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

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

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

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

  5. Type "Fire" into the text field that appears, then click on 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.

    CreateProjectileClass.png

Adding a USphere Component

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

  2. Add a reference to a USphereComponent in the FPSProjectile interface.

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

    // Fill out your copyright notice in the Description page of Project Settings.
    
    #pragma once
    
    #include "GameFramework/Actor.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 DeltaSeconds ) override;
    
        // Sphere collision component.
        UPROPERTY(VisibleDefaultsOnly, Category = Projectile)
        USphereComponent* CollisionComponent;
    };
  4. Locate the FPSProjectile class CPP file in the Solution Explorer and open FPSProjectile.cpp.

  5. Add the following code to the AFPSProjectile constructor in FPSProjectile.cpp:

    // 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're making CollisionComponent a RootComponent since the simulation will drive it.

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

    // Fill out your copyright notice in the Description page of Project Settings.
    
    #include "FPSProject.h"
    #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;
    
        // 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 following code to FPSProjectile.h:

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

    // Fill out your copyright notice in the Description page of Project Settings.
    
    #pragma once
    
    #include "GameFramework/Actor.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 DeltaSeconds ) override;
    
        // Sphere collision component.
        UPROPERTY(VisibleDefaultsOnly, Category = Projectile)
        USphereComponent* CollisionComponent;
    
        // Projectile movement component.
        UPROPERTY(VisibleAnywhere, Category = Movement)
        UProjectileMovementComponent* ProjectileMovementComponent;
    };
  4. Locate the FPSProjectile CPP file in the Solution Explorer and open FPSProjectile.cpp.

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

    // 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;
  6. FPSProjectile.cpp should now look like the following:

    // Fill out your copyright notice in the Description page of Project Settings.
    
    #include "FPSProject.h"
    #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;
    
        // 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;
    
        // 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;
    }
    
    // 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. Locate the FPSProjectile class header file in the Solution Explorer and open FPSProjectile.h.

  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:

    // Fill out your copyright notice in the Description page of Project Settings.
    
    #pragma once
    
    #include "GameFramework/Actor.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 DeltaSeconds ) 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. Locate the FPSProjectile CPP file in the Solution Explorer and open FPSProjectile.cpp.

  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:

    // Fill out your copyright notice in the Description page of Project Settings.
    
    #include "FPSProject.h"
    #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;
    
        // 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;
    
        // 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;
    }
    
    // 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. Locate the FPSCharacter class header file in the Solution Explorer and open FPSCharacter.h.

  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:

    // Fill out your copyright notice in the Description page of Project Settings.
    
    #pragma once
    
    #include "GameFramework/Character.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 DeltaSeconds ) override;
    
        // Called to bind functionality to input
        virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) 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. In FPSCharacter.cpp, add the following code to SetupPlayerInputComponent to bind the OnFire function:

    InputComponent->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:

    // Fill out your copyright notice in the Description page of Project Settings.
    
    #include "FPSProject.h"
    #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"));
        // Attach the camera component to our capsule component.
        FPSCameraComponent->AttachTo(GetCapsuleComponent());
        // Position the camera slightly above the eyes.
        FPSCameraComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 50.0f + BaseEyeHeight));
        // Allow the pawn to control camera rotation.
        FPSCameraComponent->bUsePawnControlRotation = true;
    
        // Create a first person mesh component for the owning player.
        FPSMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("FirstPersonMesh"));
        // Only the owning player sees this mesh.
        FPSMesh->SetOnlyOwnerSee(true);
        // Attach the FPS mesh to the FPS camera.
        FPSMesh->AttachTo(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)
        {
            // Put up a debug message for five seconds. The -1 "Key" value (first argument) indicates that we will never need to update or refresh this message.
            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* InputComponent)
    {
        Super::SetupPlayerInputComponent(InputComponent);
    
        // Set up "movement" bindings.
        InputComponent->BindAxis("MoveForward", this, &AFPSCharacter::MoveForward);
        InputComponent->BindAxis("MoveRight", this, &AFPSCharacter::MoveRight);
    
        // Set up "look" bindings.
        InputComponent->BindAxis("Turn", this, &AFPSCharacter::AddControllerYawInput);
        InputComponent->BindAxis("LookUp", this, &AFPSCharacter::AddControllerPitchInput);
    
        // Set up "action" bindings.
        InputComponent->BindAction("Jump", IE_Pressed, this, &AFPSCharacter::StartJump);
        InputComponent->BindAction("Jump", IE_Released, this, &AFPSCharacter::StopJump);
        InputComponent->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 (this is so the FPSCharacter and its derived Blueprint know what projectile to spawn)

You're going to use a camera-space offset vector to determine the projectile's spawn location. You'll 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.

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

  2. Add the following code to FPSCharacter.h:

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

    The EditAnywhere specifier allow 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 allows you to get and set the value of the muzzle offset within a Blueprint.

  3. Add the following code to FPSCharacter.h:

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

    The EditDefaultsOnly specifier 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.

  4. FPSCharacter.h should look like the following:

    // Fill out your copyright notice in the Description page of Project Settings.
    
    #pragma once
    
    #include "GameFramework/Character.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 DeltaSeconds ) override;
    
        // Called to bind functionality to input
        virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) 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;
    
        // Gun muzzle's offset from the camera location.
        UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay)
        FVector MuzzleOffset;
    
        // Projectile class to spawn.
        UPROPERTY(EditDefaultsOnly, Category = Projectile)
        TSubclassOf<class AFPSProjectile> ProjectileClass;
    };

Compiling and Checking Your Code

It's 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.