[UE] Behavior Tree: 활 Enemy 생성, Player 바라보기

이번에는 궁수 AI를 만들것이다. 원거리에서 화살을 날리는 궁수 AI는 근거리 AI와 함께 게임에 배치하면 적 공격 패턴이 다양해져 게임에 재미를 더할 수 있다. Behavior Tree는 일전에 구현한 Melee와 크게 다르지 않다. 적을 인식하면 무기를 장착하고 활 시위를 당긴 후 화살을 날리게 만들 것이다.
목차
Plugins |
||||
Weapon |
||||
Resource |
||||
Icon128.png weapon_thumbnail_icon.png |
||||
Source |
||||
Weapon | ||||
SWeaponCheckBoxes.h .cpp SWeaponDetailsView.h .cpp SWeaponDoActionData.h .cpp SWeaponEquipmentData.h .cpp SWeaponHitData.h .cpp SWeaponLeftArea.h .cpp Weapon.Build.cs WeaponAssetEditor.h .cpp WeaponAssetFactory.h .cpp WeaponCommand.h .cpp WeaponContextMenu.h .cpp WeaponModule.h .cpp WeaponStyle.h .cpp |
||||
Source | ||
BehaviorTree | ||
CBTService_Melee.h .cpp CBTService_Range.h .cpp CBTTaskNode_Action.h .cpp CBTTaskNode_Equip.h .cpp CBTTaskNode_Hitted.h .cpp CBTTaskNode_Patrol.h .cpp CBTTaskNode_Speed.h .cpp CPatrol.h .cpp |
||
Characters | ||
CAIController.h .cpp CAnimInstance.h .cpp CEnemy.h .cpp CEnemy_AI.h.cpp CPlayer.h.cpp ICharacter.h .cpp |
||
Components | ||
CAIBehaviorComponent.h .cpp CFeetComponent.h .cpp CMontagesComponent.h .cpp CMovementComponent.h .cpp CStateComponent.h .cpp CStatusComponent.h .cpp CWeaponComponent.h .cpp CZoomComponent.h .cpp |
||
Notifies | ||
CAnimNotifyState_BeginAction.h .cpp CAnimNotifyState_BowString.h .cpp CAnimNotify_CameraShake.h .cpp CAnimNotify_End_Parkour.h .cpp CAnimNotifyState_EndAction.h .cpp CAnimNotify_EndState.h .cpp CAnimNotifyState.h .cpp CAnimNotifyState_CameraAnim.h .cpp CAnimNotifyState_Collision.h .cpp CAnimNotifyState_Combo.h .cpp CAnimNotifyState_Equip.h .cpp CAnimNotifyState_SubAction.h .cpp |
||
Parkour | ||
CParkourComponent.h .cpp | ||
Utilities | ||
CHelper.h CLog.h .cpp |
||
Weapons | ||
CArrow.h .cpp CAura.h .cpp CCamerModifier.h .cpp CGhostTrail.h .cpp CRotate_Object.h .cpp CThornObject.h .cpp CAnimInstance_Bow.h .cpp CAttachment_Bow.h .cpp CDoAction_Around.h .cpp CDoAction_Bow.h .cpp CDoAction_Combo.h .cpp CDoAction_Warp.h .cpp CSubAction_Around.h .cpp CSubAction_Bow.h .cpp CSubAction_Fist.h .cpp CSubAction_Hammer.h .cpp CSubAction_Sword.h .cpp CDoAction_Warp.h .cpp CAttachment.h .cpp CDoAction.h .cpp CEquipment.h .cpp CSubAction.h .cpp CWeaponAsset.h .cpp CWeaponData.h .cpp CWeaponStructures.h .cpp |
||
Global.h CGameMode.h .cpp U2212_06.Build.cs |
||
U2212_06.uproject | ||
CBTTaskNode_Action에 Abort 추가하기
CDoAction
CDoAction.h
더보기
#pragma once #include "CoreMinimal.h" #include "UObject/NoExportTypes.h" #include "Weapons/CWeaponStructures.h" #include "CDoAction.generated.h" UCLASS(Abstract)//DoAction 그 자체로는 객체화되면 안 되기 때문에 Abstract을 붙여준다. class U2212_06_API UCDoAction : public UObject { GENERATED_BODY() public: FORCEINLINE bool GetBeginAction() { return bBeginAction; } FORCEINLINE bool GetInAction() { return bInAction; } public: UCDoAction(); virtual void BeginPlay //재정의 할 수 있도록 virtual로 만든다. ( class ACAttachment* InAttachment, class UCEquipment* InEquipment, class ACharacter* InOwner, const TArray<FDoActionData>& InDoActionDatas,//CWeaponStucture내의 FDoActionData const TArray<FHitData>& InHitDatas //CWeaponStucture내의 FHitData ); virtual void Tick(float InDeltaTime) { } public: //재정의 할 수 있도록 virtual로 만든다. virtual void DoAction(); virtual void Begin_DoAction(); virtual void End_DoAction(); public: //CDoAction_Bow에서 재정의 시키기위해 virtual로 만든다. //BeginEquip될 때 충돌체를 꺼주고 Unequip될 때 충돌체를 켜준다. UFUNCTION() virtual void OnBeginEquip() { } UFUNCTION() virtual void OnUnequip() { } public: UFUNCTION() virtual void OnAttachmentBeginCollision() {} UFUNCTION() virtual void OnAttachmentEndCollision() {} UFUNCTION() virtual void OnAttachmentBeginOverlap(class ACharacter* InAttacker, AActor* InAttackCauser, class ACharacter* InOther) { } UFUNCTION() virtual void OnAttachmentEndOverlap(class ACharacter* InAttacker, class ACharacter* InOther) { } protected: bool bInAction; bool bBeginAction; class ACharacter* OwnerCharacter; class UWorld* World; class UCMovementComponent* Movement; class UCStateComponent* State; TArray<FDoActionData> DoActionDatas; TArray<FHitData> HitDatas; };
인라인 Get 함수 추가
- FORCEINLINE bool GetBeginAction() { return bBeginAction; }
CDoAction.cpp
더보기
#include "Weapons/CDoAction.h" #include "Global.h" #include "CAttachment.h" #include "CEquipment.h" #include "GameFramework/Character.h" #include "Components/CStateComponent.h" #include "Components/CMovementComponent.h" UCDoAction::UCDoAction() { } void UCDoAction::BeginPlay(ACAttachment* InAttachment, UCEquipment* InEquipment, ACharacter* InOwner, const TArray<FDoActionData>& InDoActionDatas, const TArray<FHitData>& InHitDatas) { OwnerCharacter = InOwner; World = OwnerCharacter->GetWorld(); State = CHelpers::GetComponent<UCStateComponent>(OwnerCharacter); Movement = CHelpers::GetComponent<UCMovementComponent>(OwnerCharacter); DoActionDatas = InDoActionDatas; HitDatas = InHitDatas; } void UCDoAction::DoAction() { bInAction = true; State->SetActionMode(); } void UCDoAction::Begin_DoAction() { bBeginAction = true; } void UCDoAction::End_DoAction() { bInAction = false; bBeginAction = false; State->SetIdleMode(); Movement->Move(); Movement->DisableFixedCamera(); }
변경사항 없음.
CBTTaskNode_Action
CBTTaskNode_Action.h
더보기
#pragma once #include "CoreMinimal.h" #include "BehaviorTree/BTTaskNode.h" #include "CBTTaskNode_Action.generated.h" UCLASS() class U2212_06_API UCBTTaskNode_Action : public UBTTaskNode { GENERATED_BODY() public: UCBTTaskNode_Action(); protected: EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override; void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override; EBTNodeResult::Type AbortTask(UBehaviorTreeComponent& Owner0Comp, uint8* NodeMemory) override; };
Abort 함수 추가
- EBTNodeResult::Type AbortTask(UBehaviorTreeComponent& Owner0Comp, uint8* NodeMemory) override;
CBTTaskNode_Action.cpp
더보기
#include "BehaviorTree/CBTTaskNode_Action.h" #include "Global.h" #include "Characters/CEnemy_AI.h" #include "Characters/CAIController.h" #include "Components/CStateComponent.h" #include "Components/CWeaponComponent.h" #include "Weapons/CDoAction.h" UCBTTaskNode_Action::UCBTTaskNode_Action() { NodeName = "Action"; bNotifyTick = true; } EBTNodeResult::Type UCBTTaskNode_Action::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) { Super::ExecuteTask(OwnerComp, NodeMemory); ACAIController* controller = Cast<ACAIController>(OwnerComp.GetOwner()); ACEnemy_AI* ai = Cast<ACEnemy_AI>(controller->GetPawn()); UCWeaponComponent* weapon = CHelpers::GetComponent<UCWeaponComponent>(ai); CheckNullResult(weapon, EBTNodeResult::Failed); //Weapon 안에 공격할 때 정지여부(CanMove)가 있다. //Player기준: Weapon에서 DoAction을 한다. DoAction은 WeaponStructure를 가지고 있다. WeaponSturcture 내의 movement를 통해서 정지 여부를 결정한다. //하지만 Enemy는 알 수 없다. 그래서 controller->StopMovement()로 정지시킨다. controller->StopMovement(); weapon->DoAction(); return EBTNodeResult::InProgress; } void UCBTTaskNode_Action::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()); UCWeaponComponent* weapon = CHelpers::GetComponent<UCWeaponComponent>(ai); UCStateComponent* state = CHelpers::GetComponent<UCStateComponent>(ai); bool bCheck = true; bCheck &= (state->IsIdleMode()); bCheck &= (weapon->GetDoAction()->GetInAction() == false); if (bCheck) { FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);//Succeeded로 끝낸다. 리턴이 없는 Tick 같은 곳에서 사용한다. return; } } 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); if (weapon == nullptr)//무기가 없다면 return EBTNodeResult::Failed;//실패 종료한다. bool bBeginAction = weapon->GetDoAction()->GetBeginAction();//CDoAction.h에서 만든 FORCEINLINE Get함수. bBeginAction 변수의 true, false 여부를 알려준다. //bBeginAction=true면 공격이 시작되었다는 의미, false면 공격이 시작하지 않았다는 의미다. if (bBeginAction == false) weapon->GetDoAction()->Begin_DoAction();//공격을 시작한다. weapon->GetDoAction()->End_DoAction();//공격을 끝낸다. return EBTNodeResult::Succeeded;//성공 종료한다. }
Abort 함수 정의
- EBTNodeResult::Type UCBTTaskNode_Action::AbortTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
- if (weapon == nullptr)
return EBTNodeResult::Failed;- WeaponComponent가 없다면 EBTNodeResult::Failed 리턴하고 종료.
- bool bBeginAction = weapon->GetDoAction()->GetBeginAction();
- 위의 CDoAction.h에서 만든 FORCEINLINE Get함수
- bBeginAction 변수가 true, false 인지 알려준다.
- if (bBeginAction == false)
weapon->GetDoAction()->Begin_DoAction();- 공격을 시작하지 않았다면 공격을 시작한다.
- return EBTNodeResult::Succeeded;
- 위에서 EBTNodeResult::Failed 리턴하지 않았다면 EBTNodeResult::Succeeded를 리턴하고 종료.
- if (weapon == nullptr)
실행화면

