Artificial Intelligence

An tutorial about Jumping, Avoidance & Wandering

Updated over 2 years ago Edit Page Revisions

The very first thing I want to cover in this tutorial series is how to make AI know when to jump when following a navigation path. This tutorial is using C++ approach.

There's also an alternative way of achieving jump behavior, and that's by placing SmartNavigationLinks on the level, doable purely in Blueprint, but this will get covered in one of the upcoming tutorials.

There are three steps required to achieve this tutorial's goal.

===1. Implement a special "jump" navigation area===

One of the ways of annotating navmesh in UE4 is by applying navigation area types to it. There'll be a in-depth tutorial on navigation areas so I'll just say areas can apply a specific set of flags and costs to the navigation mesh.

Creating a navigation area is very straightforward. Just create a class deriving from UNavArea, like so:

    UCLASS()
    class UNavArea_Jump : public UNavArea
    {
        GENERATED_UCLASS_BODY()
    };

We'll also need to name our "jump" flag so that we can consistently use it throughout our code. Like so:

 
   UENUM()
   namespace ENavAreaFlag
   {
       // up to 15 values
       enum Type
       {
           Default,
           Jump,
           Crouch,
           // and what not
       };
   }

Note that we're not using the first bit, since it has a special meaning in our navmesh internals. If first bit in a area is set it means it's walkable, and it's treated as not walkable otherwise. Let's also implement some helper functions for operating on area flags:

    namespace FNavAreaHelper
    {
        FORCEINLINE bool IsSet(uint16 Flags, ENavAreaFlag::Type Bit) { return (Flags & (1 < Bit)) != 0; }
        FORCEINLINE void Set(uint16& Flags, ENavAreaFlag::Type Bit) { Flags |= (1 < Bit); }
 
        FORCEINLINE bool IsNavLink(const FNavPathPoint& PathVert) { return (FNavMeshNodeFlags(PathVert.Flags).PathFlags & RECAST_STRAIGHTPATH_OFFMESH_CONNECTION) != 0; }
        FORCEINLINE bool HasJumpFlag(const FNavPathPoint& PathVert) { return     IsSet(FNavMeshNodeFlags(PathVert.Flags).AreaFlags, ENavAreaFlag::Jump); }
        FORCEINLINE bool HasCrouchFlag(const FNavPathPoint& PathVert) { return IsSet(FNavMeshNodeFlags(PathVert.Flags).AreaFlags, ENavAreaFlag::Crouch); }
    }

SetMovementMode(MOVE_Flying); } else { // regular move CharacterMoveComp->SetMovementMode(MOVE_Walking); } } }


Remember you can find missing code details at tutorial's [Github repository](https://github.com/MieszkoZ/AITutorialCPP/commit/99450d6cc742f8cfe164c585f8b9c59b41208b03).

  
\===3. Make AI use the new component===

This one's easy, but not obvious. To make AI use **UTutorialPathFollowingComponent** we need to implement a new AI controller class and configure it to use the new component. Create a class deriving from **AAIController** (let's call it **ATutorialAIController**) and implement its constructor like so:

