[UE] Behavior Tree: 순찰(Patrol)
적의 상태에 따라 이동속도를 다르게 설정해보자. 적 Behavior Tree에 순찰 기능을 추가하고 Player가 접근하면 달려오도록 수정해보자.
목차
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 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 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 CWeaponStructures.h .cpp |
||
Global.h CGameMode.h .cpp U2212_06.Build.cs |
||
U2212_06.uproject | ||
Speed 변경하기
CBTTaskNode_Speed 생성
새 C++ 클래스 - BTTaskNode - CBTTaskNode_Speed 생성
CBTTaskNode_Speed.h
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTTaskNode.h"
#include "Components/CMovementComponent.h"
#include "CBTTaskNode_Speed.generated.h"
UCLASS()
class U2212_06_API UCBTTaskNode_Speed : public UBTTaskNode
{
GENERATED_BODY()
private:
UPROPERTY(EditAnywhere, Category = "Type")
ESpeedType Type;
public:
UCBTTaskNode_Speed();
private:
EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
};
CBTTaskNode_Speed.cpp
#include "BehaviorTree/CBTTaskNode_Speed.h"
#include "Global.h"
#include "Characters/CEnemy_AI.h"
#include "Characters/CAIController.h"
UCBTTaskNode_Speed::UCBTTaskNode_Speed()
{
NodeName = "Speed";
}
EBTNodeResult::Type UCBTTaskNode_Speed::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
//EBTNodeResult::Type은 Composite과 Task 공유해서 사용한다.
Super::ExecuteTask(OwnerComp, NodeMemory);
ACAIController* controller = Cast<ACAIController>(OwnerComp.GetOwner());//OwnerComp로부터 controller를 가져온다.
ACEnemy_AI* ai = Cast<ACEnemy_AI>(controller->GetPawn());//가져온 controller의 Pawn을 사용하여 Enemy_AI를 캐스팅한다.
UCMovementComponent* movement = CHelpers::GetComponent<UCMovementComponent>(ai);
movement->SetSpeed(Type);//CMovementComponent의 SetSpeed함수로 Type으로 지정한 속도를 넣고 변경한다.
return EBTNodeResult::Succeeded;
}
※ 참고) EBTNodeResult의 Type들 - Succeeded, Failed, Aborted, InProgress
EBTNodeResult는 BehaviorTreeTypes.h에 선언되어 있고,
Behavior Tree 내의 Composite, Task 모두 사용한다.
BT_Melee
실행화면
Patrol
Build.cs
Build.cs
using UnrealBuildTool;
public class U2212_06 : ModuleRules
{
public U2212_06(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.Add(ModuleDirectory);
PublicDependencyModuleNames.Add("Core");
PrivateDependencyModuleNames.Add("CoreUObject");
PrivateDependencyModuleNames.Add("Engine");
PrivateDependencyModuleNames.Add("InputCore");
PublicDependencyModuleNames.Add("Niagara");
PublicDependencyModuleNames.Add("AIModule");
PublicDependencyModuleNames.Add("GameplayTasks");
PublicDependencyModuleNames.Add("NavigationSystem");
}
}
모듈 추가
- PublicDependencyModuleNames.Add("NavigationSystem");
CPatrolPath 생성
새 C++ 클래스 - Actor - CPatrolPath 생성
CPatrolPath.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "CPatrolPath.generated.h"
UCLASS()
class U2212_06_API ACPatrolPath : public AActor
{
GENERATED_BODY()
private:
UPROPERTY(EditAnywhere, Category = "Loop")
bool bLoop;
UPROPERTY(EditAnywhere, Category = "Path")
int32 Index;
UPROPERTY(EditAnywhere, Category = "Path")
bool bReverse;
private:
UPROPERTY(VisibleDefaultsOnly)
class USceneComponent* Root;
UPROPERTY(VisibleDefaultsOnly)
class USplineComponent* Spline;
UPROPERTY(VisibleDefaultsOnly)
class UTextRenderComponent* Text;
public:
FORCEINLINE class USplineComponent* GetSpline() { return Spline; }
public:
ACPatrolPath();
void OnConstruction(const FTransform& Transform) override;
protected:
virtual void BeginPlay() override;
public:
FVector GetMoveTo();
void UpdateIndex();
};
CPatrolPath.cpp
#include "BehaviorTree/CPatrolPath.h"
#include "Global.h"
#include "Components/SplineComponent.h"
#include "Components/TextRenderComponent.h"
ACPatrolPath::ACPatrolPath()
{
bRunConstructionScriptOnDrag = false;
CHelpers::CreateComponent<USceneComponent>(this, &Root, "Root");
CHelpers::CreateComponent<USplineComponent>(this, &Spline, "Spline", Root);
CHelpers::CreateComponent<UTextRenderComponent>(this, &Text, "Text", Root);
Spline->SetRelativeLocation(FVector(0, 0, 30));
Spline->bHiddenInGame = false;
Text->SetRelativeLocation(FVector(0, 0, 120));
Text->SetRelativeRotation(FRotator(0, 180, 0));
Text->HorizontalAlignment = EHorizTextAligment::EHTA_Center;
Text->TextRenderColor = FColor::Red;
Text->bHiddenInGame = true;
}
void ACPatrolPath::OnConstruction(const FTransform& Transform)
{
Super::OnConstruction(Transform);
#if WITH_EDITOR
Text->Text = FText::FromString(GetActorLabel());//GetActorLabel()은 Editor에 있는 함수라서 Project Package 시에는 지워줘야 한다.
#endif
Spline->SetClosedLoop(bLoop);
}
void ACPatrolPath::BeginPlay()
{
Super::BeginPlay();
}
FVector ACPatrolPath::GetMoveTo()
{
//다음 Spline Point 이동을 위해 Spline의 다음 Point의 World 위치를 리턴.
return Spline->GetLocationAtSplinePoint(Index, ESplineCoordinateSpace::World);
}
void ACPatrolPath::UpdateIndex()
{
int32 count = Spline->GetNumberOfSplinePoints();//cout변수에 Spline Points 숫자를 넣어준다.
if (bReverse)//역방향
{
if (Index > 0)
{
Index--;
return;
}
if (Spline->IsClosedLoop())
{
Index = count - 1;
return;
}
Index = 1;
bReverse = false;
return;
}
if (Index < count - 1)//정방향
{
Index++;
return;
}
if(Spline->IsClosedLoop())
{
Index = 0;
return;
}
Index = count - 2;
bReverse = true;//역방향으로 만든다.
}
BP_CPatrolPath 생성 + Viewport에 Spline 그리기
BP_CPatrolPath 생성 후 Viewport에 Spline을 배치한다.
BP_Enemy_Melee의 Patrol - Project Path에 Viewport에 배치한 BP_CPatrolPath 중 하나를 할당한다.
CBTTaskNode_Patrol 생성
새 C++ 클래스 - BTTaskNode - CBTTaskNode_Patrol 생성
CBTTaskNode_Patrol.h
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTTaskNode.h"
#include "CBTTaskNode_Patrol.generated.h"
UCLASS()
class U2212_06_API UCBTTaskNode_Patrol : public UBTTaskNode
{
GENERATED_BODY()
private:
UPROPERTY(EditAnywhere, Category = "Patrol")
bool bDebugMode;//디버그 모드 on/off
UPROPERTY(EditAnywhere, Category = "Patrol")
float AcceptanceDistance = 20;//순찰 경로 간격
UPROPERTY(EditAnywhere, Category = "Random")
float RandomRadius = 1500;//순찰 경로가 없을 때 랜덤으로 움직이는 범위
public:
UCBTTaskNode_Patrol();
protected:
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
};
CBTTaskNode_Patrol.cpp
#include "BehaviorTree/CBTTaskNode_Patrol.h"
#include "Global.h"
#include "CPatrolPath.h"
#include "Components/SplineComponent.h"
#include "Components/CAIBehaviorComponent.h"
#include "Characters/CEnemy_AI.h"
#include "Characters/CAIController.h"
#include "NavigationSystem.h"
UCBTTaskNode_Patrol::UCBTTaskNode_Patrol()
{
NodeName = "Patrol";
bNotifyTick = true;//작성해야 Tick이 실행된다. Tick을 실행하려면 반드시 작성해야 한다.
}
EBTNodeResult::Type UCBTTaskNode_Patrol::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
Super::ExecuteTask(OwnerComp, NodeMemory);
ACAIController* controller = Cast<ACAIController>(OwnerComp.GetOwner());
ACEnemy_AI* ai = Cast<ACEnemy_AI>(controller->GetPawn());
UCAIBehaviorComponent* behavior = CHelpers::GetComponent<UCAIBehaviorComponent>(ai);
//PatrolPath가 있을때
if (!!ai->GetPatrolPath())
{
//움직일 위치를 Blackboard에 넣는다.
FVector moveToPoint = ai->GetPatrolPath()->GetMoveTo();
behavior->SetPatrolLocation(moveToPoint);
if (bDebugMode)//순찰 경로 DrawDebug
DrawDebugSphere(ai->GetWorld(), moveToPoint, 25, 25, FColor::Green, true, 5);
return EBTNodeResult::InProgress;
}
//PatrolPath가 없을때
FVector location = ai->GetActorLocation();
UNavigationSystemV1* navSystem = FNavigationSystem::GetCurrent<UNavigationSystemV1>(ai->GetWorld());
CheckNullResult(navSystem, EBTNodeResult::Failed);//navSystem이 없다면 EBTNodeResult::Failed 리턴
//결과값을 리턴 받는다.
FNavLocation point(location);
while (true)
{
//갈 수 있는 위치가 나올때까지 계속 돌린다.
if (navSystem->GetRandomPointInNavigableRadius(location, RandomRadius, point))
break;
}
behavior->SetPatrolLocation(point.Location);
if (bDebugMode)//순찰 경로 DrawDebug
DrawDebugSphere(ai->GetWorld(), point.Location, 25, 25, FColor::Green, true, 5);
return EBTNodeResult::InProgress;
}
void UCBTTaskNode_Patrol::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());
UCAIBehaviorComponent* behavior = CHelpers::GetComponent<UCAIBehaviorComponent>(ai);
FVector location = behavior->GetPatrolLocation();//이동할 위치를 location 변수에 담아준다.
EPathFollowingRequestResult::Type result = controller->MoveToLocation(location, AcceptanceDistance, false);
switch(result)
{
case EPathFollowingRequestResult::Failed:
{
FinishLatentTask(OwnerComp, EBTNodeResult::Failed);
}
break;
case EPathFollowingRequestResult::AlreadyAtGoal:
{
if (ai->GetPatrolPath())
ai->GetPatrolPath()->UpdateIndex();//다음 위치를 갱신해준다.
FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
}
break;
}
}
※ 참고) PathFollwingComponent.h의 EPathFollowingRequestResult
CEnemy_AI
CEnemy_AI.h
#pragma once
#include "CoreMinimal.h"
#include "Characters/CEnemy.h"
#include "CEnemy_AI.generated.h"
UCLASS()
class U2212_06_API ACEnemy_AI : public ACEnemy
{
GENERATED_BODY()
private:
UPROPERTY(EditDefaultsOnly, Category = "AI")
class UBehaviorTree* BehaviorTree;
UPROPERTY(EditDefaultsOnly, Category = "AI")
uint8 TeamID = 2;//TeamID를 0~255번까지 지정가능하다. 255번은 중립이다. ID 같으면 아군이고 ID가 다르면 적이다.
private:
UPROPERTY(EditDefaultsOnly, Category = "Label")
float LabelViewAmount = 3000.0f;
private:
UPROPERTY(EditAnywhere, Category = "Patrol")
class ACPatrolPath* PatrolPath;//클래스 밖에서도 지정할 수 있어야 한다. 서로간 만들어진 객체를 참조할것이라서 softObjectPtr 사용
#if WITH_EDITOR
private:
UPROPERTY(VisibleDefaultsOnly)
class UWidgetComponent* LabelWidget;
#endif
private:
UPROPERTY(VisibleDefaultsOnly)
class UCWeaponComponent* Weapon;
UPROPERTY(VisibleDefaultsOnly)
class UCAIBehaviorComponent* Behavior;
public:
FORCEINLINE uint8 GetTeamID() { return TeamID; }
FORCEINLINE class UBehaviorTree* GetBehaviorTree() { return BehaviorTree; }
FORCEINLINE class ACPatrolPath* GetPatrolPath() { return PatrolPath; }
public:
ACEnemy_AI();
protected:
virtual void BeginPlay() override;
public:
virtual void Tick(float DeltaTime) override;
private:
void UpdateLabelRenderScale();
};
변수 추가
- UPROPERTY(EditAnywhere, Category = "Patrol")
class ACPatrolPath* PatrolPath;- 클래스 밖에서도 지정할 수 있어야 한다. 서로간 만들어진 객체를 참조할것이라서 softObjectPtr 사용
인라인 함수 추가
- FORCEINLINE class ACPatrolPath* GetPatrolPath() { return PatrolPath; }
CEnemy_AI.cpp
#include "Characters/CEnemy_AI.h"
#include "Global.h"
#include "Components/CWeaponComponent.h"
#include "Components/CAIBehaviorComponent.h"
#include "Components/WidgetComponent.h"
#include "Components/CStatusComponent.h"
#include "Widgets/CUserWidget_Label.h"
ACEnemy_AI::ACEnemy_AI()
{
PrimaryActorTick.bCanEverTick = true;
CHelpers::CreateComponent<UWidgetComponent>(this, &LabelWidget, "Label", GetMesh());
CHelpers::CreateActorComponent<UCWeaponComponent>(this, &Weapon, "Weapon");
CHelpers::CreateActorComponent<UCAIBehaviorComponent>(this, &Behavior, "Behavior");
TSubclassOf<UCUserWidget_Label> labelClass;
CHelpers::GetClass<UCUserWidget_Label>(&labelClass, "WidgetBlueprint'/Game/Widgets/WB_Label.WB_Label_C'");
LabelWidget->SetWidgetClass(labelClass);
LabelWidget->SetRelativeLocation(FVector(0, 0, 220));
LabelWidget->SetDrawSize(FVector2D(120, 0));
LabelWidget->SetWidgetSpace(EWidgetSpace::Screen);
}
void ACEnemy_AI::BeginPlay()
{
Super::BeginPlay();
LabelWidget->InitWidget();
UCUserWidget_Label* label = Cast<UCUserWidget_Label>(LabelWidget->GetUserWidgetObject());
label->UpdateHealth(Status->GetHealth(), Status->GetMaxHealth());
label->UpdateName(GetName());
label->UpdateControllerName(GetController()->GetName());
}
void ACEnemy_AI::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
UCUserWidget_Label* label = Cast<UCUserWidget_Label>(LabelWidget->GetUserWidgetObject());
if (!!label)
{
label->UpdateHealth(Status->GetHealth(), Status->GetMaxHealth());
UpdateLabelRenderScale();
}
}
void ACEnemy_AI::UpdateLabelRenderScale()
{
UCUserWidget_Label* label = Cast<UCUserWidget_Label>(LabelWidget->GetUserWidgetObject());
CheckNull(label);
APlayerCameraManager* cameraManager = UGameplayStatics::GetPlayerCameraManager(GetWorld(), 0);
FVector cameraLocation = cameraManager->GetCameraLocation();
FVector targetLocation = GetController()->GetTargetLocation();
float distance = FVector::Distance(cameraLocation, targetLocation);
float sizeRate = 1.0f - (distance / LabelViewAmount);
if (distance > LabelViewAmount)
{
label->SetVisibility(ESlateVisibility::Collapsed);
return;
}
label->SetVisibility(ESlateVisibility::Visible);
label->SetRenderScale(FVector2D(sizeRate, sizeRate));
}
변경사항 없음.
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";
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:
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 PatrolLocationKey = "Patrol_Location";
함수 추가
- FVector GetPatrolLocation();
- void SetPatrolLocation(const FVector& InLocation);
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);
}
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::GetPatrolLocation()
- return Blackboard->GetValueAsVector(PatrolLocationKey);
void UCAIBehaviorComponent::SetPatrolLocation(const FVector& InLocation)
- Blackboard->SetValueAsVector(PatrolLocationKey, InLocation);
BB_Melee
새 키 - Patrol_Location 추가
※ 참고) Instance Synced
- 같은 Blackboard를 가진 애들 끼리 해당 값을 공유해준다.
- Instance Synced를 체크하면 군집 이동 등이 용이하다. ex. Enemy 한 명이 Player를 발견하면 발견한 Enemy뿐만 아니라 나머지 Enemy들이 다 같이 접근한다.
MT_Melee
실행화면
'⭐ Unreal Engine > UE RPG AI' 카테고리의 다른 글
[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: 무기 장착(Equip) (0) | 2023.07.26 |
[UE] Behavior Tree 시작 (1) | 2023.07.24 |
댓글
이 글 공유하기
다른 글
-
[UE] Behavior Tree: 피격(Hitted)
[UE] Behavior Tree: 피격(Hitted)
2023.07.31 -
[UE] Behavior Tree: 무기 장착 및 공격
[UE] Behavior Tree: 무기 장착 및 공격
2023.07.27 -
[UE] Behavior Tree: 무기 장착(Equip)
[UE] Behavior Tree: 무기 장착(Equip)
2023.07.26 -
[UE] Behavior Tree 시작
[UE] Behavior Tree 시작
2023.07.24