[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이번에는 궁수 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: 무기 장착 및 공격
[UE] Behavior Tree: 무기 장착 및 공격
2023.07.27지난 시간에 구현한 Enemy 무기장착 시, Player쪽에서 무기 장착 몽타주가 재생되며 무기가 제대로 장착되지 않는 버그가 있었다. 이는 Weapon이 가지고 있는 정보들이 Player와 연관되어 있기 때문이었다. 이번 시간에 WeaponAsset의 일부 데이터를 WeaponData라는 새로운 클래스에 옮겨 분리시켜 줄 것이다. 이렇게 함으로써 무기 장착 시 무기가 Player쪽으로 불리는 문제를 방지시켜 줄 것이다. 목차 Plugins Weapon Resource Icon128.pngweapon_thumbnail_icon.png Source Weapon SWeaponCheckBoxes.h .cppSWeaponDetailsView.h .cppSWeaponDoAction… -
[UE] Behavior Tree: 무기 장착(Equip)
[UE] Behavior Tree: 무기 장착(Equip)
2023.07.26현재 적 AI 순찰을 진행하다 Player가 시야 범위 내에 들어오면 접근한다. 이번 시간에는 Player에게 접근한 후 무기를 장착하는 것을 Behavior Tree를 통해 구현할 것이다. BTTask_Node를 새로 만들어 무기 장착 Task를 전담하게 할 것이다. 목차 Plugins Weapon Resource Icon128.pngweapon_thumbnail_icon.png Source Weapon SWeaponCheckBoxes.h .cppSWeaponDetailsView.h .cppSWeaponDoActionData.h .cppSWeaponEquipmentData.h .cppSWeaponHitData.h .cppSWeaponLeftArea.h .cppWeapo…
댓글을 사용할 수 없습니다.