EQS(Environment Query System)란?
언리얼 엔진의 AI시스템중 한개, AI가 환경을 실시간으로 분석, 최적의 위치를 계산하는것을 도와주는 시스템입니다
AI가 플레이어를 피해서 숨을때 적절한 엄폐물 위치를 계산하거나
원하는 조건 위치로 최적의 위치포인트를 잡을때라던지
여러가지 환경에서 여러가지 후보를 계산하고 가장 최적의 결과를 반환합니다.
EQS를 생성해보겠습니다
인바이런먼트쿼리를 생성해줍니다
그럼 BehaviorTree와 비슷한 환경이 나옵니다
루트에서 자기가 사용할 포인트방식을 지정해주겠습니다 저는 도넛모양을 선택해주었습니다
EQS는 단독으로 실행이 불가능합니다
EQS Testing Pawn을 생성하여 확인해보겠습니다
블루프린트생성 -> EQSTestingPawn을 생성해주겠습니다
TestingPawn의 블루프린트생성이 완료되었으면 생성해준 eqs를 할당해주겠습니다
도넛모양으로 동그란 모양이 깔린것을 확인할수있습니다
해당 원은 아이템 이라고 불리며 EQS는 이 아이템들중 최적의 아이템을 찾는시스템이라고 할수 있습니다
단순한 거리값으로 EQS를 사용해보겠습니다
테스트추가를 하여 Distance를 추가하였습니다
테스트목적을 score only로 바꿔보겠습니다
또한 계산식을 Inverse Linear로 설정해주었습니다
그러면 TestingPawn근처로 거리가 멀면 0에 가까워지고 가까워지면 1에 가까워지는것을 확인할수있습니다
이번에는 내적(Dot)을 사용해보겠습니다
Dot이 내적을 할려면 반드시 두개의 방향 이 필요로 합니다
Dot의 LineA와 LineB 의 벡터로 Dot계산을 합니다
필터타입이 Maxamium이면 Cos시점으로 봤을때 후방이면 음수를 도출합니다
Cos
TestingPawn 후방기준으로 아이템의 결과값이 도출되며 전방의 Dot은 0의 점수를 가지게됩니다
Distance | 거리계산 / 멀거나 가까울수록 score 계산 |
Dot | 아이템의 점수가 특정 방향과 얼마나 일치하는가 계산 |
Gameplay Tags | 아이템의 점수나 액터가 가지고있는 Gameplay Tag를 조건으로 계산 |
Overlap | 아이템의 점수가 특정 역역 또는 컴포넌트와 겹치는지를 판단후 계산 |
Pathfinding | AI가 이 포인트까지 길 찾기가 가능한지 계산 |
PathfindingBatch | 여러 포인트를 한번에 경로 탐색 평가 (Pathfinding의 상위호환) |
Project | 포인트를 지정된 지형에 투영하여 평가 |
Trace | 라인트레이스를 통해 장애물 등을 평가 |
Volume | 포인트가 특정 볼륨 액터 내부에 있는지를 평가 |
이제 AI에서 이 EQS를 실행하도록 하겠습니다
먼저 C++에서 EQS를 사용하기위해서는 Build.Cs에 해당 모듈을 추가해줘야합니다
"AIModule",
"GameplayTasks"
그리고 블랙보드에 값을 넘겨줄
EnvQueryContext가 필요합니다
컨텍스트의 역할은 내가 원하는 기준을 세울때 필요합니다
어떤대상을 떄릴래
어떤대상을 기준으로 회전할래 라고 할떄 어떤대상을 정의할때 필요합니다
UEnvQueryContext 를 부모클래스를 받은 Context를 생성해주겠습니다
플레이어만 리턴해주면되니 간단하게 블루프린트로 작업하겠습니다
EnvQueryContext_BlueprintBase를 생성해주겠습니다
블루프린트의 노드를 설정해주겠습니다
TestingPawn으로 EQS의 기본 동작예시를 설명드리겠습니다
인바이러먼트 쿼리에 후보를 Circle로 추가하고 해당 그림과 같이 세팅해주겠습니다
원의 반지름을 600으로 설정 원간격포인트를 By Number Of Point로 그리고 포인트수를 8로 설정해주었습니다
그럼 테스팅폰이 가지고있는 쿼리의 후보값으로 설정값이 나타납니다
Distance 후보를 추가해주었습니다
또한 필터타입을 Maximum 그리고 값을 Circle의 최대값인 600값으로 설정하였습니다
게임에서 확인해보면 플로트값 600에 도달했을떄 아이템이 1로 최적의 값을 뽑아내는것을 확인할수있습니다
쿼리의 후보를 원의반지름, 포인트수를 쿼리 파라미터로 수정해주고 원중앙 을 아까만든
Context 블루프린트로 설정해주겠습니다
Distance 쿼리 후보도 쿼리 파라미터로 변경해주겠습니다
값을 None으로 직접 지정해주는게 아닌 쿼리파라미터값으로 변경해준뒤
BehaviorTree에서 쿼리를 넣으면 방금 설정해준 쿼리파라미터의 갯수만큼
쿼리 환경설정 인덱스가 나타나게 됩니다
블랙보드에 쿼리 벡터를 받을 Vector Key를 설정해줍니다
BehaviorTree에서 Run Eqs Query노드에서 인덱스값을 설정해준뒤
블랙보드키를 만들어둔 벡터 키를 설정해주겠습니다
그다음 TestingPawn을 삭제한뒤 다시 꺼내주게되면 플레이어 기준으로 eqs 의 item이 생성이 되게됩니다
그럼 테스팅폰의 위치가 바뀔때마다 아이템의 score값과 색상이 변하는걸 확인할수있습니다
BehaviorTree에서 Move To노드를 세팅하고 키를 EQS Query에서의 키 로 변경해주겠습니다
Context 블루프린트를 플레이어를 리턴하도록 변경해주겠습니다
그럼 EQS 시스템이 구한 최적의 아이템의 값으로 움직이는것을 확인할수있습니다
BehaviorTree를 수정해주겠습니다
공격할때 그냥 공격하는게 아니고 EQS아이템좌표를 구해 그쪽으로 두번 움직인뒤
잠시 기다렸다가 다시 공격을 하게 설정하였습니다
수정된 BT에 따라 AI는 바로공격하지않고 아이템의 위치로 두번 이동후 공격하게됩니다
플레이어를 바라보면서 움직이게 해보겠습니다
블랙보드의 Target (플레이어) 위치로 SetFocus 시켜주겠습니다
Service Task를 생성후 코드를 작성해주었습니다
.h입니다
UCLASS()
class BLASTERDREAM_API UService_StrafeFocus : public UBTService_BlackboardBase
{
GENERATED_BODY()
public:
UService_StrafeFocus();
virtual void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
};
.cpp입니다
블랙보드에서 aicontroller안의 Target 블랙보드값을 들고와서
그값에 focus 시켜주는 ticktask입니다
UService_StrafeFocus::UService_StrafeFocus()
{
NodeName = "Strafe Focus";
bNotifyTick = true;
}
void UService_StrafeFocus::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);
AAIController* aICon = OwnerComp.GetAIOwner();
if (aICon == nullptr)
{
return;
}
UBlackboardComponent* blackBoard = aICon->GetBlackboardComponent();
AActor* targetActor = Cast<AActor>(blackBoard->GetValueAsObject("Target"));
if (targetActor)
{
FVector TargetLocation = targetActor->GetActorLocation();
aICon->SetFocalPoint(TargetLocation);
}
}
EQS이동할떄와 따라올때 플레이어를 바라보도록 Service를 추가해주었습니다
플레이어를 바라보면서 strafe이동을 하면서 플레이어를 공격합니다
strafe이동을 할떄 AI의 이동속도를 낮춰주도록 하겠습니다
AI의 maxwalkspeed를 변경할 task를 만들어주었습니다
Property에 사용되는 aiSpeed값 만큼 스피드를 변경합니다
public:
UTask_SkeletonSpeedChange();
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& ownerComp, uint8* nodeMemory) override;
UPROPERTY(EditAnywhere, Category="MySettings")
float aiSpeed;
};
UTask_SkeletonSpeedChange::UTask_SkeletonSpeedChange()
{
NodeName = "Change Speed";
}
EBTNodeResult::Type UTask_SkeletonSpeedChange::ExecuteTask(UBehaviorTreeComponent& ownerComp, uint8* nodeMemory)
{
Super::ExecuteTask(ownerComp, nodeMemory);
ABasicSkeletonEnemy* skeleton = Cast<ABasicSkeletonEnemy>(ownerComp.GetAIOwner()->GetPawn());
skeleton->GetCharacterMovement()->MaxWalkSpeed = aiSpeed;
return EBTNodeResult::Succeeded;
}
eqs이동을 할때는 230으로 공격한뒤에는 원래의 이동속도 400으로 변경시켜주었습니다
결과물
애니메이션, 스켈레탈 메시 다른버전을 적용해주었습니다
'Unreal5 프로젝트 다이어리' 카테고리의 다른 글
Unreal - 쿼터뷰 시점 벽에 붙었을떄 카메라의 이동 (0) | 2025.05.15 |
---|---|
Unreal - AI의 회피 알고리즘( RVO ) (0) | 2025.05.10 |
Unreal - AI에게 HP 프로그래스바 붙히기 (0) | 2025.05.09 |
Unreal - ProjectTile을 사용한 원거리 AI 만들기 (0) | 2025.05.09 |
Unreal - Behavior Tree(5) 스폰 / 디스폰 (0) | 2025.05.08 |