What Is the Proper Method to Check if AI Can Reach a Location?

Hello again everyone,

After help from this forum helped me resolve my previous issue, which involved getting a reference to a character, ‘created’ (from an object pool) in another class, I now have this issue.

I wanted this (AI) character, to use as a ‘dummy’ to check if they would be able to reach their objective, when the Player is placing a wall obstacle (which could block their path to their objective).

I have had four* methods tried for this, these being (all using UNavigationSystemV1):

  • Using GetRandomReachablePointInRadius(), with the objective’s location passed in as the Origin, with a radius of 100 UU. This method would sometimes return true or false for an unknown reason
  • Using GetRandomPointInNavigableRadius() with the same parameters again. This method would always return true (matches up to this method’s description)
  • I then tried that of getting the bespoke AI controller of the ‘dummy’ enemy (AGruxAIController), and having the MoveToLocation() method called on that controller
  • After this, as a final attempt, the ProjectPointToNavigation() method was used, with the location of the objective, passed in as the point to project to the navigation system

All of these methods, allowed the Player to place walls, that would block off a path to the Enemy’s objective (video shows the third method detailed above, using the MoveToLocation() method to test this:

The code of this method, is just below:

bool AGridPlacementSystem::EnemyHasPathToObjective()
{
	bool bEnemyHasPathToObjective = false;
	
	if (EnemySpawnerReference && UtilityFunctionLibraryReference)
	{
		AActor* TestObstacle = PlaceObstacle(EGridSquareType::Gst_WallSquare, false, true);
		if (TestObstacle)
		{
			FVector TestEnemySpawnLocation = EnemySpawnerReference->GetActorLocation();
			TestEnemySpawnLocation.Z = UtilityFunctionLibraryReference->GetCharacterGroundLevel();
			ACharacter* TestEnemy = UtilityFunctionLibraryReference->SpawnCharacter(TestEnemySpawnLocation, true);
			if (TestEnemy)
			{
				// First method:

				//FNavLocation ResultLocation;
				
				// This method will always return true:
				//bEnemyHasPathToObjective = NavigationSystemReference->GetRandomPointInNavigableRadius(GoalTargetReference->GetActorLocation(),
				//	10.f, ResultLocation);

				// Second method:
				
				// Use of the method below, sometimes returns true or false, I am not quite sure why...
				//NavigationSystemReference->GetRandomReachablePointInRadius(GoalTargetReference->GetActorLocation(),
				//	10.f, ResultLocation);

				// Third method:
				
				//AGruxAIController* GruxControllerReference = Cast<AGruxAIController>(TestEnemy->GetController());
				//if (GruxControllerReference)
				//{
				//	const EPathFollowingRequestResult::Type MovementResult = GruxControllerReference->MoveToLocation(
				//		GruxControllerReference->GetTargetLocationKeyValue());
				//	switch (MovementResult)
				//	{
				//	case EPathFollowingRequestResult::AlreadyAtGoal:
				//	case EPathFollowingRequestResult::RequestSuccessful:
				//		bEnemyHasPathToObjective = true;
				//		break;
				//	case EPathFollowingRequestResult::Failed:
				//		bEnemyHasPathToObjective = false;
				//		break;
				//	default:
				//		break;
				//	}
				//}

				// Fourth method:
				const AActor* GoalTargetReference = UGameplayStatics::GetActorOfClass(GetWorld(), AGoalTarget::StaticClass());
				UNavigationSystemV1* NavigationSystemReference = UNavigationSystemV1::GetCurrent(GetWorld());
				if (GoalTargetReference && NavigationSystemReference)
				{
					FNavLocation OutLocation;
					bEnemyHasPathToObjective = NavigationSystemReference->ProjectPointToNavigation(GoalTargetReference->GetActorLocation(),
						OutLocation);
				}
				
				GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Orange, FString::Printf(
					TEXT("Enemy has path to objective: %hhd"), bEnemyHasPathToObjective));
				TestObstacle->Destroy();
				UtilityFunctionLibraryReference->ReturnObjectToPool(TestEnemy);		
			}
		}
	}

	return bEnemyHasPathToObjective;
}

