[UE] Behavior Tree: 활 Enemy 생성, Player 바라보기

이번에는 궁수 AI를 만들것이다. 원거리에서 화살을 날리는 궁수 AI는 근거리 AI와 함께 게임에 배치하면 적 공격 패턴이 다양해져 게임에 재미를 더할 수 있다. Behavior Tree는 일전에 구현한 Melee와 크게 다르지 않다. 적을 인식하면 무기를 장착하고 활 시위를 당긴 후 화살을 날리게 만들 것이다.
목차
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 CBTService_Range.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 | ||
CBTTaskNode_Action에 Abort 추가하기
CDoAction
CDoAction.h
더보기
#pragma once #include "CoreMinimal.h" #include "UObject/NoExportTypes.h" #include "Weapons/CWeaponStructures.h" #include "CDoAction.generated.h" UCLASS(Abstract)//DoAction 그 자체로는 객체화되면 안 되기 때문에 Abstract을 붙여준다. class U2212_06_API UCDoAction : public UObject { GENERATED_BODY() public: FORCEINLINE bool GetBeginAction() { return bBeginAction; } FORCEINLINE bool GetInAction() { return bInAction; } public: UCDoAction(); virtual void BeginPlay //재정의 할 수 있도록 virtual로 만든다. ( class ACAttachment* InAttachment, class UCEquipment* InEquipment, class ACharacter* InOwner, const TArray<FDoActionData>& InDoActionDatas,//CWeaponStucture내의 FDoActionData const TArray<FHitData>& InHitDatas //CWeaponStucture내의 FHitData ); virtual void Tick(float InDeltaTime) { } public: //재정의 할 수 있도록 virtual로 만든다. virtual void DoAction(); virtual void Begin_DoAction(); virtual void End_DoAction(); public: //CDoAction_Bow에서 재정의 시키기위해 virtual로 만든다. //BeginEquip될 때 충돌체를 꺼주고 Unequip될 때 충돌체를 켜준다. UFUNCTION() virtual void OnBeginEquip() { } UFUNCTION() virtual void OnUnequip() { } public: UFUNCTION() virtual void OnAttachmentBeginCollision() {} UFUNCTION() virtual void OnAttachmentEndCollision() {} UFUNCTION() virtual void OnAttachmentBeginOverlap(class ACharacter* InAttacker, AActor* InAttackCauser, class ACharacter* InOther) { } UFUNCTION() virtual void OnAttachmentEndOverlap(class ACharacter* InAttacker, class ACharacter* InOther) { } protected: bool bInAction; bool bBeginAction; class ACharacter* OwnerCharacter; class UWorld* World; class UCMovementComponent* Movement; class UCStateComponent* State; TArray<FDoActionData> DoActionDatas; TArray<FHitData> HitDatas; };
인라인 Get 함수 추가
- FORCEINLINE bool GetBeginAction() { return bBeginAction; }
CDoAction.cpp
더보기
#include "Weapons/CDoAction.h" #include "Global.h" #include "CAttachment.h" #include "CEquipment.h" #include "GameFramework/Character.h" #include "Components/CStateComponent.h" #include "Components/CMovementComponent.h" UCDoAction::UCDoAction() { } void UCDoAction::BeginPlay(ACAttachment* InAttachment, UCEquipment* InEquipment, ACharacter* InOwner, const TArray<FDoActionData>& InDoActionDatas, const TArray<FHitData>& InHitDatas) { OwnerCharacter = InOwner; World = OwnerCharacter->GetWorld(); State = CHelpers::GetComponent<UCStateComponent>(OwnerCharacter); Movement = CHelpers::GetComponent<UCMovementComponent>(OwnerCharacter); DoActionDatas = InDoActionDatas; HitDatas = InHitDatas; } void UCDoAction::DoAction() { bInAction = true; State->SetActionMode(); } void UCDoAction::Begin_DoAction() { bBeginAction = true; } void UCDoAction::End_DoAction() { bInAction = false; bBeginAction = false; State->SetIdleMode(); Movement->Move(); Movement->DisableFixedCamera(); }
변경사항 없음.
CBTTaskNode_Action
CBTTaskNode_Action.h
더보기
#pragma once #include "CoreMinimal.h" #include "BehaviorTree/BTTaskNode.h" #include "CBTTaskNode_Action.generated.h" UCLASS() class U2212_06_API UCBTTaskNode_Action : public UBTTaskNode { GENERATED_BODY() public: UCBTTaskNode_Action(); protected: EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override; void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override; EBTNodeResult::Type AbortTask(UBehaviorTreeComponent& Owner0Comp, uint8* NodeMemory) override; };
Abort 함수 추가
- EBTNodeResult::Type AbortTask(UBehaviorTreeComponent& Owner0Comp, uint8* NodeMemory) override;
CBTTaskNode_Action.cpp
더보기
#include "BehaviorTree/CBTTaskNode_Action.h" #include "Global.h" #include "Characters/CEnemy_AI.h" #include "Characters/CAIController.h" #include "Components/CStateComponent.h" #include "Components/CWeaponComponent.h" #include "Weapons/CDoAction.h" UCBTTaskNode_Action::UCBTTaskNode_Action() { NodeName = "Action"; bNotifyTick = true; } EBTNodeResult::Type UCBTTaskNode_Action::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) { Super::ExecuteTask(OwnerComp, NodeMemory); ACAIController* controller = Cast<ACAIController>(OwnerComp.GetOwner()); ACEnemy_AI* ai = Cast<ACEnemy_AI>(controller->GetPawn()); UCWeaponComponent* weapon = CHelpers::GetComponent<UCWeaponComponent>(ai); CheckNullResult(weapon, EBTNodeResult::Failed); //Weapon 안에 공격할 때 정지여부(CanMove)가 있다. //Player기준: Weapon에서 DoAction을 한다. DoAction은 WeaponStructure를 가지고 있다. WeaponSturcture 내의 movement를 통해서 정지 여부를 결정한다. //하지만 Enemy는 알 수 없다. 그래서 controller->StopMovement()로 정지시킨다. controller->StopMovement(); weapon->DoAction(); return EBTNodeResult::InProgress; } void UCBTTaskNode_Action::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()); UCWeaponComponent* weapon = CHelpers::GetComponent<UCWeaponComponent>(ai); UCStateComponent* state = CHelpers::GetComponent<UCStateComponent>(ai); bool bCheck = true; bCheck &= (state->IsIdleMode()); bCheck &= (weapon->GetDoAction()->GetInAction() == false); if (bCheck) { FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);//Succeeded로 끝낸다. 리턴이 없는 Tick 같은 곳에서 사용한다. return; } } EBTNodeResult::Type UCBTTaskNode_Action::AbortTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) { Super::AbortTask(OwnerComp, NodeMemory); ACAIController* controller = Cast<ACAIController>(OwnerComp.GetOwner()); ACEnemy_AI* ai = Cast<ACEnemy_AI>(controller->GetPawn()); UCWeaponComponent* weapon = CHelpers::GetComponent<UCWeaponComponent>(ai); if (weapon == nullptr)//무기가 없다면 return EBTNodeResult::Failed;//실패 종료한다. bool bBeginAction = weapon->GetDoAction()->GetBeginAction();//CDoAction.h에서 만든 FORCEINLINE Get함수. bBeginAction 변수의 true, false 여부를 알려준다. //bBeginAction=true면 공격이 시작되었다는 의미, false면 공격이 시작하지 않았다는 의미다. if (bBeginAction == false) weapon->GetDoAction()->Begin_DoAction();//공격을 시작한다. weapon->GetDoAction()->End_DoAction();//공격을 끝낸다. return EBTNodeResult::Succeeded;//성공 종료한다. }
Abort 함수 정의
- EBTNodeResult::Type UCBTTaskNode_Action::AbortTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
- if (weapon == nullptr)
return EBTNodeResult::Failed;- WeaponComponent가 없다면 EBTNodeResult::Failed 리턴하고 종료.
- bool bBeginAction = weapon->GetDoAction()->GetBeginAction();
- 위의 CDoAction.h에서 만든 FORCEINLINE Get함수
- bBeginAction 변수가 true, false 인지 알려준다.
- if (bBeginAction == false)
weapon->GetDoAction()->Begin_DoAction();- 공격을 시작하지 않았다면 공격을 시작한다.
- return EBTNodeResult::Succeeded;
- 위에서 EBTNodeResult::Failed 리턴하지 않았다면 EBTNodeResult::Succeeded를 리턴하고 종료.
- if (weapon == nullptr)
실행화면

