[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