Range 구현하기
Melee를 구현한 것과 같이
- BP_CAIController_Range
- BP_CEnemy_Range
- BB_Range
- BT_Range
를 생성한다.
CBTService_Range 생성
새 C++ 클래스 - BTService - CBTService_Range 생성


CBTService_Range.h
더보기
#pragma once #include "CoreMinimal.h" #include "BehaviorTree/BTService.h" #include "CBTService_Range.generated.h" UCLASS() class U2212_06_API UCBTService_Range : public UBTService { GENERATED_BODY() private: UPROPERTY(EditAnywhere, Category = "Action") float AvoidRange = 500;//탐지되었을때 회피범위 UPROPERTY(EditAnywhere, Category = "Action") bool bDrawDebug;//디버깅 그리기 회피범위 public: UCBTService_Range(); protected: void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override; };
CBTService_Range.cpp
더보기
#include "BehaviorTree/CBTService_Range.h" #include "Global.h" #include "GameFramework/Character.h" #include "Characters/CAIController.h" #include "Characters/CEnemy_AI.h" #include "Components/CStateComponent.h" #include "Components/CAIBehaviorComponent.h" UCBTService_Range::UCBTService_Range() { NodeName = "Range"; Interval = 0.1f;//호출 간격. 0.1초 마다 호출 RandomDeviation = 0.0f; } void UCBTService_Range::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) { Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds); ACAIController* controller = Cast<ACAIController>(OwnerComp.GetOwner()); ACEnemy_AI* ai = Cast<ACEnemy_AI>(controller->GetPawn()); UCStateComponent* state = CHelpers::GetComponent<UCStateComponent>(ai); UCAIBehaviorComponent* aiState = CHelpers::GetComponent<UCAIBehaviorComponent>(ai); if (bDrawDebug) { FVector start = ai->GetActorLocation();//ACEnemy_AI의 위치 start.Z -= 25; FVector end = start; end.Z += 50; DrawDebugCylinder(ai->GetWorld(), start, end, AvoidRange, 10, FColor::Red, true, Interval, 0, 0);//디버깅 원기둥 그리기 } if (state->IsHittedMode()) { aiState->SetHittedMode(); return; } ACharacter* target = aiState->GetTarget(); if (target == nullptr)//target이 없다면(=시야 범위 내에 적이 없다면) { //EAIFocusPriorty: Gameplay(Gameplay모드), LastFocusPriority(마지막 Focus 받은애를 우선순위에서 제거), Move(이동 Focus) controller->ClearFocus(EAIFocusPriority::Gameplay);//바라보는 Focus를 Gameplay 모드에서 제거 aiState->SetWaitMode();//타겟이 없다면 Wait모드로 만들어준다. return; } //감지가 되었을 때 controller->SetFocus(target);//감지가 된 target쪽으로 SetFocus하여 바라보게 만든다. float distance = ai->GetDistanceTo(target);//현재 나의 위치에서 target까지의 거리를 구한다. if (distance < AvoidRange)//거리가 설정한 공격범위 보다 작다면 { aiState->SetAvoidMode();//Avoid 모드로 만들어준다. return;//회피를 하고 리턴 } aiState->SetActionMode();//Action 모드로 만들어준다. BT_Range에 Action 블랙보드 밑에 Equip, Action를 연결하여 무기를 장착하고 공격하게 만든다. }
CBTTaskNode_Equip
CBTTaskNode_Equip.h
더보기
#pragma once #include "CoreMinimal.h" #include "BehaviorTree/BTTaskNode.h" #include "Components/CWeaponComponent.h" #include "CBTTaskNode_Equip.generated.h" UCLASS() class U2212_06_API UCBTTaskNode_Equip : public UBTTaskNode { GENERATED_BODY() private: UPROPERTY(EditAnywhere, Category = "Type") EWeaponType Type; public: UCBTTaskNode_Equip(); protected: EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override; void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override; EBTNodeResult::Type AbortTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override; };
변경사항 없음
CBTTaskNode_Equip.cpp
더보기
#include "BehaviorTree/CBTTaskNode_Equip.h" #include "Global.h" #include "Characters/CEnemy_AI.h" #include "Characters/CAIController.h" #include "Components/CStateComponent.h" #include "Weapons/CEquipment.h" UCBTTaskNode_Equip::UCBTTaskNode_Equip() { NodeName = "Equip"; bNotifyTick = true; } EBTNodeResult::Type UCBTTaskNode_Equip::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) { Super::ExecuteTask(OwnerComp, NodeMemory); ACAIController* controller = Cast<ACAIController>(OwnerComp.GetOwner()); ACEnemy_AI* ai = Cast<ACEnemy_AI>(controller->GetPawn()); UCWeaponComponent* weapon = CHelpers::GetComponent<UCWeaponComponent>(ai); CheckNullResult(weapon, EBTNodeResult::Failed);//weapon이 없다면 EBTNodeResult::Failed 리턴 if (Type == weapon->GetWeaponType())//장착될 무기 == 현재 장착된 무기 return EBTNodeResult::Succeeded;//새로 장착시킬 필요 없으니 바로 Succeeded를 리턴 switch (Type) { case EWeaponType::Sword: weapon->SetSwordMode(); break;//Sword 장착 case EWeaponType::Bow: weapon->SetBowMode(); break;//Bow 장착 } return EBTNodeResult::InProgress;//장착 동작이 나올 수 있도록 InProgress 리턴 } void UCBTTaskNode_Equip::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()); UCWeaponComponent* weapon = CHelpers::GetComponent<UCWeaponComponent>(ai);//weapon을 가져온다. const bool* bEquipped = weapon->GetEquipment()->GetEquipped(); UCStateComponent* state = CHelpers::GetComponent<UCStateComponent>(ai); if(state->IsIdleMode() && *bEquipped) { FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);//Succeeded로 끝낸다. 리턴이 없는 Tick 같은 곳에서 사용한다. return; } } 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); if (weapon == nullptr)//무기가 없다면 return EBTNodeResult::Failed;//실패 리턴 bool bBeginEquip = weapon->GetEquipment()->GetBeginEquip();//GetBeginEquip()으로 CEquipment의 bBeginEquip를 넣어준다. bBeginEquip=true면 Equip이 시작되었다는 의미다. if (bBeginEquip == false) weapon->GetEquipment()->Begin_Equip(); weapon->GetEquipment()->End_Equip(); return EBTNodeResult::Aborted;//Aborted는 취소 마감, 위(Root)로 올라감. Succeeded는 성공종료. }
Bow 장착 추가하기
- EBTNodeResult::Type UCBTTaskNode_Equip::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
- switch (Type)
{
case EWeaponType::Sword: weapon->SetSwordMode(); break;//Sword 장착
case EWeaponType::Bow: weapon->SetBowMode(); break;//Bow 장착
}
- switch (Type)
BP_CAIController_Range 생성