```cpp
    ATutorialAIController::ATutorialAIController(const FPostConstructInitializeProperties& PCIP)
    : Super(PCIP.SetDefaultSubobjectClass(TEXT("PathFollowingComponent")))
    {
    }

This has to be done this way since PathFollowingComponent is the AIController's "sub-object pointer" which means we expect it to be set to an actual object instance all the time.

===Level setup===

One last thing that needs to be done to observe our AI enjoy its newly acquired jumping skill is to have some actual level with jump links.

The easiest way to create navigation links on a map is by placing NavLinkProxy actors on it (just drag&drop them from the ClassView in the editor). To make these links jump-able you need to modify selected link's properties to use our newly created navigation:

File:AreaClass Jump.png

Check out T01_Jump in this tutorial's content package

Summary

We've created a way to annotate navmesh with additional information and a way to have AI use that knowledge. Feel free to implement a more sophisticated move in place of my "flying jump" ;)

Feel free to suggest new thing I should tackle in this tutorial series. I have a little list to cover, but if your request is on the list then it will get covered even sooner. If it's not I'll handle it once the list is empty. If a feature is widely requested it will make it to the list! :))

And keep the feedback coming!

Cheers!

Part 2

__NoTOC__ Authored by: Mieszko Zielinski

In this super quick tutorial I'll show you how to make your AI avoid each other while following paths. There are two separate ways UE4 supports AI avoidance and both are very easy to set up but the one resulting in better results has some restrictions and caveats. Let's start with the easier one (the caveats-less one).

UCharacterMovementComponent's RVO

UCharacterMovementComponent has an embedded implementation of a simple RVO algorithm (Reciprocal Velocity Obstacle). To enable it just set the UCharacterMovementComponent::bUseRVOAvoidance flag on your AI and that's it (you can even do it in Blueprint!).

One thing you need to be aware of when using this feature is that it's navigation-agnostic which in practice means it doesn't care about navmesh and its bounds which in turn means AI can end up being pushed outside of navmesh by RVO simulation (see the T02_Avoidance map for an example).

===Detour Crowd===

UE4 has an integration of DetourCrowd, which is a natural thing to have since we use (modified) Recast for navmesh generation. It's a lot more sophisticated then the CharacterMovementComponent's RVO and as such more complex.

The easiest way to enable DetourCrowd for your AI is to have your AI use UCrowdFollowingComponent for your AIController's PathFollowingComponent. You just need to do something like this:

    AMyVeryOwnAIController::AMyVeryOwnAIController(const FPostConstructInitializeProperties& PCIP)
    : Super(PCIP.SetDefaultSubobjectClass(TEXT("PathFollowingComponent")))
    {
        // ...
    }

This is curently the only way to enable detour crowd for your project, unfortunately. It's the case since there's no way to override Actor's default subobjects in blueprints just yet. Alternatively we'll introduce an out of the box AIController that uses crowd pathfollowing. Stay tuned for more information on this topic ;)

These don't cooperate!

The two avoidance methods described here do not work together. I haven't tried enabling both on a single AI so I can only imagine what would happen exactly, but since RVO works on movement component level I'd guess it will override whatever DetourCrowd tries to do.

===Summary===

There's a lot more to cover in terms of configuring or tweaking avoidance, especially DetourCrowd, bit I would prefer to introduce stuff in bite-sized chunks. The defaults work well enough to get anyone off the ground when setting up a project, and I'll cover avoidance tweaking in one of the future, more advanced tutorials.

As always there's some github goodies (not a lot this time around) and content (just a single map) that come along. Give them a try to see UE4 avoidance in action!

Don't hesitate to share your feedback!

Previous Tutorial

Part 3

__NoTOC__ Authored by: Mieszko Zielinski

This tutorial is done purely in data and can easily be pulled of in vanilla UE4 4.3.

==Behavior Trees!== It's high time I've started talking about behavior trees :D I'll try to keep it as simple as I can this time, but there's a fair amount of things to cover regardless.

Behavior Trees, as an AI technique, have been around for many years already. Just google "behavior trees" and you'll see how much have been said about them. So I won't waste time repeating all that :D

One of the things that distinguish UE4 implementation is that it has built-in support for being fully event driven, which means you can create a BT that will not require any ticking and will only change it's state in response to external events, like an AI reaching the end of a path or changes to an AI's world knowledge (via blackboard). More on event-driven BTs in future tutorials, now let's focus on a basic case.

==Blackboard== UE4 AI uses Blackboards (BBs for short) to store AI's world knowledge. Among many benefits of blackboards, like having every AI its own view of the world, this gives us an opportunity to easily observe AI's world knowledge changes. It's also very easy to get data from a BB in a generic fashion.

Lets start by creating a new Blackboard asset. You can create one by right clicking in the content browser, it's under Miscellaneous category.

A new BB is empty and we need to define key-value pairs we want this BB type to store. Every entry in a BB consists of a string "key" and a value of a specified type. Play around with BB editor to to see all the supported types.

For the purposes of this tutorial we need to create just one entry, to store the actor we'll want AI to go to. Once done it should look like this in the editor:

File:T03 BBKey.png

==Finding where to go with a custom BT task== To have AI move somewhere we need to find that "somewhere" first. One of the ways of doing that is having a behavior tree task that will search for "somewhere" and store it in the BB for later use by other BT tasks. We're going to do just that. And we're going to do that in blueprint! Because why not? And it's fun! :D

In content browser create a Blueprint derived from

BTTask_BlueprintBase

. This is a special class that hosts BP API for BT tasks. Let's name it BTTask_FindWanderPoint.

We just need a task that will look through all interesting actors in the world (I used Note actors for this purpose), pick one and store it somewhere. Let's make the task store data in AI's blackboard.

Getting all actors of selected class in BP is super easy. Just do something like this:

File:T03 GetAllNotes.png

TempActor is a variable declared in the BP itself and after executing this bit of BP TempActor contains a random Note actor from the world.

==Implementing actual task== What we want here is an "instant" task, which means it executes and finishes in "one go", without spreading actions or calculations over multiple frames.

A blueprint-implemented instant task has to do two things:

:*implement

EventReceiveExecute

- this will get called when a task gets picked for execution.

:**

OwnerActor

parameter is the actor BT is running for. In our case it will always be an

AIController

:*make sure all possible code paths finish with

FinishExecute

call - calling this function lets the BT know given task has finished its work. If you don't call it the BT will assume it's a latent task and will continue running it in future frames, until a FinishExecute call is made.

:**

Success

parameter let's BT know if give task's execution was successful or not

One more thing we want the BTTask_FindWanderPoint to do is to store its findings in the AI's blackboard. To do that we need to add a public variable that will indicate a BB key being the destination of calculated information. You do that by adding a variable of type

BlackboardKeySelector

to BTTask_FindWanderPoint's blueprint (I've called it

Put Result To

) and using it to set a value in an AI's blackboard, like so:

File:T03 SetBBValue.png

==Perform the actual move==

We've created a BB to store information and a task to supply a BB with data so all that's left to be done is to use that information. Let's create a Behavior Tree that will pick an interesting note actor and send AI there. It's as simple as this:

File:T03 BT.png

The meat of the tree is the node labeled "Sequence" and its goal is to do just that: execute tasks in a sequence. The first task to execute is our freshly implemented BTTask_FindWanderPoint. It's supposed to pick a random Note actor and assign it as "GoalActor" blackboard key's value. The second task in the sequence is the engine-supplied

BTTask_MoveTo

that picks an

AActor

of an

FVector

from an indicated BB key and sends AI that way.

==Run it!== The content I've prepared for this tutorial contains a little map with four Note actors and an AI that is running between the note actors. Re-do it yourself or just grab the content package. Just remember, you need the code from the previous tutorials, but only for the "jumping" part of the demo map. Everything else is doable in vanilla UE4!

Enjoy!