적(Enemy)의 피격과 Behavior Tree가 연결되어 있지 않다.  적이 피격 되었을 때 Behavior Tree에 알리고 피격이 끝났을 때 Behavior를 WaitMode로 변경하는 코드를 추가하였다.

 

 

목차

     

     


     

     

     

     
    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_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
     

     

     

     

     

    Hitted 구현하기

     

     


     

     

    CBTTaskNode_Hitted 생성

     

    새 C++ 클래스 - BTTaskNode - CBTTaskNode_Hitted 생성

     

     

    CBTTaskNode_Hitted.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "BehaviorTree/BTTaskNode.h"
    #include "CBTTaskNode_Hitted.generated.h"
    
    UCLASS()
    class U2212_06_API UCBTTaskNode_Hitted : public UBTTaskNode
    {
    	GENERATED_BODY()
    
    public:
    	UCBTTaskNode_Hitted();
    
    protected:
    	virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
    	virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
    };

     

     

     

    CBTTaskNode_Hitted.cpp

    더보기
    #include "BehaviorTree/CBTTaskNode_Hitted.h"
    #include "Global.h"
    #include "Characters/CEnemy_AI.h"
    #include "Characters/CAIController.h"
    #include "Components/CStateComponent.h"
    
    UCBTTaskNode_Hitted::UCBTTaskNode_Hitted()
    {
    	bNotifyTick = true;
    
    	NodeName = "Hitted";
    }
    
    EBTNodeResult::Type UCBTTaskNode_Hitted::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
    {
    	Super::ExecuteTask(OwnerComp, NodeMemory);
    
    	ACAIController* controller = Cast<ACAIController>(OwnerComp.GetOwner());//Enemy의 controller 캐스팅하여 가져온다.
    
    	controller->StopMovement();//맞은 순간 Enemy를 멈추게 해준다.
    
    	return EBTNodeResult::InProgress;//맞으면서 동작 플레이가 되므로 InProgress
    }
    
    void UCBTTaskNode_Hitted::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());
    
    	UCStateComponent* state = CHelpers::GetComponent<UCStateComponent>(ai);
    	if (state->IsHittedMode() == false)//Hitted가 끝나면
    	{
    		//Task를 Succeeded로 만들어주고 리턴한다.
    		FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
    
    		return;
    	}
    }

     

     

     

     


     

     

     

    CEnemy

     

    CEnemy.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Characters/ICharacter.h"
    #include "Components/CStateComponent.h"
    #include "GameFramework/Character.h"
    #include "CEnemy.generated.h"
    
    UCLASS()
    class U2212_06_API ACEnemy : public ACharacter, public IICharacter //다중상속
    {
    	GENERATED_BODY()
    
    private:
    	UPROPERTY(EditAnywhere, Category = "Color")
    		FLinearColor OriginColor = FLinearColor::White;//색상 설정.
    
    protected:
    	UPROPERTY(VisibleAnywhere)
    		class UCMontagesComponent* Montages;
    
    	UPROPERTY(VisibleAnywhere)
    		class UCMovementComponent* Movement;
    
    	UPROPERTY(VisibleAnywhere)
    		class UCStateComponent* State;
    
    	UPROPERTY(VisibleAnywhere)
    		class UCStatusComponent* Status;
    
    public:
    	ACEnemy();
    
    public:
    	float TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent, class AController* EventInstigator, AActor* DamageCauser) override;//TakeDamage 오버라이드하여 재정의.
    
    protected:
    	virtual void BeginPlay() override;
    
    private:
    	UFUNCTION()
    		void OnStateTypeChanged(EStateType InPrevType, EStateType InNewType);
    
    protected:
    	virtual void Hitted();//자식에서 재정의 할 수 있도록 virtual로 만든다.
    
    public:
    	virtual void End_Hitted() override;//ICharacter의 가상함수 오버라이드. CEnemy_AI에서 재정의하도록 virtual.
    
    private:
    	UFUNCTION()
    	void RestoreColor();
    
    private:
    	void Dead();
    
    public:
    	void End_Dead() override;//ICharacter의 가상함수 오버라이드.
    
    private:
    	struct FDamageData
    	{
    		float Power;
    		class ACharacter* Character;
    		class AActor* Causer;
    
    		struct FActionDamageEvent* Event; //CWeaponStructure의 FActionDamageEvent
    	} Damage; //Damage란 이름으로 저장.
    
    private:
    	FTimerHandle RestoreColor_TimerHandle;//변한 색이 일정시간 후에 되돌아오도록 시간을 기록하는 변수.
    };

    자식 클래스(CEnemy_AI)에서 재정의할 수 있도록 virtual 함수로 변경

    • 변경 전: 
      • void Hitted();
      • void End_Hitted() override;
    • 변경 후: 
      • virtual void Hitted();
      • virtual void End_Hitted() override;
        • ICharacter의 가상함수 오버라이드. CEnemy_AI에서 재정의하도록 virtual.

     

     

     

    CEnemy.cpp

    더보기
    #include "Characters/CEnemy.h"
    #include "Global.h"
    #include "CAnimInstance.h"
    #include "GameFramework/CharacterMovementComponent.h"
    #include "Components/SkeletalMeshComponent.h"
    #include "Components/CapsuleComponent.h"
    #include "Components/CMontagesComponent.h"
    #include "Components/CMovementComponent.h"
    #include "Components/CStatusComponent.h"
    #include "Weapons/CWeaponStructures.h"
    
    ACEnemy::ACEnemy()
    {
    	CHelpers::CreateActorComponent<UCMontagesComponent>(this, &Montages, "Montage");
    	CHelpers::CreateActorComponent<UCMovementComponent>(this, &Movement, "Movement");
    	CHelpers::CreateActorComponent<UCStateComponent>(this, &State, "State");
    	CHelpers::CreateActorComponent<UCStatusComponent>(this, &Status, "Status");
    
    	GetMesh()->SetRelativeLocation(FVector(0, 0, -90));
    	GetMesh()->SetRelativeRotation(FRotator(0, -90, 0));
    
    	USkeletalMesh* mesh;
    	CHelpers::GetAsset<USkeletalMesh>(&mesh, "SkeletalMesh'/Game/Character/Mesh/SK_Mannequin.SK_Mannequin'");
    	GetMesh()->SetSkeletalMesh(mesh);
    
    	TSubclassOf<UCAnimInstance> animInstance;
    	CHelpers::GetClass<UCAnimInstance>(&animInstance, "AnimBlueprint'/Game/ABP_Character.ABP_Character_C'");
    	GetMesh()->SetAnimClass(animInstance);
    
    	GetCharacterMovement()->RotationRate = FRotator(0, 720, 0);
    
    }
    
    void ACEnemy::BeginPlay()
    {
    	Super::BeginPlay();
    
    	Movement->OnWalk();//기본값 설정.
    
    	Create_DynamicMaterial(this);//IICharacter의 함수 사용. 색이 적용되는 객체를 this(여기서는 Enemy)로 설정.
    	Change_Color(this, OriginColor);//색상 할당.
    
    	State->OnStateTypeChanged.AddDynamic(this, &ACEnemy::OnStateTypeChanged);
    }
    
    void ACEnemy::OnStateTypeChanged(EStateType InPrevType, EStateType InNewType)
    {
      	switch (InNewType)
    	{
    		case EStateType::Hitted: Hitted(); break;//실제처리는 아래의 Hitted()에서 일어난다.
    		case EStateType::Dead:   Dead(); break;//실제처리는 아래의 Dead()에서 일어난다.
    	}
    }
     
    void ACEnemy::Hitted()
    {
    	//Apply Damage 데미지 처리
    	{   //다음 타격 때 꼬이지 않게 하기위해 초기화 해준다.
    		Status->Damage(Damage.Power);
    		Damage.Power = 0;
    	}
    	
    	//Change Color
    	{	
    		Change_Color(this, FLinearColor::Red);
    
    		FTimerDelegate timerDelegate;
    		timerDelegate.BindUFunction(this, "RestoreColor");
    
    		GetWorld()->GetTimerManager().SetTimer(RestoreColor_TimerHandle, timerDelegate, 0.2f, false);//0.2초 동안 반복없이 실행.
    	}
    	
    	//Damage는 CEnemy.h의 FDamageData이다.
    	if(!!Damage.Event && !!Damage.Event->HitData)
    	{
    		FHitData* data = Damage.Event->HitData;//FDamageData의 FActionDamageEvent* Event내의 HitData
    
    		data->PlayMontage(this);  //몽타주 재생
    		data->PlayHitStop(GetWorld());  //HitStop 재생
    		data->PlaySoundWave(this);//소리 재생
    		data->PlayEffect(GetWorld(), GetActorLocation(), GetActorRotation());//Effect 재생
    
    		if (Status->IsDead() == false)
    		{
    			FVector start = GetActorLocation(); //start=현재 위치
    			FVector target = Damage.Character->GetActorLocation();//target=데미지를 전달하는 대상
    			FVector direction = target - start;//direction=target을 바라보는 방향
    			direction.Normalize();
    
    			LaunchCharacter(-direction * data->Launch, false, false);
    			SetActorRotation(UKismetMathLibrary::FindLookAtRotation(start, target));
    		}
    	}
    
    	//사망 처리
    	if (Status->IsDead())
    	{
    		State->SetDeadMode();//State을 DeadMode로 만들어준다.
    
    		return;
    	}
    
    	Damage.Character = nullptr;
    	Damage.Causer = nullptr;
    	Damage.Event = nullptr;
    }
    
    void ACEnemy::End_Hitted()
    {
    	State->SetIdleMode();
    }
    
    void ACEnemy::RestoreColor()
    {
    	Change_Color(this, OriginColor);//원래 색으로 돌려준다.
    
    	GetWorld()->GetTimerManager().ClearTimer(RestoreColor_TimerHandle);//RestoreColor_TimerHandle를 초기화.
    }
    
    void ACEnemy::Dead()
    {
    	GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);//Collision 꺼줌.
    
    	Montages->PlayDeadMode();
    }
    
    void ACEnemy::End_Dead()
    {
    	Destroy(); //죽으면 소멸시켜준다.
    }
    
    float ACEnemy::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
    {
    	float damage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
    
    	Damage.Power = damage;
    	Damage.Character = Cast<ACharacter>(EventInstigator->GetPawn());
    	Damage.Causer = DamageCauser;
    	Damage.Event = (FActionDamageEvent*)&DamageEvent;//레퍼런스로 캐스팅하면 캐스팅이 안 되거나 캐스팅 후 상속이 안 먹는 경우가 발생할 수 있다. //안전하게 C스타일 캐스팅하기 위해 포인터로 캐스팅한다. 
    
    	State->SetHittedMode();//HittedMode로 변경.
    
    	return damage;
    }

    변경사항 없음.

     

     


     

     

    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();
    
    protected:
    	void Hitted() override;//CEnemy의 Hitted 재정의
    
    public:
    	void End_Hitted() override;//CEnemy의 End_Hitted 재정의
    };

    부모 클래스인 CEnemy의 함수 재정의

    • void Hitted() override;
    • void End_Hitted() override;

     

     

     

    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));
    }
    
    void ACEnemy_AI::Hitted()
    {
    	Super::Hitted();
    
    	CheckTrue(State->IsDeadMode());//이미 DeadMode면 Hitted 처리할 필요없다.
    
    	Behavior->SetHittedMode();
    }
    
    void ACEnemy_AI::End_Hitted()
    {
    	Super::End_Hitted();
    
    	Behavior->SetWaitMode();//Hitted가 끝나면 Behavior를 WaitMode로 만들어준다.
    }

    함수 재정의

    • void ACEnemy_AI::Hitted()
      • Super::Hitted();
        CheckTrue(State->IsDeadMode()); //이미 DeadMode면 Hitted 처리할 필요없다.
        Behavior->SetHittedMode();
    • void ACEnemy_AI::End_Hitted()
      • Super::End_Hitted();
        Behavior->SetWaitMode(); //Hitted가 끝나면 Behavior를 WaitMode로 만들어준다.

     

     

     

    ※참고: BP 프로젝트의 BP_Enemy_AI 블루프린트 노드

    Enemy가 피격되었을때 AI Behavior의 상태를 바꾸어주어야 한다.

     

     


     

     

     

     

    CAnimNotify_EndState

     

     

    CAnimNotify_EndState.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Animation/AnimNotifies/AnimNotify.h"
    #include "Components/CStateComponent.h"
    #include "CAnimNotify_EndState.generated.h"
    
    UCLASS()
    class U2212_06_API UCAnimNotify_EndState : public UAnimNotify
    {
    	GENERATED_BODY()
    
    private:
    	UPROPERTY(EditAnywhere, Category = "Type")
    		EStateType StateType;
    
    public:
    	FString GetNotifyName_Implementation() const override;
    
    	void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override;
    };

    변경사항 없음.

     

     

     

    CAnimNotify_EndState.cpp

    더보기
    #include "CAnimNotify_EndState.h"
    #include "Global.h"
    #include "Characters/ICharacter.h"
    
    FString UCAnimNotify_EndState::GetNotifyName_Implementation() const
    {
    	return "EndState";
    }
    
    void UCAnimNotify_EndState::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
    {
    	Super::Notify(MeshComp, Animation);
    	CheckNull(MeshComp);
    	CheckNull(MeshComp->GetOwner());
    
    	IICharacter* character = Cast<IICharacter>(MeshComp->GetOwner());
    	CheckNull(character);
    
    	switch(StateType)
    	{
    		//Dead를 Hitted보다 먼저 검사할 수 있게 순서를 맞춰준다.
    		case EStateType::BackStep: character->End_BackStep(); break;
    		case EStateType::Dead: character->End_Dead(); break;
    		case EStateType::Hitted: character->End_Hitted(); break;
    	}
    }

    void UCAnimNotify_EndState::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)

    • switch(StateType)
      {
      case EStateType::BackStep: character->End_BackStep(); break;
      case EStateType::Dead: character->End_Dead(); break;
      case EStateType::Hitted: character->End_Hitted(); break;
      }
      • DeadHitted보다 먼저 검사할 수 있게 순서를 맞춰준다.
      • End_Hitted();는 ICharacter, CEnemy, CEnemy_AI 순서로 재정의되며 실행될 것이다.

     


     

     

    피격(Hitted) 몽타주들

     

    피격이 되는 몽타주들의 맨 뒤에 CAnimNotify_EndState를 넣고 State TypeHitted로 설정한다.

     


     

     

    BT_Melee

     

     

    Wait을 맨 앞쪽에 넣은 이유는?

    • 처음 상태가 Wait으로 들어오기 때문에 처음 위치에 Wait이 없으면 Wait을 판단할 수 없어 제대로 동작하지 않는다.
    • 위의 경우 맨 앞의 Wait Task가 없으면 Enemy_AI가 움직이지 않는다. 
    • 시작부분이 되는 Task를 앞쪽에 넣으면 해결된다.
      • 해결방안
        • 방법1. 위의 방법처럼 맨 앞에 Wait Task를 넣는다.
        • 방법2. 코드에서 처음 상태를 Patrol로 만들어주면 Wait Task가 필요없다.    

     


     

     

    실행화면