Download

NavLink and making AI jump?

I was curious if there was a way for the AI to be able to know to jump from one place to another with a NavLink? If not, how would I tell my AI to be able to jump from one platform to another???\

252e4838477bf702b892712ccd43361fe9722d33.jpeg

It’s currently not supported out of the box and would require some C++ coding. You can however implement a kind of jumping behavior with navlinks and triggers. Navigation links tells AI “you can traverse this segment”, so if you place a link between two platforms AI will find a path as expected. You just need to take care of the jumping part, and you can “fake” it by placing a trigger on the link start that will make AI move to the other end.

Making “AI move” could be done in couple of ways: you can simply teleport your AI, matinee-animate him, or set his physics to Flying and just let him continue (should work, but I haven’t tried it :D). If using the flying option don’t forget to turn Walking back on once “jump” is done :wink:

If you’re interested in proper way of solving this in C++ just let me know.

@MieszkoZ: Thanks for your insight! :D! I am interested in the proper way of doing this in C++ actually ^_^. Cause then It would be easier to set level blueprints by calling the AI’s function.

Issue I’m having is how would I go about changing the AI’s Physics to be set to Flying ?

A proper C++ solution would involve following steps:

  • Implement a new Navigation Area class - you do that by deriving from [FONT=Lucida Console]UNavArea. Let’s call it [FONT=Lucida Console]UNavArea_Jump
  • Define a “jump flag” - the implementation details don’t really matter, but a enum would work best here.
  • Set the “jump flag” in [FONT=Lucida Console]UNavArea_Jump::AreaFlags
  • Now you’ll be able to assign [FONT=Lucida Console]UNavArea_Jump as a navigation area of navigation links.
  • Next step is to implement your own path following component. Derive from [FONT=Lucida Console]UPathFollowingComponent. Let’s call it [FONT=Lucida Console]UMyPathFollowingComponent.
  • Have [FONT=Lucida Console]UMyPathFollowingComponent override [FONT=Lucida Console]UPathFollowingComponent::SetMoveSegment(uint32 SegmentStartIndex) function. This function gets called whenever AI is starting to follow new path segment
    • if [FONT=Lucida Console]FNavMeshNodeFlags(Path->PathPoints[SegmentStartIndex].Flags).AreaFlags has your defined “jump flag” set it means it’s “jumpable”
    • the simplest solution would be to enable flying in such a case. You do that by calling [FONT=Lucida Console]SetMovementMode(MOVE_Flying) on your movement component. From within path following component you can get this by casting [FONT=Lucida Console]UPathFollowingComponent::MovementComp to [FONT=Lucida Console]UCharacterMovementComponent.
    • don’t forget to set movement mode back to [FONT=Lucida Console]MOVE_Flying once AI finished following “jump segment” :wink:

That should pretty much do it :slight_smile: Sorry for any potential misspelling of class or function names :smiley:

MieszkoZ, Thank you for this!!! Must say a clever way to get things working!! If I have troubles I will reply through this thread ^_^!!!

“Clever” is what we aim for with our AI code :slight_smile: Thanks!

Ok, so far, I got everything set up. However, what do i do with the UMyPathFollowingComponent to have the AI follow that particular component? or does it do it by default if the UNavArea is set to my custom UNavArea? I’m trying to do a debug message to see if it appears when it attempts to go to the NavLink, but nothing happens.

UNavArea_Jump.h





#pragma once

#include "AI/Navigation/NavAreas/NavArea.h"
#include "UNavArea_Jump.generated.h"

/**
 * 
 */
UCLASS()
class UUNavArea_Jump : public UNavArea
{
	GENERATED_UCLASS_BODY()

	enum AI_Path_Choice
	{
		Jump
	};

	
	
};



.cpp





#include "SideScrollerConcept.h"
#include "UNavArea_Jump.h"


