공부/Unreal

24.08.30

월러비 2024. 8. 31. 15:25

어제거 정리

트리 정리

  • 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로 올라가고 다시 내려온다.

Equip 에러 설명 정리

  • Equip을 했더니 플레이어의 무기도 같이 작동했다.
  • 이유 : Enemy_AI에 WeaponComponent를 넣었다.
    • WeaponComponent에는 WeaponAsset Datas[]가 있었다.
      • DA들이 Datas에 들어가있다.
      • 그런데, 적이 가지고있는 DA와 플레이어의 DA가 같은거였다.
      • 같은거이니 같이 작동이 되는 것이다.
  • 해결방법 : 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 : 포커스 기본 설정이다.

'공부 > Unreal' 카테고리의 다른 글

24.08.29  (1) 2024.08.30
24.08.28  (0) 2024.08.30
24.08.27  (6) 2024.08.28
24.08.26  (1) 2024.08.27
24.08.23  (0) 2024.08.24