AI
서비스로 값 전달
- Composit이 성공되는 동안 그곳에 붙인 서비스(Melee)의 값에 따라 접근, 순찰, 공격이 나뉘어진다.
- 즉, ‘타겟’을 감지하면 트리에 값을 세팅한다.
- 순서
- AIController에서 타겟을 감지하면 블랙보드의 ‘타겟’키에 해당 엑터를 세팅한다.
- AIController에 있는 AIBehaviorComponent의 GetTarget으로 해당 타겟을 가져온다.
- CBTService_Melee
- CPP 파일 - TickNode 함수 확인 - AIBehaviorCOmponent를 지정하고, CEnemy_AI에 붙은 컴포넌트를 가져오기 위해 ‘컴포넌트 가져오기’ 함수를 호출하고 AIBehaviorComponent 포인터 변수 ‘비헤이비어’를 선언하고 저장한다. - ‘비헤이비어’의 ‘타겟 가져오기’를 호출한 결과를 캐릭터 포인터 변수 ‘타겟’을 선언하고 저장한다. (Null이 아니라면 감지 됐음, Null이라면 감지되지 않았음) - ‘타겟’이 Null이라면 : ‘비헤이비어’의 ‘대기 모드 설정’ 함수 호출하고 반환한다. (나중에는 순찰모드로 바꾼다.) - ‘ai’의 ‘지정된 객체부터의 거리 가져오기’에 ‘타겟’을 넣은 결과를 ‘거리’ 변수를 선언하고 저장한다. (트리를 사용하는 AI와 타겟 과의 거리 구하기) - ‘거리’가 ‘공격 범위’보다 작다면 : ‘비헤이비어’의 ‘공격 모드 설정’ 함수를 호출하고 반환한다. - ‘비헤이비어’의 ‘접근 모드 설정’ 함수 호출
//CPP //OwnerComp : 트리를 실행하는 엑터(Controller)를 반환한다. //트리는 컨트롤러에서 실행시키기 때문이다. //즉, 오너 : 트리 컴포넌트, 오너의 오너 : 컨트롤러, 생성한 컨트롤러로 캐스팅 한것이다. ACAIController* controller = Cast<ACAIController>(OwnerComp.GetOwner()); ACEnemy_AI* ai = Cast<ACEnemy_AI>(controller->GetPawn()); //GetPawn : 컨트롤러가 빙의한 캐릭터의 폰이 반환된다. UCAIBehaviorComponent* behavior = CHelpers::GetComponent<UCAIBehaviorComponent>(ai); //ai 상태 설정 ACharacter* target = behavior->GetTarget(); //타겟 탐지 못하면 if (target == nullptr) { //대기 상태 설정 behavior->SetWaitMode(); return; } float distance = ai->GetDistanceTo(target); if (distance < ActionRange) { behavior->SetActionMode(); return; } //추격 모드 설정 behavior->SetApproachMode(); - CAIBehaviorComponent
- 헤더 파일 - ‘타겟 가져오기’ 함수 선언
//header public: class ACharacter* GetTarget();- CPP 파일 - ‘타겟 가져오기’ 함수 정의 - ‘블랙 보드’의 ‘오브젝트 값 가져오기’ 함수에 ‘타겟 키’를 넣어 호출하면 ‘플레이어 엑터’가 반환되니 그것을 캐릭터 자료형으로 ‘캐스트’하여 반환한다.
//CPP ACharacter* UCAIBehaviorComponent::GetTarget() { //타겟 반환 return Cast<ACharacter>(Blackboard->GetValueAsObject(TargetKey)); } - BT_Melee
- AI의 상태가 변할때 AIBehaviorComponent의 ChangeType 함수가 호출되고 이때, 블랙보드의 AIStateTypeKey에 AI 상태 타입이 설정된다.
- 주의 사항 : 블랙보드에서 키의 키타입을 제대로 지정하지 않았다면, 트리에서 키를 설정할때 키 타입을 지정하지 않은 키는 리스트에도 나오지 않는다.
- 루트 - Selector 검색 - Selector 우클릭 후 서비스 : Melee - Wait 검색 후 연결 - Wait 우클릭 후 데코레이트 : 블랙보드 , 블랙보드 키 : AIState , 키 쿼리 : Is Equal To , 노드 이름 변경 : Wait - 노드 복사 붙여넣기 - 이름 변경 : Approach - 복사 붙여넣기 후 이름변경 : Action
- Wait 노드 클릭 - 관찰자 중단 : Self - 관찰자 노티파이 : On Result Change
- Move To 검색 - 허용 가능 반경 : 5.0 , 도달 테스트 에이전트 ‘ 목표 반경 포함 체크 , 블랙보드 키 : Target - Approach 데코레이트 노드를 Move To 노드에 할당 - Wait 노드 삭제 후 그 자리에 연결
- 결과 : 탐지를 벗어나면 Wait 노드 , 탐지에 걸리면 Approach 노드 , 가까이 다가가면 Action 노드로 바뀌는것을 확인했다.
- 관찰자 중단 : Self - 관찰자 노티파이 : On Result Change로 설정해서 감지가 되면 즉시 넘어가게 된다.
Enemy를 타겟에게 접근시키기
- AI를 이동시키기 위해서는 NavMesh가 필요하다.
- NavMeshBoundsVolume : NavigationMesh가 계산될 영역이다.
- NavMeshBoundsVolume이 2개 이상 있다면 트리에서 ‘필터 클래스’ 라는것을 사용해야하지만 그런 일은 많이 없다.
- 크기는 ‘스케일’이 아니라 ‘브러시 세팅’으로 크기 조절을 한다.
- 브러시 세이프 : NavMeshBoundsVolume의 영역 모양을 지정하는 옵션이다.
- 볼륨의 ‘높이’는 발을 딛는 메쉬를 전부 감쌀 정도의 높이여야한다.
- 감싸는 높이가 메쉬의 높이 도중과 겹치게 된다면 계산이 안될 수 있다.
- 새로운 엑터를 볼륨 안에 배치하면 해당 오브젝트를 비켜갈 수 있도록 해당 영역을 비포함시켜 재계산한다.
- NavMesh를 사용하는 AI와 타겟으로 지정한 엑터가 모두 볼륨 영역 안에 있어야 계산을 한다.
- AI 디버깅 모드에서 확인하면 현재 수행되는 노드, 수행되는 컴포짓, 조건을 만족해서 수행되는 Task등을 볼 수 있다.
- NavMesh 배치
- 엑터 배치 - 볼륨 - NavMeshBoundsVolume을 레벨 에디터에 배치
- CMovementComponent
- 헤더 파일 - 걷기, 달리기, 질주 속도를 가져오는 함수를 선언하고 해당 속도를 반환하는 것으로 정의한다.
//header //트리 속도 테스크 속도 설정 가져오기 FORCEINLINE float GetWalkSpeed() { return Speed[(int32)ESpeedType::Walk]; } FORCEINLINE float GetRunSpeed() { return Speed[(int32)ESpeedType::Run]; } FORCEINLINE float GetSprintSpeed() { return Speed[(int32)ESpeedType::Sprint]; } - CBTTaskNode_Speed
- 생성 - BTTaskNode 상속
- 헤더 파일 - 생성자 선언 - 트리 노드 결과 열거형 : 타입 자료형의 ‘테스크 실행’ 함수 재정의 선언 - MovementComponent 클래스의 열거형 자료형인 ESpeedType을 사용하기 위해 변수 ‘타입’을 선언
//header private: UPROPERTY(EditAnywhere, Category = "Type") ESpeedType Type; //속도 타입 저장 public: UCBTTaskNode_Speed(); private: EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory);- CPP 파일 - 생성자 정의 - 클래스 내부에 선언된 ‘노드 이름’을 ‘Speed’로 설정한다. - ‘테스크 실행’ 함수 정의 - 매개 변수로 들어온 서비스의 오너 컴포넌트인 ‘비헤이비어 트리’의 ‘오저 가져오기’함수를 호출하고 해당 트리를 실행하는 엑터가 AIController이기 때문에 해당 클래스로 ‘캐스트’한 결과를 AIController 포인터 변수 ‘컨트롤러’를 선언하고 저장한다. - ‘컨트롤러’의 ‘폰 가져오기’ 함수를 호출하고 해당 폰이 Enemy_AI이기 때문에 해당 클래스로 ‘캐스트’하고 결과를 Enemy_AI 포인터 변수 ‘ai’ 를 선언하고 저장한다. - 적 엑터가 저장되어있는 ‘ai’내부의 MovementComponent를 지정해서 ‘컴포넌트 가져오기’ 함수를 호출한 결과를 MovementCOmponent 포인터 변수 ‘무브먼트’ 변수를 선언하고 저장한다. - ‘무브먼트’의 ‘속도 설정’ 함수에 ‘타입’을 넣고 호출한다. - 노드 결과 열거형의 ‘성공’을 반환한다.
//CPP UCBTTaskNode_Speed::UCBTTaskNode_Speed() { NodeName = "Speed"; } EBTNodeResult::Type UCBTTaskNode_Speed::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) { Super::ExecuteTask(OwnerComp, NodeMemory); //OwnerComp : 트리를 실행하는 엑터(Controller)를 반환한다. //트리는 컨트롤러에서 실행시키기 때문이다. //즉, 오너 : 트리 컴포넌트, 오너의 오너 : 컨트롤러, 생성한 컨트롤러로 캐스팅 한것이다. ACAIController* controller = Cast<ACAIController>(OwnerComp.GetOwner()); ACEnemy_AI* ai = Cast<ACEnemy_AI>(controller->GetPawn()); //GetPawn : 컨트롤러가 빙의한 캐릭터의 폰이 반환된다. UCMovementComponent* movement = CHelpers::GetComponent<UCMovementComponent>(ai); //무브먼트 컴포넌트 가져오기 movement->SetSpeed(Type); //속도 설정 //노드 실행 결과(성공, 실패, 진행중) 반환 return EBTNodeResult::Succeeded; //Task노드가 열거 자료형이라서 반환도 열거로 해야한다. } - BT_Melee
- Speed 테스크 노드 검색했을떄 나오는 ‘BTTask Node Speed 0’ 노드는 해당 노드가 컴파일 되기 전 노드 결과를 반환하는 노드이니 컴파일 된 노드인 ‘Speed’ 노드를 사용한다.
- Sequence 검색 후 Approach 데코 노드를 Sequence 노드로 옮긴다. - 남은 Move To 노드를 Sequence 다음으로 연결하고 있던 자리에 Sequence노드를 Selector와 연결한다. - Speed 노드 검색 후 연결
- 결과 : 실행시 Speed가 먼저 성공하고 Sequence에 의해 Move To가 수행되고 해당 속도로 이동하게 된다.
순찰 모드 정의
- CBTService_Melee
- CPP 파일 - TickNode 확인 - 타겟 비었을떄 확인 - SetWaitMode를 PatrolMode로 변경
//CPP //타겟 탐지 못하면 if (target == nullptr) { //순찰 상태 설정 //behavior->SetWaitMode(); behavior->SetPatrolMode(); return; } - CAIBehaviorComponent
- 순찰 모드도 여기서 선언하고, 이동하려는 순찰 위치도 여기서 선언한다.
- 블랙보드 키도 여기서 선언하고 설정한다.
- 헤더 파일 - ‘순찰 위치 가져오기’ 함수 선언 - ‘순찰 위치 설정’ 함수를 선언하고 ‘FVector 주소 레퍼런스 변수’를 받을 수 있도록하고 값이 복사되지 않도록 ‘상수’로 설정한다. - ‘페트롤 위치 키’ 변수를 선언하고 ‘PatrolLocation’으로 초기화한다. - 블랙보드를 저장할 ‘블랙보드’ 변수 선언
//header //순찰 위치 설정 및 가져오기 UPROPERTY(EditAnywhere, Category = "Key") FName PatrolLocationKey = "PatrolLocation"; private: class UBlackboardComponent* Blackboard; //블렉보드 저장 FVector GetPatrolLocation(); void SetPatrolLocation(const FVector& InLocation);- CPP 파일 - ‘순찰 위치 가져오기’ 함수 정의 - ‘블랙 보드’의 ‘벡터 값 가져오기’ 함수에 ‘순찰 위치 키’를 넣고 호출 결과를 반환한다. - ‘순찰 위치 설정’ 함수 정의 - ‘블랙보드’의 ‘벡터 값 설정’ 함수에 ‘순찰 위치 키, 매개변수로 들어온 위치’를 넣고 호출한다.
//CPP FVector UCAIBehaviorComponent::GetPatrolLocation() { return Blackboard->GetValueAsVector(PatrolLocationKey); } void UCAIBehaviorComponent::SetPatrolLocation(const FVector& InLocation) { Blackboard->SetValueAsVector(PatrolLocationKey, InLocation); } - CBTTaskNode_Patrol
- 생성 - BTTaskNode 상속
- 헤더 파일 - 기즈모 그리는것을 띄울 토글인 ‘디버그 모드’ 변수 선언 - ‘도착 거리’ 변수 선언하고 20으로 초기화한다. - 시작 지점에서 반경 내에서 랜덤하게 이동하기 위해 ‘랜덤 반경’ 변수를 선언하고 1500으로 초기화한다. - 생성자 선언 - 클래스 내부 함수인 ‘테스크 실행’ 함수 재정의 선언 - 클래스 내부 함수인 ‘TickTask’ 함수 재정의 선언
//header private: UPROPERTY(EditAnywhere, Category = "Patrol") bool bDebugMode; //순찰 포인트 디버그 보이는거 끄고 키기 UPROPERTY(EditAnywhere, Category = "Patrol") float GoalDistance = 20; UPROPERTY(EditAnywhere, Category = "Patrol") float RandomRadius = 1500; //순찰 반경에서 랜덤 반경으로 이동시킬거다. public: UCBTTaskNode_Patrol(); protected: EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override; void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;- CPP 파일 - 생성자 정의 - 클래스 내부 변수 ‘노드이름’을 ‘Patrol’로 설정한다. - 클래스 내부의 틱 실행 여부 변수인 ‘NotifyTick 확인’ 변수를 true로 설정한다. - ‘테스크 실행’ 함수 정의 - 매개 변수로 들어온 서비스의 오너 컴포넌트인 ‘비헤이비어 트리’의 ‘오저 가져오기’함수를 호출하고 해당 트리를 실행하는 엑터가 AIController이기 때문에 해당 클래스로 ‘캐스트’한 결과를 AIController 포인터 변수 ‘컨트롤러’를 선언하고 저장한다. - ‘컨트롤러’의 ‘폰 가져오기’ 함수를 호출하고 해당 폰이 Enemy_AI이기 때문에 해당 클래스로 ‘캐스트’하고 결과를 Enemy_AI 포인터 변수 ‘ai’ 를 선언하고 저장한다. - ‘ai’ 내부의 컴포넌트인 ‘AIBehaviorCOmponent’를 지정해서 ‘컴포넌트 가져오기’ 함수를 호출하고 해당 클래스의 포인터 변수 ‘비헤이비어’를 선언하고 저장한다. - ‘ai’의 ‘엑터 위치 가져오기’ 함수를 호출한 결과를 ‘위치’ 변수를 선언하고 저장한다. - 네비게이션 시스템 구조체의 ‘현재 가져오기’ 함수를 ‘네이게이션 시스템 V1’ 클래스로 지정하고 ‘ai의 월드 가져오기 함수 호출’을 넣어 호출한 결과로 반환된 NavMesh 볼륨 영역을 네비게이션 시스템 V1 포인터 변수 ‘네이게이션’을 선언하고 저장한다. - ‘네이게이션’이 비어있으면 ‘노드 결과 : 실패’를 반환한다. - 네비 위피 구조체 자료형의 ‘포인트’ 이름으로 지정하고 ‘위치’를 넣고 호출한다. (해당 포인트가 Navi 위에 이동할 지점이 된다.) - ‘네비게이션’의 ‘네비 이동 가능 반경 안의 랜덤 지점 가져오기’ 함수에 ‘위치, 랜덤 반경, 반환된 지점 저장 변수 : 포인트’를 호출하고 갈 수 있는 지점이 반환될 때 까지 무한 반복을 돌린다. - ‘비헤이비어’의 직접 선언한 ‘순찰 위치 설정’함수에 ‘포인트의 위치’를 넣고 호출한다. - ‘노드 결과 : 진행중’을 반환한다.
//CPP UCBTTaskNode_Patrol::UCBTTaskNode_Patrol() { NodeName = "Patrol"; bNotifyTick = true; //틱 실행 여부 } EBTNodeResult::Type UCBTTaskNode_Patrol::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) { Super::ExecuteTask(OwnerComp, NodeMemory); ACAIController* controller = Cast<ACAIController>(OwnerComp.GetOwner()); ACEnemy_AI* ai = Cast<ACEnemy_AI>(controller->GetPawn()); UCAIBehaviorComponent* behavior = CHelpers::GetComponent<UCAIBehaviorComponent>(ai); //이동 지점 구하기 //현재 지점에서 일정 반경을 구할거다. FVector location = ai->GetActorLocation(); //현재 위치 UNavigationSystemV1* navigation = FNavigationSystem::GetCurrent<UNavigationSystemV1>(ai->GetWorld()); //자신이 있는 네비 메쉬 구역이 리턴된다. CheckNullResult(navigation, EBTNodeResult::Failed); FNavLocation point(location); //네비 메쉬 위치에 가려는 위치 지정 //GetRandomPointInNavigableRadius : 네비 메쉬에서 갈 수 없는 곳이라면 false가 나온다. //갈 수 있는 곳이 나올때까지 반복한다. while (true) { if (navigation->GetRandomPointInNavigableRadius(location, RandomRadius, point)) { break; } } behavior->SetPatrolLocation(point.Location); return EBTNodeResult::InProgress; //끝은 Tick에서 낸다. }- TickTask 함수 정의 - 매개 변수로 들어온 서비스의 오너 컴포넌트인 ‘비헤이비어 트리’의 ‘오저 가져오기’함수를 호출하고 해당 트리를 실행하는 엑터가 AIController이기 때문에 해당 클래스로 ‘캐스트’한 결과를 AIController 포인터 변수 ‘컨트롤러’를 선언하고 저장한다. - ‘컨트롤러’의 ‘폰 가져오기’ 함수를 호출하고 해당 폰이 Enemy_AI이기 때문에 해당 클래스로 ‘캐스트’하고 결과를 Enemy_AI 포인터 변수 ‘ai’ 를 선언하고 저장한다. - ‘ai’ 내부의 컴포넌트인 ‘AIBehaviorCOmponent’를 지정해서 ‘컴포넌트 가져오기’ 함수를 호출하고 해당 클래스의 포인터 변수 ‘비헤이비어’를 선언하고 저장한다. - ‘비헤이비어’의 ‘순찰 위치 가져오기’ 함수를 호출한 결과를 ‘위치’ 변수를 선언하고 저장한다. - ‘컨트롤러’의 ‘위치로 이동’ 함수에 ‘위치, 도착 거리, 가다가 어떤 물체와 겹칠때 움직임 정지 여부 : false’를 넣고 호출한 결과를 길 이동 열거 자료형 : 타입 의 ‘결과’ 변수를 선언하고 저장한다. - 해당 결과를 가지고 이동 노드의 성공 실패를 나누기 위해 switch에 ‘결과’를 넣고 호출한다. : 길찾기 결과 : 실패라면 : ‘테스크가 종료될때 호출되는 함수’에 ‘오너 컴포넌트 : 트리, 노드 결과 : 실패’를 넣고 호출한뒤 반환하고 , 길찾기 결과 : 거의 도착 이라면 : ‘테스크가 종료될때 호출되는 함수’에 ‘오너 컴포넌트 : 트리, 노드 결과 : 성공’을 넣고 호출한뒤 반환한다.
//CPP void UCBTTaskNode_Patrol::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) { Super::TickTask(OwnerComp, NodeMemory, DeltaSeconds); ACAIController* controller = Cast<ACAIController>(OwnerComp.GetOwner()); ACEnemy_AI* ai = Cast<ACEnemy_AI>(controller->GetPawn()); UCAIBehaviorComponent* behavior = CHelpers::GetComponent<UCAIBehaviorComponent>(ai); FVector location = behavior->GetPatrolLocation(); //가려는 위치 저장 //Move To : 컨트롤러의 함수다. EPathFollowingRequestResult::Type result = controller->MoveToLocation(location, GoalDistance, false); switch (result) { case EPathFollowingRequestResult::Failed: { FinishLatentTask(OwnerComp, EBTNodeResult::Failed); break; } case EPathFollowingRequestResult::AlreadyAtGoal: { FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded); break; } } } - BT_Melee
- Wait 노드 복사 붙여넣기 후 이름 변경 : Patrol , 키 값 : Patrol
- BB_Melee
- Vector같이 명확한 키 타입은 따로 자료(열거형 자료형, 오브젝트 클래스)를 설정하지 않아도 된다.
- 새 키 - 클래스 타입 : Vector , 이름 : PatrolLocation
- Sequence 검색 후 Patrol 데코 붙이기 - 남은 Wait 삭제하고 그 자리에 Patrol 노드 묶음 연결 - Speed 검색 후 Patrol 노드 묶음에 연결 - Selector 검색 후 Patrol 노드 묶음에 연결 - Selector에 Sequence와 Wait : 1.5 +- 0.5 검색 후 연결 - Sequence에 Patrol 노드와 Wait : 1.5 +- 0.5 검색 후 연결
- 설명
- Patrol 모드가 된다면 ‘데코’가 조건을 받아들여서 Sequence 실행
- 1번 순서인 Speed 노드에서 속도 할당
- 2번 순서인 Selector 진행
- 4번 순서의 Patrol 테스크에서 실패가 나오는 이유 : 도착 반경에 들어가지 못했을때 실패가 나온다.
- 실패가 되면 Wait이 실행되고 다시 Sequence 노드로 이동한다.
- 3번 순서인 Sequence 진행
- 4번 순서인 Patrol에서 Patrol 테스크 진행
- 순찰 목적지에 도달하면 성공
- 5번 순서인 Patrol 테스크 형제노드 Wait에서 대기
- UNavigationSystemV1 컴파일 에러 문제
- 클래스 네임 복사 후 구글 검색 또는 언리얼 에디터 상단 도움말 - C++ API 레퍼런스 클릭 후 붙여넣기
- 모듈 복사 후 Build.cs의 항목에 붙여넣기
순찰 도달점 확인
- CBTTaskNode_Patrol
- CPP 파일 - ‘테스크 실행’ 함수 확인 - 마지막 반환 전에 확인 - ‘디버그 모드’가 체크되어있다면 : ‘디버그 구형 그리기’ 함수에 ‘ai의 월드, 포인트의 위치, 구형 반경 : 10, 정밀도 : 10, 색깔 : 녹색, 자동삭제 여부 : true, 유지시간 : 5초)를 넣고 호출한다.
//CPP if (bDebugMode) { DrawDebugSphere(ai->GetWorld(), point.Location, 10, 10, FColor::Green, true, 5); }
순찰 경로 생성
- CPatrolPath
- 곡선 그리기 : Spline / 곡선을 자유자재로 제어하는 기능 : Curve
- 순찰 경로를 생성하고 레벨 에디터에 배치하기 위해 생성했다.
- Label : 아웃 라이너에 띄워지는 이름이다.
- 생성 - 엑터 컴포넌트 상속
- 헤더 파일 - ‘루프 확인’ 변수 선언 - ‘포인트 순번’ 변수 선언 - ‘역방향 확인’ 변수 선언 - 씬 컴포넌트 클래스 포인터 변수 ‘루트’ 선언 - 텍스트 렌더 컴포넌트 클래스 포인터 변수 ‘텍스트’ 선언 - 곡선 컴포넌트 클래스 포인터 변수 ‘곡선’ 선언 - 생성한 곡선을 가져오기 위해 곡선 클래스 포인터 자료형의 ‘곡선 가져오기’ 함수를 선언하고 ‘곡선’ 변수를 반환하는 것으로 정의한다. - 생성자 선언 - 클래스 내부 함수인 ‘Construction 활성’ 함수 재정의 선언 - BeginPlay 재정의 선언
//header private: UPROPERTY(EditAnywhere, Category = "Path") bool bLoop; UPROPERTY(EditAnywhere, Category = "Path") int32 Index; UPROPERTY(EditAnywhere, Category = "Path") bool bReverse; private: UPROPERTY(VisibleDefaultsOnly) class USceneComponent* Root; UPROPERTY(VisibleDefaultsOnly) class USplineComponent* Spline; //직선 UPROPERTY(VisibleDefaultsOnly) class UTextRenderComponent* Text; public: FORCEINLINE class USplineComponent* GetSpline() { return Spline; } public: ACPatrolPath(); void OnConstruction(const FTransform& Transform) override; //값이 변하면 호출되는 클래스 내부 함수다. protected: virtual void BeginPlay() override;- CPP 파일 - 생성자 정의 - ‘드래그 시 컨스트럭션 스크립트 실행 확인’ 변수를 false로 설정한다. - 루트, 곡선, 텍스트 컴포넌트를 ‘컴포넌트 생성’ 함수를 호출해서 저장한다. - ‘곡선’의 ‘상대적 위치 설정’에 ‘(0, 0, 30)’의 위치를 넣고 호출한다. - ‘텍스트’의 위치, 회전, 글자정렬, 글자 색, 화면상에 보이기 설정 등의 설정을 실행한다. - ‘Contruction’ 활성 함수 정의 - 부모 함수 호출 - ‘텍스트’의 ‘텍스트 설정’에 ‘문자열 변환’ 함수에 ‘엑터 라벨 가져오기’ 함수 호출을 넣고 호출한 결과를 저장한다. - ‘곡선’의 ‘루프 닫기 설정’ 함수에 ‘루프 확인’ 변수를 넣고 호출한다.
//CPP ACPatrolPath::ACPatrolPath() { bRunConstructionScriptOnDrag = false; //드래그때 계속 호출되는 여부 확인 CHelpers::CreateComponent<USceneComponent>(this, &Root, "Root"); CHelpers::CreateComponent<USplineComponent>(this, &Spline, "Spline", Root); CHelpers::CreateComponent<UTextRenderComponent>(this, &Text, "Text", Root); Spline->SetRelativeLocation(FVector(0, 0, 30)); //살짝 띄워서 출력 Text->SetRelativeLocation(FVector(0, 0, 120)); Text->SetRelativeRotation(FRotator(0, 180, 0)); Text->HorizontalAlignment = EHorizTextAligment::EHTA_Center; Text->TextRenderColor = FColor::Red; Text->bHiddenInGame = true; } void ACPatrolPath::OnConstruction(const FTransform& Transform) { Super::OnConstruction(Transform); Text->Text = FText::FromString(GetActorLabel()); //포인트 이름 띄우기 Spline->SetClosedLoop(bLoop); //순찰 루프 만들기 설정 } - BP_CPatrolPath
- Spline의 점 하나 : 제어점이라고 부른다.
- 이것을 복사하면 구부러지는 선을 만들 수 있다.
- 제어점의 위치를 이동시키면 선도 자동으로 길어진다.
- 제어점에 붙은 각도설정 직선의 위치를 조절하면 곡선의 각도를 설정할 수 있다.
- 레벨 에디터에 배치 - 제어점 복사 - 경로 설정
- Spline의 점 하나 : 제어점이라고 부른다.
Behavior 설명
- 불편한 점 : FSM은 ‘인터럽트’가 있어서 옵션이 들어오는거에 따라 인터럽트가 발생한 곳에서 다시 시작할거냐, 중단할거냐, 점프해서 넘어갈거냐를 강제로 수행할 수 있다.
- 트리는 뒤로 되돌아가는 기능은 없지만 노드 실행이 끝나고 위로 올라갈수는 있다.
- 배치된 노드 우측 상단 숫자 : 노드 실행 순서
- Root : 트리의 시작
- Blanch : 트리의 중간
- Composite : Task를 어떻게 수행하는지를 결정하는 노드들이다.
- Selector : 성공하면 부모 노드로 올라가서 성공으로 반환한다.
- 실패하면 다음 노드로 넘어간다.
- 즉, 마지막 실행 상태를 부모 노드에게 반환한다.
- Sequence : 이전 작업이 성공하면 다음 노드로 이동한다.
- 패러렐 : 메인과 서브가있다.
- 메인이 끝나면 서브도 끝난다.
- 메인과 서브의 작업들이 동시에 돌아기면서 동작하지만, 메인이 끝나면 서브들의 작업도 끝난다.
- Selector : 성공하면 부모 노드로 올라가서 성공으로 반환한다.
- Composite : Task를 어떻게 수행하는지를 결정하는 노드들이다.
- Leaf : 트리의 끝
- Task(작업)로 배치된다.
- 성공, 실패, 진행중이 있다.
- Wait : 대기시간이 모드 끝나면 성공을 띄운다.
- 리프는 Task 하나만 있다.
- Move To : 해당 트리를 가지고 있는 컨트롤러에게 이동을 명령하는 노드다.
- ‘위치’를 줄 수도 있고, ‘엑터’를 줄 수도 있다.
- 허용 가능 반경 : 해당 반경 안에 들어가면 정지한다.
- ex, (0, 0, 0)이라는 값을 주면, 그 근처의 허용 가능 반경에 들어가게 되면 ‘도착했다고 판단’하는 범위다.
- 도달 테스트에 에이전트 반경 포함 : 자신의 엑터에 포함된 캡슐 콜리전 반경도 도착 허용 가능 반경에 포함시킬지에 대한 여부 확인 체크다.
- 도달 테스트에 목표 반경 포함 : 타깃의 엑터에 포함된 캡슐 콜리전 반경도 도착 허용 가능 반경에 포함시킬지에 대한 여부 확인 체크다.
- 블랙보드 키 : Position이나 Actor 둘 중 하나의 ‘블랙보드 키’가 들어올 수 있다.
- 설정한 키를 향해 이동하게 된다.
- Task(작업)로 배치된다.
- Decorater : 조건(블랙보드 값)을 판단하고 조건을 충족하면 노드를 실행한다.
- 조건이 맞지 않는다면 해당 노드를 ‘실패’로 띄운다.
- 조건이 참 : 성공 / 조건이 거짓 : 실패 로 해당 노드를 띄운다.
- Leaf와 Blanch 어디에나 붙을 수 있다.
- CoolTime이라는 데코레이션은 쿨타임 시간이 지나면 ‘성공’으로 띄운다.
- 조건이 맞지 않아 앞으로 실행이 되지 않더라도, 실행 도중 조건이 맞이 않게된다면 이미 실행되는 순서들은 계속 실행된다.
- 조건이 될 노드는 기획이나 AI 디자이너가 담당하게 된다.
- 기본적으로는 블랙보드를 사용한다.
- 키 쿼리
- Is Set : 키에 값이 설정되었다면 노드를 실행한다.
- Target is Not Set : 키에 값이 설정되지 않았다면 노드를 실행한다.
- 관찰자 노티파이
- On Result Change : 조건이 변경될 때 재시작한다.
- 즉, 블랙보드 값과 키값의 조건을 맞추고 있을때 비교한 결과가 달라지면 재시작 한다는 의미인것같다.
- On Value Change ; 관찰되는 블랙보드 값이 바뀔 때마다 재시작한다.
- On Result Change : 조건이 변경될 때 재시작한다.
- 관찰자 중단
- None : 데코의 조건이 실패해도 ‘중단 시키지 않고’ 다시 실행 시킨다.
- Self : 조건을 만족하지 못하면 해당 노드를 중단시키고 ‘실패’로 띄운다.
- LowerPriority : 조건이 실패하면 자신을 제외한 형제 노드들을 정지시킨다.
- Both : 조건이 실패하면 자신을 포함해 형제들을 전부 중단시킨다.
- 조건이 맞지 않는다면 해당 노드를 ‘실패’로 띄운다.
- Service : 값을 제공하기 위한 옵션이다.
- 디테일 - 서비스 - 간격 : 서비스가 실행되는 간격
- 디테일 - 서비스 - 랜덤 편차 : 간격에 + - 하는 값을 넣고 해당 범위를 간격으로 하는 옵션이다.
- Run EQS
- 디테일 - 블랙보드 - 키 쿼리 : 블랙보드 키의 키타입에 따라 키 쿼리의 목록이 달라진다.
- Object : Is Set / Is Not Set
- Enum : Is Equal To : 같을 때 / Is Not Equal To : 같지 않을 때 / Is Less Than : 적을 때 / Is Less Than Or Equal To ; 작거나 같을 때 / Is Greater Than : 클때 / Is Greater Than Or Equal To : 크거나 같을 때
순찰 경로 이론
- bLoop : 0 ~5가고 끝나면 다시 5에서 0으로 가고, 0부터 시작한다.
- bReverse : 0 ~ 5 가고 끝나면 5 ~ 0으로 시작한다.
- bIndex : 이동 포인트가 배치된 순서다.
자잘한 이론
- 제작한 언리얼 작업을 실제 게임파일로 만드는 과정 : 패키징
- 전체 ‘빌드’해서 실행 가능한 게임 파일을 만들게 된다.