UUNavArea_Jump::UUNavArea_Jump(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
{
	AI_Path_Choice AI_Choice = Jump;
}





UMyPathFollowingComponent.h





#pragma once

#include "AI/Navigation/PathFollowingComponent.h"
#include "MyPathFollowingComponent.generated.h"

/**
 * 
 */
UCLASS()
class UMyPathFollowingComponent : public UPathFollowingComponent
{
	GENERATED_UCLASS_BODY()

	virtual void SetMoveSegment(uint32 SegmentStartIndex) OVERRIDE;
	
};



.cpp





#include "SideScrollerConcept.h"
#include "MyPathFollowingComponent.h"
#include "UNavArea_Jump.h"


UMyPathFollowingComponent::UMyPathFollowingComponent(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
{

}

void UMyPathFollowingComponent::SetMoveSegment(uint32 SegmentStartIndex)
{
	if (FNavMeshNodeFlags(Path->PathPoints[SegmentStartIndex].Flags).AreaFlags == UUNavArea_Jump::Jump)
	{
		GEngine->AddOnScreenDebugMessage(-1, 2.f, FColor::Blue, TEXT("I AM JUMPING!"));
	}
}





180fef79157ecfdadcfb7c7e0e50d6423960f6d4.jpeg

I also noticed the AreaFlags is a uint6

Currently the only way to have your AI use your custom pathfollowing component is to implement your own [FONT=Lucida Console]AIController and specify the override in its controller, like so:


AMyAIController::AMyAIController(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP.SetDefaultSubobjectClass<UMyPathFollowingComponent>(TEXT("PathFollowingComponent")))
{
}

Area flags are limited to 16 bits due to compatibility with Recast library that’s running our navmesh generation. One important thing to note about AreaFlags is that it’s a collection of flags not a single integer value, so in your code you should not be checking whether [FONT=Lucida Console]FNavMeshNodeFlags(Path->PathPoints[SegmentStartIndex].Flags).AreaFlags is equal to any specific value. Instead check if flags you’re interested in are set by doing a binary AND with your flag. In your case it would be something like this:



const uint16 AreaFlags = FNavMeshNodeFlags(Path->PathPoints[SegmentStartIndex].Flags).AreaFlags;
const uint16 JumpFlag = uint16(1 << UUNavArea_Jump::Jump);
if ((AreaFlags & JumpFlag) != 0)
{
    ...
}

One last thing, the image you attached indicates you’re setting up a smart link rather then a regular navigation link. Use a regular link, that’s guaranteed to work :slight_smile:

Alright! I got everything to work! HOWEVER, the AI keeps jumping all the time… how could I tell the AI to jump when it gets to that link? cause it will keep jumping even far away from the link :(.

MyPathFollowingComponent.cpp



#include "SideScrollerConcept.h"
#include "MyPathFollowingComponent.h"
#include "UNavArea_Jump.h"
#include "CorruptBot.h"


UMyPathFollowingComponent::UMyPathFollowingComponent(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
{
	
}

void UMyPathFollowingComponent::SetMoveSegment(uint32 SegmentStartIndex)
{
	const uint16 AreaFlags = FNavMeshNodeFlags(Path->PathPoints[SegmentStartIndex].Flags).AreaFlags;
	const uint16 JumpFlag = uint16(1 << UUNavArea_Jump::Jump);

	if (AreaFlags && JumpFlag != 0)
	{

		GEngine->AddOnScreenDebugMessage(-1, 2.f, FColor::Blue, TEXT("I AM JUMPING"));
		if (AreaFlags == JumpFlag)
		{
			for (FConstPawnIterator It = GetWorld()->GetPawnIterator(); It; ++It)
			{
				ACorruptBot* AIPawn = Cast<ACorruptBot>(*It);
				if (AIPawn)
				{
					AIPawn->LaunchCharacter(FVector(0, 0, 1000), false, false);
				}
			}
			GEngine->AddOnScreenDebugMessage(-1, 2.f, FColor::Blue, TEXT("FOR REAL"));
		}
	}
}


One important thing to remember, which I should have mentioned, is that by default [FONT=Lucida Console]AreaFlags == 1, which means your [FONT=Lucida Console]UUNavArea_Jump::Jump has to be [FONT=Lucida Console]> 1.

One additional bug you have in your code is that while checking if a flag is set you need to use binary AND, and you used a logic one. Should be: [FONT=Lucida Console]if ((AreaFlags & JumpFlag) != 0)

Similarly, somparison [FONT=Lucida Console](AreaFlags == JumpFlag) will always fail.

OOOHHH the binary does make a difference… I’m used to using the && logic operator by default sometimes :/. Now the problem is that the check if the AreaFlag equals the JumpFlag. :(. And I also made the jumpflag = 2.





#include "SideScrollerConcept.h"
#include "MyPathFollowingComponent.h"
#include "UNavArea_Jump.h"
#include "CorruptBot.h"


UMyPathFollowingComponent::UMyPathFollowingComponent(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
{
	
}

void UMyPathFollowingComponent::SetMoveSegment(uint32 SegmentStartIndex)
{
	const uint16 AreaFlags = FNavMeshNodeFlags(Path->PathPoints[SegmentStartIndex].Flags).AreaFlags;
	const uint16 JumpFlag = uint16(2 << UUNavArea_Jump::Jump);

	if ((AreaFlags & JumpFlag) != 0)
	{

		GEngine->AddOnScreenDebugMessage(-1, 2.f, FColor::Blue, TEXT("I AM JUMPING"));
		if (AreaFlags >> 2)  //////////////////////////////////////////////////// THIS IS WHERE THE PROBLEM IS
		{
			for (FConstPawnIterator It = GetWorld()->GetPawnIterator(); It; ++It)
			{
				ACorruptBot* AIPawn = Cast<ACorruptBot>(*It);
				if (AIPawn)
				{
					AIPawn->LaunchCharacter(FVector(0, 0, 1000), false, false);
				}
			}
			GEngine->AddOnScreenDebugMessage(-1, 2.f, FColor::Blue, TEXT("FOR REAL"));
		}
	}
}


You’re doing it wrong :slight_smile:

Instead of [FONT=Lucida Console]const uint16 JumpFlag = uint16(2 << UUNavArea_Jump::Jump); you should have:


enum AI_Path_Choice
{
	Jump = 1
};

which will result in [FONT=Lucida Console](1 << UUNavArea_Jump::Jump) == 2.

The reason [FONT=Lucida Console]if (AreaFlags >> 2) fails in your code is that in your case [FONT=Lucida Console](2 << UUNavArea_Jump::Jump) == 2 since [FONT=Lucida Console]UUNavArea_Jump::Jump == 0 and shifting this kind of value results in 0 :smiley:

That should fix it :slight_smile:

In general when debugging path point flags I’d suggest running the debugger and simply see what is the [FONT=Lucida Console]AreaFlags value on each path point. You’ll notice when any one of them has different flags then others :slight_smile:

And please read up on bit flags, it will save you a whole lot of grief if you understand what you’re actually doing:
http://www.cplusplus.com/forum/general/1590/

Thanks for this :)!!! I apologize for the lack of knowledge with binary and enum flags Dx… Just am a bit new to binary and enum flags :/. I also did a check after those corrections… when i make the AI go to the jump NavLink, the number check goes to 0 :confused:





#include "SideScrollerConcept.h"
#include "MyPathFollowingComponent.h"
#include "UNavArea_Jump.h"
#include "CorruptBot.h"


UMyPathFollowingComponent::UMyPathFollowingComponent(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
{
	
}

void UMyPathFollowingComponent::SetMoveSegment(uint32 SegmentStartIndex)
{
	const uint16 AreaFlags = FNavMeshNodeFlags(Path->PathPoints[SegmentStartIndex].Flags).AreaFlags;
	const uint16 JumpFlag = uint16(1 << UUNavArea_Jump::Jump);

	FString Check = FString::FromInt(AreaFlags);

	GEngine->AddOnScreenDebugMessage(-1, 2.f, FColor::Red, Check);

	if ((AreaFlags & JumpFlag) != 0)
	{
		GEngine->AddOnScreenDebugMessage(-1, 2.f, FColor::Blue, TEXT("I AM JUMPING"));
		if (AreaFlags >> 2)
		{
			for (FConstPawnIterator It = GetWorld()->GetPawnIterator(); It; ++It)
			{
				ACorruptBot* AIPawn = Cast<ACorruptBot>(*It);
				if (AIPawn)
				{
					AIPawn->LaunchCharacter(FVector(0, 0, 1000), false, false);
				}
			}
			GEngine->AddOnScreenDebugMessage(-1, 2.f, FColor::Blue, TEXT("FOR REAL"));
		}
		
	}
}


i also did the correction to my enum declaration and made Jump = 1. I don’t mean to have you guys hold my hand through this… just I want to learn this as much as I can :). Since UE3, I always went to the forums for help on unanswered questions and learned from them :). Baby steps are helping me learn.

@enlight_2014: Thanks for the links!!! makes a little more sense with the enum flags :slight_smile:

I did a check on both JumpFlag and AreaFlags. my results were JumpFlag produced 2. So the JumpFlag works fine :). ONLY Problem is AreaFlags ALWAYS goes to 0 when making the AI go to the NavLink :frowning:

MieszkoZ, I’m sorry to be barking up your tree :(, but what is preventing the AreaFlags from being 2?

It should be working with all above fixes. Make sure you use regular navigation link, not a smart link. Regardless, none of path points’ AreaFlags should be equal 0. Ever. 0 means “unwalkable” which means a path shouldn’t have been found in the first place.

You can use [FONT=Lucida Console]NavigationTestingActor to debug pathfinding in editor mode. Just drag & drop two on the map and mark one’s [FONT=Lucida Console]SearchStart to true. The resulting path will be draw with additional text on path points in “%d-%d” format, where first value is the index of path point, and the other is value of AreaFlags of segment starting. If you still have issues post a screenshot with this debug information on screen.

I checked out the Navigation Testing Actor, dragged and dropped two in, one with SearchStart checked(which is on the right of the image) and one with nothing checked.

4192bf94589cd08c294caf8595b767abb8efc0a5.jpeg

However, when I start the game, nothing happens :confused:

e9935421ee733ef919e1b1bccb8ab81818f74925.jpeg

no movement,no debug messages… :frowning: … I attached a particle on them just to see the locations of the actors… I also notice for some reason, there’s a red line from the Navigation Test Actors going towards the BP_Sky_Sphere??

Don’t start the game :slight_smile: The whole point of NavigationTestingActors is that they work in pure editor mode. Set two up and start moving them around and you’ll see a path drawn in real time. There should be a “green ball” drawn at the navmesh level for each actor - it represents actor’s location projected to navmesh. If it’s missing then there’s something horribly wrong.

OOHHHH!!! blaaaahh srry bout that… didnt kno. well, the results for all of the navmesh was all green for the Navigation Testing Actor… but there was no debug messages showing up? :/.

What do I do from here? If it all checks green… :confused:

[EDIT]

another thing I noticed is that this code here



		if (AreaFlags >> 2)
		{
			for (FConstPawnIterator It = GetWorld()->GetPawnIterator(); It; ++It)
			{
				ACorruptBot* AIPawn = Cast<ACorruptBot>(*It);
				if (AIPawn)
				{
					AIPawn->LaunchCharacter(FVector(0, 0, 1000), false, false);
				}
			}
			GEngine->AddOnScreenDebugMessage(-1, 2.f, FColor::Blue, TEXT("FOR REAL"));
		}


doesnt work, but goes back to the constant jumping if i move it to this




		if (AreaFlags << 2)       ////////////////////// this part here
		{
			for (FConstPawnIterator It = GetWorld()->GetPawnIterator(); It; ++It)
			{
				ACorruptBot* AIPawn = Cast<ACorruptBot>(*It);
				if (AIPawn)
				{
					AIPawn->LaunchCharacter(FVector(0, 0, 1000), false, false);
				}
			}
			GEngine->AddOnScreenDebugMessage(-1, 2.f, FColor::Blue, TEXT("FOR REAL"));
		}


is it going to constantly jump by default? or should i put a parameter to tell it if the character is close enough to jump?

For Further Visualization, I made a short video on this. the first part is the (AreaFlags << 2). the Second part is (AreaFlags >> 2)