Before You Begin
Ensure you’ve completed the following objectives in the previous section, Equip Your Character:
Created a respawning pickup item and added it to your level
Created an equippable dart launcher for your character to hold and use
Basic Projectiles
Your character can hold your dart launcher, and your tool has control bindings to use it, but it’s not quite living up to its name. In this section, you’ll implement projectile logic to make darts launch out from the equipped item.
Unreal Engine has a Projectile Movement component class that you can add to an actor to turn it into a projectile. It includes many helpful variables, such as projectile speed, bounciness, and gravity scale.
To handle the math of implementing projectiles, you’ll need to think about several things:
The initial transform, velocity, and direction of the projectile.
Whether you want to spawn projectiles from the center of the character or the tool they have equipped.
What behavior you want the projectile to exhibit when it collides with another object.
Create a Projectile Base Class
You’ll first create a base projectile class, and then subclass from it to create unique projectiles for your tools.
To start setting up a base projectile class, follow these steps:
In the Unreal Editor, go to Tools > New C++ Class. Select Actor as the parent class and name the class
FirstPersonProjectile. Click Create Class.In VS, go to
FirstPersonProjectile.h. At the top of the file, forward declare both aUProjectileMovementComponentand aUSphereComponent.You’ll use the sphere component to simulate collisions between the projectile and other objects.
C++// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "FirstPersonProjectile.generated.h" class UProjectileMovementComponent; class USphereComponent;Add the
BlueprintTypeandBlueprintablespecifiers to expose this class to Blueprints:C++UCLASS(BlueprintType, Blueprintable) class FIRSTPERSON_API AFirstPersonProjectile : public AActorOpen
FirstPersonProjectile.cpp, att the top of the file, add an include statement for”GameFramework/ProjectileMovementComponent.h”to include the projectile movement component class.
#include "FirstPersonProjectile.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Components/SphereComponent.h"
// Sets default values
AFirstPersonProjectile::AFirstPersonProjectile()Implement Projectile Behavior When Hitting Objects
To make your projectile more realistic, you can make it exert some force (an impulse) on objects it hits. For example, if you are shooting at a physics-enabled block, the projectile would nudge the block along the ground. Then, remove the projectile after impact instead of letting it live out its default lifespan. Create an OnHit() function to implement this behavior.
To implement projectile on-hit behavior, follow these steps:
In
FirstPersonProjectile.h, in thepublicsection, define afloatproperty namedPhysicsForce.Give it a
UPROPERTY()macro withEditAnywhere,BlueprintReadOnly, andCategory = “Projectile | Physics”.This is the amount of force the projectile imparts when it hits something.
C++// The amount of force this projectile imparts on objects it collides with UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Projectile | Physics") float PhysicsForce = 100.0f;Define a
voidfunctionOnHit(). This is a function from AActor called when the actor collides with another component or actor. It takes the following arguments:HitComp: The component that was hit.OtherActor: The actor that was hit.OtherComp: The component that created the hit (in this case, the projectile’s CollisionComponent).NormalImpulse: The normal impulse of the hit.Hit: AFHitResultreference that contains more data about the hit event such as time, distance, and location.
C++// Called when the projectile collides with an object UFUNCTION() void OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);In
FirstPersonProjectile.cpp, implement theOnHit()function you defined in your header file. InsideOnHit(), in anifstatement, check that:OtherActorisn't null and isn't the projectile itself.OtherCompisn't null and is also simulating physics.
C++void AFirstPersonProjectile::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit) { // Only add impulse and destroy projectile if we hit a physics if ((OtherActor != nullptr) && (OtherActor != this) && (OtherComp != nullptr) && OtherComp->IsSimulatingPhysics()) { } }This checks that the projectile hit an object that wasn’t itself and participates in physics.
Inside the
ifstatement, useAddImpulseAtLocation()to add an impulse to theOtherCompcomponent.Pass this function the velocity of the projectile multiplied by the
PhysicsForceand apply it at the location of the projectile actor.C++if ((OtherActor != nullptr) && (OtherActor != this) && (OtherComp != nullptr) && OtherComp->IsSimulatingPhysics()) { OtherComp->AddImpulseAtLocation(GetVelocity() * PhysicsForce, GetActorLocation()); }AddImpulseAtLocation()is a physics function in Unreal Engine that applies an instantaneous force (impulse) to a simulated physics object at a specific world-space location. It’s useful when you want to simulate an impact, like an explosion throwing something, a bullet hitting an object, or a door swinging open.Last, since this projectile hit another actor, remove the projector from the scene by calling
Destroy().
Your complete OnHit() function should look like the following:
void AFirstPersonProjectile::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
// Only add impulse and destroy projectile if we hit a physics
if ((OtherActor != nullptr) && (OtherActor != this) && (OtherComp != nullptr) && OtherComp->IsSimulatingPhysics())
{
OtherComp->AddImpulseAtLocation(GetVelocity() * PhysicsForce, GetActorLocation());
Destroy();
}
}Add the Projectile’s Mesh, Movement, Collision Components
Next, add a static mesh, projectile movement logic, and a collision sphere to your projectile and define how the projectile should move.
To add these components to your projectile, follow these steps:
In
FirstPersonProjectile.h, in thepublicsection, declare aTObjectPtrto aUStaticMeshComponentnamedProjectileMesh. This is the static mesh of the projectile in the world.Give it a
UPROPERTY()macro withEditAnywhere,BlueprintReadOnly, andCategory = “Projectile | Mesh“.C++// Mesh of the projectile in the world UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Projectile | Mesh") TObjectPtr<UStaticMeshComponent> ProjectileMesh;In the
protectedsection, declare:A
TObjectPtrto aUSphereComponentnamedCollisionComponent.A
TObjectPtrto aUProjectileMovementComponentnamedProjectileMovement.
Give both of these a
UPROPERTY()macro withVisibleAnywhere,BlueprintReadOnly, andCategory = “Projectile | Components”.C++// Sphere collision component UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Projectile | Components") TObjectPtr<USphereComponent> CollisionComponent; // Projectile movement component UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Projectile | Components") TObjectPtr<UProjectileMovementComponent> ProjectileMovement;The ProjectileMovementComponent handles movement logic for you. It calculates where its parent Actor should go based on velocity, gravity, and other variables. Then, during
tick, it applies the movement to the projectile.In
FirstPersonProjectile.cpp, in theAFirstPersonProjectile()constructor function, create default subobjects for the projectile’s mesh, collision, and projectile movement components. Then, attach the projectile mesh to the collision component.C++// Sets default values AFirstPersonProjectile::AFirstPersonProjectile() { // 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("CollisionComponent")); check(CollisionComponent != nullptr);Call
InitSphereRadius()to initialize theCollisionComponentwith a radius of5.0f.Set the name of the collision component’s collision profile to
”Projectile”usingBodyInstance.SetCollisionProfileName().In Unreal Editor, your collision profiles are stored under Project Settings > Engine > Collision, and you can define up to 18 custom collision profiles to use in code. The default behavior of this ”Projectile” collision profile is Block, and this creates collisions on any object it collides with.
C++CollisionComponent->InitSphereRadius(5.0f); // Set the collision profile to the "Projectile" collision preset CollisionComponent->BodyInstance.SetCollisionProfileName("Projectile");You defined an
OnHit()function earlier to activate when the projectile hits something, but you need a way to notify when that collision occurs. To do this, use theAddDynamic()macro to subscribe a function toOnComponentHitEventinCollisionComponent. Pass this macro theOnHit()function.C++// Set up a notification for when this component hits something blocking CollisionComponent->OnComponentHit.AddDynamic(this, &AFirstPersonProjectile::OnHit);Set the
CollisionComponentas the projectile’sRootComponentand as theUpdatedComponentfor the movement component to track.C++// Set as root component RootComponent = CollisionComponent; ProjectileMovement->UpdatedComponent = CollisionComponent;Initialize the
ProjectileMovementcomponent with the following values:InitialSpeed: The initial speed of the projectile when it spawns. Set this to3000.0f.MaxSpeed: The maximum speed of the projectile. Set this to3000.0f.bRotationFollowVelocity: Whether the object should rotate to make the direction of its velocity. For example, how a paper airplane dips up and down as it rises and falls. Set this totrue.bShouldBounce: Whether the projectile should bounce off obstacles. Set this totrue.
C++ProjectileMovement->UpdatedComponent = CollisionComponent; ProjectileMovement->InitialSpeed = 3000.f; ProjectileMovement->MaxSpeed = 3000.f; ProjectileMovement->bRotationFollowsVelocity = true; ProjectileMovement->bShouldBounce = true;
Set the Projectile’s Lifespan
By default, you’ll make the projectile disappear a few seconds after firing it. However, once you derive your projectile Blueprints in the editor, you can experiment with changing or removing this default time to try filling your level up with foam darts!
To make the projectile disappear after a number of seconds, follow these steps:
In
FirstPersonProjectile.h, in thepublicsection, declare a float namedProjectileLifespan.Give it a
UPROPERTY()macro withEditAnywhere,BlueprintReadOnly, andCategory = “Projectile | Lifespan”.This is the lifespan of the projectile in seconds.
C++// Despawn after 5 seconds by default UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Projectile | Lifespan") float ProjectileLifespan = 5.0f;In
FirstPersonProjectile.cpp, at the end of theAFirstPersonProjectile()constructor function, set theInitialLifeSpanof the projectile toProjectileLifespanto make it disappear after five seconds.C++// Disappear after 5.0 seconds by default. InitialLifeSpan = ProjectileLifespan;InitialLifeSpanis a property inherited from AActor. It’s a float that sets how long the Actor lives before dying. A value of0means the Actor lives until the game stops.
Your complete FirstPersonProjectile.h should look like the following:
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "FirstPersonProjectile.generated.h"
class UProjectileMovementComponent;
class USphereComponent;
Your complete AFirstPersonProjectile constructor function should look like the following:
// Sets default values
AFirstPersonProjectile::AFirstPersonProjectile()
{
// 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("CollisionComponent"));
check(CollisionComponent != nullptr);
Get the Character Camera Direction
Projectiles should spawn from the dart launcher itself, so you’ll need to do some math to know where the dart launcher is and where it’s pointing. Since the launcher is attached to the player character, these values are going to change based on where the character is and where they’re looking.
Your first-person character contains some of the positional information you need to launch a dart, so start by modifying your Character class to capture that information with a line trace and return the result.
To use a trace to get the information you need from your character, follow these steps:
In VS, open your Character’s
.hand.cppfiles.In the
.hfile, in thepublicsection, declare a new function namedGetCameraTargetLocation()that returns anFVector. This function will return the location in the world that the character is looking at.C++// Returns the location in the world the character is looking at UFUNCTION() FVector GetCameraTargetLocation();In your Character's
.cppfile, implement theGetCameraTargetLocation()function. Start by declaring an FVector namedTargetPositionto return.Create a pointer to
UWorldby callingGetWorld().C++// The target position to return FVector TargetPosition; UWorld* const World = GetWorld();Add an
ifstatement to check thatWorldisn’t null. In theifstatement, declare aFHitResultnamedHit.C++if (World != nullptr) { // The result of the line trace FHitResult Hit;FHitResultis a struct in UE that stores information about the result of a collision query, including the Actor or component that was hit and where you hit it.To find the point that your character is looking at, you’re going to simulate a line trace along the vector the character is looking down to some distant point. If the line trace collides with an object, you know where in the world your character is looking.
Declare two const FVector values named TraceStart and TraceEnd:
Set
TraceStartto the location of theFirstPersonCameraComponent.Set
TraceEndtoTraceStartplus the forward vector of the camera component multiplied by a very large number. This ensures that the trace will go far enough to collide with most objects in your world as long as your character isn’t staring at the sky. (If the character is looking at the sky,TraceEndis the terminal point of the line trace.)C++// Simulate a line trace from the character along the vector they're looking down const FVector TraceStart = FirstPersonCameraComponent->GetComponentLocation(); const FVector TraceEnd = TraceStart + FirstPersonCameraComponent->GetForwardVector() * 10000.0;
Call
LineTraceSingleByChannel()from theUWorldto simulate the trace. Pass itHit,TraceStart,TraceEnd, and anECollisionChannel::ECC_Visibility.This simulates a line trace from
TraceStarttoTraceEnd, colliding with any visible objects and storing the result of the trace inHit.ECollisionChannel::ECC_Visibilityis the channel to use for tracing, and these channels define what types of objects your trace should try to hit. UseECC_Visibilityfor line-of-sight camera checks.C++World->LineTraceSingleByChannel(Hit, TraceStart, TraceEnd, ECollisionChannel::ECC_Visibility);The
Hitvalue now contains information about the hit result, such as the location and normal of the impact. It also knows if the hit was a result of an object collision. The location of impact (or the end point of the trace line) is the camera target location to return.Use a ternary operator to set
TargetPositionto either theHit.ImpactPointif the hit was a blocking hit andHit.TraceEndif not. Then, return theTargetPosition.C++TargetPosition = Hit.bBlockingHit ? Hit.ImpactPoint : Hit.TraceEnd; } return TargetPosition;
Your complete GetCameraTargetLocation() function should look like the following:
FVector AADventureCharacter::GetCameraTargetLocation()
{
// The target position to return
FVector TargetPosition;
UWorld* const World = GetWorld();
if (World != nullptr)
{
// The result of the line trace
FHitResult Hit;
Spawn Projectiles with DartLauncher::Use()
Now that you have a way to know where the character is looking, you can implement the rest of the projectile logic in your dart launcher’s Use() function. You’ll get the location and direction to launch the projectile, then spawn the projectile.
To get the location and rotation the projectile should spawn at, follow these steps:
In
DartLauncher.h, at the top of the file, add a forward declaration forAFirstPersonProjectile.In the
publicsection, declare aTSubclassOf<AFirstPersonProjectile>property named ProjectileClass. This will be the projectile that the dart launcher spawns. Give this theUPROPERTY()macro withEditAnywhereandCategory = Projectile.DartLauncher.hshould now look like the following:C++// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "AdventureGame/EquippableToolBase.h" #include "AdventureGame/FirstPersonProjectile.h" #include "DartLauncher.generated.h" class AFirstPersonProjectile;DartLauncher.cpp, add an include statement for”Kismet/KismetMathLibrary.h”. Projectile math can be complicated, and this file includes several handy functions you’ll use for launching projectiles.C++#include "DartLauncher.h" #include "Kismet/KismetMathLibrary.h" #include "AdventureGame/AdventureCharacter.h"Start implementing DartLauncher’s
Use()function:Get the
UWorldby callingGetWorld().Add an
ifstatement to check thatWorldand theProjectileClassare not null.In the
ifstatement, get the position the character is looking at by callingOwningCharacter->GetCameraTargetLocation().
C++void ADartLauncher::Use() { UWorld* const World = GetWorld(); if (World != nullptr && ProjectileClass != nullptr) { FVector TargetPosition = OwningCharacter->GetCameraTargetLocation(); } }Although you want to spawn projectiles from the tool the character is holding, you might not want to spawn them directly from the center of the object. The dart launcher’s
SKM_Pistolmesh has a ”Muzzle” socket you can use to set a precise spawn point for your darts.Declare a new
FVectornamedSocketLocationand set it to the result of callingGetSocketLocation(“Muzzle”)on theToolMeshComponent.C++// Get the correct socket to spawn the projectile from FVector SocketLocation = ToolMeshComponent->GetSocketLocation("Muzzle");Next, declare a
FRotatornamedSpawnRotation. This is the rotation (pitch, yaw, and roll values) of the projectile as it spawns.Set this to the result of calling
FindLookAtRotation()from the kismet math library, passing theSocketLocationandTargetPositionyou got from the player character.C++FRotator SpawnRotation = UKismetMathLibrary::FindLookAtRotation(SocketLocation, TargetPosition);FindLookAtRotationcalculates and returns the rotation you’d need atSocketLocationto faceTargetPosition.Declare a
FVectornamedSpawnLocation, and set it to the result of adding theSocketLocationand the forward vector of theSpawnRotationmultiplied by10.0.The muzzle socket isn’t quite at the front of the launcher, so you’ll need to multiply the vector by an offset to get the projectile firing from the right location.
C++FVector SpawnLocation = SocketLocation + UKismetMathLibrary::GetForwardVector(SpawnRotation) * 10.0;
Now that you’ve got a location and rotation, you’re ready to spawn the projectile.
To spawn a projectile, follow these steps:
Still in the
Use()function, declare aFActorSpawnParametersnamedActorSpawnParams. This class includes information about where and how to spawn the actor.C++//Set Spawn Collision Handling Override FActorSpawnParameters ActorSpawnParams;Set the
SpawnCollisionHandlingOverridevalue inActorSpawnParamstoESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding.This tries to find a place to spawn the projectile where it isn’t colliding with another Actor (such as inside a wall) and won’t spawn it if a suitable location isn’t found.
C++ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding;Use
SpawnActor()to spawn the projectile at the muzzle of the dart launcher, passingProjectileClass,SpawnLocation,SpawnRotation, andActorSpawnParams.C++// Spawn the projectile at the muzzle World->SpawnActor<AFirstPersonProjectile>(ProjectileClass, SpawnLocation, SpawnRotation, ActorSpawnParams);
Your complete Use() function should now look like the following:
void ADartLauncher::Use()
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Using the dart launcher!"));
UWorld* const World = GetWorld();
if (World != nullptr && ProjectileClass != nullptr)
{
FVector TargetPosition = OwningCharacter->GetCameraTargetLocation();
// Get the correct socket to spawn the projectile from
Derive a Foam Dart Class and Blueprint
Now that all the spawning logic is done, it’s time to build a real projectile! Your dart launcher class needs a subclass of AFirstPersonProjectile to launch, so you’ll need to build one in code to use in your level.
To derive a foam dart projectile class to use in-game, follow these steps:
In Unreal Editor, go to Tools > New C++ Class.
Go to All Classes, search for and select First Person Projectile as the parent class, and name the class
FoamDart. Click Create Class.In VS, leave these files as-is, save your code, and compile it.
In this tutorial, you won’t be implementing any custom projectile code beyond what you defined in FirstPersonProjectile, but you can modify the FoamDart class on your own to suit the needs of your project. For example, you could try making the dart projectiles stick to objects instead of disappearing or bouncing around.
To create a foam dart Blueprint, follow these steps:
In Unreal Editor, create a Blueprint class based on FoamDart and name it
BP_FoamDart. Save this in yourFirstPerson/Blueprints/Toolsfolder.With the Blueprint open, select the Projectile Mesh component and set the Static Mesh to SM_FoamBullet.
Save and compile your Blueprint.
In the Content Browser, open
BP_DartLauncher.In its Details panel, in the Projectile section, set the Projectile Class to
BP_FoamDart, then save and compile the Blueprint.If you don't see
BP_FoamDartin the list, go to the Content Browser, selectBP_FoamDart, then go back to the Projectile Class property and click Use Selected Asset from Content Browser.
It’s time for the big reveal. Save your assets and click Play. When the game begins, you can run to the dart launcher to pick it up. Pressing the left mouse button spawns a projectile from the muzzle of the dart launcher and bounces it around the level! These projectiles should disappear after five seconds and impart a small physics force on objects they collide with (including you!).
Next Up
Congratulations! You’ve completed the First-Person Programmer Track tutorial and learned a lot along the way!
You’ve implemented custom characters and movement, created pickups and data assets, and even made equippable tools with their own projectiles. You’ve got everything you need to take this project and turn it into something all your own.
Here are a few suggestions:
Can you expand the player’s inventory with different types of items? What about stacks of items?
Can you combine pickups and projectiles to create pickupable ammo? How about implementing this ammo system in the dart launcher?
Can you develop the idea of consumables into equippable consumables? How about a health pack the player holds, or a ball they can pick up and throw?
Complete Code
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "FirstPersonProjectile.generated.h"
class UProjectileMovementComponent;
class USphereComponent;
// Fill out your copyright notice in the Description page of Project Settings.
#include "FirstPersonProjectile.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Components/SphereComponent.h"
// Sets default values
AFirstPersonProjectile::AFirstPersonProjectile()
{
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Camera/CameraComponent.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "GameFramework/Character.h"
#include "InputActionValue.h"
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AdventureCharacter.h"
#include "EquippableToolBase.h"
#include "EquippableToolDefinition.h"
#include "ItemDefinition.h"
#include "InventoryComponent.h"
// Sets default values
AAdventureCharacter::AAdventureCharacter()
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "AdventureGame/EquippableToolBase.h"
#include "AdventureGame/FirstPersonProjectile.h"
#include "DartLauncher.generated.h"
class AFirstPersonProjectile;
// Copyright Epic Games, Inc. All Rights Reserved.
#include "DartLauncher.h"
#include "Kismet/KismetMathLibrary.h"
#include "AdventureGame/AdventureCharacter.h"
void ADartLauncher::Use()
{
UWorld* const World = GetWorld();