궁수 AI를 만들어 Player를 향해 화살을 쏘아 날리는 것을 구현하였다. 게임의 재미를 더하기 위해 궁수 AI 회피를 추가할 것이다. 적이 궁수AI에 접근하여 일정거리에 도달하면 궁수 AI는 워프 스킬을 시전하여 달아날 것이다. EQS를 사용하여 적의 위치의 뒷공간 지정범위 내에서 랜덤으로 워프 이동할 것이다. 

 

 

 


 

 

 

 
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
CEnvQueryContext_Target.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
 

 

 

 

 

 

워프(Warp) 구현하기

 


 

 

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 장착
case EWeaponType::Warp: weapon->SetWarpMode(); break;//Warp 장착
}
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는 성공종료.
}

무기 장착에 Warp 추가

  • EBTNodeResult::Type UCBTTaskNode_Equip::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
    • switch (Type)
      {
      case EWeaponType::Sword: weapon->SetSwordMode(); break;
      case EWeaponType::Bow: weapon->SetBowMode(); break;
      case EWeaponType::Warp: weapon->SetWarpMode(); break;
      }

 

 

CAIBehaviorComponent

 

CAIBehaviorComponent.h

더보기
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "CAIBehaviorComponent.generated.h"
UENUM(BlueprintType)
enum class EAIStateType : uint8
{
Wait = 0, Approach, Action, Patrol, Hitted, Avoid, Dead, Max,
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FAIStateTypeChanged, EAIStateType, InPrevType, EAIStateType, InNewType);
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class U2212_06_API UCAIBehaviorComponent : public UActorComponent
{
GENERATED_BODY()
private:
UPROPERTY(EditAnywhere, Category = "Key")
FName AIStateTypeKey = "AIState";
UPROPERTY(EditAnywhere, Category = "Key")
FName TargetKey = "Target";
UPROPERTY(EditAnywhere, Category = "Key")
FName PatrolLocationKey = "Patrol_Location";
UPROPERTY(EditAnywhere, Category = "Key")
FName AvoidLocationKey = "Avoid_Location";
private:
EAIStateType GetType();
public:
bool IsWaitMode();
bool IsApproachMode();
bool IsActionMode();
bool IsPatrolMode();
bool IsHittedMode();
bool IsAvoidMode();
bool IsDeadMode();
public:
UCAIBehaviorComponent();
protected:
virtual void BeginPlay() override;
public:
FORCEINLINE void SetBlackboard(class UBlackboardComponent* InBlackboard) { Blackboard = InBlackboard; }
public:
class ACharacter* GetTarget();
public:
FVector GetPatrolLocation();
void SetPatrolLocation(const FVector& InLocation);
public:
FVector GetAvoidLocation();
public:
void SetWaitMode();
void SetApproachMode();
void SetActionMode();
void SetPatrolMode();
void SetHittedMode();
void SetAvoidMode();
void SetDeadMode();
private:
void ChangeType(EAIStateType InType);
public:
FAIStateTypeChanged OnAIStateTypeChanged;
private:
class UBlackboardComponent* Blackboard;
};

변수 추가

  • UPROPERTY(EditAnywhere, Category = "Key")
    FName AvoidLocationKey = "Avoid_Location";

 

함수 추가

  • FVector GetAvoidLocation();

 

 

 

CAIBehaviorComponent.cpp

