Tracking projectiles and you

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.

Creating the 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);

Preparing the projectile

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.

Creating the custom channel and testing the tracking!

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:

Creating the custom channel and testing the tracking!

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:

Creating the custom channel and testing the tracking!

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!

Creating the custom channel and testing the tracking!
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.183.221