이제 단일 지점으로의 Patrol은 가능한 것을 볼 수 있다. 이제 한 Patrol Point에서 다른 Patrol Point로 이동할 수 있도록 구현해보자.

이를 위해 타켓포인트를 여러개 만들어주고 배열에 할당해주자

 

그리고 Tick함수에서 Target과의 거리를 측정해주는 함수가 작동할 수 있도록 Actor와의 지정해준 거리에 따라 bool값을 반환해주는 새로운 함수를 만들어주자.

Enemy.cpp

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

 

이를 바탕으로  Tick함수에서 HealthBar 조건문도 변경해주고 PatrolTarget조건문도 만들어주자.

이때 PatrolRadius(멈출 거리)를 지정해주어야하는데 이 값이 double인데 moveTo에서는 이 반경보다 조금 더 이동하기 때문에 조금 더 거리를 지정해주어야한다.

Enemy.h

	UPROPERTY(EditAnywhere)
	double PatrolRadius = 200.f;

Enemy.cpp

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

	if (CombatTarget)
	{
		if (!InTargetRange(CombatTarget,CombatRadius))
		{
			CombatTarget = nullptr;
			if (HealthBarWidget)
			{
				HealthBarWidget->SetVisibility(false);
				Attributes->SetHealth(100.f);
			}
		}
	}

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


		if (InTargetRange(PatrolTarget, PatrolRadius))
		{
			const int32 TargetSelection = FMath::RandRange(0, ValidTargets.Num() - 1);
			PatrolTarget = ValidTargets[TargetSelection];
			
			FAIMoveRequest MoveRequest;
			MoveRequest.SetGoalActor(PatrolTarget);
			//도착이라고 생각할 거리
			MoveRequest.SetAcceptanceRadius(15.f);
			EnemyController->MoveTo(MoveRequest);
		}
	}
}

 

이렇게 해주고 실행해주면 랜덤한 순찰포인트로 순찰하는 것을 볼 수 있다.

 

하지만 지금은 순찰 포인트로 이동하고 바로 다른 포인트로 이동하는데 이것을 조금 기다렸다가 다른 순찰포인트로 이동하도록 구현해보자. 

이를 위해 FTimerHandle 구조체 변수를 선언해주자. 이 구조체 변수는 일정 시간이 지난후  호출될 콜백함수가 필요하기 때문에 함수도 같이 선언해주자.

Enemy.h

private:
	FTimerHandle PatrolTimer;
	void PatrolTimerFinished();

 

Enemy.cpp

void AEnemy::PatrolTimerFinished()
{
	MoveToTarget(PatrolTarget);
}

 

이렇게 콜백함수를 만들어준 다음 기능별로 나눌 수 있는것은 나눠주자.

Enemy.cpp

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

void AEnemy::MoveToTarget(AActor* Target)
{
	if (EnemyController == nullptr || Target == nullptr) return;

	FAIMoveRequest MoveRequest;
	MoveRequest.SetGoalActor(Target);
	//도착이라고 생각할 거리
	MoveRequest.SetAcceptanceRadius(15.f);
	EnemyController->MoveTo(MoveRequest);
}

AActor* AEnemy::ChoosePatrolTarget()
{
	TArray<AActor*> ValidTargets;
	for (AActor* Target : PatrolTargets)
	{
		if (Target != PatrolTarget)
		{
			ValidTargets.AddUnique(Target);
		}
	}

	if (ValidTargets.Num() > 0)
	{
		const int32 TargetSelection = FMath::RandRange(0, ValidTargets.Num() - 1);
		return ValidTargets[TargetSelection];
	}
	return nullptr;
}

 

만약 타켓이 거리안에 있다면 5초를 대기한 다음 이동하게 해주자 이때 GetWorldTimerManager의 SetTimer 기능을 활용하면 된다.

Enemy.cpp

void AEnemy::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	
	if (InTargetRange(PatrolTarget, PatrolRadius))
	{
		PatrolTarget = ChoosePatrolTarget();
		GetWorldTimerManager().SetTimer(PatrolTimer, this, &AEnemy::PatrolTimerFinished, 5.f);
	}
	
}

 

이렇게 해주면 5초를 대기한 다음 다음 순찰포인트로 이동한다.

 

+ Recent posts