팀플 기획 중에 동물들 중 사슴이 무리 지어 이동한다는 기획이 있어 관련 구현 내용을 정리해 보았습니다.
처음에 바로 떠오른 아이디어는 Boids Algorithm이었으나 구현이 어려울 것 같아 단순하게 리더 정해서 리더 아닌 사슴들은 리더를 타깃으로 이동하도록 구현했었습니다.
다만 AI들이 각 AIController이 독립적으로 작동하면서 각 AI마다 새로운 BlackboardComponent가 생성되다보니, 각 사슴은 같은 Blackboard 데이터셋을 가져도 독립적인 BlackboardComponent를 사용해 데이터가 공유되지 않는 문제가 발생했습니다. 즉, AI가 같은 블랙 보드를 공유해야지 팔로워 사슴이 리더 사슴의 주소를 가져올 수 있으므로 현재 팔로워 사슴이 LeaderPosKey를 읽을 때, 이상한 큰 값을 가져오는 문제가 발생했습니다.
다른 방법이 떠오르지 않아 결국 Boids Algorithm의 Cohension(응집력)을 사용해 구현하였습니다.
Boids Algorithm의 자세한 내용은 아래 블로그를 통해 공부하였습니다.
https://leekangw.github.io/posts/92/
Boids(Flocking) Algorithm 만들어보기 (feat. Unity)
Boids(Flocking) 알고리즘을 만들어 보면서 정리한 글입니다. 공부하면서 정리한 글이기에 잘못된 점이 있을 수 있습니다. 이 점 참고 부탁드리며 오류를 발견하셨다면 편하게 댓글로 공유해 주세
leekangw.github.io
응집력은 모든 이웃들의 중간점을 찾고 중간점을 향해 이동하는 규칙으로 아래와 같이 구현했습니다.
#include "AI/Creatures/DeerDoe.h"
#include "Kismet/GameplayStatics.h"
void ADeerDoe::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// 1초마다 정보 갱신
static float UpdateInterval = 1.0f;
static float Time = 0.0f;
Time += DeltaTime;
if (Time >= UpdateInterval)
{
FindNearbyDeer();
Time = 0.0f;
}
}
// 주변 사슴들 찾기
void ADeerDoe::FindNearbyDeer()
{
NearbyDeer.Empty();
TArray<AActor*> FoundDeer;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), ADeerDoe::StaticClass(), FoundDeer);
for (AActor* Deer : FoundDeer)
{
if (Deer != this && FVector::Dist(Deer->GetActorLocation(), GetActorLocation()) < HerdRadius)
{
NearbyDeer.Add(Deer);
}
}
}
// 주변 사슴들의 평균 위치를 계산
FVector ADeerDoe::CalculateAveragePos()
{
if (NearbyDeer.Num() == 0)
{
return GetActorLocation();
}
FVector CenterPoint = GetActorLocation();
for (AActor* Deer : NearbyDeer)
{
CenterPoint += Deer->GetActorLocation();
}
return CenterPoint / NearbyDeer.Num();
}
#include "AI/Creatures/BTTaskNode_DeerFindPatrolPos.h"
#include "AI/Creatures/DeerDoe.h"
#include "AI/AIControllerCustom.h"
#include "NavigationSystem.h"
#include "BehaviorTree/BlackboardComponent.h"
UBTTaskNode_DeerFindPatrolPos::UBTTaskNode_DeerFindPatrolPos()
{
NodeName = TEXT("DeerFindPatrolPos");
}
EBTNodeResult::Type UBTTaskNode_DeerFindPatrolPos::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
ADeerDoe* DeerCharacter = Cast<ADeerDoe>(OwnerComp.GetAIOwner()->GetPawn());
if (!DeerCharacter) return EBTNodeResult::Failed;
FVector HerdCenter = DeerCharacter->CalculateAveragePos();
UNavigationSystemV1* NavSystem = UNavigationSystemV1::GetNavigationSystem(DeerCharacter->GetWorld());
if (!NavSystem) return EBTNodeResult::Failed;
FNavLocation NextPatrol;
if (NavSystem->GetRandomReachablePointInRadius(HerdCenter, 2000.0f, NextPatrol))
{
OwnerComp.GetBlackboardComponent()->SetValueAsVector(AAIControllerCustom::PatrolPosKey, NextPatrol.Location);
return EBTNodeResult::Succeeded;
}
return EBTNodeResult::Failed;
}
'내일배움캠프 > TIL' 카테고리의 다른 글
[내일배움캠프 Day56] 브루트포스 과제 (1) | 2025.03.11 |
---|---|
[내일배움캠프 Day55] GGF 프로젝트 1주차 WIL (0) | 2025.03.10 |
[내일배움캠프 Day44] BlueprintImplementableEvent (0) | 2025.02.20 |
[내일배움캠프 Day43] BT 애니메이션 실행 (0) | 2025.02.19 |
[내일배움캠프 Day42] WIL (5) | 2025.02.18 |