ASBaseCharacter* ASZombieCharacter::GetClosestPlayer()
{
//Set up local Arrays
TArray<ASBaseCharacter*> AlivePlayerArray;
TArray<float> DistanceArray;
TArray<ASBaseCharacter*> PlayerArray;
//Get all PlayerControllers in the multiplayer gameworld
for (TActorIterator<APlayerController> ActorItr(GetWorld()); ActorItr; ++ActorItr)
{
if (!ActorItr) return;
//For each of the found player characters get the ASBaseCharacter
ASBaseCharacter* Player = Cast<ASBaseCharacter>(ActorItr->GetPawn());
//add the ASBaseCharacter to total PlayerArray
PlayerArray.Add(Player);
}
//PlayerArray count > 0
if (PlayerArray.Num() > 0)
{
//for all Players in PlayerArray
for (ASBaseCharacter* Player : PlayerArray)
{
//make sure the Player is not dead (is Alive is set in SBaseCharacter
if (Player->IsAlive())
{
//get the distance (float) from the AI to the Player Pawn position
FVector difference = Player->GetActorLocation() - GetActorLocation();
float distance = difference.Size();
//Add the distance to the distance array
DistanceArray.Add(distance);
//Add the player to the Alive player array
AlivePlayerArray.Add(Player);
}
}
}
//distanceArray count > 0
if (DistanceArray.Num() > 0)
{
//find the lowest float number in the distance array and get its integer value
int32 MinIndex;
const float MinDistance = FMath::Min<float>(DistanceArray, &MinIndex);
//use integer value to set the Closest player in the AlivePlayerArray
ASBaseCharacter* ClosestPlayer = AlivePlayerArray[MinIndex];
return ClosestPlayer;
}
}
This will compile but it crashes the engine on play.
Now the issues I have with above, other than crashing.
1)I would rather work with PlayerPawn than ASBaseCharacter but I have yet to figure out how to search for PlayerPawn in C++.
2)TActorIterator doesn’t work like Get all actors of class, which is a bummer (or maybe it does and I am not understanding Iteration in C++)
3)I am not sure if I am using Rama’s FMath::Min array stuff correctly.
I suggest that you don’t use Rama’s functions. They may be great, but you stated you want to learn C++ in UE4, so just think the boilerplate out yourself.
Second suggestion. You don’t need to fill arrays and do this in separate loops. In your initial actor loop test the distance and just keep the reference to the closest (get distance, compare if less than current distance, if so set reference. On the first one you need to do something different, either have a flag or specially initial value such as -1 for the distance that you compare against so the first actor is always set as the shortest to start), no need to fill an array then reloop that.
Reason for crash. Most likely is an the index is overflowing your Array, that’s not a safe way to access an array. You should initialize the int32 to 0, and then make sure it is less than the length of the array minus 1 before you try to get something out of it, otherwise, that will crash. But, there’s no way to tell for sure without debugging it. Set a breakpoint in your code and run it in debug then step through each line and see where it crashes.
ASBaseCharacter* ASZombieCharacter::GetClosestPlayer()
{
float ClosestDistance = 10000.0f;
ASBaseCharacter* ClosestPlayer;
//Get all PlayerControllers in the multiplayer gameworld
for (TActorIterator<APlayerController> ActorItr(GetWorld()); ActorItr; ++ActorItr)
{
if (!ActorItr) return; //will this line break out of function if no PlayerController found?
//For each of the found player characters get the ASBaseCharacter
ASBaseCharacter* Player = Cast<ASBaseCharacter>(ActorItr->GetPawn());
//get the distance (float) from the AI to the Player Pawn position
FVector difference = Player->GetActorLocation() - GetActorLocation();
float distance = difference.Size();
if (distance < ClosestDistance)
{
ClosestDistance = distance;
ClosestPlayer = Player;
}
}
return ClosestPlayer;
}
That’s not going to work. You are Iterating on APlayerController and then trying to cast it to a ASBaseCharacter. Unless you have extending PLayerController with your character class name which would be really confusing. Iterate on the Characters, or Get the ControlledPawn from the Controller and then cast that result.
Yes on the loop simplification, that’s what I meant. You can do it all in one shot. Sugestion though is initialize the pointer to 0 at the start and make sure you test the result because it can be NULL.
First of all in a scenario when there are no PlayerControllers (yet), the loop body will never run. This could be an issue since you return ClosestPlayer which never had any value assigned explicitly. So it could contain garbage and upon trying to access anything through the pointer lead to a crash. Simply initialize it to NULL or nullptr and you should be safe (as long as you check whether your pointers are valid).
Something that shouldn’t really harm but is also not really useful in any way is the additional “if (!ActorItr) return;”. You’re already checking whether your iterator is valid in the loop condition (ActorItr;) and if it isn’t then you didn’t enter the loop body in the first place. And to answer your question in the comment in that line: Yes return will break out of the function and return the result (which you haven’t specified at this point) to the point where GetClosestPlayer has been called from, but you will not get to this point at all if there is no PlayerController found (see above).
EDIT (in response to mikepurvis):
The PlayerController isn’t what’s being casted here. It’s the PlayerController’s Pawn: ActorItr->GetPawn().
And something I forgot to mention: You should be able to simply replace TActorIterator<APlayerController> with TActorIterator<APawn> and this should allow you to iterate over all Pawns instead of all PlayerControllers.
If I use APawn it will also find the AI but if I use a Tag for player I should be able to make it work that way.
So it will
Find all Pawns -> Exclude Pawns without the Tag Player (using if statement) and then do everything needed.
My question revolves around performance. Because I need to constantly update a zombies target I am running this on tick. If I am searching for all pawns in the world every frame (which could be up to 100 or more) wouldn’t that be pretty slow or at the very least destroy performance?
I’ve been thinking about this issues and wondering if I should limit the area the Zombie can search (on tick) or use sensing component to only check distance while multiple characters are sensed (use onHearNoise, OnSeePawn).
edit: Just to let you know I made the changes yall pointed out and it works awesome. Thanks for the help!
Instead of APawn, search on the base class for your PlayerCharacter whatever that is. That way you only look at those. It looked like it was ASBaseCharacter, but if the AI use that as well get the sub class that only the Player Characters extend from and not the AI’s.
I mentioned to initialize your return value pointer to 0, but UnrealEverything said it more modern, set it to nullptr. 0 is the old C way. That way, if you don’t find it anything to set it to in the loop you can safely return it as a nullptr, that you can test against null. If you don’t initialize it and use it, you will crash.