Please let me know of a method to allow the AI to check if it can reach a location. I am not sure if I am overcomplicating things this time…

Please also let me know if you require any further details, to help me resolve this issue, thanks :slight_smile:.

EDIT: This is the latest version for path checking I am trying:

Okay, this is the logic I have for my new method of checking to see if a path is valid before placing the final obstacle:

bool AGridPlacementSystem::EnemyHasPathToObjective()
{
	bool bEnemyHasPathToObjective = false;

	if (!TestObstacleReference)
	{
		TestObstacleReference = PlaceObstacle(EGridSquareType::Gst_WallSquare, false);
	}
	
	if (EnemySpawnerReference && UtilityFunctionLibraryReference)
	{
		bEnemyHasPathToObjective = CheckGridPathToObjective();
		
		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Orange, FString::Printf(
			TEXT("Enemy has path to objective: %hhd"), bEnemyHasPathToObjective));
	}

	return bEnemyHasPathToObjective;
}

bool AGridPlacementSystem::CheckGridPathToObjective()
{
	bool bEnemyHasGridPathToObjective = false;
	int CurrentValidPathIndex = 0;

	while (CurrentValidPathIndex < BaseGridTileActors.Num())
	{
		TArray<AActor*> BasicGridTileAttachedActors = TArray<AActor*>();
		// If path traversal has got to the last node in the grid, there must be a path to
		// the objective:
		if (CurrentValidPathIndex == BaseGridTileActors.Num() - 1)
		{
			bEnemyHasGridPathToObjective = true;
			break;
		}
		// There is a path on the column to the left:
		if (BaseGridTileActors.IsValidIndex(CurrentValidPathIndex + 4))
		{
			BaseGridTileActors[CurrentValidPathIndex + 4]->GetAttachedActors(
				BasicGridTileAttachedActors);
			if (BasicGridTileAttachedActors.Num() <= 1)
			{
				CurrentValidPathIndex += 4;
			}
		}
		// There is not a path on the column to the left, so try the node below the current one:
		else if (BaseGridTileActors.IsValidIndex(CurrentValidPathIndex + 1))
		{
			BaseGridTileActors[CurrentValidPathIndex + 1]->GetAttachedActors(
			BasicGridTileAttachedActors);
			if (BasicGridTileAttachedActors.Num() <= 1)
			{
				++CurrentValidPathIndex;
			}
		}
		// There is not a path on the column to the left, or of the node below the
		// current one, so move back one node if possible:
		else if (BaseGridTileActors.IsValidIndex(CurrentValidPathIndex - 1))
		{
			BaseGridTileActors[CurrentValidPathIndex - 1]->GetAttachedActors(
			BasicGridTileAttachedActors);
			if (BasicGridTileAttachedActors.Num() <= 1)
			{
				--CurrentValidPathIndex;
			}
		}
		// There is not a path on the column to the left, or of the node below the
		// current one, or of the previous node, so move back a column to the right:
		else if (BaseGridTileActors.IsValidIndex(CurrentValidPathIndex - 4))
		{
			BaseGridTileActors[CurrentValidPathIndex - 4]->GetAttachedActors(
				BasicGridTileAttachedActors);
			if (BasicGridTileAttachedActors.Num() <= 1)
			{
				CurrentValidPathIndex -= 4;
			}
		}
		// There is no path to the objective:
		else
		{
			bEnemyHasGridPathToObjective = false;
			break;
		}
	}

	return bEnemyHasGridPathToObjective;
}

The grid is as shown here (with virtual tiles, placed starting at the top right hand corner and ending and the bottom left hand corner):

The red circles indicate the spawn points of the enemies.

This new method would still allow the Player to place walls that completely block the enemies’ path to the objective.

