Tracking projectiles are very easy to set up in UE4. We must simply provide the projectile movement component with information on how much the tracking will affect the projectiles during flight, and a target to track to. The first is simple, as I will be providing you with good starting values for any tracking behavior. The latter, on the other hand, will require that we create another custom collision channel and perform something called a line trace.
Let's first acquire the target we wish the projectiles to track to. We can do this by creating another object collision channel specifically for boss objects, we will then perform a line trace looking for objects that are part of this collision channel. If the line trace returns a successful collision, we have found a target for our projectiles.
We are going to have to add a couple more members to the ABMCharacter
object as well. Navigate to BMCharacter.h
now and add the following code to the class definition:
protected: USceneComponent* TrackingSceneComponent;
This USceneComponent
will act as the tracking target for our projectile. Thankfully the ABMBossCharacter
contains one via inheritance from the AActor
interface. Now we must simply perform our trace and, upon successful collision, obtain a reference to the USceneComponent
. Add the following protected
method to the ABMCharacter
class definition:
void OnTrack();
The purpose of this function will be to perform a line trace that begins from the center of the screen and extends outwards into the game world to some specified length. If a boss is found somewhere along this trace, we will use it as the tracking target. Navigate to BMCharacter.cpp,
we are going to be defining this function via the following code:
void ABMCharacter::OnTrack() { FVector MousePos; FVector MouseDir; FHitResult Hit; FCollisionObjectQueryParams ObjectQueryParams; ObjectQueryParams.AddObjectTypesToQuery(ECC_GameTraceChannel2); APlayerController* playerController = Cast<APlayerController>(GetController());
Here we are simply declaring the variables we will be using for the trace logic. The line trace call we will be making is very similar to that of the sphere sweep test we performed in the previous chapter during the agro check C++ service. We have declared two vectors, MousePos
and MouseDir
, to hold the origin and direction of the line trace in world space. We have also declared the FHitResult Hit
to save the output of the line trace function.
The final object we declared is a FCollisionObjectQueryParams
object that will be used to specify the collision query details of this line trace. Via the constructor for this object we specify to look for objects that are of type ECC_GameTraceChannel2
. As we are using an additional custom object channel, we must look for ECC_GameTraceChannel2
instead of the ECC_GameTraceChannel1
we used in the previous chapter. The last thing we do is get a handle to our player controller so that we may call functions that project screen space positions into world space.
Beneath this code, add the following:
if (GEngine->GameViewport != nullptr && playerController != nullptr) { FVector2D ScreenPos = GEngine->GameViewport->Viewport- >GetSizeXY(); playerController->DeprojectScreenPositionToWorld(ScreenPos.X / 2.0f, ScreenPos.Y / 2.0f, MousePos, MouseDir); MouseDir *= 100000.0f; GetWorld()->LineTraceSingleByObjectType(Hit, MousePos, MouseDir, ObjectQueryParams);
Here we are ensuring that a valid game viewport has been set and that the player controller handle we populated earlier is valid. With these handles we are going to be populating the MouseDir
and MousePos
vectors.
We use the GameViewport
handle to get the current screen dimensions and assign them to the ScreenPos
2D vector. We can then use the player controller to de-project this 2D screen space position to provide us with a world space point and direction. This is done via the DeprojectScreenPositionToWorld()
method. This method takes in an X
and Y
value to use for the screen space point, and references to vectors that are to be populated with the 3D point and direction.
Here we have passed mousePos
and mouseDir
to the function to be populated. For the screen space position values, we have provided the halved components of the ScreenPos FVector2D
we populated earlier. This will project the center of the screen into world space with regards to the current position and angle of the game camera.
This function will return false
if the point could not be determined. It would be good practice to check the result of this de-projection but I have omitted this check for code brevity. We then scale the mouseDir
vector by a very large amount to ensure that the line trace we perform reaches far into the game scene.
Next we perform the actual line trace via LineTraceSingleByObjectType()
. This method takes in a FHitResult
struct to save the results of the trace, a start position and direction for the trace, and a FCollisionObjectQueryParams
to specify the parameters of the trace. We pass Hit
to this function to be populated with the results of the trace, MousePos
and MouseDir
as the start and direction of the trace, and ObjectQueryParams
to ensure we only search for objects that are part of ECC_GameTraceChannel2
. Underneath this, add the following code:
if (Hit.bBlockingHit) { UE_LOG(LogTemp, Warning, TEXT("TRACE HIT WITH %s"), *(Hit.GetActor()->GetName())); TrackingSceneComponent = Cast<USceneComponent>(Hit.GetActor()-> GetComponentByClass(USceneComponent::StaticClass())); } else { UE_LOG(LogTemp, Warning, TEXT("No Trace")); TrackingSceneComponent = nullptr; } } <-- closing If(GEngine->GameViewport != nullptr...) } <-- closing OnTrack()
Here we are checking the result of the trace. If a blocking hit was found, it means our trace has successfully picked up a boss! Here I am logging that our trace succeeded via the UE_LOG
macro, this macro can be used to log to the output window from anywhere. I then retrieve the USceneComponent
from the hit actor and assign it to TrackingSceneComponent
. If the trace fails, I log that no trace was found and set TrackingSceneComponent
to nullptr.
This means we can also untrack by purposefully missing a track.
Before we get on to preparing the projectile, let's make sure we bind the Track input action we created in the previous chapter to the OnTrack()
method we just made by adding the following code to the SetupPlayerInputComponent()
method:
InputComponent->BindAction("Track", IE_Pressed, this, &ABMCharacter::OnTrack);
Ok, now that we have our scene component to track to, let's set up the projectile so that it is ready to act as a homing missile. Let's start by exposing the ProjectileMovementComponent
via adding the following code to the BMProjectile
class definition found in BMProjectile.h
:
FORCEINLINE class UProjectileMovementComponent* GetProjectileMovement() const { return ProjMovement; }
This function acts as a quick accessor method for the ProjectileMovementComponent
. This function has been specified with the FORCELINE
macro to ensure that this function is inlined regardless of compiler heuristics. Now we can add a couple of settings to the constructor for this object so that the projectile movement component acts as a tracking projectile. Add the following code to ABMProjectile::ABMProjectile()
where we are setting up the ProjMovement
component:
ProjMovement->bIsHomingProjectile = true; ProjMovement->HomingAccelerationMagnitude = 10000.0f;
This simply sets the projectile to act as a homing projectile and ensures that the tracking acceleration is very large to ensure the projectile tracks drastically within a short flight time. Feel free to adjust that value to your liking. With these settings in place, all we need to do is assign the scene component we want the projectile to track to. Navigate back to BMCharacter.cpp
and add the following code to the OnFire()
method:
ABMProjectile* ThisProjectile = GetWorld()-> SpawnActor<ABMProjectile>(ProjectileClass, ProjSpawn-> GetComponentLocation(), GetControlRotation()); ThisProjectile->GetProjectileMovement()->HomingTargetComponent = TrackingSceneComponent;
This simply modifies our previous functionality to obtain a handle to the created projectile so we can assign TrackingSceneComponent
as the HomingTargetComponent
via the GetProjectileMovement()
accessor we made earlier. Ensure this new code has replaced the old spawn logic.
The last thing we need to do is create the custom object collision channel and assign it to our boss object. Do this now by navigating to the Collision section of the Project Settings and adding another Object channel via the New Object Channel… button. This time call it BMBoss
. You should see something similar to this:
Now navigate to the FPBossCharacter
Blueprint and select the Capsule Component from the components panel. With this selected, address the Details panel under the collision section, set the Collision Presets to Custom…, and ensure the setting matches the following:
We are done! That was easy. Now all you need to do is compile the code and right-click when the boss is in the center of the screen, and then let some projectiles fly! You should see the following output upon a successful trace:
[2016.03.16-10.05.00:007][309]LogTemp:Warning: TRACE HIT WITH FPBossCharacter_3
You will also see that, with the boss tracked, the projectiles will home towards it no matter where you are looking!
3.135.183.221