Add wildlife navigation support

This commit is contained in:
2026-05-16 11:23:17 -07:00
parent 82463f3b99
commit 578220cf60
7 changed files with 295 additions and 12 deletions
+162 -8
View File
@@ -3,14 +3,19 @@
#include "AgrarianWildlifeBase.h"
#include "AgrarianGameCharacter.h"
#include "AgrarianInventoryComponent.h"
#include "AIController.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Navigation/PathFollowingComponent.h"
#include "NavigationSystem.h"
#include "Net/UnrealNetwork.h"
AAgrarianWildlifeBase::AAgrarianWildlifeBase()
{
PrimaryActorTick.bCanEverTick = true;
bReplicates = true;
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
AIControllerClass = AAIController::StaticClass();
GetCharacterMovement()->MaxWalkSpeed = WanderSpeed;
GetCharacterMovement()->bOrientRotationToMovement = true;
@@ -25,6 +30,10 @@ void AAgrarianWildlifeBase::BeginPlay()
SpawnLocation = GetActorLocation();
Health = FMath::Clamp(Health, 0.0f, MaxHealth);
if (HasAuthority() && !GetController())
{
SpawnDefaultController();
}
ChooseWanderTarget();
BroadcastHealthChanged();
BroadcastStateChanged();
@@ -159,34 +168,174 @@ void AAgrarianWildlifeBase::ServerThink(float DeltaSeconds)
void AAgrarianWildlifeBase::ChooseWanderTarget()
{
FVector ReachableTarget = FVector::ZeroVector;
if (ChooseReachableWanderTarget(ReachableTarget))
{
CurrentMoveTarget = ReachableTarget;
ClearNavigationMove();
return;
}
const FVector RandomOffset = FVector(
FMath::FRandRange(-WanderRadius, WanderRadius),
FMath::FRandRange(-WanderRadius, WanderRadius),
0.0f);
CurrentMoveTarget = SpawnLocation + RandomOffset;
ClearNavigationMove();
}
void AAgrarianWildlifeBase::MoveTowardTarget()
{
FVector DesiredDirection = FVector::ZeroVector;
FVector DesiredTarget = GetActorLocation();
float DesiredSpeed = WanderSpeed;
if (WildlifeState == EAgrarianWildlifeState::Fleeing && FocusActor)
{
DesiredDirection = GetActorLocation() - FocusActor->GetActorLocation();
GetCharacterMovement()->MaxWalkSpeed = FleeSpeed;
FVector DesiredDirection = GetActorLocation() - FocusActor->GetActorLocation();
DesiredDirection.Z = 0.0f;
if (DesiredDirection.IsNearlyZero())
{
DesiredDirection = GetActorForwardVector();
DesiredDirection.Z = 0.0f;
}
DesiredTarget = GetActorLocation() + DesiredDirection.GetSafeNormal() * FleeRadius;
DesiredSpeed = FleeSpeed;
}
else if (WildlifeState == EAgrarianWildlifeState::Chasing && FocusActor)
{
DesiredDirection = FocusActor->GetActorLocation() - GetActorLocation();
GetCharacterMovement()->MaxWalkSpeed = FleeSpeed;
DesiredTarget = FocusActor->GetActorLocation();
DesiredSpeed = FleeSpeed;
}
else if (WildlifeState == EAgrarianWildlifeState::Wandering)
{
DesiredDirection = CurrentMoveTarget - GetActorLocation();
GetCharacterMovement()->MaxWalkSpeed = WanderSpeed;
DesiredTarget = CurrentMoveTarget;
DesiredSpeed = WanderSpeed;
}
if (RequestNavigationMove(DesiredTarget))
{
GetCharacterMovement()->MaxWalkSpeed = DesiredSpeed;
return;
}
DirectMoveTowardLocation(DesiredTarget, DesiredSpeed);
}
bool AAgrarianWildlifeBase::ChooseReachableWanderTarget(FVector& OutTarget) const
{
if (!bUseNavigationMovement)
{
return false;
}
const UWorld* World = GetWorld();
const UNavigationSystemV1* NavigationSystem = World ? FNavigationSystem::GetCurrent<UNavigationSystemV1>(World) : nullptr;
if (!NavigationSystem)
{
return false;
}
FNavLocation ReachableLocation;
if (!NavigationSystem->GetRandomReachablePointInRadius(SpawnLocation, WanderRadius, ReachableLocation))
{
return false;
}
OutTarget = ReachableLocation.Location;
return true;
}
bool AAgrarianWildlifeBase::ProjectPointToNavigation(const FVector& CandidateLocation, FVector& OutProjectedLocation) const
{
if (!bUseNavigationMovement)
{
return false;
}
const UWorld* World = GetWorld();
const UNavigationSystemV1* NavigationSystem = World ? FNavigationSystem::GetCurrent<UNavigationSystemV1>(World) : nullptr;
if (!NavigationSystem)
{
return false;
}
FNavLocation ProjectedLocation;
if (!NavigationSystem->ProjectPointToNavigation(CandidateLocation, ProjectedLocation, NavigationProjectionExtent))
{
return false;
}
OutProjectedLocation = ProjectedLocation.Location;
return true;
}
bool AAgrarianWildlifeBase::RequestNavigationMove(const FVector& TargetLocation)
{
if (!bUseNavigationMovement)
{
return false;
}
AAIController* AIController = Cast<AAIController>(GetController());
if (!AIController)
{
return false;
}
FVector ProjectedTarget = TargetLocation;
if (!ProjectPointToNavigation(TargetLocation, ProjectedTarget))
{
return false;
}
if (FVector::DistSquared(GetActorLocation(), ProjectedTarget) <= FMath::Square(NavigationAcceptanceRadius))
{
AIController->StopMovement();
ClearNavigationMove();
return true;
}
if (bHasNavigationMoveTarget
&& AIController->GetMoveStatus() != EPathFollowingStatus::Idle
&& FVector::DistSquared(LastNavigationMoveTarget, ProjectedTarget) <= FMath::Square(NavigationRepathDistance))
{
return true;
}
const EPathFollowingRequestResult::Type MoveResult = AIController->MoveToLocation(
ProjectedTarget,
NavigationAcceptanceRadius,
true,
true,
true,
true,
nullptr,
true);
if (MoveResult == EPathFollowingRequestResult::Failed)
{
ClearNavigationMove();
return false;
}
LastNavigationMoveTarget = ProjectedTarget;
bHasNavigationMoveTarget = true;
return true;
}
void AAgrarianWildlifeBase::ClearNavigationMove()
{
bHasNavigationMoveTarget = false;
LastNavigationMoveTarget = FVector::ZeroVector;
}
void AAgrarianWildlifeBase::DirectMoveTowardLocation(const FVector& TargetLocation, float MovementSpeed)
{
ClearNavigationMove();
GetCharacterMovement()->MaxWalkSpeed = MovementSpeed;
FVector DesiredDirection = TargetLocation - GetActorLocation();
DesiredDirection.Z = 0.0f;
if (!DesiredDirection.IsNearlyZero())
{
@@ -198,6 +347,11 @@ void AAgrarianWildlifeBase::EnterDeadState()
{
Health = 0.0f;
SetWildlifeState(EAgrarianWildlifeState::Dead);
if (AAIController* AIController = Cast<AAIController>(GetController()))
{
AIController->StopMovement();
}
ClearNavigationMove();
GetCharacterMovement()->StopMovementImmediately();
GetCharacterMovement()->DisableMovement();
SetLifeSpan(0.0f);