Advanced collision in UE4

Before we define the functionality for our new service, let's quickly talk about collision in UE4. Collision in UE4 uses a channel system. Collision channels are effectively flags we can set on each collidable object to inform how the object should behave in any given collision situation. You may have noticed when we set up the collidable component of an object we set the collision profile name or collision type to either NoCollision, OverlapAllDyanmic, Pawn, and so forth. These profiles not only set the collision type of the object but also how that object will react to different collision channels.

Most of our interactions with UE4's collision system have appeared as follows:

Advanced collision in UE4

The important elements to note here are the Object Type property and the CollisionResponses matrix. In the previous example we are stating that this object is of type Pawn. Meaning that any time something detects a collision with this object, it will be considered part of the Pawn channel. Within the CollisionResponses matrix, we are informing the engine how this object is going to react to other trace and object channels during the event of a collision. In the previous example, this object will block ALL collision queries apart from that with a trace channel type of Visibility.

This is a very powerful tool as we could create a custom arrangement of these settings so that a given object collides with all pawns but ignores vehicles completely as they are two different collision channels.

For example, imagine if object A was to collide with another object B, and A is of type Pawn and B is of type Vehicle. If we have stated in the CollisionResponses matrix of A to block Vehicle channel collisions, and in the collision response grid of B to block Pawn channel collisions, then this clash will result in the objects successfully colliding. However, if either of the objects have set to ignore the opposing collision channel then this collision will not take place. This is very useful for setting up collision queries where we would have an object pass through some object types, yet be blocked by others.

The Object A in the previous example where a collision does not take place with Vehicle types would have collision properties that look like this:

Advanced collision in UE4

UE4 also allows us to define up to 18 custom collision channels via the project settings. During the course of this chapter and the next, we will be using some of these channels. These custom channels can also be used in code collision queries, however we must use previously defined enumerated types that will resolve to the correct collision channel during compilation.

Alright, let's finish defining our new C++ service!

Defining the service tick

Navigate to the BMAgroCheck.cpp now and modify the #include list at the top of the file to match the following:

#include "BossMode.h"
#include "BMAIController.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "BehaviorTree/Blackboard/BlackboardKeyAllTypes.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BMBossCharacter.h"
#include "DrawDebugHelpers.h"
#include "BMAgroCheck.h"

The previous include list will ensure that we have included all required code types for the following functionality. Let's start by performing an assignment check similar to that of the AI_Controller check that took place in our custom blueprint AI assets.

Within the TickNode() method we are going to be performing a sphere sweep check for any players within a certain radius of the AI character that is currently being controlled by the parsed UBehaviorTreeComponent. That means we need access to the BMAIController, the BMBossCharacter, and the behavior tree.

Add the following code to TickNode():

void UBMAgroCheck::TickNode(UBehaviorTreeComponent&OwnerComp, uint8* NodeMemory, floatDeltaSeconds)
{
    Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);

if (ThisTree == nullptr || ThisController == nullptr ||  
        ThisAICharacter == nullptr)
      {
        ThisTree = OwnerComp.GetCurrentTree();

        ThisController = 
        Cast<ABMAIController>(OwnerComp.GetAIOwner());

        ThisAICharacter = Cast<ABMBossCharacter>
        (ThisController->GetPawn());

        if (ThisTree == nullptr || ThisController == nullptr||
        ThisAICharacter == nullptr))
                {
                        UE_LOG(LogTemp, Warning, 
                        TEXT("Warning Agro Service                         performed on invalid AI"));
                        return;
                }
        }

The previous code checks whether any of the handles we declared in the class definition are invalid. If so, we need to repopulate all of our handles with the appropriate values. We can get the behavior tree asset from the parsed UBehaviorTreeComponent OwnerComp via the method GetCurrentTree(). We get a handle to the AI controller by casting the return from the GetAIOwner() method of OwnerComp to a ABMAIController. We then get a handle to the AI character via casting the result of the GetPawn() method of the ABMAIController to a ABMBossCharacter. If any of these fail, we use the UE_LOG macro to log a warning, stating that the service is trying to perform on an invalid AI and we return out of the function.

To perform the actual sweep check we are going to require the use of a few new objects. One is FCollisionQueryParams. This object is a struct that acts as an input parameter to most collision query functions. It is with this struct we are able to define most of the options for the sweep check. We can specify whether we are to use complex checking, what actors to ignore, and the name of the sweep check. We are also going to use FCollisionObjectQueryParams. It is with this struct we will define which objects we are going to be checking for via the object collision channels mentioned before. The final struct we are going to be using is FHitResult. This struct is used to populate the return information from a collision query function. It is with this struct we can get relevant collision data such as the offending actor and penetration details.

Add the following code to the TickNode() method:

// Initialize a sweep params struct with trace complex set to true
FCollisionQueryParams SphereSweepParams(FName(TEXT("AgroCheckSweep")), true, ThisAICharacter);

FCollisionObjectQueryParams ObjectQuery(ECC_GameTraceChannel1);

