[UE] Behavior Tree: 피격(Hitted)

적(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();
- Super::Hitted();
- void ACEnemy_AI::End_Hitted()
- Super::End_Hitted();
Behavior->SetWaitMode(); //Hitted가 끝나면 Behavior를 WaitMode로 만들어준다.
- Super::End_Hitted();
※참고: 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;
}- Dead를 Hitted보다 먼저 검사할 수 있게 순서를 맞춰준다.
- End_Hitted();는 ICharacter, CEnemy, CEnemy_AI 순서로 재정의되며 실행될 것이다.
피격(Hitted) 몽타주들

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

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

'⭐ Unreal Engine > UE RPG AI' 카테고리의 다른 글
[UE] Behavior Tree: 활 Enemy 생성, Player 바라보기 (0) | 2023.08.07 |
---|---|
[UE] Behavior Tree: 무기 장착 Abort (0) | 2023.08.01 |
[UE] Behavior Tree: 무기 장착 및 공격 (0) | 2023.07.27 |
[UE] Behavior Tree: 무기 장착(Equip) (0) | 2023.07.26 |
[UE] Behavior Tree: 순찰(Patrol) (0) | 2023.07.25 |
댓글
이 글 공유하기
다른 글
-
[UE] Behavior Tree: 활 Enemy 생성, Player 바라보기
[UE] Behavior Tree: 활 Enemy 생성, Player 바라보기
2023.08.07 -
[UE] Behavior Tree: 무기 장착 Abort
[UE] Behavior Tree: 무기 장착 Abort
2023.08.01 -
[UE] Behavior Tree: 무기 장착 및 공격
[UE] Behavior Tree: 무기 장착 및 공격
2023.07.27 -
[UE] Behavior Tree: 무기 장착(Equip)
[UE] Behavior Tree: 무기 장착(Equip)
2023.07.26
댓글을 사용할 수 없습니다.