Please let me know what the faults in more logic could be/any further information you require to help me resolve this problem.

You would have to have other objects announce you, or if you have a terrain and mapped it if he gets to a specific sets of coordinates , it should announce you.

Problem with the AI in Unreal is that he is part of some unreal api functions, and you have to look deep where those functions get their functionality from within the engine. For starting out you should try to use the trace system, you could ignore some objects to the trace and make other objects react to the trace, single line trace, I don’t think that is a good idea, sphere trace maybe.

I made a manechine follow me around and as long as he saw me he would follow me, if I got behind a box his trace system could not see me and he stopped for example, you could use distance factors, if he is further than for example he could react in some manner until he gets to that distance.

You should make state machines with dynamic functions with bulion variables, and if he meets the bulion requierment it turns on and when it turns on it can trigger some function that announces you he has arrived there for example.

The simplest way is the bulion var, no matter what mechanism you choose, if true and condition is meet the var turns to true and triggers what ever you have for it.

1 Like

Ah, thanks for replying to my post .

Yes, it seems that one has to look through quite a bit of the engine code to be able to find such a method, that could provide what I’m looking for.

I think for now then, I will come up with my own system, and report back here. If anyone has any other insight/ideas for a solution to my problem though, feel free to post them :slightly_smiling_face:.

You should look into state machines with bulions, regular C++ not unreal state machines. And then hook up that to your AI code somehow after you figure out how, connect them so when the AI has reached destination bulion is triggered and function comes on, because bulion if bulion is true then function A else function B (b meaning carry on) Until function a comes into play. So the most simple state machine is two functions with a bulion and maybe 4 sets of conditions.

Thinking again you actually need two bulions, two functions and 4 sets of conditions, two conditions as preconditions to trigger the bulion first. So this condition will say if bulion is true then procede to the functions section. So it interogates the bulion to see if it’s true or false and if it’s true it can procede to the functions layout and there is another bulion that says if the other bulion is true then function A if not true function B , B is for to go on.
So it’s a double check on the true condition, you got to have one with the functions also to have an else option, a way of exiting sort of the state it is in.

1 Like

Thanks again for your thoughts , I am currently working on a way to track through the grid system I have in place already, to check if there is a path. I will report back soon enough, as stated.

Okay, this is the logic I have for my new method of checking to see if a path is valid before placing the final obstacle:

bool AGridPlacementSystem::EnemyHasPathToObjective()
{
	bool bEnemyHasPathToObjective = false;

	if (!TestObstacleReference)
	{
		TestObstacleReference = PlaceObstacle(EGridSquareType::Gst_WallSquare, false);
	}
	
	if (EnemySpawnerReference && UtilityFunctionLibraryReference)
	{
		bEnemyHasPathToObjective = CheckGridPathToObjective();
		
		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Orange, FString::Printf(
			TEXT("Enemy has path to objective: %hhd"), bEnemyHasPathToObjective));
	}

	return bEnemyHasPathToObjective;
}