Range 구현하기
Melee를 구현한 것과 같이
- BP_CAIController_Range
- BP_CEnemy_Range
- BB_Range
- BT_Range
를 생성한다.
CBTService_Range 생성
새 C++ 클래스 - BTService - CBTService_Range 생성


CBTService_Range.h
더보기
#pragma once #include "CoreMinimal.h" #include "BehaviorTree/BTService.h" #include "CBTService_Range.generated.h" UCLASS() class U2212_06_API UCBTService_Range : public UBTService { GENERATED_BODY() private: UPROPERTY(EditAnywhere, Category = "Action") float AvoidRange = 500;//탐지되었을때 회피범위 UPROPERTY(EditAnywhere, Category = "Action") bool bDrawDebug;//디버깅 그리기 회피범위 public: UCBTService_Range(); protected: void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override; };
CBTService_Range.cpp
더보기
#include "BehaviorTree/CBTService_Range.h" #include "Global.h" #include "GameFramework/Character.h" #include "Characters/CAIController.h" #include "Characters/CEnemy_AI.h" #include "Components/CStateComponent.h" #include "Components/CAIBehaviorComponent.h" UCBTService_Range::UCBTService_Range() { NodeName = "Range"; Interval = 0.1f;//호출 간격. 0.1초 마다 호출 RandomDeviation = 0.0f; } void UCBTService_Range::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) { Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds); ACAIController* controller = Cast<ACAIController>(OwnerComp.GetOwner()); ACEnemy_AI* ai = Cast<ACEnemy_AI>(controller->GetPawn()); UCStateComponent* state = CHelpers::GetComponent<UCStateComponent>(ai); UCAIBehaviorComponent* aiState = CHelpers::GetComponent<UCAIBehaviorComponent>(ai); if (bDrawDebug) { FVector start = ai->GetActorLocation();//ACEnemy_AI의 위치 start.Z -= 25; FVector end = start; end.Z += 50; DrawDebugCylinder(ai->GetWorld(), start, end, AvoidRange, 10, FColor::Red, true, Interval, 0, 0);//디버깅 원기둥 그리기 } if (state->IsHittedMode()) { aiState->SetHittedMode(); return; } ACharacter* target = aiState->GetTarget(); if (target == nullptr)//target이 없다면(=시야 범위 내에 적이 없다면) { //EAIFocusPriorty: Gameplay(Gameplay모드), LastFocusPriority(마지막 Focus 받은애를 우선순위에서 제거), Move(이동 Focus) controller->ClearFocus(EAIFocusPriority::Gameplay);//바라보는 Focus를 Gameplay 모드에서 제거 aiState->SetWaitMode();//타겟이 없다면 Wait모드로 만들어준다. return; } //감지가 되었을 때 controller->SetFocus(target);//감지가 된 target쪽으로 SetFocus하여 바라보게 만든다. float distance = ai->GetDistanceTo(target);//현재 나의 위치에서 target까지의 거리를 구한다. if (distance < AvoidRange)//거리가 설정한 공격범위 보다 작다면 { aiState->SetAvoidMode();//Avoid 모드로 만들어준다. return;//회피를 하고 리턴 } aiState->SetActionMode();//Action 모드로 만들어준다. BT_Range에 Action 블랙보드 밑에 Equip, Action를 연결하여 무기를 장착하고 공격하게 만든다. }
CBTTaskNode_Equip
CBTTaskNode_Equip.h
더보기
#pragma once #include "CoreMinimal.h" #include "BehaviorTree/BTTaskNode.h" #include "Components/CWeaponComponent.h" #include "CBTTaskNode_Equip.generated.h" UCLASS() class U2212_06_API UCBTTaskNode_Equip : public UBTTaskNode { GENERATED_BODY() private: UPROPERTY(EditAnywhere, Category = "Type") EWeaponType Type; public: UCBTTaskNode_Equip(); protected: EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override; void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override; EBTNodeResult::Type AbortTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override; };
변경사항 없음
CBTTaskNode_Equip.cpp
더보기
#include "BehaviorTree/CBTTaskNode_Equip.h" #include "Global.h" #include "Characters/CEnemy_AI.h" #include "Characters/CAIController.h" #include "Components/CStateComponent.h" #include "Weapons/CEquipment.h" UCBTTaskNode_Equip::UCBTTaskNode_Equip() { NodeName = "Equip"; bNotifyTick = true; } EBTNodeResult::Type UCBTTaskNode_Equip::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) { Super::ExecuteTask(OwnerComp, NodeMemory); ACAIController* controller = Cast<ACAIController>(OwnerComp.GetOwner()); ACEnemy_AI* ai = Cast<ACEnemy_AI>(controller->GetPawn()); UCWeaponComponent* weapon = CHelpers::GetComponent<UCWeaponComponent>(ai); CheckNullResult(weapon, EBTNodeResult::Failed);//weapon이 없다면 EBTNodeResult::Failed 리턴 if (Type == weapon->GetWeaponType())//장착될 무기 == 현재 장착된 무기 return EBTNodeResult::Succeeded;//새로 장착시킬 필요 없으니 바로 Succeeded를 리턴 switch (Type) { case EWeaponType::Sword: weapon->SetSwordMode(); break;//Sword 장착 case EWeaponType::Bow: weapon->SetBowMode(); break;//Bow 장착 } return EBTNodeResult::InProgress;//장착 동작이 나올 수 있도록 InProgress 리턴 } void UCBTTaskNode_Equip::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()); UCWeaponComponent* weapon = CHelpers::GetComponent<UCWeaponComponent>(ai);//weapon을 가져온다. const bool* bEquipped = weapon->GetEquipment()->GetEquipped(); UCStateComponent* state = CHelpers::GetComponent<UCStateComponent>(ai); if(state->IsIdleMode() && *bEquipped) { FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);//Succeeded로 끝낸다. 리턴이 없는 Tick 같은 곳에서 사용한다. return; } } EBTNodeResult::Type UCBTTaskNode_Equip::AbortTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) { Super::AbortTask(OwnerComp, NodeMemory); ACAIController* controller = Cast<ACAIController>(OwnerComp.GetOwner()); ACEnemy_AI* ai = Cast<ACEnemy_AI>(controller->GetPawn()); UCWeaponComponent* weapon = CHelpers::GetComponent<UCWeaponComponent>(ai); if (weapon == nullptr)//무기가 없다면 return EBTNodeResult::Failed;//실패 리턴 bool bBeginEquip = weapon->GetEquipment()->GetBeginEquip();//GetBeginEquip()으로 CEquipment의 bBeginEquip를 넣어준다. bBeginEquip=true면 Equip이 시작되었다는 의미다. if (bBeginEquip == false) weapon->GetEquipment()->Begin_Equip(); weapon->GetEquipment()->End_Equip(); return EBTNodeResult::Aborted;//Aborted는 취소 마감, 위(Root)로 올라감. Succeeded는 성공종료. }
Bow 장착 추가하기
- EBTNodeResult::Type UCBTTaskNode_Equip::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
- switch (Type)
{
case EWeaponType::Sword: weapon->SetSwordMode(); break;//Sword 장착
case EWeaponType::Bow: weapon->SetBowMode(); break;//Bow 장착
}
- switch (Type)
BP_CAIController_Range 생성