// Initialize Hit Result
FHitResult HitOut(ForceInit);

Here we are creating a FCollisionQueryParam object called SphereSweepParams. We are using the constructor to initialize this struct with the name of the sweep (AgroCheckSweep) to trace complex (higher accuracy), and we are passing the ABMBossCharacter handle to the constructor as an actor to ignore so that the sweep check does not detect the ABMBossCharacter itself.

We then initialize a FCollisionObjectQueryParams struct with the enum variable ECC_GameTraceChannel1. This enum represents one of the custom collision channels mentioned previously. Once we have completed this object we will add the custom collision channel via the editor and assign it as a collision type to the player character.

The next thing we are going to do is draw a debug sphere so we have a visual representation of the sweep check we are about perform. Add the following code to the TickNode() method:

// Draw debug sphere to check we are sweeping the right area
DrawDebugSphere(ThisAICharacter->GetWorld(),
ThisAICharacter->GetActorLocation(),
1500, 12, FColor::Red, false, 4.0f);

The previous code will draw a sphere via the DrawDebugSphere method that is part of DrawDebugHelpers.h, which we added to our include list earlier. The first parameter is the world context to draw the sphere in, the second is the location to draw the sphere, the third is sphere radius, the fourth is the number of segments that will make up the sphere (higher number, higher sphere fidelity), the fifth is the color, the sixth is a boolean representing whether the lines are to be persistent after drawing, and the last is the lifetime the sphere will be visible. The previous function call will draw a red sphere with a 1500 unit radius at the location of the AI character for 4 seconds.

Now we can finally perform the sweep test! A sweep test simply sweeps a sphere along a path. If any objects collide with the sphere along the sweep, a collision will be registered. Add the following code to the TickNode() method:

// Perform the sweep and check boolean return, we will only be checking for BMCharacter objects
bool bResult = ThisAICharacter->GetWorld()->
SweepSingleByObjectType(HitOut,
        ThisAICharacter->GetActorLocation(),
        ThisAICharacter->GetActorLocation() +FVector(0.0f, 0.0f, 
        10.0f),
        FQuat(),
        ObjectQuery,
        FCollisionShape::MakeSphere(1500),
        SphereSweepParams);

The previous code is using the SweepSingleByObjectType method of the UWorld object. This function will perform a sphere sweep and return a hit upon the first positive collision. The function takes in a FHitResult struct to populate with return data, an origin location for the sweep, a destination location for the sweep, the rotation of the sweep, the FCollisionObjectQueryParams struct for information on the type of objects to seek collisions with, a shape to perform the sweep with (in our case a sphere with a 1500 radius) and, finally, a FCollisionQueryParams struct used to detail the information about the query itself. The SweepSingleByObjectType returns a bool that represents the success of the sweep, true if there was a collision, false if there was not.

The previous function call will sweep for objects that are of type ECC_GameTraceChannel1 or in our case the first custom collision channel we create. The sweep will be performed from the ABMBossCharacters location to 10 units up on the Z axis. It is important to provide different locations for the sweep origin and sweep end as, if they are the same, the sweep will fail. This sweep query will use the details we outlined earlier within the SphereSweepParams struct.

Finally, we have to check whether the sweep returned a positive hit. If so, we need to set those blackboard keys we did all this work for! Add the following code to the TickNode() method:

if (bResult)
{
    ThisController->GetBlackboard()->
    SetValueAsObject(TEXT("TargetToFollow"), HitOut.GetActor());

    ThisController->GetBlackboard()->SetValueAsVector(TEXT("HomeLocation"), ThisAICharacter->GetActorLocation());

    ThisController->GetBlackboard()->
    SetValueAsVector(TEXT("TargetLocation"), HitOut.GetActor()->
    GetActorLocation());

}
else
{
    ThisController->GetBlackboard()->
    SetValueAsObject(TEXT("TargetToFollow"), nullptr);
}

} // <-- closing TickNode()

The previous code will check whether the result of the sphere sweep was successful. If so, we are to set the blackboard keys TargetToFollow, HomeLocation, and TargetLocaiton respectively. We are doing this via the UBlackboardComponent of the ABMAIController reference we retrieved earlier. If the hit result fails, we wish to set the TargetToFollow key to nullptr as we do not wish for the AI to have a target.

Adding the C++ service to the BossBT

Now compile the code and re-run the project. We are about to include the service in our BossBT. Once the project has compiled, open the BossBT again. Within the tree, select the first selector node we created. Right-click within the node and hover over Add Service…. From the provided dropdown, select BMAgroCheck. Your final behavior tree should appear as follows:

Adding the C++ service to the BossBT

Before we are done with this tree, we need to change one property of the BMAgroCheck service. Select the service and address the details panel. Under the Service section, click the down expansion arrow and then tick the Call Tick on Search Start property. This will ensure the service is called from the beginning of the behavior tree logic search. The details panel will appear as follows:

Adding the C++ service to the BossBT
..................Content has been hidden....................

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