Perception
- AI Perception
- Sense Config
- Implementation: AISense_Sight
- Sight Radius: 1000.0
- Lose Sight Radius: 1200.0
- PeripheralVisionHalfAngleDegrees: 180.0
- 최대수명: 5.0
- Sense Config

- AISense_Sight
- AI Hearing config: Make Noise 감지
- AI Touch config: 닿았을 때 감지. Capsule의 Overlap 시 감지.
- AI Damaged config: Damage가 0보다 클 때 감지. TakeDamage 함수에 감지가 내장되어있다.
BP_CEnemy_Range 생성


BB_Range 생성

BT_Range 생성

실행화면

'를 눌러 디버깅 정보가 보이도록 하고 오른쪽 숫자패드 4번을 눌러 AISense_Sight이 보이도록 한다.
'⭐ Unreal Engine > UE RPG AI' 카테고리의 다른 글
[UE] Behavior Tree: 워프(Warp), EQS (0) | 2023.08.09 |
---|---|
[UE] Behavior Tree: 활 조준 및 발사하기 (0) | 2023.08.08 |
[UE] Behavior Tree: 무기 장착 Abort (0) | 2023.08.01 |
[UE] Behavior Tree: 피격(Hitted) (0) | 2023.07.31 |
[UE] Behavior Tree: 무기 장착 및 공격 (0) | 2023.07.27 |
댓글
이 글 공유하기
다른 글
-
[UE] Behavior Tree: 워프(Warp), EQS
[UE] Behavior Tree: 워프(Warp), EQS
2023.08.09 -
[UE] Behavior Tree: 활 조준 및 발사하기
[UE] Behavior Tree: 활 조준 및 발사하기
2023.08.08 -
[UE] Behavior Tree: 무기 장착 Abort
[UE] Behavior Tree: 무기 장착 Abort
2023.08.01 -
[UE] Behavior Tree: 피격(Hitted)
[UE] Behavior Tree: 피격(Hitted)
2023.07.31
댓글을 사용할 수 없습니다.