더보기
#include "Components/CAIBehaviorComponent.h"
#include "Global.h"
#include "GameFramework/Character.h"
#include "BehaviorTree/BlackboardComponent.h"
UCAIBehaviorComponent::UCAIBehaviorComponent()
{
}
void UCAIBehaviorComponent::BeginPlay()
{
Super::BeginPlay();
}
EAIStateType UCAIBehaviorComponent::GetType()
{
return (EAIStateType)Blackboard->GetValueAsEnum(AIStateTypeKey);
}
bool UCAIBehaviorComponent::IsWaitMode()
{
return GetType() == EAIStateType::Wait;
}
bool UCAIBehaviorComponent::IsApproachMode()
{
return GetType() == EAIStateType::Approach;
}
bool UCAIBehaviorComponent::IsActionMode()
{
return GetType() == EAIStateType::Action;
}
bool UCAIBehaviorComponent::IsPatrolMode()
{
return GetType() == EAIStateType::Patrol;
}
bool UCAIBehaviorComponent::IsHittedMode()
{
return GetType() == EAIStateType::Hitted;
}
bool UCAIBehaviorComponent::IsAvoidMode()
{
return GetType() == EAIStateType::Avoid;
}
bool UCAIBehaviorComponent::IsDeadMode()
{
return GetType() == EAIStateType::Dead;
}
ACharacter* UCAIBehaviorComponent::GetTarget()
{
//Blackboard 내의 TargetKey를 리턴해준다.
return Cast<ACharacter>(Blackboard->GetValueAsObject(TargetKey));
}
FVector UCAIBehaviorComponent::GetPatrolLocation()
{
return Blackboard->GetValueAsVector(PatrolLocationKey);
}
void UCAIBehaviorComponent::SetPatrolLocation(const FVector& InLocation)
{
Blackboard->SetValueAsVector(PatrolLocationKey, InLocation);
}
FVector UCAIBehaviorComponent::GetAvoidLocation()
{
return Blackboard->GetValueAsVector(AvoidLocationKey);
}
void UCAIBehaviorComponent::SetWaitMode()
{
ChangeType(EAIStateType::Wait);
}
void UCAIBehaviorComponent::SetApproachMode()
{
ChangeType(EAIStateType::Approach);
}
void UCAIBehaviorComponent::SetActionMode()
{
ChangeType(EAIStateType::Action);
}
void UCAIBehaviorComponent::SetPatrolMode()
{
ChangeType(EAIStateType::Patrol);
}
void UCAIBehaviorComponent::SetHittedMode()
{
ChangeType(EAIStateType::Hitted);
}
void UCAIBehaviorComponent::SetAvoidMode()
{
ChangeType(EAIStateType::Avoid);
}
void UCAIBehaviorComponent::SetDeadMode()
{
ChangeType(EAIStateType::Dead);
}
void UCAIBehaviorComponent::ChangeType(EAIStateType InType)
{
EAIStateType prevType = GetType();
Blackboard->SetValueAsEnum(AIStateTypeKey, (uint8)InType);
if (OnAIStateTypeChanged.IsBound())
OnAIStateTypeChanged.Broadcast(prevType, InType);
}

함수 정의

  • FVector UCAIBehaviorComponent::GetAvoidLocation()
    • return Blackboard->GetValueAsVector(AvoidLocationKey);

 

 


 

 

 

CDo_Action_Warp

 

CDo_Action_Warp.h

더보기
#pragma once
#include "CoreMinimal.h"
#include "Weapons/CDoAction.h"
#include "CDoAction_Warp.generated.h"
UCLASS(Blueprintable)
class U2212_06_API UCDoAction_Warp : public UCDoAction
{
GENERATED_BODY()
public:
UCDoAction_Warp();
virtual void BeginPlay
(
class ACAttachment* InAttachment,
class UCEquipment* InEquipment,
class ACharacter* InOwner,
const TArray<FDoActionData>& InDoActionData,
const TArray<FHitData>& InHitData
);
void Tick(float InDeltaTime) override;
public:
void DoAction() override;
void Begin_DoAction() override;
private:
bool GetCursorLocationAndRotation(FVector& OutLocation, FRotator& OutRotation);
private:
class APlayerController* PlayerController;
class UDecalComponent* Decal;
class UCAIBehaviorComponent* Behavior;//UCAIBehaviorComponent(=Behavior 변수) 유무에 따라 Enemy인지 Player인지 구분할 수 있다.
private:
FVector MoveToLocation;
};

변수 추가

  • class UCAIBehaviorComponent* Behavior;
    • UCAIBehaviorComponent(=Behavior 변수) 유무에 따라 Enemy인지 Player인지 구분할 수 있다.

 

 

 

CDo_Action_Warp.cpp

