본문 바로가기

게임프로그래밍/실습1

[언리얼 실습] 57. 에너미 실제로 패트롤 시키기


목차

  1. 서론
  2. 함수 리팩터링하기
  3. 에너미 패드롤 시키기

1. 서론

 

현재 우리에게는 에너미가 패트롤 할 위치(액터)를 저장할 배열을 가지고 있다. 이를 활용해 에너미가 실제로 패트롤을 하게 구현해보자.

 

 

c++에서 이를 구현하기 전에 혹시 까먹을 수도 있으니 배열들을 먼저 채워주었다.


2. 함수 리팩터링하기

 

이제 패드롤 기능을 구현할 것이다. Tick함수에서 구현할 것인데 해당 함수를 한번 봐보자.

 

void AEnemy::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (CombatTarget)
	{
		const double DistanceToTarget = (CombatTarget->GetActorLocation() - GetActorLocation()).Length();

		if (DistanceToTarget > CombatRadius)
		{
			CombatTarget = nullptr;
			if (HealthBarWidget)
			{
				HealthBarWidget->SetVisibility(false);
			}
		}
	}

}

 

현재 Tick 함수 내부에서는 타겟과 에너미의 거리를 구하고 일정 거리 안에 있는지 없는지에 따라 어떤 행동을 할지 정하고 있다.

 

타겟과의 거리를 구하는 부분을 함수로 구현해 코드의 가독성을 쉽게 하려고 한다.

 

헤더파일에서 아래 함수를 선언하고,

 

bool InTargetRange(AActor* Target, double Radius);

 

cpp 파일에서 정의하도록 하자

bool AEnemy::InTargetRange(AActor* Target, double Radius)
{
	const double DistanceToTarget = (Target->GetActorLocation() - GetActorLocation()).Length();
	return DistanceToTarget <= Radius;
}

 

이제 Tick 함수를 바꿔보자.

 

void AEnemy::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (CombatTarget)
	{
		if (!InTargetRange(CombatTarget, CombatRadius))
		{
			CombatTarget = nullptr;
			if (HealthBarWidget)
			{
				HealthBarWidget->SetVisibility(false);
			}
		}
	}
}

 

타겟이 CombatRadius 밖에 있으면 실행하는 구문이라 부정연산자를 추가하였다.


3. 에너미 패트롤 시키기

 

이제 에너미를 패트롤 시켜보려고 한다.

 

먼저 패트롤 타겟과의 위치를 구해야 하고 InTargetRange를 사용할 것이다. 그러기 위해서는 Radius값이 필요하다.

 

헤더파일에서 선언해주자.

UPROPERTY(EditAnywhere)
double PatrolRadius = 200.f;

 

현재는 테스트를 위해 PatrolRadius를 길게 잡아주었다.

 

이제 구현을 해주면 된다.

 

Tick함수 내부에 추가해주자.

if (PatrolTarget && EnemyController)
{
	if (InTargetRange(PatrolTarget, PatrolRadius))
	{
		const int32 NumPatrolTargets = PatrolTargets.Num();
		if (NumPatrolTargets > 0)
		{
			const int32 TargetSelection = FMath::RandRange(0, NumPatrolTargets - 1);
			AActor* Target = PatrolTargets[TargetSelection];
			PatrolTarget = Target;
		}
		FAIMoveRequest MoveRequest;
		MoveRequest.SetGoalActor(PatrolTarget);
		MoveRequest.SetAcceptanceRadius(15.f);
		EnemyController->MoveTo(MoveRequest);
	}
}

 

타겟이 타겟 범위 안에 들어오면 타겟배열에서 랜덤 인덱스를 타겟으로 재설정해 다시 그쪽으로 이동시킨다.

 

하지만 이렇게 하면 매 프레임마다 타겟범위 안에 들어왔는지 확인을 할 것이며 범위 안에만 들어오면 다시 타겟을 세팅하고  정말 말도 안되는 가능성으로 계속 똑같은 목표를 설정할 수도 있다. 이러한 것을 방지할 방법을 찾아야 한다.

 

또 다른 배열을 만들어 이를 방지하려고 한다.

 

if (PatrolTarget && EnemyController)
{
	if (InTargetRange(PatrolTarget, PatrolRadius))
	{
		TArray<AActor*> ValidTargets;
		for (AActor* Target : PatrolTargets)
		{
			if (Target != PatrolTarget)
			{
				ValidTargets.AddUnique(Target);
			}
		}

		const int32 NumPatrolTargets = ValidTargets.Num();
		if (NumPatrolTargets > 0)
		{
			const int32 TargetSelection = FMath::RandRange(0, NumPatrolTargets - 1);
			AActor* Target = ValidTargets[TargetSelection];
			PatrolTarget = Target;
		}
		FAIMoveRequest MoveRequest;
		MoveRequest.SetGoalActor(PatrolTarget);
		MoveRequest.SetAcceptanceRadius(15.f);
		EnemyController->MoveTo(MoveRequest);
	}
}

 

TArray<AActor*> ValidTargets; 을 추가하여 현재 순찰 대상인 타겟을 제외한 대상만 다시 타겟이 될 수 있도록 바꿔주었다.

 

이제 컴파일하고 제대로 움직이는지 확인해보자.

 

 

 

에너미가 정상적으로 잘 움직이는 것을 볼 수 있다. 다음에는 순찰지점에 도착하면 잠시 멈췄다가 이동하는 것을 구현해보도록 하겠다.