이전 글 보기
https://mountain-noroo.tistory.com/60
탐색 로직
1. 0.5초마다 범위 내에 플레이어가 들어와 있는지 탐색
2. 플레이어가 있다면 플레이어 쪽으로 이동
3. 플레이어가 없다면 스폰 위치 주변을 순찰(1.5초 대기, 스폰 위치 반경 5미터 내에서 이동 반복)
비헤이비어 트리
셀렉터는 어디로 이동할지 선택하는 노드라면
시퀀스는 하위 노드를 순서대로 실행하는 노드이다.
전위순회 방식을 사용하기 때문에 BTDWithinRange(주변에 플레이어가 있는지 탐색하는 데코레이터)가 성공하면 Move To 노드를 실행, 실패하면 오른쪽의 Sequence 하위 노드를 순차적으로 실행한다.
블랙보드
SelfActor는 기본적으로 생성되어 있는 자기 자신을 가리키는 키
Target은 플레이어를 발견하면 가리킬 키
SpawnPosition은 자신(몬스터)의 스폰 벡터를 가리킬 키
AttackRange는 몬스터의 공격 범위
DetectRange는 몬스터의 인식 범위
NextPos는 순찰 중인 몬스터가 다음으로 이동할 벡터이다.
자세한 역할은 코드를 살펴보면서 설명한다.
데코레이터
UCLASS()
class UNREAL_3TH_API UBTDWithinRange : public UBTDecorator
{
GENERATED_BODY()
private:
UPROPERTY(EditAnywhere, Category = "Data", meta = (AllowPrivateAccess = true))
float m_Range;
UPROPERTY(EditAnywhere, Category = "Blackboard", meta = (AllowPrivateAccess = true))
FBlackboardKeySelector m_TargetKey;
UPROPERTY(EditAnywhere, Category = "Blackboard", meta = (AllowPrivateAccess = true))
FBlackboardKeySelector m_RangeKey;
public:
UBTDWithinRange();
virtual bool CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const override;
};
추적 대상과의 거리에 따라 추적할지 순찰할지를 구분하는 데코레이터의 헤더이다.
FBlackboardKeySelector라는 타입을 볼 수 있는데
키 셀렉터란 블랙보드 키를 노드의 디테일 창에서 선택하도록 하는 타입이다.
UBTDWithinRange::UBTDWithinRange()
{
m_TargetKey.AddObjectFilter(this, GET_MEMBER_NAME_CHECKED(UBTDWithinRange, m_TargetKey), AActor::StaticClass());
m_RangeKey.AddFloatFilter(this, GET_MEMBER_NAME_CHECKED(UBTDWithinRange, m_RangeKey));
}
생성자에서 필터를 통해 이 키가 어떤 타입이지. 오브젝트 타입의 경우에는 부모 클래스가 무엇인지 지정해 준다.
bool UBTDWithinRange::CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const
{
// 범위 체크할 타겟을 가져온다.
// 타겟을 m_TargetKey 에 연결되어있는 블랙보드에서 가져온다.
if (m_TargetKey.IsNone())
{
// 블랙보드 키가 연결이 안되어있으면 범위체크할 대상을 알 수 없으므로 실패처리를 한다.
return false;
}
// 블랙보드에 Target 이 null 이라면(아직 못찾았다면)
UObject* pObject = OwnerComp.GetBlackboardComponent()->GetValueAsObject(m_TargetKey.SelectedKeyName);
if (!IsValid(pObject))
{
return false;
}
AActor* pTarget = Cast<AActor>(pObject);
// 기본적으루 m_Range 에 있는 값을 범위로 사용한다.
float fRange = m_Range;
if (!m_RangeKey.IsNone())
{
// m_RangeKey 에 범위값을 저장하고있는 블랙보드키가 연결되어있으면
// 해당 값을 블랙보드에서 가져와서 범위로 사용한다.
fRange = OwnerComp.GetBlackboardComponent()->GetValueAsFloat(m_RangeKey.SelectedKeyName);
}
// 플레이어가 범위안에 있는지 체크한다.
AAIController* pController = OwnerComp.GetAIOwner();
AMonster_Base* pMonster = Cast<AMonster_Base>(pController->GetPawn());
if (IsValid(pMonster))
{
float Distance = FVector::Distance(pTarget->GetActorLocation(), pMonster->GetActorLocation());
if (Distance < fRange)
{
return true;
}
}
return false;
}
위는 부모 클래스에서 상속받은, 캐릭터와의 거리를 바탕으로 true, false를 반환해 주는 함수로 true라면 하위 노드로 넘어가고 false라면 위의 셀렉터 노드로 돌아간다.
블랙보드 키 값 Set, Get
// BTDWithinRange.cpp에서 키의 값을 가져오는 코드
OwnerComp.GetBlackboardComponent()->GetValueAsObject(m_TargetKey.SelectedKeyName);
// Monster_Base.cpp에서 키에 값을 넣는 코드
pAIController->GetBlackboardComponent()->SetValueAsVector(FName("SpawnPosition"), GetActorLocation());
TASK
BTTTaskNode 부모 클래스를 상속받는다.
어떤 일을 수행하는 노드.
EBTNodeResult::Type UBTTFindNextPos::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
if (m_CenterPosKey.IsNone())
return EBTNodeResult::Failed;
FVector vInitPos = OwnerComp.GetBlackboardComponent()->GetValueAsVector(m_CenterPosKey.SelectedKeyName);
// 중심위치에서 반경범위내에 랜덤한 위치를 가져온다.
FNavLocation location;
UNavigationSystemV1* pNaviSys = UNavigationSystemV1::GetNavigationSystem(GetWorld());
FVector vNextPos = location.Location;
// 찾은 다음 이동위치를 NextPosKey 에 세팅된 블랙보드키에 저장시킨다.
if (m_NextPosKey.IsNone())
{
return EBTNodeResult::Failed;
}
// 몬스터의 상태를 이동상태로 변경한다.
AMonster_Base* pMonster = Cast<AMonster_Base>(OwnerComp.GetAIOwner()->GetPawn());
if (IsValid(pMonster))
{
pMonster->ChangeState(EMON_STATE::MOVE);
}
// 다음 이동위치에 기록한다.
OwnerComp.GetBlackboardComponent()->SetValueAsVector(m_NextPosKey.SelectedKeyName, vNextPos);
return EBTNodeResult::Succeeded;
}
다음 함수는 몬스터의 스폰 위치와 설정한 range값을 바탕으로 몬스터가 이동할 다음 지점을 정하고 몬스터의 상태를 이동으로 변경한다.
ExecuteTask함수 또한 오버라이드한 가상 함수로 노드가 시작하면 호출된다.
EBTNodeResult라는 값을 반환하여 위 노드로 올라갈지 다음으로 넘어갈지 이 task를 지속할지 여부를 나타낸다.
UENUM(BlueprintType)
namespace EBTNodeResult
{
// keep in sync with DescribeNodeResult()
enum Type
{
// finished as success
Succeeded,
// finished as failure
Failed,
// finished aborting = failure
Aborted,
// not finished yet
InProgress,
};
}
덧붙여 네비게이션 시스템을 사용하기 위해서는 월드에 NavMeshBoundsVolume이라는 액터를 설치해주어야 한다.
액터를 설치하고 영역을 설정해준다.
네비게이션 빌드에 시간이 좀 걸리니 기다리자.
'언리얼 > Assortrack UE5' 카테고리의 다른 글
[언리얼] 학원 16일차: UMG: 언리얼 UI (0) | 2023.09.18 |
---|---|
[언리얼] 학원 15일차: AI Controller 플레이어 추적 (0) | 2023.09.15 |
[언리얼] 학원 13일차: 데이터 테이블 핸들, AI Controller (0) | 2023.09.13 |
[언리얼] 학원 12일차: 데칼, 몬스터 개선(데이터 테이블) (0) | 2023.09.12 |
[언리얼] 학원 11일차: 또 다른 싱글톤, 콜리전 트레이스 채널 (0) | 2023.09.11 |