궁수 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

     

     


     

     

    실행화면