언리얼의 MoveToLocation, MoveToActor를 통해 Agent가 언리얼의 Pathfinding Algorithm을 통해 destination까지 최소 비용을 계산하여 이동을 하는것을 알 수 있었다. 하지만 움직이지 않는 오브젝트의 비용을 Obstacle로 설정하여 고비용으로 만들어 우회하도록 한 것일뿐 움직이는 액터를 자연스럽게 회피할 수 있진 않다. 또한 움직이는 Obstacle이 경로상에 있을 때 매번 경로 재계산을 해야 되므로 비효율 적이다.
경로는 유지하되 Agent의 방향이나 속도를 조금 수정함으로써 자연스럽게 충돌 회피가 가능하도록 하는 기능이 RVO(Reciprocal Velocity Obstacles)이다. 비용도 효율적
언리얼에서 RVO는 CharacterMovement Component에서 활성화 시킬 수 있다.
// 예시
GetCharacterMovement()->bUseRVOAvoidance = true;
GetCharacterMovement()->AvoidanceConsiderationRadius = 300.f;
GetCharacterMovement()->AvoidanceWeight = 0.5f;
GetCharacterMovement()->SetAvoidanceGroup(1); // 자신이 어떤 그룹인지
GetCharacterMovement()->SetGroupsToAvoid(1); // 회피할 대상 그룹
GetCharacterMovement()->SetGroupsToIgnore(0); // 무시할 그룹
bUseRVOAvoidance 는 회피 시스템의 활성화 여부를 결정하는 가장 기본적인 설정.
AvoidanceConsiderationRadius 는 회피시스템이 발동하는 반경이다
AvoidanceWeight 는 회피 가중치 세팅이다.
Project Setting에서 Navigation System에 Navigation Invorker는 off로 설정
RVOCharacter를 새로 만들어서 로직을 작성해보았다.
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "RVO_Character.generated.h"
UCLASS()
class AI_TEST_API ARVO_Character : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
ARVO_Character();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
// 타겟 위치로 이동
UFUNCTION(BlueprintCallable, Category = "AI Movement")
void MoveToTarget();
// RVO On/Off
UFUNCTION(BlueprintCallable, Category = "RVO")
void SetRVOAvoidanceEnabled(bool bEnable);
public:
// 이동할 타겟 액터
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI Movement")
AActor* TargetActor;
// RVO 회피 설정
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RVO")
float AvoidanceRadius = 300.0f;
// RVO 계급 설정
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RVO")
float AvoidanceWeight = 0.5f;
private:
// AI 컨트롤러 캐싱
class AAIController* AIController;
};
#include "RVO_Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "AIController.h"
// Sets default values
ARVO_Character::ARVO_Character()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
UCharacterMovementComponent* MovementComponent = GetCharacterMovement();
if (MovementComponent)
{
MovementComponent->bUseRVOAvoidance = true;
MovementComponent->AvoidanceConsiderationRadius = AvoidanceRadius;
MovementComponent->AvoidanceWeight = AvoidanceWeight;
}
}
// Called when the game starts or when spawned
void ARVO_Character::BeginPlay()
{
Super::BeginPlay();
AIController = Cast<AAIController>(GetController());
if (!AIController)
{
UE_LOG(LogTemp, Warning, TEXT("%s is not controlled by an AIController."), *GetName());
}
else if (TargetActor)
{
MoveToTarget();
}
}
// Called every frame
void ARVO_Character::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// Called to bind functionality to input
void ARVO_Character::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
}
void ARVO_Character::MoveToTarget()
{
if (!AIController)
{
UE_LOG(LogTemp, Warning, TEXT("MoveToTarget failed: No AI Controller for %s"), *GetName());
return;
}
if (!TargetActor)
{
UE_LOG(LogTemp, Warning, TEXT("MoveToTarget failed: No Target Actor set for %s"), *GetName());
return;
}
// 위의 조건에 해당되지 않으면 액터를 향해 이동
AIController->MoveToActor(
TargetActor,
50.0f,
true,
true,
false
);
UE_LOG(LogTemp, Display, TEXT("%s moving to target: %s"), *GetName(), *TargetActor->GetName());
}
void ARVO_Character::SetRVOAvoidanceEnabled(bool bEnable)
{
UCharacterMovementComponent* MovementComponent = GetCharacterMovement();
if (MovementComponent)
{
MovementComponent->bUseRVOAvoidance = bEnable;
}
}
서로를 잘피해가는지 확인하기 위해 맵에 병목현상이 일어나게 환경을 바꾸었다.