Perception
- AI Perception
- Sense Config
- Implementation: AISense_Sight
- Sight Radius: 1000.0
- Lose Sight Radius: 1200.0
- PeripheralVisionHalfAngleDegrees: 180.0
- 최대수명: 5.0
- Sense Config

- AISense_Sight
- AI Hearing config: Make Noise 감지
- AI Touch config: 닿았을 때 감지. Capsule의 Overlap 시 감지.
- AI Damaged config: Damage가 0보다 클 때 감지. TakeDamage 함수에 감지가 내장되어있다.
BP_CEnemy_Range 생성


BB_Range 생성

BT_Range 생성

실행화면

'를 눌러 디버깅 정보가 보이도록 하고 오른쪽 숫자패드 4번을 눌러 AISense_Sight이 보이도록 한다.
'⭐ Unreal Engine > UE RPG AI' 카테고리의 다른 글
[UE] Behavior Tree: 워프(Warp), EQS (0) | 2023.08.09 |
---|---|
[UE] Behavior Tree: 활 조준 및 발사하기 (0) | 2023.08.08 |
[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: 워프(Warp), EQS
[UE] Behavior Tree: 워프(Warp), EQS
2023.08.09궁수 AI를 만들어 Player를 향해 화살을 쏘아 날리는 것을 구현하였다. 게임의 재미를 더하기 위해 궁수 AI 회피를 추가할 것이다. 적이 궁수AI에 접근하여 일정거리에 도달하면 궁수 AI는 워프 스킬을 시전하여 달아날 것이다. EQS를 사용하여 적의 위치의 뒷공간 지정범위 내에서 랜덤으로 워프 이동할 것이다. 목차 Plugins Weapon Resource Icon128.pngweapon_thumbnail_icon.png Source Weapon SWeaponCheckBoxes.h .cppSWeaponDetailsView.h .cppSWeaponDoActionData.h .cppSWeaponEquipmentData.h .cppSWeaponHitData.h .cpp… -
[UE] Behavior Tree: 활 조준 및 발사하기
[UE] Behavior Tree: 활 조준 및 발사하기
2023.08.08Player에 구현한 화살 발사를 궁수 AI에도 적용한다. SetFocus를 사용하여 적을 지속적으로 바라보게 만든다. 몽타주를 재생시켜 활의 시위를 당기고 화살 발사 준비를 마친 후 CSubAction_Bow 코드를 재사용하여 화살을 발사한다. CSubAction_Bow 코드 내에 AIController일 때도 Press()와 Released(), 즉 조준 후 발사가 가능하도록 코드를 추가한다. 목차 Plugins Weapon Resource Icon128.pngweapon_thumbnail_icon.png Source Weapon SWeaponCheckBoxes.h .cppSWeaponDetailsView.h .cppSWeaponDoActionData.h .cpp… -
[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: 피격(Hitted)
[UE] Behavior Tree: 피격(Hitted)
2023.07.31적(Enemy)의 피격과 Behavior Tree가 연결되어 있지 않다. 적이 피격 되었을 때 Behavior Tree에 알리고 피격이 끝났을 때 Behavior를 WaitMode로 변경하는 코드를 추가하였다. 목차 Plugins Weapon Resource Icon128.pngweapon_thumbnail_icon.png Source Weapon SWeaponCheckBoxes.h .cppSWeaponDetailsView.h .cppSWeaponDoActionData.h .cppSWeaponEquipmentData.h .cppSWeaponHitData.h .cppSWeaponLeftArea.h .cppWeapon.Build.csWeaponAssetEditor.h ….
댓글을 사용할 수 없습니다.