Some improvements to the walkers' AI

This commit is contained in:
nsubiron 2017-06-15 13:07:17 +01:00
parent 02cb709afa
commit 0773c6694f
4 changed files with 235 additions and 71 deletions

View File

@ -11,25 +11,142 @@
#include "WheeledVehicleMovementComponent.h"
#ifdef CARLA_AI_WALKERS_EXTRA_LOG
# define LOG_AI_WALKER(Verbosity, Text) UE_LOG(LogCarla, Verbosity, Text, *GetPawn()->GetName());
# include <DrawDebugHelpers.h>
# define LOG_AI_WALKER(Verbosity, Text) UE_LOG(LogCarla, Verbosity, TEXT("Walker %s " Text), *GetPawn()->GetName());
# define EXTRA_LOG_ONLY(predicate) predicate
#else
# define LOG_AI_WALKER(Verbosity, Text)
# define EXTRA_LOG_ONLY(predicate)
#endif // CARLA_AI_WALKERS_EXTRA_LOG
static constexpr float UPDATE_TIME_IN_SECONDS = 10.0f;
static constexpr float PREVISION_TIME_IN_SECONDS = 5.0f;
static constexpr float WALKER_SIGHT_RADIUS = 500.0f;
static constexpr float WALKER_PERIPHERAL_VISION_ANGLE_IN_DEGREES = 90.0f;
static constexpr float VEHICLE_SAFETY_RADIUS = 400.0f;
// =============================================================================
// -- PawnPath -----------------------------------------------------------------
// =============================================================================
class PawnPath {
private:
static FVector GetLocation(const AActor &Actor)
{
const FVector &Location = Actor.GetActorLocation();
return {Location.X, Location.Y, 0.0f};
}
static FVector GetForwardVector(const AActor &Actor)
{
return Actor.GetTransform().GetRotation().GetForwardVector();
}
static float GetForwardSpeed(const AWheeledVehicle &Vehicle)
{
const auto *MovementComponent = Vehicle.GetVehicleMovementComponent();
check(nullptr != MovementComponent);
return MovementComponent->GetForwardSpeed();
}
#ifdef CARLA_AI_WALKERS_EXTRA_LOG
static FVector GetPointForDrawing(const FVector &Location)
{
return {Location.X, Location.Y, 50.0f};
}
#endif // CARLA_AI_WALKERS_EXTRA_LOG
/// Helper constructor for vehicles.
explicit PawnPath(const FVector &Location, const FVector &ForwardDirection, const float Speed) :
Start(Location - ForwardDirection * VEHICLE_SAFETY_RADIUS),
End(Location + ForwardDirection * (VEHICLE_SAFETY_RADIUS + Speed * PREVISION_TIME_IN_SECONDS)) {}
private:
explicit PawnPath(const APawn &Walker) :
Start(GetLocation(Walker)),
End(GetLocation(Walker) + GetForwardVector(Walker) * WALKER_SIGHT_RADIUS) {}
explicit PawnPath(const AWheeledVehicle &Vehicle) :
PawnPath(GetLocation(Vehicle), GetForwardVector(Vehicle), GetForwardSpeed(Vehicle)) {}
static bool Intersect(const PawnPath &Lhs, const PawnPath &Rhs, UWorld* EXTRA_LOG_ONLY(World))
{
EXTRA_LOG_ONLY(Lhs.DrawDebugArrow(World));
EXTRA_LOG_ONLY(Rhs.DrawDebugArrow(World));
FVector IntersectionPoint;
const bool bIntersect = FMath::SegmentIntersection2D(
Lhs.Start, Lhs.End,
Rhs.Start, Rhs.End,
IntersectionPoint);
#ifdef CARLA_AI_WALKERS_EXTRA_LOG
if (bIntersect) {
DrawDebugPoint(World, GetPointForDrawing(IntersectionPoint), 10.0f, FColor::Red, false, 2.0f);
}
#endif // CARLA_AI_WALKERS_EXTRA_LOG
return bIntersect;
}
#ifdef CARLA_AI_WALKERS_EXTRA_LOG
void DrawDebugArrow(UWorld *World) const
{
DrawDebugDirectionalArrow(World, GetPointForDrawing(Start), GetPointForDrawing(End), 60.0f, FColor::Red, false, 1.0f);
}
#endif // CARLA_AI_WALKERS_EXTRA_LOG
public:
/// Check if the paths of @a Walker and @a Vehicle intersect.
///
/// It checks if within the update time the vehicle will cross the straight
/// line of @a Walker sight radius on its forward direction.
static bool Intersect(const APawn &Walker, const AWheeledVehicle &Vehicle)
{
return Intersect(PawnPath(Walker), PawnPath(Vehicle), Walker.GetWorld());
}
private:
FVector Start;
FVector End;
};
// =============================================================================
// -- Other static functions ---------------------------------------------------
// =============================================================================
static bool IntersectsWithVehicle(const APawn &Self, const TArray<AActor *> &Actors)
{
for (auto *Actor : Actors) {
const auto *Vehicle = Cast<AWheeledVehicle>(Actor);
if ((Vehicle != nullptr) && PawnPath::Intersect(Self, *Vehicle)) {
return true;
}
}
return false;
}
// =============================================================================
// -- AWalkerAIController ------------------------------------------------------
// =============================================================================
AWalkerAIController::AWalkerAIController(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer.SetDefaultSubobjectClass<UCrowdFollowingComponent>(TEXT("PathFollowingComponent")))
{
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.TickInterval = 5.0f; // seconds.
PrimaryActorTick.TickInterval = UPDATE_TIME_IN_SECONDS;
auto Perception = CreateDefaultSubobject<UAIPerceptionComponent>(TEXT("AIPerception Component"));
check(Perception != nullptr);
SetPerceptionComponent(*Perception);
SightConfiguration = CreateDefaultSubobject<UAISenseConfig_Sight>(TEXT("SightConfiguration"));
SightConfiguration->SightRadius = 800.0f;
SightConfiguration->LoseSightRadius = (SightConfiguration->SightRadius + 100.0f);
SightConfiguration->PeripheralVisionAngleDegrees = 90.0f;
SightConfiguration->SightRadius = WALKER_SIGHT_RADIUS;
SightConfiguration->LoseSightRadius = (WALKER_SIGHT_RADIUS + 100.0f);
SightConfiguration->PeripheralVisionAngleDegrees = WALKER_PERIPHERAL_VISION_ANGLE_IN_DEGREES;
SightConfiguration->DetectionByAffiliation.bDetectEnemies = true;
SightConfiguration->DetectionByAffiliation.bDetectNeutrals = true;
SightConfiguration->DetectionByAffiliation.bDetectFriendlies = true;
@ -39,85 +156,107 @@ AWalkerAIController::AWalkerAIController(const FObjectInitializer& ObjectInitial
Perception->OnPerceptionUpdated.AddDynamic(this, &AWalkerAIController::SenseActors);
}
void AWalkerAIController::Possess(APawn *aPawn)
{
Super::Possess(aPawn);
check(aPawn != nullptr);
aPawn->OnTakeAnyDamage.AddDynamic(this, &AWalkerAIController::OnPawnTookDamage);
}
void AWalkerAIController::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
switch (GetMoveStatus()) {
case EPathFollowingStatus::Idle:
case EPathFollowingStatus::Waiting:
LOG_AI_WALKER(Warning, TEXT("Walker %s is stuck!"));
bIsStuck = true;
break;
case EPathFollowingStatus::Paused:
LOG_AI_WALKER(Log, TEXT("Walker %s is paused"));
bIsPaused = true;
TryResumeMovement();
break;
};
if (Status != EWalkerStatus::RunOver) {
switch (GetMoveStatus()) {
case EPathFollowingStatus::Idle:
case EPathFollowingStatus::Waiting:
LOG_AI_WALKER(Warning, "is stuck!");
Status = EWalkerStatus::Stuck;
break;
case EPathFollowingStatus::Paused:
LOG_AI_WALKER(Log, "is paused");
TryResumeMovement();
break;
};
}
}
FPathFollowingRequestResult AWalkerAIController::MoveTo(
const FAIMoveRequest& MoveRequest,
FNavPathSharedPtr* OutPath)
{
bIsStuck = false;
LOG_AI_WALKER(Log, TEXT("Walker %s requested to move"));
LOG_AI_WALKER(Log, "requested to move");
Status = EWalkerStatus::Moving;
return Super::MoveTo(MoveRequest, OutPath);
}
static bool VehicleIsMoving(const AWheeledVehicle *Vehicle)
void AWalkerAIController::OnMoveCompleted(
const FAIRequestID RequestID,
const FPathFollowingResult &Result)
{
const auto *MovementComponent =
(Vehicle != nullptr ? Vehicle->GetVehicleMovementComponent() : nullptr);
return
(MovementComponent != nullptr) &&
(FMath::Abs(MovementComponent->GetForwardSpeed()) > 0.0f);
}
static bool ContainsAMovingVehicle(const TArray<AActor *> &Actors)
{
for (auto *Actor : Actors) {
if (VehicleIsMoving(Cast<AWheeledVehicle>(Actor))) {
return true;
}
}
return false;
Super::OnMoveCompleted(RequestID, Result);
LOG_AI_WALKER(Log, "completed move");
Status = EWalkerStatus::MoveCompleted;
}
void AWalkerAIController::SenseActors(const TArray<AActor *> Actors)
{
if (!bIsPaused && ContainsAMovingVehicle(Actors)) {
const auto *aPawn = GetPawn();
if ((aPawn != nullptr) && IntersectsWithVehicle(*aPawn, Actors)) {
TryPauseMovement();
} else if (Status == EWalkerStatus::Paused) {
TryResumeMovement();
}
}
void AWalkerAIController::TryResumeMovement()
{
auto MoveRequestID = GetCurrentMoveRequestID();
if (MoveRequestID == FAIRequestID()) { // equals invalid request.
LOG_AI_WALKER(Error, TEXT("Walker %s: Invalid move ID"));
} else {
if (!ResumeMove(MoveRequestID)) {
LOG_AI_WALKER(Error, TEXT("Unable to resume walker %s move"));
if (Status != EWalkerStatus::Moving) {
auto MoveRequestID = GetCurrentMoveRequestID();
if (MoveRequestID == FAIRequestID()) { // equals invalid request.
LOG_AI_WALKER(Error, "has invalid move ID");
} else {
LOG_AI_WALKER(Log, TEXT("Resume walker %s's move"));
bIsPaused = false;
if (!ResumeMove(MoveRequestID)) {
LOG_AI_WALKER(Error, "is unable to resume movement");
} else {
LOG_AI_WALKER(Log, "resuming movement");
Status = EWalkerStatus::Moving;
}
}
}
}
void AWalkerAIController::TryPauseMovement()
void AWalkerAIController::TryPauseMovement(const bool bItWasRunOver)
{
auto MoveRequestID = GetCurrentMoveRequestID();
if (MoveRequestID == FAIRequestID()) { // equals invalid request.
LOG_AI_WALKER(Error, TEXT("Walker %s: Invalid move ID"));
} else {
if (!PauseMove(MoveRequestID)) {
LOG_AI_WALKER(Error, TEXT("Unable to pause walker %s move"));
if ((Status != EWalkerStatus::Paused) && (Status != EWalkerStatus::RunOver)) {
auto MoveRequestID = GetCurrentMoveRequestID();
if (MoveRequestID == FAIRequestID()) { // equals invalid request.
LOG_AI_WALKER(Error, "has invalid move ID");
} else {
LOG_AI_WALKER(Log, TEXT("Pause walker %s's move"));
bIsPaused = true;
if (!PauseMove(MoveRequestID)) {
LOG_AI_WALKER(Error, "is unable to pause movement");
} else {
LOG_AI_WALKER(Log, "paused");
Status = (bItWasRunOver ? EWalkerStatus::RunOver : EWalkerStatus::Paused);
}
}
}
}
void AWalkerAIController::OnPawnTookDamage(
AActor *DamagedActor,
float Damage,
const UDamageType *DamageType,
AController *InstigatedBy,
AActor *DamageCauser)
{
LOG_AI_WALKER(Warning, "has been run over");
constexpr bool bItWasRunOver = true;
TryPauseMovement(bItWasRunOver);
Status = EWalkerStatus::RunOver;
}
#undef EXTRA_LOG_ONLY
#undef LOG_AI_WALKER

View File

@ -7,6 +7,17 @@
class UAISenseConfig_Sight;
UENUM(BlueprintType)
enum class EWalkerStatus : uint8 {
Moving UMETA(DisplayName = "Walker Is Moving"),
Paused UMETA(DisplayName = "Walker Movement Is Paused"),
MoveCompleted UMETA(DisplayName = "Walker Completed Move"),
Stuck UMETA(DisplayName = "Walker Is Stuck"),
RunOver UMETA(DisplayName = "Walker Has Been Run Over"),
Invalid UMETA(DisplayName = "Walker Is Invalid"),
Unknown UMETA(DisplayName = "Unknown"),
};
UCLASS()
class CARLA_API AWalkerAIController : public AAIController
{
@ -16,32 +27,36 @@ public:
AWalkerAIController(const FObjectInitializer& ObjectInitializer);
virtual void Possess(APawn *aPawn) override;
virtual void Tick(float DeltaSeconds) override;
virtual FPathFollowingRequestResult MoveTo(
const FAIMoveRequest& MoveRequest,
FNavPathSharedPtr* OutPath = nullptr) override;
virtual void OnMoveCompleted(FAIRequestID RequestID, const FPathFollowingResult &Result) override;
UFUNCTION(BlueprintCallable)
void SenseActors(TArray<AActor *> Actors);
bool WalkerIsStuck() const
EWalkerStatus GetWalkerStatus() const
{
return bIsStuck;
return Status;
}
private:
void TryResumeMovement();
void TryPauseMovement();
void TryPauseMovement(bool bItWasRunOver = false);
UFUNCTION()
void OnPawnTookDamage(AActor *DamagedActor, float Damage, const UDamageType *DamageType, AController *InstigatedBy, AActor *DamageCauser);
UPROPERTY(Category = "Walker AI Controller", VisibleAnywhere)
UAISenseConfig_Sight *SightConfiguration;
UPROPERTY(VisibleAnywhere)
bool bIsPaused = false;
UPROPERTY(VisibleAnywhere)
bool bIsStuck = false;
EWalkerStatus Status = EWalkerStatus::Unknown;
};

View File

@ -14,7 +14,7 @@
// -- Static local methods -----------------------------------------------------
// =============================================================================
static bool WalkerIsValid(ACharacter *Walker)
static bool WalkerIsValid(const ACharacter *Walker)
{
return ((Walker != nullptr) && !Walker->IsPendingKill());
}
@ -34,6 +34,12 @@ static float GetDistance(const AActor &Actor0, const AActor &Actor1)
return GetDistance(Actor0.GetActorLocation(), Actor1.GetActorLocation());
}
static EWalkerStatus GetWalkerStatus(ACharacter *Walker)
{
const auto *Controller = GetController(Walker);
return (Controller == nullptr ? EWalkerStatus::Invalid : Controller->GetWalkerStatus());
}
// =============================================================================
// -- Constructor and destructor -----------------------------------------------
// =============================================================================
@ -109,11 +115,10 @@ void AWalkerSpawnerBase::Tick(float DeltaTime)
// If still stuck in the black list, just kill it.
const int32 Index = (++CurrentIndexToCheck % WalkersBlackList.Num());
auto Walker = WalkersBlackList[Index];
auto Controller = GetController(Walker);
if ((Controller == nullptr) ||
(Controller->WalkerIsStuck())) {
const auto Status = GetWalkerStatus(Walker);
if (Status != EWalkerStatus::Moving) {
WalkersBlackList.RemoveAtSwap(Index);
if (Walker != nullptr) {
if ((Walker != nullptr) && (Status != EWalkerStatus::RunOver)) {
Walker->Destroy();
}
}
@ -123,13 +128,19 @@ void AWalkerSpawnerBase::Tick(float DeltaTime)
// Check one walker, if fails black-list it or kill it.
const int32 Index = (++CurrentIndexToCheck % Walkers.Num());
auto Walker = Walkers[Index];
auto Controller = GetController(Walker);
if (Controller == nullptr) {
const auto Status = GetWalkerStatus(Walker);
if ((Status == EWalkerStatus::MoveCompleted) ||
(Status == EWalkerStatus::Invalid) ||
(Status == EWalkerStatus::RunOver)) {
// Kill it.
Walkers.RemoveAtSwap(Index);
if (Walker != nullptr) {
// If it was run over will self-destroy.
if ((Walker != nullptr) && (Status != EWalkerStatus::RunOver)) {
Walker->Destroy();
}
} else if (Controller->WalkerIsStuck()) {
} else if (Status == EWalkerStatus::Stuck) {
// Black-list it.
TrySetDestination(*Walker);
WalkersBlackList.Add(Walker);
Walkers.RemoveAtSwap(Index);

View File

@ -129,8 +129,7 @@ static bool SendAndReadSceneValues(
for (auto i = 0u; i < sceneValues.possible_positions.size(); ++i) {
auto *StartSpot = AvailableStartSpots[i];
check(StartSpot != nullptr);
const FVector &Location = StartSpot->GetActorLocation();
sceneValues.possible_positions[i] = {Location.X, Location.Y};
Set(sceneValues.possible_positions[i], StartSpot->GetActorLocation());
}
// Send the positions.
UE_LOG(LogCarlaServer, Log, TEXT("Sending %d available start positions"), sceneValues.possible_positions.size());