이번에는 궁수 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를 리턴하고 종료.

 

 


 

실행화면

 

 

 


 

 

 

 

 

 

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 장착
      }

 


 

 

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

 

 

  • 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이 보이도록 한다.