The way we implement Tank AI Controller is not fair for the player
the AI always knows our location . So it is possible for the AI to fire from behind a mountain (without player being visible ) , as it knows our location it can calculate proper angle for the projectile
you may argue that the player can also follow the same trajectory in reverse but that is not possible as the player calculates the turret rotation and barrel elevation through ray-casting , since we can’t se the AI tank we can’t ray-cast to it for proper turret rotation and barrel elevation
so this gives and advantage to the AI
thinking about this the simplest solution that I could come up for this was to make the AI also ray cast
if the hit result from the AI ray cast is the player then only fire ( assuming firing state = locked )
to ray cast from AI we need a start and end location
end location is the player location and for the start location I created an empty scene component and placed it inside the turret whose location can be the start location ( Barrel location can also be used but this is more customizable)
tag is added to the component so that we can easily get this component in c++ by using
GetComponentsByTag();
like this
// Checks whether the player is in sight or not
bool ATankAIController::PlayerInSight()
{
auto AIRayCast = Cast<USceneComponent>((GetPawn()->GetComponentsByTag(USceneComponent::StaticClass(), FName("AI")))[0]);
auto StartLocation = AIRayCast->GetComponentLocation();
auto EndLocation = GetWorld()->GetFirstPlayerController()->GetPawn()->GetActorLocation();
FHitResult OutHitResult;
if (GetWorld()->LineTraceSingleByChannel(
OutHitResult,
StartLocation,
EndLocation,
ECollisionChannel::ECC_Visibility
))
{
if (OutHitResult.GetActor() == Cast<AActor>(GetWorld()->GetFirstPlayerController()->GetPawn()))
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
and then we ray-cast and check the actor hit
depending on this we can also modify MoveToActor() as if the player is not visible we can move with AcceptanceRadius = 0 and when the player becomes visible we can use the default Acceptance radius (in my case 7500)
BUT SUCH DYNAMIC ACCEPTANCE RADIUS DOESN’T WORK THE WAY I IMPLEMENTED IT
void ATankAIController::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// This In Tick As AI or Player Tank may die
auto PlayerTank = (GetWorld()->GetFirstPlayerController()->GetPawn());
auto ControlledTank = GetPawn();
auto AimingComponent = ControlledTank->FindComponentByClass<UTankAimingComponent>();
/// DOUBLE CHECK ensure() MACRO
if (!ensure(PlayerTank && ControlledTank && AimingComponent)) { return; }
if (PlayerInSight())
{
/// Movement Component Dependency
MoveToActor(
PlayerTank,
AcceptanceRadius
);
/// Always Aim at the Player
AimingComponent->AimAt(PlayerTank->GetActorLocation());
/// Fire at the Player if locked
if ((AimingComponent->GetFiringStatus() == EFiringStatus::Locked))
{
AimingComponent->Fire();
}
}
else
{
MoveToActor(
PlayerTank,
0.0f
);
}
}
this is definitely not the best method of getting such a result but it is the simplest for me
any suggestions are welcomed