어제거 정리
트리 정리
- Root - Selector(Service : 상태 세팅 / Target : 추적 대상 / AIState : AI 상태)
- 블랙보드를 관리하기 위해 둔것 : UCBehaviorComponent
- PatrolPath를 놓은 곳 : Enemy_AI
- Patrol (Target이 null일때 조건 충족)
- Approach - Speed - MoveTo
- Action - Equip - Action - Wait
- PatrolPath(Enemy_AI)
- Index, bReverse, bLoop
- MoveTo ; 갈 지점을 가져온다.
- 해당 번호에 도달하면 UpdateIndex를 호출해서 다음 올 순번을 가져온다.
- Loop와 Reverse 순번에 대한 내용도 UpdateIndex에 있다.
- 그 후, 성공이 반환되니 Root로 올라가고 다시 내려온다.
- 해당 번호에 도달하면 UpdateIndex를 호출해서 다음 올 순번을 가져온다.
Equip 에러 설명 정리
- Equip을 했더니 플레이어의 무기도 같이 작동했다.
- 이유 : Enemy_AI에 WeaponComponent를 넣었다.
- WeaponComponent에는 WeaponAsset Datas[]가 있었다.
- DA들이 Datas에 들어가있다.
- 그런데, 적이 가지고있는 DA와 플레이어의 DA가 같은거였다.
- 같은거이니 같이 작동이 되는 것이다.
- WeaponComponent에는 WeaponAsset Datas[]가 있었다.
- 해결방법 : CWeaponAsset과 CWeaponData를 나눈다.
- 컴포넌트에서 BeginPlay로 Data를 받아서 각 변수마다 웨폰 데이터를 하나씩 받아서 저장한다.
- 즉, Asset에 Attachment ~ CSubAction이 묶여있으니 1대 1 관계였다.
- 이 클래스들의 정보를 WeaponData 클래스를 NewObject로 생성해서 따로 모았다.
- 여태까지는 캐릭터 하나가 WeaponComponent - DA들 - Attachment 등의 클래스들 이었지만, 지금은 캐릭터 - 웨폰 컴포넌트 - DA들 - NewObject - 복사된 Attachment 등의 클래스들이 된것이다.
- 실행 순서 : 웨폰 컴포넌트 BeginPlay 실행 - 웨폰 에셋 생성 - BeginPlay 호출 - WeaponData의 NewObject 생성 - 각 데이터 저장
AI
데미지 모드 제작
- IStatable
- 상태 종료 함수들을 사용해야하는 객체들한테 붙이는 인터페이스다.
- 헤더 파일 - ‘데미지 종료’ 함수 선언
//header public: virtual void End_Backstep() {} virtual void End_Dead() {} virtual void End_Damaged() {} - CEnemy
- IStatable 인터페이스를 상속했으니 사용하는 상태를 추가해야한다.
- Damaged 함수를 사용하는 AI의 기능을 사용하기 위해 자식에서 재정의할 수 있도록 해야한다.
- 헤더 파일 - ‘데미지 종료’ 함수 재정의 선언 - ‘데미지 받음’ 함수 선언 수정 : 자식 사용 가능, 가상화
//header protected: virtual void Damaged(); public: virtual void End_Damaged() override; //애니메이션 노티파이용 함수- CPP 파일 - ‘데미지 종료’ 함수 정의 - ‘상태’의 ‘기본 상태 모드 설정’ 함수 호출
//CPP void ACEnemy::End_Damaged() { State->SetIdleMode(); } - CEnemy_AI
- 데미지 재정의 : 부모의 데미지 정의를 실행하고, 이곳에서는 데미지 받을때의 AI 상태를 정의한다.
- 헤더 파일 - ‘데미지 받음’ 함수 재정의 - ‘데미지 받음 종료’ 함수 재정의
//header protected: void Damaged() override; public: void End_Damaged() override; //애니메이션 노티파이용 함수- CPP 파일 - ‘데미지 받음’ 함수 정의 - 부모 함수 호출(데미지 처리) - ‘상태’의 ‘사망 모드 확인’ 함수 호출 결과가 true라면 실행하지 않는다. - ‘비헤이비어’의 ‘데미지 모드 설정’ 함수 호출 - ‘데미지 받음 종료’ 함수 정의 - 부모 함수 호출 - ‘비헤이비어’의 ‘대기 모드 설정’ 함수 호출
//CPP void ACEnemy_AI::Damaged() { Super::Damaged(); CheckTrue(State->IsDeadMode()); Behavior->SetHittedMode(); } void ACEnemy_AI::End_Damaged() { Super::End_Damaged(); Behavior->SetWaitMode(); } - CAnimNotify_EndState
- 백스탭, 사망, 데미지 같이 상태가 변화하는 이벤트를 종료시키는 노티파이다.
- 헤더 파일 - 기존 노티파이 헤더와 동일
- CPP 파일 - StateType의 조건에 따라 실행하는 코드 확인 - ‘상태 타입 : 데미지’라면 : ‘상태’의 ‘데미지 종료’ 함수를 호출하고 조건문을 종료한다.
//CPP switch (StateType) { case EStateType::Evade: state->End_Backstep(); break; case EStateType::Dead: state->End_Dead(); break; case EStateType::Damaged: state->End_Damaged(); break; } - 데미지 애니메이션 몽타주 파일
- 애니메이션 종료지점 근처에 노티파이 추가 - EndState 노티파이 클릭 - StateType : Damaged 선택
BehaviorTree에서의 데미지 상태 기능들 처리
- CStateComponent
- 외부에서 사용하는 상태 (기본상태, 사망, 데미지)등을 가져오는것을 이곳에서 선언한다.
- 헤더 파일 - ‘데미지 모드 확인’ 함수를 선언하고 ‘현재 타입’이 ‘상태 타입 : 데미지’와 같은지 확인하는 결과를 반환하는것으로 정의한다.
//header FORCEINLINE bool IsDamagedMode() { return Type == EStateType::Damaged; } - CBTTaskNode_Hitted
- 헤더 파일 - 기존 테스크 노드와 같이 선언한다. : 생성자, 테스크 실행, 틱 테스크
//header public: UCBTTaskNode_Hitted(); protected: virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override; virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;- CPP 파일 - 기존 테스크 노드와 같이 겹치는부분을 정의한다. : 생성자(틱 실행, 이름 설정) , 테스크 실행, 틱 테스크 (컨크롤러, ai 캐릭터, 상태 컴포넌트) - 테스크 실행 함수 확인 - ‘컨트롤러’의 ‘움직임 정지’ 함수 호출 - ‘노드 결과 : 진행중’ 반환한다. - TickTask 함수 확인 - ‘상태’의 ‘데미지 모드 확인’ 함수 호출 결과가 false와 같다면 : ‘테스크 정상 종료’에 ‘오너 컴프 : 트리, 노드 결과 : 성공’을 넣고 호출한다.
//CPP UCBTTaskNode_Hitted::UCBTTaskNode_Hitted() { bNotifyTick = true; NodeName = "Hitted"; } EBTNodeResult::Type UCBTTaskNode_Hitted::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) { Super::ExecuteTask(OwnerComp, NodeMemory); ACAIController* controller = Cast<ACAIController>(OwnerComp.GetOwner()); controller->StopMovement(); return EBTNodeResult::InProgress; } void UCBTTaskNode_Hitted::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()); UCStateComponent* state = CHelpers::GetComponent<UCStateComponent>(ai); if (state->IsDamagedMode() == false) { FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded); } } - BT_Melee
- Hitted 부분 확인 - Sequence 노드에 핀을 끌고 ‘Hitted’ 테스크를 검색하고 연결한다.
장착 중 일때 데미지를 받는 처리
- 장착 중에 공격받는다면 : 장착 완료 상태 로 만들고, 공격 중에 공격받는다면 : 공격을 종료시킨다.
- 장착중에 맞았다면 ‘루트’ 노드로 이동되지만 결과가 성공이나 실패가 아니라 ‘중단 : Abort’ 결과로 넘어가게 된다.
- 테스크 노드 클래스에는 기본적으로 ‘테스크 실행 되었을떄’ 함수, ‘테스크 중단 되었을때’ 함수 등이 선언되어있다.
- 장착중은 BeginEquip과 EndEquip을 구분해서 확인해야한다.
- BeginEquip이 시작되지 않았다면 BeginEquip을 실행시켜주고 EndEquip으로 넘어가게된다.
- BeginEquip중이라면 EndEquip을 시켜주고 종료되어야한다.
- CEquipment
- 헤더 파일 - BeginEquip이 호출되었는지 확인하기위해 ‘BeginEquip 확인’ 변수 선언 - ‘BeginEquip 확인 가져오기’ 함수를 선언하고 ‘BeginEquip 확인’ 변수를 반환하는 것으로 정의한다.
//header private: bool bBeginEquip; //BeginEquip이 호출되었는지 확인 (맞았을때 처리하려고)- CPP 파일 - BeginEquip이 실행 되었을때 ‘BeginEquip 확인’ 변수를 true로하고, EndEquip이 실행되었을때 ‘BeginEquip 확인’ 변수를 false로 설정한다.
//CPP void UCEquipment::Begin_Equip_Implementation() { bBeginEquip = true; if (OnEquipmentBeginEquip.IsBound()) OnEquipmentBeginEquip.Broadcast(); } void UCEquipment::End_Equip_Implementation() { bBeginEquip = false; bEquipped = true; //장착 되었다. if (Data.bCanMove == false) Movement->Move(); State->SetIdleMode(); } - CBTTaskNode_Equip
- 헤더 파일 - ‘테스크 중단’ 함수 재정의 선언
//header EBTNodeResult::Type AbortTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;- CPP 파일 - ‘테스크 중단’ 함수 정의 - 기존 함수와 같이 ‘컨트롤러’, ‘적 캐릭터’, ‘무기’를 받는다. - ‘무기’의 ‘Equipment 가져오기’ 함수 호출의 ‘BeginEquip 확인 가져오기’ 함수 호출의 결과를 ‘BeginEquip 확인’ 변수를 선언하고 저장한다. - ‘BeginEquip 확인’ 변수가 false 라면 : 무기 장착 시작을 못한것이니 ‘무기’의 ‘Equipment 가져오기’ 함수 호출의 ‘장착 시작’ 함수를 호출한다. - BeginEquip은 시작한것이니 장착 종료를 호출하기 위해 ‘무기’의 ‘Equipment 가져오기’ 함수 호출의 ‘장착 종료’ 함수를 호출한다. - ‘노드 결과 : 중단됨’ 을 반환한다.
//CPP EBTNodeResult::Type UCBTTaskNode_Equip::AbortTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) { Super::AbortTask(OwnerComp, NodeMemory); ACAIController* controller = Cast<ACAIController>(OwnerComp.GetOwner()); ACEnemy_AI* ai = Cast<ACEnemy_AI>(controller->GetPawn()); UCWeaponComponent* weapon = CHelpers::GetComponent<UCWeaponComponent>(ai); bool bBeginEquip = weapon->GetEquipment()->GetBeginEquip(); if (bBeginEquip == false) { weapon->GetEquipment()->Begin_Equip(); } weapon->GetEquipment()->End_Equip(); //갑자기 중단되었을때의 처리 return EBTNodeResult::Aborted; }
공격 중일때 데미지 받는 처리
- 장착중 처리처럼 BeginAction이 시작 되었는지 확인해야한다.
- CDoAction
- 헤더 파일 - ‘BeginAction 확인’ 변수 선언 - ‘BeginAction 확인 가져오기’ 함수를 선언하고 ‘BeginAction 확인’ 변수를 반환하는 것으로 정의한다.
//header FORCEINLINE bool GetBeginAction() { return bBeginAction; } private: bool bBeginAction;- CPP 파일 - 공격이 시작했을때 ‘BeginAction 확인’ 변수를 true로 하고, 끝날떄 false로 설정한다.
//CPP void UCDoAction::Begin_DoAction() { bBeginAction = true; } void UCDoAction::End_DoAction() { bBeginAction = false; bInAction = false; State->SetIdleMode(); Movement->Move(); } - CBTTaskNode_Action
- 헤더 파일 - ‘테스크 중단’ 함수 재정의 선언
//header EBTNodeResult::Type AbortTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;- CPP 파일 - ‘테스크 중단’ 함수 정의 - ‘무기’ 받는 부분까지 다른 테스크 노드와 같다. - ‘무기’의 ‘DoAction 가져오기’ 함수 호출의 ‘BeginAction 확인 가져오기’ 함수 호출 결과를 ‘BeginAction 확인’ 변수를 선언하고 저장한다. - ‘BeginAction 확인’이 fasle와 같다면 : ‘무기’의 ‘DoAction 가져오기’ 함수 호출의 ‘공격 실행 시작’ 함수를 호출한다. - ‘무기’의 ‘DoAction 가져오기’ 함수 호출의 ‘공격 실행 종료’ 함수를 호출한다. - ‘노드 결과 : 성공’을 반환한다.
//CPP EBTNodeResult::Type UCBTTaskNode_Action::AbortTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) { Super::AbortTask(OwnerComp, NodeMemory); ACAIController* controller = Cast<ACAIController>(OwnerComp.GetOwner()); ACEnemy_AI* ai = Cast<ACEnemy_AI>(controller->GetPawn()); UCWeaponComponent* weapon = CHelpers::GetComponent<UCWeaponComponent>(ai); bool bBeginAction = weapon->GetDoAction()->GetBeginAction(); if (bBeginAction == false) { weapon->GetDoAction()->Begin_DoAction(); } weapon->GetDoAction()->End_DoAction(); return EBTNodeResult::Succeeded; }
장착 중 정지 처리
- 테스크 노드에서 ‘컨트롤러’의 ‘움직임 정지’ 함수를 호출해라
- 지금은 그냥 하지 않았다.
- 나중에 추가하고 싶다면 추가해라
조금 더 확실하게 데미지 처리
- 데미지 받으면 Service도 실행 안되도록 한다.
- CBTService_Melee
- CPP 파일 - ‘상태’의 ‘데미지 모드 확인’ 함수 호출 결과가 true 라면 : ‘비헤이비어’의 ‘데미지 받음 모드 설정’ 함수를 호출하고 반환한다.
//CPP UCStateComponent* state = CHelpers::GetComponent<UCStateComponent>(ai); if (state->IsDamagedMode()) { behavior->SetHittedMode(); return; }
원거리 AI 준비
- Melee 캐릭터의 블랙 보드, AI 컨트롤러, Enemy 캐릭터, BehaviorTree를 복사 후 Melee이름을 Range로 변경한다.
- 블랙보드 파일 - PatorlLocation 키 삭제 ⇒ Range는 순찰하지 않는다. (터렛 같은 느낌)
- AI 컨트롤러 파일 - 시야 거리 : 1000, 상실 반경 : 1200 , 시야 반각도 : 180 , 최대 수명 : 5 ⇒ 넓은 범위에서 360도를 전부 감시하게 설정한다.
- Enemy 캐릭터 파일 - BehaviorTree를 Range용으로 변경한다. - AIControllerClass를 Range 용으로 변경한다.
- BehaiorTree 파일 - 블랙 보드 클래스를 Range 용으로 변경한다.
- 순찰, 접근 노드들은 전부 지운다.
- 서비스를 Range용으로 변경한다.
- CBTService_Range
- 헤더 파일 - 기본 베이스는 Melee와 같다. - 이동 거리를 ‘회피 반경’으로 변경하고 500으로 초기화한다. - ‘디버그 그리기 확인’ 변수 선언
//header private: UPROPERTY(EditAnywhere, Category = "Action") float AvoidRange = 500; //АјАн ЙќРЇ UPROPERTY(EditAnywhere, Category = "Debug") bool bDrawDebug; public: UCBTService_Range(); protected: void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;- CPP 파일 - 기본 베이스는 Melee와 같다. - ‘디버그 그리그 확인’ 변수가 true 라면 : ‘ai’의 ‘엑터 위치 가져오기’ 함수를 호출한 결과를 ‘시작’ 변수를 선언하고 저장한다. - ‘시작’의 ‘Z’에 25를 - 하도록 설정한다. - ‘시작’위치를 ‘끝’ 변수를 선언하고 저장한다. - ‘끝’의 ‘Z’에 50을 + 하도록 설정한다. - ‘디버그 실린더 그리기’ 함수에 ‘ai의 월드 가져오기 함수 호출, 시작, 끝, 회피 반경, 유지 시간 : 10 , 색깔 : red , 무한 유지 : false, 새로 그려지는 시간 : 인터벌’을 넣고 호출한다. - ‘타겟’이 비었으면 순찰이 아니라 ‘대기 모드 설정’ 함수를 호출한다. - ‘컨트롤러’의 ‘포커스 재설정’ 함수에 ‘포커스 우선순위 : 게임 플레이’를 넣고 호출한다. - ‘컨트롤러’의 ‘포커스 설정’ 함수에 ‘타겟’을 넣고 호출한다. - 엑션 반경을 ‘회피 반경’으로 변경한다. - ‘거리’가 ‘회피 반경’보다 작다면 : ‘비헤이비어’의 ‘워프 모드 설정’ 함수를 호출하고, 반환한다. - 회피 반경이 아니라면 ‘비헤이비어’의 ‘공격 모드 설정’ 함수를 호출한다.
//CPP UCBTService_Range::UCBTService_Range() { NodeName = "Range"; Interval = 0.1f; //실행 간격 RandomDeviation = 0.0f; //랜덤 격차 } void UCBTService_Range::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) { Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds); //OwnerComp : 트리를 실행하는 엑터(Controller)를 반환한다. //트리는 컨트롤러에서 실행시키기 때문이다. //즉, 오너 : 트리 컴포넌트, 오너의 오너 : 컨트롤러, 생성한 컨트롤러로 캐스팅 한것이다. ACAIController* controller = Cast<ACAIController>(OwnerComp.GetOwner()); ACEnemy_AI* ai = Cast<ACEnemy_AI>(controller->GetPawn()); //GetPawn : 컨트롤러가 빙의한 캐릭터의 폰이 반환된다. UCAIBehaviorComponent* behavior = CHelpers::GetComponent<UCAIBehaviorComponent>(ai); UCStateComponent* state = CHelpers::GetComponent<UCStateComponent>(ai); if (bDrawDebug) { FVector start = ai->GetActorLocation(); start.Z -= 25.0f; FVector end = start; end.Z += 50.0f; DrawDebugCylinder(ai->GetWorld(), start, end, AvoidRange, 10, FColor::Red, false, Interval); } if (state->IsDamagedMode()) { behavior->SetHittedMode(); return; } //ai 상태 설정 ACharacter* target = behavior->GetTarget(); //타겟 탐지 못하면 if (target == nullptr) { controller->ClearFocus(EAIFocusPriority::Gameplay); behavior->SetWaitMode(); return; } //타겟 발견 했다. controller->SetFocus(target); float distance = ai->GetDistanceTo(target); if (distance < AvoidRange) { behavior->SetAvoidMode(); return; } //추격 모드 설정 behavior->SetActionMode(); } - CBTTaskNode_Equip
- CPP 파일 - 장착 타입에 Bow와 Warp를 넣는다.
//CPP switch (Type) { case EWeaponType::Sword: weapon->SetSwordMode(); break; case EWeaponType::Warp: weapon->SetWarpMode(); break; case EWeaponType::Bow: weapon->SetBowMode(); break; }
TaskNode의 결과 Abort와 Succeeded의 차이
- Abort : 중단 함수 처리 후 중단 ‘루트’ 노드로 바로 이동하게 된다.
- Succeeded : 중단 함수 처리 후 부모 노드에 성공 결과를 반환하고 ‘루트’ 노드로 올라간다.
C++ 함수 모음
- AAIController
- SetFocus(타겟 엑터) : 타겟 엑터로 들어온 타겟을 바라보도록 회전 하는 함수다.
- ClearFocus(포커스 우선순위) : 현재 설정되어있는 포커스를 지우고, 들어온 포커스로 재설정한다.
- EAIFocusPriority : AI 포커스 우선순위가 선언된 열거형 자료형이다.
- Gameplay : 포커스 기본 설정이다.
- EAIFocusPriority : AI 포커스 우선순위가 선언된 열거형 자료형이다.