bool AGridPlacementSystem::CheckGridPathToObjective()
{
	bool bEnemyHasGridPathToObjective = false;
	int CurrentValidPathIndex = 0;

	while (CurrentValidPathIndex < BaseGridTileActors.Num())
	{
		TArray<AActor*> BasicGridTileAttachedActors = TArray<AActor*>();
		// If path traversal has got to the last node in the grid, there must be a path to
		// the objective:
		if (CurrentValidPathIndex == BaseGridTileActors.Num() - 1)
		{
			bEnemyHasGridPathToObjective = true;
			break;
		}
		// There is a path on the column to the left:
		if (BaseGridTileActors.IsValidIndex(CurrentValidPathIndex + 4))
		{
			BaseGridTileActors[CurrentValidPathIndex + 4]->GetAttachedActors(
				BasicGridTileAttachedActors);
			if (BasicGridTileAttachedActors.Num() <= 1)
			{
				CurrentValidPathIndex += 4;
			}
		}
		// There is not a path on the column to the left, so try the node below the current one:
		else if (BaseGridTileActors.IsValidIndex(CurrentValidPathIndex + 1))
		{
			BaseGridTileActors[CurrentValidPathIndex + 1]->GetAttachedActors(
			BasicGridTileAttachedActors);
			if (BasicGridTileAttachedActors.Num() <= 1)
			{
				++CurrentValidPathIndex;
			}
		}
		// There is not a path on the column to the left, or of the node below the
		// current one, so move back one node if possible:
		else if (BaseGridTileActors.IsValidIndex(CurrentValidPathIndex - 1))
		{
			BaseGridTileActors[CurrentValidPathIndex - 1]->GetAttachedActors(
			BasicGridTileAttachedActors);
			if (BasicGridTileAttachedActors.Num() <= 1)
			{
				--CurrentValidPathIndex;
			}
		}
		// There is not a path on the column to the left, or of the node below the
		// current one, or of the previous node, so move back a column to the right:
		else if (BaseGridTileActors.IsValidIndex(CurrentValidPathIndex - 4))
		{
			BaseGridTileActors[CurrentValidPathIndex - 4]->GetAttachedActors(
				BasicGridTileAttachedActors);
			if (BasicGridTileAttachedActors.Num() <= 1)
			{
				CurrentValidPathIndex -= 4;
			}
		}
		// There is no path to the objective:
		else
		{
			bEnemyHasGridPathToObjective = false;
			break;
		}
	}

	return bEnemyHasGridPathToObjective;
}

The grid is as shown here (with virtual tiles, placed starting at the top right hand corner and ending and the bottom left hand corner):

The red circles indicate the spawn points of the enemies.

This new method would still allow the Player to place walls that completely block the enemies’ path to the objective.

Please let me know what the faults in more logic could be/any further information you require to help me resolve this problem.

You are calling lots of functions from other places.

bool AGridPlacementSystem::CheckGridPathToObjective()
bool bEnemyHasPathToObjective = false;

You put this o false, I always set the bool to false first in the header and just set true conditions if conditions are being met.
Where are you calling this from ?

bool AGridPlacementSystem::EnemyHasPathToObjective()

Is it a local function you made, or is part of the unreal api function regarding AI ? You usually call on Api functions inside the body of the function, so I take it it’s a fuction you made and declared in the header ?
Is this part a tutorial you found ?

Also

