Unreal_Engine 5/UE5 AI(NPC, Enemy)

UE5 AI(NPC, Enemy)(5) RVO

언리얼배우기 프로젝트 2025. 4. 26. 19:20

RVO

 

RVO (Reciprocal Velocity Obstacles) 는 움직이는 객체들끼리 서로의 속도와 방향을 고려하여 충돌을 실시간으로 피하는 알고리즘이다.

 

RVO 핵심 코드

GetCharacterMovement()->bUseRVOAvoidance = true;
GetCharacterMovement()->AvoidanceConsiderationRadius = 200.0f;
GetCharacterMovement()->AvoidanceWeight = 0.5f;

 

 

 

GetCharacterMovement()->bUseRVOAvoidance = true;

 

이 코드는 RVO(Reciprocal Velocity Obstacles) 회피 시스템의 활성화 여부를 결정하는 가장 기본적인 설정이다.

  • 기능: RVO 충돌 회피 알고리즘 활성화 On/Off
  • 값의 의미: true로 설정하면 캐릭터가 다른 오브젝트나 장애물과의 충돌을 피하기 위한 자동 회피 기능이 작동한다. 이 값이 false라면 회피 기능이 작동하지 않는다.
  • 적합한 상황: 여러 AI 캐릭터가 서로 상호작용하며 이동해야 하는 상황에서 필수적이다.

 

GetCharacterMovement()->AvoidanceConsiderationRadius = 200.0f;

이 코드는 AI가 다른 오브젝트를 감지하고 회피를 시작하는 거리를 결정한다.

  • 기능: 회피 반경 세팅
  • 값의 의미: 200 uu(Unreal Unit) 내에 다른 오브젝트가 들어오면 회피를 시작한다.
  • 영향:
    • 값이 작을 경우 오브젝트가 가까워질 때까지 회피하지 않으므로 급격한 회피 동작이 발생할 수 있으며, 충돌 위험이 높아진다.
    • 값이 클 경우 오브젝트가 멀리 있을 때부터 회피를 시작하므로 부드러운 회피가 가능하지만, 불필요한 자원낭비가 증가할 수 있다.

 

GetCharacterMovement()->AvoidanceWeight = 0.5f;

이 코드는 해당 클래스를 상속받은 캐릭터의 회피 우선순위를 결정한다.

  • 기능: 회피 가중치 세팅
  • 값의 범위: 0.0f ~ 1.0f
  • 값의 의미:
    • 0.0f에 가까울수록 이 캐릭터가 다른 캐릭터를 먼저 회피한다. (낮은 계급이 됨)
    • 1.0f에 가까울수록 다른 캐릭터들이 이 캐릭터를 먼저 회피한다. (높은 계급이 됨)

 

RVO를 적용한 AICharacter 전체 코드

AI_RVO_Character.h


#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "AI_RVO_Character.generated.h"

UCLASS()
class AI_TEST_API AI_RVO_Character : public ACharacter
{
	GENERATED_BODY() 
public:
	// Sets default values for this character's properties
	AI_RVO_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 회피 활성화/비활성화
	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;
};

 

AI_RVO_Character.cpp


#include "AI_RVO_Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "AIController.h"

// Sets default values
AI_RVO_Character::AI_RVO_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;

	// RVO 회피 시스템 활성화
	UCharacterMovementComponent* MovementComponent = GetCharacterMovement();
	if (MovementComponent)
	{
		MovementComponent->bUseRVOAvoidance = true;
		MovementComponent->AvoidanceConsiderationRadius = AvoidanceRadius;
		MovementComponent->AvoidanceWeight = 0.5f;
	}
}

// Called when the game starts or when spawned
void AI_RVO_Character::BeginPlay()
{
	Super::BeginPlay();

	// AI 컨트롤러 참조 얻기
	AIController = Cast<AAIController>(GetController());

	// AI 컨트롤러가 없으면 로그 출력
	if (!AIController)
	{
		UE_LOG(LogTemp, Warning, TEXT("%s is not controlled by an AIController. Movement functions will not work."), *GetName());
	}
	else if (TargetActor)
	{
		// 타겟 액터가 설정되어 있으면 자동으로 이동 시작
		MoveToTarget();
	}
}

// Called every frame
void AI_RVO_Character::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
}

// Called to bind functionality to input
void AI_RVO_Character::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
}

void AI_RVO_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           // 목적지를 네비게이션 메시에 투영(Projection)하지 않음
	);

	UE_LOG(LogTemp, Display, TEXT("%s moving to target: %s"),
		*GetName(), *TargetActor->GetName());
}

void AI_RVO_Character::SetRVOAvoidanceEnabled(bool bEnable)
{
	UCharacterMovementComponent* MovementComponent = GetCharacterMovement();
	if (MovementComponent)
	{
		MovementComponent->bUseRVOAvoidance = bEnable;
	}
}

 


충돌 처리 확인

 

언리얼 에디터를 실행하고 생성한 RVOCharacter를 레벨에 배치한 다음 TargetActor를 설정해준다.

 

 

 


길이 좁을 때 RVO를 끄게되면 일어나는 상황

 

RVO Character 블루프린트에서 "캐릭터 무브먼트"를 클릭하고 디테일창에서 RVO회피 사용을 false로 변경해준다.

 

 

서로 충돌해서 지나갈 수 없는걸 확인할 수 있다.

 

다시 RVO를 켜주면

 

충돌을 피해서 정상적으로 MoveToActor로 설정된 Actor까지 이동하는것을 확인할 수 있다.


AICharcter가 이동하는 목표를 디버깅하는 방법

 

Tick 이벤트노드에 Draw Debug Arrow 노드를 연결하고 Line Start/End노드에 각각 Get Actor Location노드를 연결해준 다음 Line End노드 Get Actor Location에 Target에는 AICharacter가 MoveToActor로 이동하는 목표인 Target Actor를 연결해준다.