Unreal has some exising documentation on Agent Navigation Avoidance, which works fine if you only want your agents to avoid each other.
What if you have friendly / neutral agents that should avoid bumping into the player? There’s basically no documentation on this, and all the advice I could find on the forums was either incorrect or incomplete as of UE 5.4+. So here’s how you actually do it.
Agent Setup
We’re going to use Detour Crowd Avoidance, not RVO avoidance. You can’t realistically use RVO with player characters, and its results are pretty meh anyway.
So firstly you set up your Agents to use Detour Crowd Avoidance, which you do by:
- Setting your Agents default AI Controller to
DetourCrowdAIController, or - If you have a custom AI Controller, setting its Path Following Component to
UCrowdPathFollowingComponent
However, while your Agents will now avoid each other, they will stubbornly try to move through players.
Player Setup
The reason is that the player is not registered with the crowd system. We need to do that, and to add a component
to our player which implements ICrowdAgentInterface. So let’s do that now.
Here’s our new component. You have to do this in C++, sorry.
// PlayerCrowdAgentInterface.h
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Navigation/CrowdAgentInterface.h"
#include "PlayerCrowdAgentInterface.generated.h"
class IRVOAvoidanceInterface;
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class GAME_API UPlayerCrowdAgentInterface : public UActorComponent, public ICrowdAgentInterface
{
GENERATED_BODY()
protected:
mutable TWeakInterfacePtr<IRVOAvoidanceInterface> AvoidanceInterface;
public:
UPlayerCrowdAgentInterface();
FVector GetCrowdAgentLocation() const;
FVector GetCrowdAgentVelocity() const;
void GetCrowdAgentCollisions(float& CylinderRadius, float& CylinderHalfHeight) const;
float GetCrowdAgentMaxSpeed() const;
int32 GetCrowdAgentAvoidanceGroup() const override;
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
};
// PlayerCrowdAgentInterface.cpp
#include "PlayerCrowdAgentInterface.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Navigation/CrowdManager.h"
UPlayerCrowdAgentInterface::UPlayerCrowdAgentInterface()
{
PrimaryComponentTick.bCanEverTick = false;
}
FVector UPlayerCrowdAgentInterface::GetCrowdAgentLocation() const
{
return GetOwner() ? GetOwner()->GetActorLocation() : FVector::ZeroVector;
}
FVector UPlayerCrowdAgentInterface::GetCrowdAgentVelocity() const
{
return GetOwner() ? GetOwner()->GetVelocity() : FVector::ZeroVector;
}
void UPlayerCrowdAgentInterface::GetCrowdAgentCollisions(float& CylinderRadius,
float& CylinderHalfHeight) const
{
if (GetOwner())
{
GetOwner()->GetSimpleCollisionCylinder(CylinderRadius, CylinderHalfHeight);
}
}
float UPlayerCrowdAgentInterface::GetCrowdAgentMaxSpeed() const
{
return 800;
}
int32 UPlayerCrowdAgentInterface::GetCrowdAgentAvoidanceGroup() const
{
if (!AvoidanceInterface.IsValid())
{
if (auto MoveComp = GetOwner()->FindComponentByClass<UCharacterMovementComponent>())
{
AvoidanceInterface = TWeakInterfacePtr<IRVOAvoidanceInterface>(MoveComp);
}
}
if (AvoidanceInterface.IsValid())
{
return AvoidanceInterface->GetAvoidanceGroupMask();
}
return 0;
}
void UPlayerCrowdAgentInterface::BeginPlay()
{
Super::BeginPlay();
UCrowdManager* CrowdManager = UCrowdManager::GetCurrent(GetWorld());
if (CrowdManager)
{
CrowdManager->RegisterAgent(this);
}
}
void UPlayerCrowdAgentInterface::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
UCrowdManager* CrowdManager = UCrowdManager::GetCurrent(GetWorld());
if (CrowdManager)
{
CrowdManager->UnregisterAgent(this);
}
}
Then you just need to add this component to your player character. That’s it!
If you need to make certain agents NOT avoid the player (e.g. melee enemy units), assign the player an Avoidance Group in their Character Movement Component, and then reference that group in the Avoid Groups and Ignore Groups avoidance settings in the enemies.
Why this works
In BeginPlay, the new component registers itself with the UCrowdManager, a crucial part of making sure the avoidance
system knows the character exists.
The ICrowdAgentInterface implementation we supply provides the information required for agents to avoid the player,
such as its position, velocity and collision shape. We also implement GetCrowdAgentAvoidanceGroup which takes the
avoidance group defined in the existing Character Movement Component and reports it to the crowd manager. Note that
we don’t bother passing through the Avoid Groups or Ignore Groups settings for the player, because the player won’t be
avoiding anyone else (they’re player controlled), just being an obstacle.
Fin
I hope this is useful. I couldn’t find any complete description of these steps and even though they’re not egregious, it took me two separate attempts over the last few months to arrive at the correct combination, via judicious debugging. So at least you don’t have to do that. 🙂