Choose your operating system:
Windows
macOS
Linux
In order to understand and use the content on this page, make sure you are familiar with the following topics:
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
In the Edit menu, click Project Settings.
Under the Engine heading on the left side of the Project Settings tab, click Input.
Under Bindings, click the plus sign next to Action Mappings.
Click the arrow to the left of Action Mappings.
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.
In the dropdown menu, select Left Mouse Button from the Mouse dropdown list.
Your input settings should now look like the following:
Close the Project Settings menu.
Adding a Projectile Class
In the File menu, select New C++ Class... to choose your new parent class.
The Choose Parent Class menu will open. Scroll down, select Actor as the parent class, and click Next.
Name the new class "FPSProjectile," then click Create Class.
Adding a USphere Component
Locate the
FPSProjectile
class header in the Solution Explorer and openFPSProjectile.h
.Add the SphereComponent header file:
#include "Components/SphereComponent.h"
Add a reference to a
USphereComponent
in theFPSProjectile
interface.// Sphere collision component. UPROPERTY(VisibleDefaultsOnly, Category = Projectile) USphereComponent* CollisionComponent;
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; };
Locate the
FPSProjectile
class CPP file in the Solution Explorer and openFPSProjectile.cpp
.Add the following code to the
AFPSProjectile
constructor (afterPrimaryActorTick.bcanEverTick
) inFPSProjectile.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
aRootComponent
since the simulation will drive it.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
Locate the
FPSProjectile
class header file in the Solution Explorer and openFPSProjectile.h
.Add the ProjectileMovementComponent header file.
#include "GameFramework/ProjectileMovementComponent.h"
Add the following code to
FPSProjectile.h
:// Projectile movement component. UPROPERTY(VisibleAnywhere, Category = Movement) UProjectileMovementComponent* ProjectileMovementComponent;
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; };
Open
FPSProjectile.cpp
from the Solution Explorer.Add the following lines of code to the
FPSProjectile
constructor inFPSProjectile.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; }
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
Open
FPSProjectile.h
in the Solution Explorer.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.
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); };
Open
FPSProjectile.cpp
in the Solution Explorer.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
.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
Open
FPSCharacter.h
in the Solution Explorer.Add the following function declaration in
FPSCharacter.h
:// Function that handles firing projectiles. UFUNCTION() void Fire();
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; };
Locate the
FPSCharacter
CPP file in the Solution Explorer and openFPSCharacter.cpp
.To bind the Fire function, add the following code to the
SetupPlayerInputComponent
function inFPSCharacter.cpp:
PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &AFPSCharacter::Fire);
Now, add the following function definition to
FPSCharacter.cpp
:void AFPSCharacter::Fire() { }
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
When spawning the
FPSProjectile
actor, there are two points to consider when implementing theOnFire
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.Open
FPSCharacter.h
in the Solution Explorer.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. TheBlueprintReadWrite
specifier enables you to get and set the value of the muzzle offset within a Blueprint.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.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.
Save all of your header and implementation files in Visual Studio.
Locate FPSProject in the Solution Explorer.
Right-click on FPSProject and select Build to compile your project.
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
Add the following line to FPSCharacter.h.
#include "FPSProjectile.h"
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); } } } }
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; };
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); } } } }
Save
FPSCharacter.h
andFPSCharacter.cpp
in Visual Studio.Locate FPSProject in the Solution Explorer.
Right-click on FPSProject and select Build to compile your project.
Importing the Projectile Mesh
Before continuing, download and extract the sample mesh from the following link: "Projectile Mesh"
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:
Click 'Import to /Game...' to open the Import dialog box.
Locate and select the Sphere.fbx mesh file.
Click Open to begin importing the mesh to your project.
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:
This mesh still illustrates the first person mesh setup and it will work with the animations you'll set-up in a later section.
Click the Save button to save your imported static mesh.
Adding the Projectile's Mesh
Add the following code to FPSProjectile.h:
// Projectile mesh UPROPERTY(VisibleDefaultsOnly, Category = Projectile) UStaticMeshComponent* ProjectileMeshComponent;
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); } }
Open the Content Browser, right-click the Sphere static mesh, and select Copy Reference:
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.
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); };
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; }
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
Add the following code to FPSProjectile.h:
// Projectile material UPROPERTY(VisibleDefaultsOnly, Category = Movement) UMaterialInstanceDynamic* ProjectileMaterialInstance;
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);
Right-click in the Content Browser and select Material.
Name the new material "SphereMaterial".
Set up the new material's node graph with properties similar to the following:
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.
After setting up the new material's node graph, click Save, and open the Content Browser.
Right-click the Sphere material and select Copy Reference.
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.
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); };
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; }
Head to the Blueprints folder in the Content Browser, and open the BP_FPSCharacter file.
Open the full editor (if necessary) then navigate to the Detail panel.
Locate the Projectile header, and in the dropdown next to Projectile Class, select FPSProjectile.
Build FPSProject, and run the game in PIE mode to verify that the Static Mesh and Material are being spawned in the scene.
While firing the projectiles, you will observe in the World Outliner that the number of projectiles keeps increasing because they have no defined lifespan.
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
Open FPSProjectile.cpp.
Add the following code to the FPSProjectile constructor to set the projectile's lifespan:
// Delete the projectile after 3 seconds. InitialLifeSpan = 3.0f;
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; }
Save and build FPSProject.
To verify that the projectiles are being destroyed after three seconds, run the game in PIE mode.
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.
To create a custom collision channel, open Project Settings, and under Engine - Collision, expand Preset.
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.
Select New... under Preset and name your new profile "Projectile". Reference the following image to set your collision presets. Then click Accept.
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
Open FPSProjectile.cpp.
In the FPSProjectile constructor, add the following line below CreateDefaultSubobject
// Set the sphere's collision profile name to "Projectile". CollisionComponent->BodyInstance.SetCollisionProfileName(TEXT("Projectile"));
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; }
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
Open
FPSProjectile.h.
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);
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); };
Locate the
FPSProjectile
class CPP file in the Solution Explorer and openFPSProjectile.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(); }
In the
FPSProjectile
constructor, add the following line afterBodyInstance.SetCollisionProfileName:
// Event called when component hits something. CollisionComponent->OnComponentHit.AddDynamic(this, &AFPSProjectile::OnHit);
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
After the build finishes, go back to Unreal Editor and open FPSProject.
Select the Floor StaticMesh.
Copy-and-paste the floor mesh.
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}.
Position the floor mesh copy at {320, 0, 170}.
Scroll down to the Physics section and check the Simulate Physics box.
Click on the image to zoom in.
Save the map.
Click Play In in the Level Editor Toolbar.
To verify that projectiles are colliding with the cube, left-click your mouse button to fire projectiles and move the cube around your level.
[
Congratulations, your projectiles are complete!
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:
Right-click inside the Content Browser's file box to open the Import Asset dialog box
Click 'Import to / Game...' to open the Import dialog box.
Locate and select the crosshair.TGA image file.
Click Open to begin importing the image file to your project.
Click the Save button to save your imported image.
Adding a New HUD Class
In the File menu, select New C++ Class... to choose your new parent class.
The Choose Parent Class menu will open. Scroll down, select HUD as the parent class and click Next.
Name the new class "FPSHUD," then click Create Class.
Locate the
FPSHUD
class header file in the Solution Explorer, openFPSHUD.h
and add the following protected variable:protected: // This will be drawn at the center of the screen. UPROPERTY(EditDefaultsOnly) UTexture2D* CrosshairTexture;
Add the following function declaration in
FPSHUD.h
:public: // Primary draw call for the HUD. virtual void DrawHUD() override;
Add the following header file to FPSHUD.h:
#include "Engine/Canvas.h"
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; };
Now implement the
DrawHUD
function inFPSHUD.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); } }
Save
FPSHUD.h
andFPSHUD.cpp
in Visual Studio.Locate FPSProject in the Solution Explorer.
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.
Right-click the
FPSHUD
class to open the C++ Class Actions menu.Click Create Blueprint class based on FPSHUD to open the Add Blueprint Class dialog menu.
Name your new Blueprint Class "BP_FPSHUD" and choose the Blueprints folder before clicking the Create Blueprint Class button.
By now, you should have a newly created
BP_FPSHUD
Blueprint Class located inside of the Blueprints folder.Make sure to save your
BP_FPSHUD
Blueprint before closing the Blueprint Editor.
Setting the Default HUD Class
In the Edit menu, click on Project Settings.
Under the Project heading on the left side of the Project Settings tab, click on Maps & Modes.
Select BP_FPSHUD in the Default HUD dropdown menu.
Close the Project Settings menu.
Go back and open the
BP_FPSHUD
Blueprint editor.Now, click on the dropdown menu located in the
FPSHUD
section of the Blueprint editor to select your crosshair texture.Finally, save the
BP_FPSHUD
Blueprint before closing the Blueprint editor.
Verifying your HUD
Click the Play button in the Level Editor Toolbar. You should now be able to aim the projectiles with your newly added crosshair.
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.