[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