AI가 원하는 나무(Object)를 찾아서 이동해야 한다 !

 

나무의 위치를 GetActorLocation으로 받아서 SimpleMoveToLocation의 인자로 받아준다면 쉽게 될 일이지만

 

이럴 경우 동적으로 움직이는 것 들이 [ex)플레이어] 끼어든다면 NavMesh가 동적으로 수정이 안돼서 AI가 플레이어에게 막혀 가만히 멈춰 서버리게되는,,, 현상이 발생한다.

 

그래서 약간의 꼼수를 곁들인 나무찾기 알고리즘을 개발

 

먼저, 기존의 FindTree노드와 GototheTree 노드로 분리돼있던걸 하나로 합쳐서 PathFindingForFarm이라는 TaskNode를 만들었다.(.cpp/.h)

 

대략적인 알고리즘 설명은 

- 나무를 찾고, 나무를 향해 가는 도중, 장애물이 끼어들으면 다른 우회선로를 선택하는 알고리즘 -

이 되겠다. 

 

먼저 OverlapMultiByChannel 함수를 통해 반경감지 내의 오브젝트를 싹싹 긁어 모아버린다.

 

필요한 것만 Channel에 넣어서 최적화 하는 방법도 있지만~ 본인은 ECC_WorldStatic 으로 긁어모았음.

 

쓸 데 없는 것도 모아져 있을테니 분리를 한번 쓱~ 해준다.

 

이때 본인은 vector에 분리된 오브젝트를 넣어주었다.

 

오브젝트의 클래스는 요렇게 된다.

class TreeInfo {
public:
	class ATree* mTree;
	bool bIgnored;	//캐릭터한테 막혀있을 경우 ignored가 켜져서 탐색에 사용되지 않을 예정
	TreeInfo();
	TreeInfo(class ATree* tree);
};

그다음 예외처리 한번 해주고~

// --- 가장 가까운 나무 찾기,
	if (trees.size() <= 0)
		return EBTNodeResult::Failed;

	//Trees에 있는 객체가 아닌 Tree의 index를 꺼내주는건, 탐색에 실패했을 때, trees의 ignore값을 true로 바꿔주고
	//재 탐색해야하기 때문.
	//재사용을 위한 코드
   TargetTreeNum = -1;
   fTreeDistance = TNumericLimits<float>::Max();
	FVector mAILocation = mAI->GetActorLocation();

	for (int i = 0; i < trees.size(); ++i)
	{
		float distance = FVector::DistSquared(mAILocation, trees[i].mTree->GetActorLocation());
		//Ignored 상태인 tree는 탐색에 넣지 않는다.
		if (trees[i].bIgnored) continue;
		if (trees[i].mTree->CanHarvest)
		{
			if (distance < fTreeDistance)
			{
				fTreeDistance = distance;
				TargetTreeNum = i;
			}
		}
	}

trees라는 vector에서 하나씩 꺼내 distance를 측정하고 제~일 가까운친구를 TargetTreeNum에다가 기록해준다.

bIgnored가 있는 이유는 이따가 설명해주겠다.