더보기
#include "Weapons/DoActions/CDoAction_Warp.h"
#include "Global.h"
#include "GameFramework/Character.h"
#include "GameFramework/PlayerController.h"
#include "Components/CStateComponent.h"
#include "Components/DecalComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/CAIBehaviorComponent.h"
#include "Weapons/CAttachment.h"
UCDoAction_Warp::UCDoAction_Warp()
{
}
void UCDoAction_Warp::BeginPlay(ACAttachment* InAttachment, UCEquipment* InEquipment, ACharacter* InOwner, const TArray<FDoActionData>& InDoActionData, const TArray<FHitData>& InHitData)
{
Super::BeginPlay(InAttachment, InEquipment, InOwner, InDoActionData, InHitData);
Decal = CHelpers::GetComponent<UDecalComponent>(InAttachment);
PlayerController = OwnerCharacter->GetController<APlayerController>();
Behavior = CHelpers::GetComponent<UCAIBehaviorComponent>(InOwner);
}
void UCDoAction_Warp::Tick(float InDeltaTime)
{
Super::Tick(InDeltaTime);
FVector location = FVector::ZeroVector;
FRotator rotation = FRotator::ZeroRotator;
//GetCursorLocationAndRotation이 false면 hit가 안 된 것이다.
if (GetCursorLocationAndRotation(location, rotation) == false)
{
Decal->SetVisibility(false);
return;
}
//Warp 실행 중에는 Cursor가 움직이지 않도록 만든다.
if (bInAction)//Action이 실행중이라면 실행을 할 필요가 없으므로
return;//리턴
Decal->SetVisibility(true);
//Decal이 그려지는 위치와 회전값을 설정해준다.
Decal->SetWorldLocation(location);
Decal->SetWorldRotation(rotation);
}
void UCDoAction_Warp::DoAction()
{
CheckFalse(DoActionDatas.Num() > 0);
CheckFalse(State->IsIdleMode());
Super::DoAction();
FRotator rotation;
if (GetCursorLocationAndRotation(MoveToLocation, rotation))
{
//땅에 뭍히는것을 방지하기 위해 CapsuleHalfHeight만큼 높이를 보정해준다.
float height = OwnerCharacter->GetCapsuleComponent()->GetScaledCapsuleHalfHeight();
MoveToLocation = FVector(MoveToLocation.X, MoveToLocation.Y, MoveToLocation.Z + height);
float yaw = UKismetMathLibrary::FindLookAtRotation(OwnerCharacter->GetActorLocation(), MoveToLocation).Yaw;
OwnerCharacter->SetActorRotation(FRotator(0, yaw, 0));
}
DoActionDatas[0].DoAction(OwnerCharacter);
}
void UCDoAction_Warp::Begin_DoAction()
{
Super::Begin_DoAction();
//Player
if (!!PlayerController)
{
OwnerCharacter->SetActorLocation(MoveToLocation);//DoAction에서 설정한 MoveToLocation 위치로 이동한다.
MoveToLocation = FVector::ZeroVector;//이동 후에 MoveToLocation 위치를 ZeroVector로 초기화해준다.
return;//밑의 Enemy AI 기준의 Warp를 실행하지 않기 위해 리턴.
}
//Enemy AI
CheckNull(Behavior);
OwnerCharacter->SetActorLocation(Behavior->GetAvoidLocation());
}
bool UCDoAction_Warp::GetCursorLocationAndRotation(FVector& OutLocation, FRotator& OutRotation)
{
CheckNullResult(PlayerController, false);//PlayerController가 없으면 실행하면 안 되니 체크해준다.
FHitResult hitResult;
PlayerController->GetHitResultUnderCursorByChannel(ETraceTypeQuery::TraceTypeQuery1, false, hitResult);
CheckFalseResult(hitResult.bBlockingHit, false);//부딪히는게 없다면 false 리턴.
OutLocation = hitResult.Location;//hit된 위치
OutRotation = hitResult.ImpactNormal.Rotation();//hit된 Normal의 회전값
return true;
}

헤더 추가

  • #include "Components/CAIBehaviorComponent.h"

 

void UCDoAction_Warp::BeginPlay(ACAttachment* InAttachment, UCEquipment* InEquipment, ACharacter* InOwner, const TArray<FDoActionData>& InDoActionData, const TArray<FHitData>& InHitData)

  • Behavior = CHelpers::GetComponent<UCAIBehaviorComponent>(InOwner);
    • UCAIBehaviorComponent* Behavior 생성

 

void UCDoAction_Warp::Begin_DoAction()

  • CheckNull(Behavior);
    • Behavior를 가지고 있으면 Enemy라는 간주할 수 있다.
  • OwnerCharacter->SetActorLocation(Behavior->GetAvoidLocation());
    • CAIBehaviorComponent.h.cpp에 만든 GetAvoidLocation() 함수를 호출하여 Blackboard->GetValueAsVector(AvoidLocationKey);를 가져온다. (=Avoid_Location을 가져온다).

 

 


 

 

 

BP_CEnemy_Range

 

Weapon

  • Data Asset
    • DA_Warp 추가

 

 

BT_Range

 

 


 

 

실행화면