if (!TestObstacleReference)
	{

I can’t see where this is coming from, what is this value type and where did you bring it in from. To begin to understand your code I would need more insight.

Many values that need explaining like TestObstacleRefrence that you want to equal and load into.

PlaceObstacle(EGridSquareType::Gst_WallSquare, false);

What do you really want to do. You should explain your code since I can’t see the api where you are pulling things, I can’t see the types. If Iyou solve your problem I may use this too because I want to develope AI in the future.

You should start from begining with.

bool AGridPlacementSystem::EnemyHasPathToObjective()

So this is a bool function you made with an Actor class.
The if’s with the values you have in them, I can’t see what they are.
You are loading data into ```
TestObstacleReference

That is the type of ?
Where do you get this call from
“EGridSquareType::Gst_WallSquare”

What do you want to do, just AI get to a location and you say, “he arrived” or is “he is blocked by a wall” You are getting errors ?

There is no guaranty you will get it working and I would have to spend lots of time on your stuff, not that it would not intrest me, why I roam the forums to understand things and add to my capability. Try to reduce the if statements set bool in the header to false and then just try to just turn the variable on in your cpp. I start with this mind set, just to validate things in cpp, at the end when you end a cycle in the state it is, you can set it to false back so it exists the state. You don’t need to turn the bool off if it’s set to off in the header, and it applies for the rest of your cpp globally in this case, so set the bool there.

Use an else condition to carry on, if it’s turned on you can turn it off in the else statement. I always use else conditions to turn things off, just the way I do things and the if remains strictly for validation with a local function call maybe that I have.
FunctionName();

1 Like

This is a negative operator ! I was confused by it, it can be confusing at times, because it can negate or validate, it can mean if it’s not false, or it can be negative, it can actually be both ways depending what you have in your statement. If(!it does not match)

You could try:
if (YourObject.Succeeded()) and there for it is for validating things only. So the if statement only becomes a validator in this case.

Try not to use the negative operator “!” for validation or negation.

if (condition) {
//code to execute if condition is true
}

! is the not operator. !x is true if x is false and vice-versa.

This operator can become confusing and it can negate or validate depending how you have things in the condition. It can be “not valid” if not, or it can be “it is not false”

1 Like

Okay, to clarify then, the Enumeration is declared in GridPlacementSystem.h*, as:

/** To identify the different types of grid squares. */
UENUM(BlueprintType)
enum class EGridSquareType : uint8
{
	Gst_BasicSquare,
	Gst_SampleBasicSquare,
	Gst_WallSquare,
	Gst_SampleWallSquare,
	Gst_MachineGunTowerSquare,
	Gst_SampleMachineGunTowerSquare
};

Where each type of grid square also has a sample version of itself (the basic grid squares are placed automatically and the MachineGunTowerSquare type of obstacle, has not been implemented yet. So only the basic walls can be placed).

TestObstacleReference is declared in the AGridPlacementSystem class (plus other pointers used to test AI-pathing):

class THIRDPERSONTD_API AGridPlacementSystem : public AActor
{
	GENERATED_BODY()
        ...
protected:
        ...
/** For the grid square that the obstacle being placed, is to be attached to. */
	UPROPERTY()
	AGridSquare* CurrentGridSquareReference = nullptr;

	/** For a test obstacle (to check for AI-pathing). */
	UPROPERTY()
	AActor* TestObstacleReference = nullptr;

/** To refer to the sample obstacle that's to be placed. */
	UPROPERTY()
	AActor* SampleObstacleReference = nullptr;
       ...

For when the Player wants to place an obstacle, ShowSampleObstacle() is called (when the ‘T’ keyboard key is pressed). Upon releasing this key FinishPlacingObstacle() is called (this is where the EnemyHasPathToObjective() method is called).

These methods are as declared here:

void AGridPlacementSystem::ShowSampleObstacle(const EGridSquareType TargetSquareType)
{
	// Make sure this sample's root component is movable too:
	if (!SampleObstacleReference)
	{
		SampleObstacleReference = PlaceObstacle(TargetSquareType, true);
		if (SampleObstacleReference)
		{
			SampleObstacleReference->GetRootComponent()->SetMobility(EComponentMobility::Movable);
		}		
	}
	else if (SampleObstacleReference->IsHidden())
	{
		FHelperClass::ActivateActor(SampleObstacleReference);
	}

	if (SampleObstacleReference)
	{
		GetWorldTimerManager().SetTimer(SampleObstacleTimerHandle, this, &AGridPlacementSystem::UpdateSampleObstaclePosition, 0.001f, true);
	}	
}

void AGridPlacementSystem::FinishPlacingObstacle(const EGridSquareType TargetSquareType)
{
	if (SampleObstacleReference)
	{
		FHelperClass::DeactivateActor(SampleObstacleReference);
		GetWorldTimerManager().ClearTimer(SampleObstacleTimerHandle);
	}

	// The enemy has to be able to get to their objective:
	if (EnemyHasPathToObjective())
	{
		PlaceObstacle(TargetSquareType, false);
		if (TestObstacleReference)
		{
			TestObstacleReference->Destroy();
		}
	}
}

I will have a video put together, showing what happens with this method of AI-path tracing (it also fails though).

Thanks for your responses so far though :slight_smile:.

EDIT: This is my own code, that I have put together using various helpful sources found online for certain parts of it. In reference to this, the FHelperClass is implemented as such:

#include "HelperClass.h"

void FHelperClass::ActivateActor(AActor* TargetActor)
{
	// Activate this TargetActor:
	TargetActor->SetActorHiddenInGame(false);
	TargetActor->SetActorEnableCollision(true);
	TargetActor->SetActorTickEnabled(true);

	// Call on children:
	TArray<AActor*> ActorChildren;
	TargetActor->GetAllChildActors(ActorChildren);			// Actual ActorChildren
	TargetActor->GetAttachedActors(ActorChildren, false);	// Attached Actors. Don't reset array
	for (auto Child : ActorChildren)
	{
		ActivateActor(Child);
	}
}

void FHelperClass::DeactivateActor(AActor* TargetActor)
{
	// Deactivate this TargetActor:
	TargetActor->SetActorHiddenInGame(true);
	TargetActor->SetActorEnableCollision(false);
	TargetActor->SetActorTickEnabled(false);

	// Call on ActorChildren:
	TArray<AActor*> ActorChildren;
	TargetActor->GetAllChildActors(ActorChildren);			// Actual ActorChildren
	TargetActor->GetAttachedActors(ActorChildren, false);	// Attached TargetActors. Don't reset array
	for (const auto Child : ActorChildren)
	{
		DeactivateActor(Child);
	}
}

So what happens when you run it, it does not work in what way ?
Did you get this tutorial somehwere or is it made by you ?

Ohh sorry
EDIT: This is my own code, that I have put together using various helpful sources found online for certain parts of it. In reference to this, the FHelperClass is implemented as such:

Where is the problem with your code ?

1 Like

No worries :slightly_smiling_face:. There is also a bit more…

EnemySpawnerReference refers to an Actor class in the level that spawns enemies (this class works as intended).

The UtilityFunctionLibraryReference refers to an actor class instantiated in the level, which offers more utility then the FHelperClass class. It is declared as such:

/**
 * For handling gameplay utility.
 */
UCLASS()
class THIRDPERSONTD_API AUtilityFunctionLibrary : public AActor
{
	GENERATED_BODY()

public:

	// Methods:

	/**
	 * 'Spawn' an enemy-character from the enemy object pool.
	 * @param SpawnLocation: the location to spawn the enemy character at.
	 * @param bIsHidden: for whether this character should be hidden in
	 * the game world (i.e. if being used to test for pathing).
	*/
	UFUNCTION(BlueprintCallable, Category = Spawning)
	ACharacter* SpawnCharacter(const FVector SpawnLocation, const bool bIsHidden);

	/**
	 * Have an enemy-character returned to their respective object pool.
	 * @param TargetObject: the object to be returned to the pool.
	*/
	UFUNCTION(BlueprintCallable, Category = ObjectPool)
	void ReturnObjectToPool(AActor* TargetObject);

	// Get Methods:

	UFUNCTION(BlueprintCallable, Category = "Get Methods")
	float GetCharacterGroundLevel() const;

protected:

	// Methods:

	/**
	 * Initialise this function library actor for play.
	*/
	virtual void BeginPlay() override;

	// Properties:

	/** For reference to the object pool containing enemies. */
	UPROPERTY()
	AObjectPool* EnemyObjectPool;

	/** 
	* The ground level for characters spawned via this class.
	*/
	UPROPERTY(Editanywhere, BlueprintReadWrite, Category = Spawning)
	float CharacterGroundLevel = 225.003616f;
};

(The AObjectPool* class contains a pool of enemy objects for now, that also works as intended).

This class is implemented as such:

#include "ThirdPersonTD/Public/UtilityFunctionLibrary.h"
#include "GameFramework/Character.h"
#include "Kismet/GameplayStatics.h"
#include "ThirdPersonTD/ObjectPool.h"

void AUtilityFunctionLibrary::BeginPlay()
{
	Super::BeginPlay();

	EnemyObjectPool = Cast<AObjectPool>(UGameplayStatics::GetActorOfClass(AActor::GetWorld(),
		AObjectPool::StaticClass()));
}

ACharacter* AUtilityFunctionLibrary::SpawnCharacter(const FVector SpawnLocation, const bool bIsHidden)
{
	// Just an enemy character for now:
	ACharacter* SpawnedEnemyReference = nullptr;
	
	if (EnemyObjectPool)
	{
		SpawnedEnemyReference = Cast<ACharacter>(EnemyObjectPool->GetPoolObject());
		if (SpawnedEnemyReference)
		{
			SpawnedEnemyReference->SetActorTransform(FTransform(SpawnLocation));
			SpawnedEnemyReference->SetActorHiddenInGame(bIsHidden);
		}
	}

	return SpawnedEnemyReference;
}

void AUtilityFunctionLibrary::ReturnObjectToPool(AActor* TargetObject)
{
	if (EnemyObjectPool)
	{
		EnemyObjectPool->ReturnPoolObject(TargetObject);
	}
}

float AUtilityFunctionLibrary::GetCharacterGroundLevel() const
{
	return CharacterGroundLevel;
}

Hopefully, this answers your questions :slightly_smiling_face:.

Ah, to clarify where the problem is with my code, it is in the AGridPlacementSystem::CheckGridPathToObjective() method.

(For reference, this is implemented as shown below):

bool AGridPlacementSystem::CheckGridPathToObjective()
{
	bool bEnemyHasGridPathToObjective = false;
	int CurrentValidPathIndex = 0;

	while (CurrentValidPathIndex < BaseGridTileActors.Num())
	{
		TArray<AActor*> BasicGridTileAttachedActors = TArray<AActor*>();
		// If path traversal has got to the last node in the grid, there must be a path to
		// the objective:
		if (CurrentValidPathIndex == BaseGridTileActors.Num() - 1)
		{
			bEnemyHasGridPathToObjective = true;
			break;
		}
		// There is a path on the column to the left:
		if (BaseGridTileActors.IsValidIndex(CurrentValidPathIndex + 4))
		{
			BaseGridTileActors[CurrentValidPathIndex + 4]->GetAttachedActors(
				BasicGridTileAttachedActors);
			if (BasicGridTileAttachedActors.Num() <= 1)
			{
				CurrentValidPathIndex += 4;
			}
		}
		// There is not a path on the column to the left, so try the node below the current one:
		else if (BaseGridTileActors.IsValidIndex(CurrentValidPathIndex + 1))
		{
			BaseGridTileActors[CurrentValidPathIndex + 1]->GetAttachedActors(
			BasicGridTileAttachedActors);
			if (BasicGridTileAttachedActors.Num() <= 1)
			{
				++CurrentValidPathIndex;
			}
		}
		// There is not a path on the column to the left, or of the node below the
		// current one, so move back one node if possible:
		else if (BaseGridTileActors.IsValidIndex(CurrentValidPathIndex - 1))
		{
			BaseGridTileActors[CurrentValidPathIndex - 1]->GetAttachedActors(
			BasicGridTileAttachedActors);
			if (BasicGridTileAttachedActors.Num() <= 1)
			{
				--CurrentValidPathIndex;
			}
		}
		// There is not a path on the column to the left, or of the node below the
		// current one, or of the previous node, so move back a column to the right:
		else if (BaseGridTileActors.IsValidIndex(CurrentValidPathIndex - 4))
		{
			BaseGridTileActors[CurrentValidPathIndex - 4]->GetAttachedActors(
				BasicGridTileAttachedActors);
			if (BasicGridTileAttachedActors.Num() <= 1)
			{
				CurrentValidPathIndex -= 4;
			}
		}
		// There is no path to the objective:
		else
		{
			bEnemyHasGridPathToObjective = false;
			break;
		}
	}

	return bEnemyHasGridPathToObjective;
}

To clarify further, bEnemyHasGridPathToObjective is the return value, declared locally in this method (as it should only be determined that the enemy has a path to their objective, when the Player is trying to place an obstacle at a certain location).

This is where the problem lies, as the Player can still block off enemy paths to their objective with walls, as shown in this video:

Yes but what is it not working exactly ? Where is the problem, you gave all this code but did not say what is working and what is not working.

I understand you are trying to load from the EgridSquareType a list of objects into TestObstacle and TestObstacleReference is also external to your cpp.

This is just the begining of your code and it is not going to get solved that fast, not today anyway. I actually want to create my own AI system so why I responded here, there is a lot of digging to be done on your code to try to understand everyting.

Where is the problem,

1 Like

The problem has been highlighted in the post just above :slightly_smiling_face:.

They get stuck and forget about you, okay. This is what I see.

You need another set of conditioning then and do you would have to pull them out of the state they are in and get the player location directly turn what ever variable you have off and get them into another state. You want them to follow you after you jump the wall ?

o clarify further, bEnemyHasGridPathToObjective is the return value, declared locally in this method (as it should only be determined that the enemy has a path to their objective, when the Player is trying to place an obstacle at a certain location).

Forget the nasa explenation, I don’t understand what you said here fully.
You can break the pathfinding system and make an additional one if they get stuck based on proximity, you get the player location manually.

The pathfinding system may not work with instant stuff added by you.
Player location
FVector Youractorlocation = GetActorLocation();
FVector Character = GetWorld()->GetFirstPlayerController()->GetPawn()->GetActorLocation();

This is in your enemy actor and it can turn on based on a trace system or proximity. So you get the actor location, then you get the actor forward vector.

Then you do something like this.
if (FVector::Dist(YourActorLocation Character) < 1090.0f) or what ever proximity number you have.

You have to break the pathfinding system and resort to an independent sniplet so they switch to another state with actor location, and actor rotation where they rotate on a vector.

1 Break the pathfinding system.
2 Switch somehow to the other sniplet you have, this is a simple if else.
3 Get the player location with enemy actors in their cpp.
4 Get the forward vector towards your player in the enemy actor.

I can’t give you all the code I worked a lot on this.
You have to reset things and when there are no more obstacles to switch to basic forward vectoring that rotates on an frotator.

So we get this straight, to rotate manually the enemy actors you have to transform vectors from location to rotation so they turn towards you.

This is all in the enemy actor cpp.
1 Get player location
2 Get the location of your actors in your enemy actors.
3 Transform the location to rotation so they rotate to you and face you, Character - ActorLocation "-"operator means between and use this with Rotation() function that transforms location to rotation so it’s your location and their rotation.

From here on it’s conditioning based on proximity or trace system, at the end you can use the delta time function with " SetActorLocation(Location);
" on delta time, This will move them towards you.

So the idea is you have to break the state they are in “Stuck” and use another system that is more rudimentary once you are in the opening with them.

There was this tutorial I started this, I will give it to you if I find it, it’s the exact same steps but much more simplified.

Here it is I found it.

C++
Get locations, rotate vector, use deltatime.

Maybe you can do more like use this to move them around a bit and then restart the pathfinding system.

So it’s based on player location, actor vector to rotation towards player location, some speed variables with delta time. You can move them around a bit towards you then maybe switch back to pathfinding mode after they move towards you a little bit.

You have to “unstuck them”
This tutorial has states.

1 Like

I will have to take a look at that video, thanks for that :slightly_smiling_face:.

If it’s of use to you, post back here, I want to know if you can switch between the system you have and this one. Move them towards you then activate back what you did, change back to that state.

1 Like

I can see how she has had the pet programmed to follow her (instead of using AI pathfinding via a navmesh), but I am not sure how this will help with my problem of not allowing placement of obstacles, if that would completely block the enemies’ path to their objective.

Please reiterate what you’re trying to get at, by providing a link to this video, in relation to how it could help solve my problem.

Thanks for continuing this thread though, when no one else is…