지난 시간에 구현한 Enemy 무기장착 시, Player쪽에서 무기 장착 몽타주가 재생되며 무기가 제대로 장착되지 않는 버그가 있었다. 이는 Weapon이 가지고 있는 정보들이 Player와 연관되어 있기 때문이었다. 이번 시간에 WeaponAsset의 일부 데이터를 WeaponData라는 새로운 클래스에 옮겨 분리시켜 줄 것이다. 이렇게 함으로써 무기 장착 시 무기가 Player쪽으로 불리는 문제를 방지시켜 줄 것이다.

 

목차

     

     


     

     

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

     

     

     

    무기 장착 버그수정

     


     

     

    Enemy의 무기 장착 버그가 발생하는 이유

     

    DA_Sword
    정보 + 객체
    Player
    Enemy 1
    Enemy 2
    Enemy 3
    ...

    생성 객체 정보를 WeaponData로 뺄 것이다. 

     

     

    C++는 에셋 공유 문제를 잘 체크해야 한다. 

     

     

    ResourceManager

    Load

     

    ResourceManager::Load(" ")

     

    팩토리얼 패턴


     

     

    CWeaponAsset 내의 일부 데이터를 CWeaponData로 옮긴다.

     

     

    CWeaponAsset.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Engine/DataAsset.h"
    #include "Weapons/CWeaponStructures.h"
    #include "CWeaponAsset.generated.h"
    
    UCLASS()
    class U2212_06_API UCWeaponAsset : public UDataAsset
    {
    	GENERATED_BODY()
    
    private:
    	UPROPERTY(EditAnywhere)
    		TSubclassOf<class ACAttachment> AttachmentClass;
    
    	UPROPERTY(EditAnywhere)
    		FEquipmentData EquipmentData;
    
    	UPROPERTY(EditAnywhere)
    		TSubclassOf<class UCEquipment> EquipmentClass;
    
    	UPROPERTY(EditAnywhere)
    		TSubclassOf<class UCDoAction> DoActionClass;
    
    	UPROPERTY(EditAnywhere)
    		TArray<FDoActionData> DoActionDatas; //CWeaopnStructure내의 FDoActionData
    
    	UPROPERTY(EditAnywhere)
    		TArray<FHitData> HitDatas; //CWeaopnStructure내의 FHitData
    
    	UPROPERTY(EditAnywhere)
    		TSubclassOf<class UCSubAction> SubActionClass;
    
    public:
    	UCWeaponAsset();
    
    	void BeginPlay(class ACharacter* InOwner, class UCWeaponData** OutWeaponData);//매개변수 class UCWeaponData** OutWeaponData를 사용. 객체를 생성해서 리턴한다. 생성 리턴이기 때문에 이차 포인터를 사용하였다.
    
    
    #if WITH_EDITOR //Editor 내에서만 수행
    	void PostEditChangeChainProperty(struct FPropertyChangedChainEvent& PropertyChangedEvent) override;
    #endif
    };

    인라인 함수 삭제 (CWeaponAsset -> CWeaponData로 이동)

    • FORCEINLINE class ACAttachment* GetAttachment() { return Attachment; }  
    • FORCEINLINE class UCEquipment* GetEquipment() { return Equipment; }
    • FORCEINLINE class UCDoAction* GetDoAction() { return DoAction; }
    • FORCEINLINE class UCSubAction* GetSubAction() { return SubAction; }

     

    변수 삭제  (CWeaponAsset -> CWeaponData로 이동)

    • class ACAttachment* Attachment;
    • class UCEquipment* Equipment;
    • class UCDoAction* DoAction;
    • class UCSubAction* SubAction;

     

    BeginPlay 함수에 매개변수 추가

    • void BeginPlay(class ACharacter* InOwner, class UCWeaponData** OutWeaponData);
      • class UCWeaponData** OutWeaponData 변수 추가
      • 이기 때문에 이중 포인터를 사용하였다.

     

     

     

    CWeaponAsset.cpp

    더보기
    #include "Weapons/CWeaponAsset.h"
    #include "Global.h"
    #include "CAttachment.h"
    #include "CEquipment.h"
    #include "CDoAction.h"
    #include "CSubAction.h"
    #include "CWeaponData.h"
    #include "GameFramework/Character.h"
    
    UCWeaponAsset::UCWeaponAsset()
    {
    	AttachmentClass = ACAttachment::StaticClass();//기본값
    	EquipmentClass = UCEquipment::StaticClass();//기본값
    }
    
    void UCWeaponAsset::BeginPlay(ACharacter* InOwner, class UCWeaponData** OutWeaponData)
    {
    	//CWeaponData.h로 변수들이 이동하였기 attachment, equipment, doAction, subAction 객체 각각 생성.
    
    	ACAttachment* attachment = nullptr;
    	if (!!AttachmentClass)//AttachmentClass가 선택되어 있다면
    	{
    		FActorSpawnParameters params;
    		params.Owner = InOwner;
    
    		attachment = InOwner->GetWorld()->SpawnActor<ACAttachment>(AttachmentClass, params);
    	}
    
    	UCEquipment* equipment = nullptr;
    	if (!!EquipmentClass)//EquipmentClass가 선택되어 있다면
    	{
    		equipment = NewObject<UCEquipment>(this, EquipmentClass);
    		equipment->BeginPlay(InOwner, EquipmentData);
    
    		if (!!attachment)//Attachment가 있다면
    		{
    			equipment->OnEquipmentBeginEquip.AddDynamic(attachment, &ACAttachment::OnBeginEquip);
    			equipment->OnEquipmentUnequip.AddDynamic(attachment, &ACAttachment::OnUnequip);
    		}
    	}
    
    	UCDoAction* doAction = nullptr;
    	if(!!DoActionClass)
    	{
    		doAction = NewObject<UCDoAction>(this, DoActionClass);
    		doAction->BeginPlay(attachment, equipment, InOwner, DoActionDatas, HitDatas);
    
    		if (!!attachment)
    		{
    			attachment->OnAttachmentBeginCollision.AddDynamic(doAction, &UCDoAction::OnAttachmentBeginCollision);
    			attachment->OnAttachmentEndCollision.AddDynamic(doAction, &UCDoAction::OnAttachmentEndCollision);
    
    			attachment->OnAttachmentBeginOverlap.AddDynamic(doAction, &UCDoAction::OnAttachmentBeginOverlap);
    			attachment->OnAttachmentEndOverlap.AddDynamic(doAction, &UCDoAction::OnAttachmentEndOverlap);
    		}
    
    		if (!!equipment)//Bow_String 작업
    		{
    			equipment->OnEquipmentBeginEquip.AddDynamic(doAction, &UCDoAction::OnBeginEquip);
    			equipment->OnEquipmentUnequip.AddDynamic(doAction, &UCDoAction::OnUnequip);
    		}
    	}
    
    	UCSubAction* subAction = nullptr;
    	if(!!SubActionClass)
    	{
    		subAction = NewObject<UCSubAction>(this, SubActionClass);
    		subAction->BeginPlay(InOwner, attachment, doAction);
    	}
    
    	//매개변수 class UCWeaponData** OutWeaponData를 사용하였다. 객체를 생성해서 리턴한다. 생성 리턴이기 때문에 이차 포인터를 사용하였다.
    	*OutWeaponData = NewObject<UCWeaponData>();//동적할당
    	(*OutWeaponData)->Attachment = attachment;
    	(*OutWeaponData)->Equipment = equipment;
    	(*OutWeaponData)->DoAction = doAction;
    	(*OutWeaponData)->SubAction = subAction;
    
    }
    
    #if WITH_EDITOR //Editor 내에서만 수행
    void UCWeaponAsset::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent)
    {
    	Super::PostEditChangeChainProperty(PropertyChangedEvent);
    	CheckTrue(FApp::IsGame());//게임이 실행중이면 실행하면 안 되기 때문에 체크
    
    	bool bRefresh = false;
    	bRefresh |= PropertyChangedEvent.GetPropertyName().Compare("DoActionDatas") == 0;
    	bRefresh |= PropertyChangedEvent.GetPropertyName().Compare("HitDatas") == 0;//수정하려는 변수명 == 0 이면 이름이 동일하다는 의미.
    
    	if (bRefresh)
    	{
    		bool bCheck = false;
    		bCheck |= PropertyChangedEvent.ChangeType == EPropertyChangeType::ArrayAdd;
    		bCheck |= PropertyChangedEvent.ChangeType == EPropertyChangeType::ArrayRemove;
    		bCheck |= PropertyChangedEvent.ChangeType == EPropertyChangeType::ArrayClear;
    		bCheck |= PropertyChangedEvent.ChangeType == EPropertyChangeType::Duplicate;
    
    		if (bCheck)
    		{
    			FPropertyEditorModule& prop = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");//WITH_EDITOR로 Editor 내에서 수행하기 때문에 사용 가능.
    			TSharedPtr<IDetailsView> detailsView = prop.FindDetailView("WeaponAssetEditorDetailsView");//WeaponAssetEditor.cpp에서 설정한 arg.ViewIdentifier이름 WeaponAssetEditorDetailsView 사용. WeaponAssetEditorDetailsView를 찾는다.
    
    			if (detailsView.IsValid())//detailsView 창이 그려졌다면
    				detailsView->ForceRefresh();//새로고침
    		}
    	}
    }
    #endif

    헤더 추가

    • #include "CWeaponData.h"

     

    가지고 있던 변수가 사라졌기 때문에 .cpp의 함수 내부에 변수를 선언해서 사용해야 한다.

    • void UCWeaponAsset::BeginPlay(ACharacter* InOwner, class UCWeaponData** OutWeaponData)
      • CWeaponData.h로 변수들이 이동하였기 때문에 attachment, equipment, doAction, subAction 객체 각각 생성하여 사용하였다.
        • ACAttachment* attachment = nullptr;
        • UCEquipment* equipment = nullptr;
        • UCDoAction* doAction = nullptr;
        • UCSubAction* subAction = nullptr;
      • 매개변수 class UCWeaponData** OutWeaponData를 사용하였다. 객체를 생성해서 리턴한다. 생성 리턴이기 때문에 이차 포인터를 사용하였다.
        • *OutWeaponData = NewObject<UCWeaponData>();//동적할당
        • (*OutWeaponData)->Attachment = attachment;
        • (*OutWeaponData)->Equipment = equipment;
        • (*OutWeaponData)->DoAction = doAction;
        • (*OutWeaponData)->SubAction = subAction;

     


     

     

     

    CWeaponData 생성

     

    새 C++ 클래스 - Object - CWeaponData 생성

    객체가 생성된 것만을 보유할 것이기 때문에 UObject 상속을 받는다.

     

     

     

    CWeaponData.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "UObject/NoExportTypes.h"
    #include "CWeaponData.generated.h"
    
    UCLASS()
    class U2212_06_API UCWeaponData : public UObject
    {
    	GENERATED_BODY()
    
    //팩토리얼 패턴
    //private으로 변수들을 숨기고 아래의 FORCENILINE 함수들로 변수를 콜한다.
    //CWeaponData는 상속관계에서 분리되어 있기 때문에 아래의 변수들을 사용하게 될 클래스인 UCWeaponAsset을 friend 클래스로 선언하여 CWeaponAsset 클래스에서 CWeaponData 클래스를 접근할 수 있게 해준다.
    private:
    	friend class UCWeaponAsset;
    
    public:
    	FORCEINLINE class ACAttachment* GetAttachment() { return Attachment; }//외부에 생성된 것을 리턴해줌. 
    	FORCEINLINE class UCEquipment* GetEquipment() { return Equipment; }//외부에 생성된 것을 리턴해줌.
    	FORCEINLINE class UCDoAction* GetDoAction() { return DoAction; }//외부에 생성된 것을 리턴해줌.
    	FORCEINLINE class UCSubAction* GetSubAction() { return SubAction; }//외부에 생성된 것을 리턴해줌.
    
    private:
    	//UPROPERTY를 붙여 가비지 콜렉터가 제거하기 전까지 물고 있게 만든다.
    	//UWeaponAsset은 UObject로부터 상속받아 Actor의 생성주기에 영향을 받지 않아 가비지 콜렉터에 영향을 받는다.
    
    	UPROPERTY()
    		class ACAttachment* Attachment;
    
    	UPROPERTY()
    		class UCEquipment* Equipment;
    
    	UPROPERTY()
    		class UCDoAction* DoAction;
    
    	UPROPERTY()
    		class UCSubAction* SubAction;
    };

     

     

     

    CWeaponData .cpp

    더보기
    #include "Weapons/CWeaponData.h"

     


     

     

    CWeaponComponent

     

     

    CWeaponComponent.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Components/ActorComponent.h"
    #include "CWeaponComponent.generated.h"
    
    UENUM(BlueprintType)
    enum class EWeaponType : uint8
    {
    	Fist, Sword, Hammer, Warp, Around, Bow, Max,
    };
    DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FWeaponTypeChanged, EWeaponType, InPrevType, EWeaponType, InNewType);
    
    UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
    class U2212_06_API UCWeaponComponent : public UActorComponent
    {
    	GENERATED_BODY()
    
    private://DataAsset을 받아온다.
    	UPROPERTY(EditAnywhere, Category = "DataAsset")
    		class UCWeaponAsset* DataAssets[(int32)EWeaponType::Max];
    
    public:
    	FORCEINLINE EWeaponType GetWeaponType() { return Type; }
    
    public: //무기 Type이 맞는지 확인해주는 함수들
    	FORCEINLINE bool IsUnarmedMode() { return Type == EWeaponType::Max; }
    	FORCEINLINE bool IsFistMode() { return Type == EWeaponType::Fist; }
    	FORCEINLINE bool IsSwordMode() { return Type == EWeaponType::Sword; }
    	FORCEINLINE bool IsHammerMode() { return Type == EWeaponType::Hammer; }
    	FORCEINLINE bool IsWarpMode() { return Type == EWeaponType::Warp; }
    	FORCEINLINE bool IsAroundMode() { return Type == EWeaponType::Around; }
    	FORCEINLINE bool IsBowMode() { return Type == EWeaponType::Bow; }
    
    public:
    	UCWeaponComponent();
    
    protected:
    	virtual void BeginPlay() override;
    
    public:
    	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
    
    private:
    	bool IsIdleMode();//StateComponent, WeaponComponent 둘 다 같은 레벨이다. 서로 소유할 수 없는 관계이기 때문에 참조만해서 리턴받기 위해 IsIdleMode를 사용한다.
    
    public:
    	class ACAttachment* GetAttachment();
    	class UCEquipment* GetEquipment();
    	class UCDoAction* GetDoAction();
    	class UCSubAction* GetSubAction();
    
    public: //무기 세팅
    	void SetUnarmedMode();
    	void SetFistMode();
    	void SetSwordMode();
    	void SetHammerMode();
    	void SetWarpMode();
    	void SetAroundMode();
    	void SetBowMode();
    
    	void DoAction();
    	void SubAction_Pressed();
    	void SubAction_Released();
    
    private:
    	void SetMode(EWeaponType InType);
    	void ChangeType(EWeaponType InType);
    
    public: //무기가 바뀌었을때 통보해줄 delegate
    	FWeaponTypeChanged OnWeaponTypeChange;
    
    private:
    	class ACharacter* OwnerCharacter;
    
    	EWeaponType Type = EWeaponType::Max;
    
    private:
    	//가비지 콜랙터가 삭제하지 않도록 UPROPERTY를 붙여 직렬화 시켜준다. 직렬화되면 가비지 콜렉터가 삭제하지 않는다. UPROPERTY가 없으면 터진다.
    	UPROPERTY()
    	class UCWeaponData* Datas[(int32)EWeaponType::Max];//실제로 생성된 데이터
    };

    배열 변수 추가

    • class UCWeaponData* Datas[(int32)EWeaponType::Max];
      • 실제로 생성된 데이터를 담는 변수
      • UPROPERTY()를 붙여준다. 
      • 가비지 콜랙터가 삭제하지 않도록 UPROPERTY를 붙여 직렬화 시켜준다. 직렬화되면 가비지 콜렉터가 삭제하지 않는다. UPROPERTY가 없으면 터진다.

     

     

     

    CWeaponComponent.cpp

    더보기
    #include "Components/CWeaponComponent.h"
    #include "Global.h"
    #include "CStateComponent.h"
    #include "GameFramework/Character.h"
    #include "Weapons/CWeaponAsset.h"
    #include "Weapons/CWeaponData.h"
    #include "Weapons/CAttachment.h"
    #include "Weapons/CEquipment.h"
    #include "Weapons/CDoAction.h"
    #include "Weapons/CSubAction.h"
    
    UCWeaponComponent::UCWeaponComponent()
    {
    	//Tick을 실행시켜주는 코드
    	PrimaryComponentTick.bCanEverTick = true;
    }
    
    void UCWeaponComponent::BeginPlay()
    {
    	Super::BeginPlay();
    
    	OwnerCharacter = Cast<ACharacter>(GetOwner());
    	for (int32 i=0; i < (int32)EWeaponType::Max; i++)
    	{
    		if (!!DataAssets[i]) //DataAssets[i]이 있다면(=무기가 할당되어 있다면)
    			DataAssets[i]->BeginPlay(OwnerCharacter, &Datas[i]);//BeginPla y 시 OwnerCharacter에 Spawn시켜준다.
    	}
    }
    
    void UCWeaponComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
    {
    	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
    
    	if (!!GetDoAction())//DoAction이 있다면
    		GetDoAction()->Tick(DeltaTime);//DoAction의 Tick을 콜 해준다.
    
    	if (!!GetSubAction())//SubAction이 있다면
    		GetSubAction()->Tick(DeltaTime);//SubAction의 Tick을 콜 해준다.
    }
    
    bool UCWeaponComponent::IsIdleMode()
    {
    	return CHelpers::GetComponent<UCStateComponent>(OwnerCharacter)->IsIdleMode();
    }
    
    ACAttachment* UCWeaponComponent::GetAttachment()
    {
    	CheckTrueResult(IsUnarmedMode(), nullptr);
    	CheckFalseResult(!!Datas[(int32)Type], nullptr);
    
    	return Datas[(int32)Type]->GetAttachment();//실제 데이터를 리턴
    }
    
    UCEquipment* UCWeaponComponent::GetEquipment()
    {
    	CheckTrueResult(IsUnarmedMode(), nullptr);
    	CheckFalseResult(!!Datas[(int32)Type], nullptr);
    
    	return Datas[(int32)Type]->GetEquipment();//실제 데이터를 리턴
    }
    
    UCDoAction* UCWeaponComponent::GetDoAction()
    {
    	CheckTrueResult(IsUnarmedMode(), nullptr);
    	CheckFalseResult(!!Datas[(int32)Type], nullptr);
    
    	return Datas[(int32)Type]->GetDoAction();//실제 데이터를 리턴
    }
    
    UCSubAction* UCWeaponComponent::GetSubAction()
    {
    	CheckTrueResult(IsUnarmedMode(), nullptr);
    	CheckFalseResult(!!Datas[(int32)Type], nullptr);
    
    	return Datas[(int32)Type]->GetSubAction();//실제 데이터를 리턴
    }
    
    void UCWeaponComponent::SetUnarmedMode()
    {
    	GetEquipment()->Unequip();
    
    	ChangeType(EWeaponType::Max);
    }
    
    void UCWeaponComponent::SetFistMode()
    {
    	CheckFalse(IsIdleMode());
    
    	SetMode(EWeaponType::Fist);
    }
    
    void UCWeaponComponent::SetSwordMode()
    {
    	CheckFalse(IsIdleMode());
    
    	SetMode(EWeaponType::Sword);
    }
    
    void UCWeaponComponent::SetHammerMode()
    {
    	CheckFalse(IsIdleMode());
    
    	SetMode(EWeaponType::Hammer);
    }
    
    void UCWeaponComponent::SetWarpMode()
    {
    	CheckFalse(IsIdleMode());
    
    	SetMode(EWeaponType::Warp);
    }
    
    void UCWeaponComponent::SetAroundMode()
    {
    	CheckFalse(IsIdleMode());
    
    	SetMode(EWeaponType::Around);
    }
    
    void UCWeaponComponent::SetBowMode()
    {
    	CheckFalse(IsIdleMode());
    
    	SetMode(EWeaponType::Bow);
    }
    
    void UCWeaponComponent::SetMode(EWeaponType InType)
    {
    	if (Type == InType)
    	{
    		SetUnarmedMode();
    
    		return;
    	}
    	else if (IsUnarmedMode() == false)
    	{
    		GetEquipment()->Unequip();
    	}
    
    	if (!!Datas[(int32)InType])//실제 데이터가 있다면
    	{
    		Datas[(int32)InType]->GetEquipment()->Equip();
    
    		ChangeType(InType);
    	}
    }
    
    void UCWeaponComponent::ChangeType(EWeaponType InType)
    {
    	EWeaponType prevType = Type;
    	Type = InType;
    
    	if (OnWeaponTypeChange.IsBound())
    		OnWeaponTypeChange.Broadcast(prevType, InType);
    }
    
    void UCWeaponComponent::DoAction()
    {
    	if (!!GetDoAction())
    		GetDoAction()->DoAction();
    }
    
    void UCWeaponComponent::SubAction_Pressed()
    {
    	if (!!GetSubAction())
    		GetSubAction()->Pressed();
    }
    
    void UCWeaponComponent::SubAction_Released()
    {
    	if (!!GetSubAction())
    		GetSubAction()->Released();
    }

    헤더 추가

    • #include "Weapons/CWeaponData.h"

     

    void UCWeaponComponent::BeginPlay()

    • for (int32 i=0; i < (int32)EWeaponType::Max; i++)
      • if (!!DataAssets[i])
        DataAssets[i]->BeginPlay(OwnerCharacter, &Datas[i]);
        • CWeaponAsset.h .cpp BeginPlay 생성 리턴으로 수정하였다. 
        • CWeaponAsset.h .cpp의 BeginPlay의 매개변수가 추가되었기 때문에  class UCWeaponData** OutWeaponData 매개변수 자리에 &Datas[i]를 넣어준다. 실제로 생성될 데이터인 &Datas[i]를 넣어주면 CWeaponAsset.h .cpp의 BeginPlay에서 데이터를 받아 생성한다.

     

     


     

     

     

     

    Action

     


     

    CBTTaskNode_Action 생성

     

    새 C++ 클래스 - BTTaskNode - 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:
    	virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
    	virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) 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);
    
    		return;
    	}
    }

     

     


     

    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 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;
    };

    인라인 함수 추가

    • FORCEINLINE bool GetInAction() { return bInAction; }
      • bInAction 변수를 받아오는 인라인 함수를 추가하였다. 
      • CBTTaskNode_Action.cppTickTask()함수에서 공격가능 여부를  bInAction 변수로 체크하기 위해 GetInAction() 함수를 콜한다.

     

     

     

    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();
    }

     


     

    BT_Melee

     


     

     

    실행화면

     

     

     

     

     


     

     

     

     

    Player Hitted 구현하기