Language:
Page Info
Skill Level:
Engine Version:

3.5 - Adding Crosshairs to your Viewport

During this step, we'll 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 our 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.

    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. Now, add the following function declaration in FPSHUD.h:

    public:
        // Primary draw call for the HUD.
        virtual void DrawHUD() override;
  6. 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);
        }
    }
  7. Save FPSHUD.h and FPSHUD.cpp in Visual Studio.

  8. Locate FPSProject in the Solution Explorer.

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

    BuildFPSProject.png

Extending your CPP HUD Class to Blueprints

Now is a good time to extend the CPP HUD class to Blueprints. Please feel free to 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.

    CPPClassActionsMenu.png

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

    CreateDerivedBPClass.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 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 1998-2017 Epic Games, Inc. All Rights Reserved.

#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);

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

};

FPSProjectile.cpp

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

#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"));
    CollisionComponent->BodyInstance.SetCollisionProfileName(TEXT("Projectile"));
    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;

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

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

// Function that is called when the projectile hits something.
void AFPSProjectile::OnHit(AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit)
{
    if (OtherActor != this && OtherComponent->IsSimulatingPhysics())
    {
        OtherComponent->AddImpulseAtLocation(ProjectileMovementComponent->Velocity * 100.0f, Hit.ImpactPoint);
    }
}

FPSCharacter.h

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

#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(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;
};

FPSCharacter.cpp

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

#include "FPSProject.h"
#include "FPSCharacter.h"
#include "FPSProjectile.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(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()
{
    // Attempt to fire a projectile.
    if (ProjectileClass)
    {
        // Get the camera transform.
        FVector CameraLocation;
        FRotator CameraRotation;
        GetActorEyesViewPoint(CameraLocation, CameraRotation);

        // Transform MuzzleOffset from camera space to world space.
        FVector MuzzleLocation = CameraLocation + FTransform(CameraRotation).TransformVector(MuzzleOffset);
        FRotator MuzzleRotation = CameraRotation;
        // Skew the aim to be slightly upwards.
        MuzzleRotation.Pitch += 10.0f; 
        UWorld* World = GetWorld();
        if (World)
        {
            FActorSpawnParameters SpawnParams;
            SpawnParams.Owner = this;
            SpawnParams.Instigator = Instigator;
            // 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 1998-2017 Epic Games, Inc. All Rights Reserved.

#pragma once

#include "GameFramework/HUD.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 1998-2017 Epic Games, Inc. All Rights Reserved.

#include "FPSProject.h"
#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.