//-- 내 자신에서부터 나무로부터 광선을 쏴서 이 사이에 캐릭터가 있는지 확인한다.
	TArray<TEnumAsByte<EObjectTypeQuery>> ObjectTypes; // 히트 가능한 오브젝트 유형들.
	TArray<AActor*> IgnoreActors; // 무시할 액터들.
	FHitResult HitResult; // 히트 결과 값 받을 변수.

	//TEnumAsByte<EObjectTypeQuery> WorldStatic = UEngineTypes::ConvertToObjectType(ECollisionChannel::ECC_WorldStatic);
	TEnumAsByte<EObjectTypeQuery> WorldPawn = UEngineTypes::ConvertToObjectType(ECollisionChannel::ECC_Pawn);
	//ObjectTypes.Add(WorldStatic);
	ObjectTypes.Add(WorldPawn);
	IgnoreActors.Add(ControllingPawn);

	while (1) {
		UKismetSystemLibrary::LineTraceSingleForObjects(
			mAI->GetWorld()
			, mAILocation, trees[TargetTreeNum].mTree->GetActorLocation()
			, ObjectTypes, false, IgnoreActors, EDrawDebugTrace::ForOneFrame, HitResult, true);

		ABaseCharacter* BotherPlayer = Cast<ABaseCharacter>(HitResult.GetActor());
		//------------------------------
		if (nullptr != BotherPlayer)
		{
			//나무와 나 사이에 적이 있다는 얘기
			trees[TargetTreeNum].bIgnored = true;
			//다시 탐색한다.
			bool bCanNotFindTree = true;
			fTreeDistance = TNumericLimits<float>::Max();
			for (int i = 0; i < trees.size(); ++i)
			{
				float distance = FVector::DistSquared(mAILocation, trees[i].mTree->GetActorLocation());
				//Ignored 상태인 tree는 탐색에 넣지 않는다.
				if (trees[i].bIgnored) continue;
				if (trees[i].mTree->CanHarvest)
				{
					if (distance < fTreeDistance)
					{
						fTreeDistance = distance;
						TargetTreeNum = i;
						bCanNotFindTree = false;
					}
				}
			}

			if (bCanNotFindTree)
			{
				//반복문을 다 돌았는데 (모든 나무탐색을 했는데)
				// 내가 갈 나무가 아무것도 존재하지 않다면
				//랜덤무브를 해줘야함. 아직 코딩하진않겠음.
				//현재는 그냥 Failed처리
				trees.clear();
				fTreeDistance = TNumericLimits<float>::Max();
				return EBTNodeResult::Failed;
			}
			//재사용을 위한 초기화.
			fTreeDistance = TNumericLimits<float>::Max();
		}
		else {
			//방해하는 적이 없다면 탈출해서 나무로 달려간다.

			if (AIController)
			{
				OwnerComp.GetBlackboardComponent()->SetValueAsObject(AAIController_Custom::TreePosKey, trees[TargetTreeNum].mTree);
				FVector goal = FVector(trees[TargetTreeNum].mTree->GetActorLocation());
				//goal.Y += 70;
				//UAIBlueprintHelperLibrary::SimpleMoveToLocation(ControllingPawn->GetController(), goal);
				UAIBlueprintHelperLibrary::SimpleMoveToLocation(ControllingPawn->GetController(), trees[TargetTreeNum].mTree->GetActorLocation());
			}
			else if (smartAIController)
			{
				OwnerComp.GetBlackboardComponent()->SetValueAsObject(AAI_Smart_Controller_Custom::TreePosKey, trees[TargetTreeNum].mTree);
				FVector goal = FVector(trees[TargetTreeNum].mTree->GetActorLocation());
				UAIBlueprintHelperLibrary::SimpleMoveToLocation(ControllingPawn->GetController(), trees[TargetTreeNum].mTree->GetActorLocation());
			}
			
			break;
		}
	}

단순히 복붙을 했더니 코드가 길다. 설명하겠다.

 

먼저 LineTraceSingleForObjects 함수로 AI위치에서부터~ 제일 가까운 나무까지로 광선을 쏜다.

 

이때 AI몸 안에서부터 광선이 시작되니 IgnoreActor에 자기자신을 넣어주었다. (Ignore 하기 싫으면 광선 시작위치를 Forward Vector로 앞으로 조금 꺼내도 된다)

 

그리고 히트 될 오브젝트는 ECC::Pawn으로 해서 캐릭터만 히트가능할 수 있게 했다. (방해하는 애는 Pawn말고는 없으니까..)

 

그래서 만약 히트가 된다면? HitResult에 값이 쌓이게 되는데 

 

그럼 이 히트가 되버린 나무는 아 여기로 가면 안되겠구나! 하고 bIgnore를 true로 해서 무시해버리는것이다 ! 

 

그리고 다~시 나무를 탐색하는것부터 시작하게 된다.

 

아무도 안부딪히면 거기로 무브

 

이런 알고리즘인데

 

문제가 있기는 하다 

 

1. AI 중심에서 1개의 광선만 쏘다보니 몸 중앙이아니라 왼쪽오른쪽으로 연행하듯이 길을 막아버리면 여전히 멈춘다는 것...

 

해결하려면 몸의 맨 왼쪽, 몸의 가운데, 몸의 맨 오른쪽 을 시작점을 두고 도착지점까지 쏘는게 정확도를 늘릴 수 있다!

 

2. 모든 나무를 다 방해한다면 ? 

 

멈춰버리게 된다...! 

하지만 이건 혼자서는 힘들고 여러명이 합심해서 (그것도 개인전인 게임에서) AI를 그렇게 괴롭히고 싶을까..?

 

그정도로 노력해서 AI를 괴롭히고 싶다면.. 말리진 않겠다. 플레이어의 자유니까.

이부분은.. AI가 아니더라도 플레이어라도 방해받으면 못빠져나갈텐데..? 해결법은 딱히 없는듯 

'개발 > Unreal Engine 4' 카테고리의 다른 글

UE4에서의 이동 동기화.  (1) 2023.09.09

+ Recent posts