[UE] Behavior Tree: 워프(Warp), EQS

궁수 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;
}
- switch (Type)
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

실행화면

'⭐ Unreal Engine > UE RPG AI' 카테고리의 다른 글
[UE] Behavior Tree: 활 조준 및 발사하기 (0) | 2023.08.08 |
---|---|
[UE] Behavior Tree: 활 Enemy 생성, Player 바라보기 (0) | 2023.08.07 |
[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: 활 조준 및 발사하기
[UE] Behavior Tree: 활 조준 및 발사하기
2023.08.08 -
[UE] Behavior Tree: 활 Enemy 생성, Player 바라보기
[UE] Behavior Tree: 활 Enemy 생성, Player 바라보기
2023.08.07이번에는 궁수 AI를 만들것이다. 원거리에서 화살을 날리는 궁수 AI는 근거리 AI와 함께 게임에 배치하면 적 공격 패턴이 다양해져 게임에 재미를 더할 수 있다. Behavior Tree는 일전에 구현한 Melee와 크게 다르지 않다. 적을 인식하면 무기를 장착하고 활 시위를 당긴 후 화살을 날리게 만들 것이다. 목차 Plugins Weapon Resource Icon128.pngweapon_thumbnail_icon.png Source Weapon SWeaponCheckBoxes.h .cppSWeaponDetailsView.h .cppSWeaponDoActionData.h .cppSWeaponEquipmentData.h .cppSWeaponHitData.h .cppS… -
[UE] Behavior Tree: 무기 장착 Abort
[UE] Behavior Tree: 무기 장착 Abort
2023.08.01Behavior Tree의 UCBTTaskNode 내에 AbortTask를 추가할 것이다. Task의 성공과 실패 외에도 Abort가 걸리는 상황을 추가하여 Behavior Tree 실행 중 Task가 빨리 또는 느리게 실행되어 문제가 되는 상황을 방지할 것이다. 일종의 예외처리도 가능하여 Abort는 대부분의 경우 Behavior Tree에 필수적으로 사용된다. 목차 Plugins Weapon Resource Icon128.pngweapon_thumbnail_icon.png Source Weapon SWeaponCheckBoxes.h .cppSWeaponDetailsView.h .cppSWeaponDoActionData.h .cppSWeaponEquipmentData… -
[UE] Behavior Tree: 피격(Hitted)
[UE] Behavior Tree: 피격(Hitted)
2023.07.31적(Enemy)의 피격과 Behavior Tree가 연결되어 있지 않다. 적이 피격 되었을 때 Behavior Tree에 알리고 피격이 끝났을 때 Behavior를 WaitMode로 변경하는 코드를 추가하였다. 목차 Plugins Weapon Resource Icon128.pngweapon_thumbnail_icon.png Source Weapon SWeaponCheckBoxes.h .cppSWeaponDetailsView.h .cppSWeaponDoActionData.h .cppSWeaponEquipmentData.h .cppSWeaponHitData.h .cppSWeaponLeftArea.h .cppWeapon.Build.csWeaponAssetEditor.h ….
댓글을 사용할 수 없습니다.