마우스 우클릭으로 각 무기 당 스킬을 구현할 것이다. 스킬들은 SubAction으로 묶는다. 첫번째로 주먹 스킬을 구현해보자.  

 

목차

     

     


     

     

     

     
    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
      U2212_06
        Characters
        CAnimInstance.h .cpp
    CEnemy.h .cpp 
    CPlayer.h .cpp
    ICharacter.h .cpp
        Components
        CMontagesComponent.h .cpp 
    CMovementComponent.h .cpp 
    CStateComponent.h .cpp
    CStatusComponent.h .cpp  

    CWeaponComponent.h .cpp 
        Notifies
        CAnimNotifyState_BeginAction.h .cpp 
    CAnimNotify_CameraShake.h .cpp
    CAnimNotifyState_EndAction.h .cpp
    CAnimNotify_EndState.h .cpp
    CAnimNotifyState_Collision.h .cpp 
    CAnimNotifyState_Combo.h .cpp
    CAnimNotifyState_Equip.h .cpp
    CAnimNotifyState_SubAction.h .cpp 생성
        Utilities
        CHelper.h
    CLog.h .cpp
        Weapons
        CDoAction_Combo.h .cpp
    CSubAction_Fist.h .cpp 생성
    CAttachment.h .cpp
    CDoAction.h .cpp
    CEquipment.h .cpp
    CSubAction.h .cpp 생성
    CWeaponAsset.h .cpp
    CWeaponStructures.h .cpp
        Global.h
    CGameMode.h .cpp
    U2212_06.Build.cs
        U2212_06.uproject
     

     

     

     

     

    주먹 난타 스킬 구현하기 - SubAction 구조 생성하기

     


     

    CSubAction 생성

     

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

     

     

    CSubAction.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "UObject/NoExportTypes.h"
    #include "CSubAction.generated.h"
    
    UCLASS(Abstract)//객체화하지 않는 경우, Abstract 명시
    class U2212_06_API UCSubAction : public UObject
    {
    	GENERATED_BODY()
    
    public:
    	UCSubAction();
    
    public:
    	virtual void BeginPlay(class ACharacter* InOwner, class ACAttachment* InAttachment, class UCDoAction* InDoAction);
    
    public:
    	virtual void Pressed() {}
    	virtual void Released() {}
    
    public:
    	//NativeEvent는 가상화. 정의할테니 원하면 가져다가 써라는 의미.
    	//Implementation는 추상화. 함수 호출 해줄테니 필요하면 재정의해서 써라.
    	UFUNCTION(BlueprintNativeEvent)
    		void Begin_SubAction();
    	virtual void Begin_SubAction_Implementation() {}
    
    	UFUNCTION(BlueprintNativeEvent)
    		void End_SubAction();
    	virtual void End_SubAction_Implementation() {}
    
    	UFUNCTION(BlueprintNativeEvent)
    		void Tick(float InDeltaTime);
    	virtual void Tick_Implementation(float InDeltaTime) {}
    
    protected:
    	class ACharacter* Owner;
    	class ACAttachment* Attachment;
    	class UCDoAction* DoAction;
    
    	class UCStateComponent* State;
    	class UCMovementComponent* Movement;
    };

    CASubAtion는 객체화하지 않는 클래스다. 

    • UCLASS(Abstract)
      • 객체화하지 않는 경우, Abstract 명시
      • ※ 주의: 추상화 클래스에 기본값을 넣으면 안 된다. null이어야 한다. 만약 기본값을 설정하여 객체화가 일어나면 컴파일러는 "객체화 될 수 없다"고 경고 문구가 나온다.

     

    UObject 상속 클래스에서의 Begin Play 사용

    • UObject 상속 클래스Begin Play 함수가 자동으로 설정되지 않는다.
    • 해당 경우 Begin Play를 사용하려면 ACharacter를 변수로 받아서 사용한다.
    • virtual void BeginPlay(class ACharacter* InOwner, class ACAttachment* InAttachment, class UCDoAction* InDoAction);

     

     

     

    CSubAction.cpp

    더보기
    #include "Weapons/CSubAction.h"
    #include "Global.h"
    #include "GameFramework/Character.h"
    #include "Components/CStateComponent.h"
    #include "Components/CMovementComponent.h"
    #include "Components/CapsuleComponent.h"
    
    UCSubAction::UCSubAction()
    {
    
    }
    
    void UCSubAction::BeginPlay(ACharacter* InOwner, ACAttachment* InAttachment, UCDoAction* InDoAction)
    {
    	Owner = InOwner;
    	Attachment = InAttachment;
    	DoAction = InDoAction;
    
    	State = CHelpers::GetComponent<UCStateComponent>(Owner);
    	Movement = CHelpers::GetComponent<UCMovementComponent>(Owner);
    }

     

     

     


     

     

    CSubAction_Fist 생성

     

    CSubAction 파생 C++ 클래스 생성 - CSubAction_Fist 생성

     

     

    CSubAction_Fist.h

    더보기

     

    #pragma once
    #include "CoreMinimal.h"
    #include "Weapons/CSubAction.h"
    #include "Weapons/CWeaponStructures.h"
    #include "CSubAction_Fist.generated.h"
    
    UCLASS(Blueprintable)//블루프린트화해서 설정할 수 있도록 Blueprintable 명시
    class U2212_06_API UCSubAction_Fist : public UCSubAction
    {
    	GENERATED_BODY()
    
    public:
    	UPROPERTY(EditAnywhere, Category = "Action")
    		FDoActionData ActionData;
    
    	UPROPERTY(EditAnywhere, Category = "Action")
    		TArray<FHitData> HitDatas;
    
    public:
    	void Pressed() override;
    
    	void End_SubAction_Implementation() override;
    
    private:
    	TArray<class ACharacter *> Hitted;
    	int32 HitIndex;
    };

    함수 오버라이드

    • void Pressed() override;
    • void End_SubAction_Implementation() override;

     

     

     

    CSubAction_Fist.cpp

    더보기
    #include "Weapons/SubActions/CSubAction_Fist.h"
    #include "Global.h"
    #include "GameFramework/Character.h"
    #include "Components/CStateComponent.h"
    #include "Components/CMovementComponent.h"
    #include "Weapons/CAttachment.h"
    #include "Weapons/CDoAction.h"
    
    void UCSubAction_Fist::Pressed()
    {
    	CheckFalse(State->IsIdleMode());
    	CheckTrue(State->IsSubActionMode());
    
    	Super::Pressed();
    
    	State->SetActionMode();
    	State->OnSubActionMode();
    
    	ActionData.DoAction(Owner);
    }
    
    void UCSubAction_Fist::End_SubAction_Implementation()
    {
    	Super::End_SubAction_Implementation();
    
    	//원래 상태로 돌려준다.
    	State->SetIdleMode();
    	State->OffSubActionMode();
    
    	Movement->Move();
    	Movement->DisableFixedCamera();
    }

    함수 정의

    • 키를 눌렀을 때 상태를 바꾸고 동작을 실행시킨다.
      • void UCSubAction_Fist::Pressed()
    • 끝난 후 상태를 돌려준다.
      • void UCSubAction_Fist::End_SubAction_Implementation()

     


     

     

    BP_CSubAction_Fist 생성

     

    CSubAction_Fist 기반 블루프린트 클래스 생성 - BP_CSubAction_Fist 생성

     

     

     


     

     

    문제상황:   

     

    BP_CSubAction_Fist를 처음 열면 ActionData 항목이 보이지만

    웨폰 플러그인을 실행하고 다시 열면 ActionData 항목이 보이지 않는다. 

     


     

    실행화면

     

    (좌) 변경 전: ActionData가 제대로 나오지 않는 모습, (우) 변경 후

     

    BP_CSubAction_Fist를 처음 열면 ActionData 항목이 보이지만

    웨폰 플러그인을 실행하고 다시 열면 ActionData 항목이 보이지 않는다. 


     

     

    문제상황이 발생하는 이유

     

    Weapon Asset 창이 열릴 때 우리가 등록한 임의의 자료가 커스터마이징되게 해두었다.

     

    문제는 Weapon Asset(여기서는 SubAction)을 등록을 할 수 없으면 그리지 않고 리턴하기 때문에 창이 열려도 항목이 그려지지 않는다. 

     


     

    SWeaponDoActionData와 SWeaponHitData 부분을 수정한다.

     

    변경 전

    더보기
    void SWeaponDoActionData::CustomizeHeader(TSharedRef<IPropertyHandle> InPropertyHandle, FDetailWidgetRow& InHeaderRow,
    	IPropertyTypeCustomizationUtils& InCustomizationUtils)
    {
    	if (SWeaponCheckBoxes::CanDraw(InPropertyHandle, CheckBoxes.Num()) == false)
    		return;//CanDraw가 false면 그리지 않고 리턴.
        //...
    }
    
    void SWeaponDoActionData::CustomizeChildren(TSharedRef<IPropertyHandle> InPropertyHandle,
    	IDetailChildrenBuilder& InChildBuilder, IPropertyTypeCustomizationUtils& InCustomizationUtils)
    {
    	if (SWeaponCheckBoxes::CanDraw(InPropertyHandle, CheckBoxes.Num()) == false)
    		return;//CanDraw가 false면 그리지 않고 리턴.
        //...
    }

     

    변경 후

    더보기
    void SWeaponDoActionData::CustomizeHeader(TSharedRef<IPropertyHandle> InPropertyHandle, FDetailWidgetRow& InHeaderRow,
    	IPropertyTypeCustomizationUtils& InCustomizationUtils)
    {
    	if (SWeaponCheckBoxes::CanDraw(InPropertyHandle, CheckBoxes.Num()) == false)
    	{
    		InHeaderRow
    			.NameContent()
    			[
    				InPropertyHandle->CreatePropertyNameWidget()
    			]
    		.ValueContent()
    			.MinDesiredWidth(FWeaponStyle::Get()->DesiredWidth.X)
    			.MaxDesiredWidth(FWeaponStyle::Get()->DesiredWidth.Y)
    			[
    				InPropertyHandle->CreatePropertyValueWidget()
    			];
    
    		return;//CanDraw가 false면 그리지 않고 리턴.
    	}
        //...  
     }
     
     void SWeaponDoActionData::CustomizeChildren(TSharedRef<IPropertyHandle> InPropertyHandle,
    	IDetailChildrenBuilder& InChildBuilder, IPropertyTypeCustomizationUtils& InCustomizationUtils)
    {
    	if (SWeaponCheckBoxes::CanDraw(InPropertyHandle, CheckBoxes.Num()) == false)
    	{
    		uint32 number = 0;
    		InPropertyHandle->GetNumChildren(number);
    
    		for (uint32 i = 0; i < number; i++)
    		{
    			TSharedPtr<IPropertyHandle> handle = InPropertyHandle->GetChildHandle(i);
    			IDetailPropertyRow& row = InChildBuilder.AddProperty(handle.ToSharedRef());
    
    			TSharedPtr<SWidget> name;
    			TSharedPtr<SWidget> value;
    
    			row.GetDefaultWidgets(name, value);
    
    			row.CustomWidget()
    				.NameContent()
    				[
    					name.ToSharedRef()
    				]
    			.ValueContent()
    				.MinDesiredWidth(FWeaponStyle::Get()->DesiredWidth.X)
    				.MaxDesiredWidth(FWeaponStyle::Get()->DesiredWidth.Y)
    				[
    					value.ToSharedRef()
    				];
    		}//for(i)
    
    		return;//CanDraw가 false면 그리지 않고 리턴.
    	}
        //...
    }

     

    아래의 SWeaopnDoActionData와 SWeaponHitData 항목에 수정된 전체 코드가 적혀있다.

     


     

     

    SWeaponDetailsView

     

    SWeaponDetailsView.h

    변동사항 없음.

     

     

    SWeaponDetailsView.cpp

    더보기
    #include "SWeaponDetailsView.h"
    #include "SWeaponCheckBoxes.h"
    #include "SWeaponEquipmentData.h"
    #include "SWeaponDoActionData.h"
    #include "SWeaponHitData.h"
    #include "DetailLayoutBuilder.h"
    #include "DetailCategoryBuilder.h"
    #include "IDetailPropertyRow.h"
    #include "Weapons/CWeaponAsset.h"
    
    #include "Animation/AnimMontage.h"
    #include "Particles/ParticleSystem.h"
    #include "NiagaraSystem.h"
    #include "Sound/SoundWave.h"
    
    bool SWeaponDetailsView::bRefreshByCheckBoxes = false;//static 변수 초기화.
    
    TSharedRef<IDetailCustomization> SWeaponDetailsView::MakeInstance()
    {
    	//자신의 클래스 타입을 만들어서 return해준다.
    	return MakeShareable(new SWeaponDetailsView());
    }
    
    void SWeaponDetailsView::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
    {
    	UClass* type = UCWeaponAsset::StaticClass();//UClass 타입 하나를 받아온다.
    
    	//DetailBuilder.HideCategory("CWeaponAsset");//CWeaponAsset 카테고리를 숨겨준다. 현재는 모든 카테고리를 노출시키기 때문에 사용하지 않는다.
    
    	//Class Settings
    	{
    		//.EditCategory 해당 타입에 해당 카테고리가 있으면 그 카테고리를 return, 없으면 새로 만들어서 return
    		//ClassSettings는 없으므로 새로 만들어서 return
    		IDetailCategoryBuilder& category = DetailBuilder.EditCategory("ClassSettings", FText::FromString("Class Settings"));
    		//CWeaponAsset에서 직렬화된 변수들을 카테고리에 추가한다.
    		category.AddProperty("AttachmentClass", type);//CWeaponAsset의 AttachmentClass를 카테고리에 추가.
    		category.AddProperty("EquipmentClass", type);//CWeaponAsset의 EquipmentClass를 카테고리에 추가.
    		category.AddProperty("DoActionClass", type);//CWeaponAsset의 DoActionClass를 카테고리에 추가.
    		category.AddProperty("SubActionClass", type);//CWeaponAsset의 SubActionClass를 카테고리에 추가.
    	}
    
    	//EquipmentData
    	{
    		//.EditCategory 해당 타입에 해당 카테고리가 있으면 그 카테고리를 return, 없으면 새로 만들어서 return
    		IDetailCategoryBuilder& category = DetailBuilder.EditCategory("EquipmentData", FText::FromString("Equipment Data"));
    		IDetailPropertyRow& row = category.AddProperty("EquipmentData", type);
    
    		if (bRefreshByCheckBoxes == false)//새로고침이 아닐 때
    		{
    			TSharedPtr<SWeaponCheckBoxes> checkBoxes = SWeaponEquipmentData::CreateCheckBoxes();//카테고리가 처음에 만들어질 때 checkBox를 만든다.
    			checkBoxes->AddProperties(row.GetPropertyHandle());//checkBoxes에 실제로 가진 Handle를 추가
    
    			FEquipmentData data;//변수 선언한다. FEquipmentData의 변수 data 기본값을 아래에서 사용한다.
    
    			int32 index = 0;
    			checkBoxes->CheckDefaultObject(index++, data.Montage);
    			checkBoxes->CheckDefaultValue(index++, data.PlayRate);
    			checkBoxes->CheckDefaultValue(index++, data.bCanMove);
    			checkBoxes->CheckDefaultValue(index++, data.bUseControlRotation);
    		}
    	}
    
    	//DoActionData
    	{
    		//.EditCategory 해당 타입에 해당 카테고리가 있으면 그 카테고리를 return, 없으면 새로 만들어서 return
    		IDetailCategoryBuilder& category = DetailBuilder.EditCategory("DoActionData", FText::FromString("DoAction Data"));
    		IDetailPropertyRow& row = category.AddProperty("DoActionDatas", type);//변수 추가 //WeaponAsset에 있는 데이터명과 일치시킨다. DoActionDatas
    
    		if (bRefreshByCheckBoxes == false)
    		{
    			uint32 count = 0;
    			row.GetPropertyHandle()->GetNumChildren(count);//전체 개수를 구한다.
    
    			SWeaponDoActionData::EmptyCheckBoxes();//비워놓고 시작.
    
    			FDoActionData data;//기본값 사용할 변수
    			for (uint32 i =0; i < count; i++)//자식Handle를 for문 돌리기
    			{
    				TSharedPtr<IPropertyHandle> handle = row.GetPropertyHandle()->GetChildHandle(i);//헤더의 handle
    
    				TSharedPtr<SWeaponCheckBoxes> checkBoxes = SWeaponDoActionData::AddCheckBoxes();//카테고리가 처음에 만들어질 때 checkBox를 만든다.
    				checkBoxes->AddProperties(handle);
    
    				int32 index = 0;
    				checkBoxes->CheckDefaultObject(index++, data.Montage);
    				checkBoxes->CheckDefaultValue(index++, data.PlayRate);
    				checkBoxes->CheckDefaultValue(index++, data.bCanMove);
    				checkBoxes->CheckDefaultValue(index++, data.bUseControlRotation);
    				checkBoxes->CheckDefaultValue(index++, data.bFixedCamera);
    				checkBoxes->CheckDefaultObject(index++, data.Effect);
    				checkBoxes->CheckDefaultValue(index++, data.EffectLocation);
    				checkBoxes->CheckDefaultValue(index++, data.EffectScale);
    			}
    		}//if(bRefreshByCheckBoxes)
    	}
    
    	//HitData
    	{
    		//.EditCategory 해당 타입에 해당 카테고리가 있으면 그 카테고리를 return, 없으면 새로 만들어서 return
    		IDetailCategoryBuilder& category = DetailBuilder.EditCategory("HitData", FText::FromString("Hit Data"));
    		IDetailPropertyRow& row = category.AddProperty("HitDatas", type);//변수 추가 //WeaponAsset에 있는 데이터명과 일치시킨다. DoActionDatas
    
    		if (bRefreshByCheckBoxes == false)
    		{
    			uint32 count = 0;
    			row.GetPropertyHandle()->GetNumChildren(count);//전체 개수를 구한다.
    
    			SWeaponHitData::EmptyCheckBoxes();//비워놓고 시작.
    
    			FHitData data;//기본값 사용할 변수
    			for (uint32 i = 0; i < count; i++)//자식Handle를 for문 돌리기
    			{
    				TSharedPtr<IPropertyHandle> handle = row.GetPropertyHandle()->GetChildHandle(i);//헤더의 handle
    
    				TSharedPtr<SWeaponCheckBoxes> checkBoxes = SWeaponHitData::AddCheckBoxes();//카테고리가 처음에 만들어질 때 checkBox를 만든다.
    				checkBoxes->AddProperties(handle);
    
    				int32 index = 0;
    				checkBoxes->CheckDefaultObject(index++, data.Montage);
    				checkBoxes->CheckDefaultValue(index++, data.PlayRate);
    				checkBoxes->CheckDefaultValue(index++, data.Power);
    				checkBoxes->CheckDefaultValue(index++, data.Launch);
    				checkBoxes->CheckDefaultValue(index++, data.StopTime);
    				checkBoxes->CheckDefaultObject(index++, data.Sound);
    				checkBoxes->CheckDefaultObject(index++, data.Effect);
    				checkBoxes->CheckDefaultValue(index++, data.EffectLocation);
    				checkBoxes->CheckDefaultValue(index++, data.EffectScale);
    			}
    		}//if(bRefreshByCheckBoxes)
    	}
    }

    웨폰 플러그인에 SubAction 항목 추가

    • void SWeaponDetailsView::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
      { //...
      //Class Settings{
      IDetailCategoryBuilder& category = DetailBuilder.EditCategory("ClassSettings", FText::FromString("Class Settings"));

      category.AddProperty("AttachmentClass", type); //CWeaponAsset의 AttachmentClass를 카테고리에 추가.
      category.AddProperty("EquipmentClass", type); //CWeaponAsset의 EquipmentClass를 카테고리에 추가.
      category.AddProperty("DoActionClass", type); //CWeaponAsset의 DoActionClass를 카테고리에 추가.
      category.AddProperty("SubActionClass", type); //CWeaponAsset의 SubActionClass를 카테고리에 추가.
      }

     


     

     

     

     

     

    Fist 스킬 구현하기 - Weapon Plugin

     

     


     

    CWeaponAsset

     

    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:
    	FORCEINLINE class ACAttachment* GetAttachment() { return Attachment; }//외부에 생성된 것을 리턴해줌. 
    	FORCEINLINE class UCEquipment* GetEquipment() { return Equipment; }//외부에 생성된 것을 리턴해줌.
    	FORCEINLINE class UCDoAction* GetDoAction() { return DoAction; }//외부에 생성된 것을 리턴해줌.
    	FORCEINLINE class UCSubAction* GetSubAction() { return SubAction; }//외부에 생성된 것을 리턴해줌.
    
    public:
    	UCWeaponAsset();
    
    	void BeginPlay(class ACharacter* InOwner);
    
    private:
    	//UPROPERTY를 붙여 가비지 콜렉터가 제거하기 전까지 물고 있게 만든다.
    	//UWeaponAsset은 UObject로부터 상속받아 Actor의 생성주기에 영향을 받지 않아 가비지 콜렉터에 영향을 받는다.
    	UPROPERTY() 
    		class ACAttachment* Attachment;
    
    	UPROPERTY()
    		class UCEquipment* Equipment;
    
    	UPROPERTY()
    		class UCDoAction* DoAction;
    
    	UPROPERTY()
    		class UCSubAction* SubAction;
    
    #if WITH_EDITOR //Editor 내에서만 수행
    	void PostEditChangeChainProperty(struct FPropertyChangedChainEvent& PropertyChangedEvent) override;
    #endif
    };

    배열 변수 추가

    • UPROPERTY(EditAnywhere)
      TSubclassOf<class UCSubAction> SubActionClass;

     

    인라인 함수 추가

    • FORCEINLINE class UCSubAction* GetSubAction() { return SubAction; } //외부에 생성된 것을 리턴해줌.

     

    변수 추가

    • UPROPERTY()
      class UCSubAction* SubAction;

     

     

    CWeaponAsset.cpp

    더보기
    #include "Weapons/CWeaponAsset.h"
    #include "Global.h"
    #include "CAttachment.h"
    #include "CEquipment.h"
    #include "CDoAction.h"
    #include "CSubAction.h"
    #include "GameFramework/Character.h"
    
    UCWeaponAsset::UCWeaponAsset()
    {
    	AttachmentClass = ACAttachment::StaticClass();//기본값
    	EquipmentClass = UCEquipment::StaticClass();//기본값
    }
    
    void UCWeaponAsset::BeginPlay(ACharacter* InOwner)
    {
    	if (!!AttachmentClass)//AttachmentClass가 선택되어 있다면
    	{
    		FActorSpawnParameters params;
    		params.Owner = InOwner;
    
    		Attachment = InOwner->GetWorld()->SpawnActor<ACAttachment>(AttachmentClass, params);
    	}
    
    	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);
    		}
    	}
    	 
    	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(!!SubActionClass)
    	{
    		SubAction = NewObject<UCSubAction>(this, SubActionClass);
    		SubAction->BeginPlay(InOwner, Attachment, DoAction);
    	}
    }
    
    #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

    BeginPlay에 SubAction 생성

    • void UCWeaponAsset::BeginPlay(ACharacter* InOwner)
      {
      if(!!SubActionClass) {
      SubAction = NewObject<UCSubAction>(this, SubActionClass);
      SubAction->BeginPlay(InOwner, Attachment, DoAction);
      }
      }

     


     

     

    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: //무기 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;
    
    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;
    };

    함수 추가

    • class UCSubAction* GetSubAction();

     

    함수 추가

    • void SubAction_Pressed();
    • void SubAction_Released();

     

     

     

    CWeaponComponent.cpp

    더보기
    #include "Components/CWeaponComponent.h"
    #include "Global.h"
    #include "CStateComponent.h"
    #include "GameFramework/Character.h"
    #include "Weapons/CWeaponAsset.h"
    #include "Weapons/CAttachment.h"
    #include "Weapons/CEquipment.h"
    #include "Weapons/CDoAction.h"
    #include "Weapons/CSubAction.h"
    
    UCWeaponComponent::UCWeaponComponent()
    {
    
    }
    
    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);//BeginPla y 시 OwnerCharacter에 Spawn시켜준다.
    	}
    }
    
    bool UCWeaponComponent::IsIdleMode()
    {
    	return CHelpers::GetComponent<UCStateComponent>(OwnerCharacter)->IsIdleMode();
    }
    
    ACAttachment* UCWeaponComponent::GetAttachment()
    {
    	CheckTrueResult(IsUnarmedMode(), nullptr);
    	CheckFalseResult(!!DataAssets[(int32)Type], nullptr);
    
    	return DataAssets[(int32)Type]->GetAttachment();
    }
    
    UCEquipment* UCWeaponComponent::GetEquipment()
    {
    	CheckTrueResult(IsUnarmedMode(), nullptr);
    	CheckFalseResult(!!DataAssets[(int32)Type], nullptr);
    
    	return DataAssets[(int32)Type]->GetEquipment();
    }
    
    UCDoAction* UCWeaponComponent::GetDoAction()
    {
    	CheckTrueResult(IsUnarmedMode(), nullptr);
    	CheckFalseResult(!!DataAssets[(int32)Type], nullptr);
    
    	return DataAssets[(int32)Type]->GetDoAction();
    }
    
    UCSubAction* UCWeaponComponent::GetSubAction()
    {
    	CheckTrueResult(IsUnarmedMode(), nullptr);
    	CheckFalseResult(!!DataAssets[(int32)Type], nullptr);
    
    	return DataAssets[(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 (!!DataAssets[(int32)InType])
    	{
    		DataAssets[(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 "CSubAction.h"

     

    함수 정의

    • UCSubAction* UCWeaponComponent::GetSubAction()

    함수 정의

    • void UCWeaponComponent::SubAction_Pressed()
    • void UCWeaponComponent::SubAction_Released()

     


     

    CPlayer - 키 할당

     

    CPlayer.h

    변동사항 없음.

     

    CPlayer.cpp

    더보기
    #include "Characters/CPlayer.h"
    #include "Global.h"
    #include "CAnimInstance.h"
    #include "GameFramework/SpringArmComponent.h"
    #include "GameFramework/CharacterMovementComponent.h"
    #include "Camera/CameraComponent.h"
    #include "Components/SkeletalMeshComponent.h"
    #include "Components/InputComponent.h"
    #include "Components/CMontagesComponent.h"
    #include "Components/CMovementComponent.h"
    #include "Components/CWeaponComponent.h"
    
    ACPlayer::ACPlayer()
    {
    	CHelpers::CreateComponent<USpringArmComponent>(this, &SpringArm, "SpringArm", GetMesh());
    	CHelpers::CreateComponent<UCameraComponent>(this, &Camera, "Camera", SpringArm);
    
    	CHelpers::CreateActorComponent<UCWeaponComponent>(this, &Weapon, "Weapon");
    	CHelpers::CreateActorComponent<UCMontagesComponent>(this, &Montages, "Montages");
    	CHelpers::CreateActorComponent<UCMovementComponent>(this, &Movement, "Movement");
    	CHelpers::CreateActorComponent<UCStateComponent>(this, &State, "State");
    
    	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);
    
    	SpringArm->SetRelativeLocation(FVector(0, 0, 140));
    	SpringArm->SetRelativeRotation(FRotator(0, 90, 0));
    	SpringArm->TargetArmLength = 200;
    	SpringArm->bDoCollisionTest = false;
    	SpringArm->bUsePawnControlRotation = true;
    	SpringArm->bEnableCameraLag = true;
    
    	GetCharacterMovement()->RotationRate = FRotator(0, 720, 0);
    
    }
    
    void ACPlayer::BeginPlay()
    {
    	Super::BeginPlay();
    
    	Movement->OnRun(); //Movement의 기본을 Run으로 설정
    	Movement->DisableControlRotation();//Movement의 기본을 DisableControlRotation으로 설정
    
    	State->OnStateTypeChanged.AddDynamic(this, &ACPlayer::OnStateTypeChanged);
    }
    
    void ACPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
    {
    	Super::SetupPlayerInputComponent(PlayerInputComponent);
    
    	PlayerInputComponent->BindAxis("MoveForward", Movement, &UCMovementComponent::OnMoveForward);
    	PlayerInputComponent->BindAxis("MoveRight", Movement, &UCMovementComponent::OnMoveRight);
    	PlayerInputComponent->BindAxis("VerticalLook", Movement, &UCMovementComponent::OnVerticalLook);
    	PlayerInputComponent->BindAxis("HorizontalLook", Movement, &UCMovementComponent::OnHorizontalLook);
    
    	PlayerInputComponent->BindAction("Sprint", EInputEvent::IE_Pressed, Movement, &UCMovementComponent::OnSprint);
    	PlayerInputComponent->BindAction("Sprint", EInputEvent::IE_Released, Movement, &UCMovementComponent::OnRun);
    
    	PlayerInputComponent->BindAction("Avoid", EInputEvent::IE_Pressed, this, &ACPlayer::OnAvoid);
    
    	PlayerInputComponent->BindAction("Fist", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SetFistMode);
    	PlayerInputComponent->BindAction("Sword", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SetSwordMode);
    	PlayerInputComponent->BindAction("Hammer", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SetHammerMode);
    
    	PlayerInputComponent->BindAction("Action", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::DoAction);
    
    	PlayerInputComponent->BindAction("SubAction", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SubAction_Pressed);
    	PlayerInputComponent->BindAction("SubAction", EInputEvent::IE_Released, Weapon, &UCWeaponComponent::SubAction_Released);
    }
    
    void ACPlayer::OnStateTypeChanged(EStateType InPrevType, EStateType InNewType)
    {
    	switch (InNewType)
    	{
    		case EStateType::BackStep: BackStep(); break;
    	}
    }
    
    void ACPlayer::OnAvoid()
    {
    	CheckFalse(State->IsIdleMode());
    	CheckFalse(Movement->CanMove());
    
    	CheckTrue(InputComponent->GetAxisValue("MoveForward") >= 0.0f);//뒷방향을 입력했다면
    
    	State->SetBackStepMode();//State을 BackStepMode로 변경한다.
    }
    
    void ACPlayer::BackStep()
    {
    	Movement->EnableControlRotation();//정면을 바라본 상태로 뒤로 뛰어야하기 때문에 EnableControlRotation으로 만들어준다.
    
    	Montages->PlayBackStepMode();//PlayBackStepMode()를 통해 몽타주 재생.
    }
    
    void ACPlayer::End_BackStep()
    {
    	Movement->DisableControlRotation();//Backstep이 끝나면 원래대로 돌려준다.
    
    	State->SetIdleMode();//Idle상태로 돌려줌.
    }

     

    SubAction 키 할당

    • void ACPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) {
      • PlayerInputComponent->BindAction("SubAction", EInputEvent::IE_Pressed, Weapon,  &UCWeaponComponent::SubAction_Pressed);
        PlayerInputComponent->BindAction("SubAction", EInputEvent::IE_Released, Weapon, &UCWeaponComponent::SubAction_Released); }
      • UCWeaponComponent의 SubAction_PresssedSubAction_Released 함수를 사용한다.

     


     

     

     

    CStateComponent

     

    CStateComponent.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Components/ActorComponent.h"
    #include "CStateComponent.generated.h"
    
    UENUM()
    enum class EStateType : uint8
    {
    	Idle = 0, BackStep, Equip, Hitted, Dead, Action, Max,
    };
    DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FStateTypeChanged, EStateType, InPrevType, EStateType, InNewType);
    
    UCLASS()
    class U2212_06_API UCStateComponent : public UActorComponent
    {
    	GENERATED_BODY()
    
    public:
    	FORCEINLINE bool IsIdleMode() { return Type == EStateType::Idle; }
    	FORCEINLINE bool IsBackstepMode() { return Type == EStateType::BackStep; }
    	FORCEINLINE bool IsEquipMode() { return Type == EStateType::Equip; }
    	FORCEINLINE bool IsHittedMode() { return Type == EStateType::Hitted; }
    	FORCEINLINE bool IsDeadMode() { return Type == EStateType::Dead; }
    	FORCEINLINE bool IsActionMode() { return Type == EStateType::Action; }
    	FORCEINLINE bool IsSubActionMode() { return bInSubActionMode; }
    
    public:
    	UCStateComponent();
    
    protected:
    	virtual void BeginPlay() override;
    
    public:
    	void SetIdleMode();
    	void SetBackStepMode();
    	void SetEquipMode();
    	void SetHittedMode();
    	void SetDeadMode();
    	void SetActionMode();
    	void OnSubActionMode();
    	void OffSubActionMode();
    
    private:
    	void ChangeType(EStateType InType);
    
    public:
    	FStateTypeChanged OnStateTypeChanged;
    
    private:
    	EStateType Type;
    
    private:
    	bool bInSubActionMode;
    };

    인라인 함수 추가

    • FORCEINLINE bool IsSubActionMode() { return bInSubActionMode; }

     

    함수 추가

    • void OnSubActionMode();
    • void OffSubActionMode();

     

    변수 추가

    • bool bInSubActionMode;

     

     

     

    CStateComponent.cpp

    더보기
    #include "Components/CStateComponent.h"
    #include "Global.h"
    
    UCStateComponent::UCStateComponent()
    {
    
    }
    
    void UCStateComponent::BeginPlay()
    {
    	Super::BeginPlay();
    
    }
    
    void UCStateComponent::SetIdleMode()
    {
    	ChangeType(EStateType::Idle);
    }
    
    void UCStateComponent::SetBackStepMode()
    {
    	ChangeType(EStateType::BackStep);
    }
    
    void UCStateComponent::SetEquipMode()
    {
    	ChangeType(EStateType::Equip);
    }
    
    void UCStateComponent::SetHittedMode()
    {
    	ChangeType(EStateType::Hitted);
    }
    
    void UCStateComponent::SetDeadMode()
    {
    	ChangeType(EStateType::Dead);
    }
    
    void UCStateComponent::SetActionMode()
    {
    	ChangeType(EStateType::Action);
    }
    
    void UCStateComponent::OnSubActionMode()
    {
    	bInSubActionMode = true;
    }
    
    void UCStateComponent::OffSubActionMode()
    {
    	bInSubActionMode = false;
    }
    
    void UCStateComponent::ChangeType(EStateType InType)
    {
    	EStateType prevType = Type;
    	Type = InType;
    
    	if (OnStateTypeChanged.IsBound())
    		OnStateTypeChanged.Broadcast(prevType, Type);
    }

     


     

     

     

    Editor 작업: 몽타주 및 SubAction 데이터 할당

     

     


     

     

    Fist_Skill_Montage

     

     

    AnimNotifyState: SubAction과 Timed Particle Effect 추가

    • 오늘 만든 CAnimNotifyState_SubAction 할당.
    • Timed Particle Effect에 새롭게 생성한 소켓을 넣어준다.
      • Timed Particle Effect에서 파티클 이펙트의 크기를 조절할 수 없기 때문에 소켓을 생성해서 넣어주는 방식으로 해결하였다. 소켓에서는 파티클 이펙트의 스케일 조정이 가능하다.

     

     

     

    ※ 참고 - 애님 노티파이의 Timed Particle Effect에서 Destroy Immediately 옵션

    • Destroy Immediately 옵션을 체크하면 이펙트 내부 시간설정과 상관없이 AnimNotifyState이 끝나면 이펙트가 즉시 사라진다.
    • 자연스럽게 사라지게 하려면 체크를 해제하고 AnimNotifyState 범위를 줄이는게 낫다.

     


     

     

     

     

    BP_CSubAction_Fist 

     

    BP_CSubAction_Fist 에 Action Data를 넣어준다.

     


     

     

     

    DA_Fist 에 SubAction Class 할당

     

     


     

     

     

    CAnimNotifyState_SubAction 생성:  SubAction 동작 끝내기에 사용.

     

    새 C++ 클래스 - AnimNotifyState - CAnimNotifyState_SubAction 생성

     

     

    CAnimNotifyState_SubAction.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Animation/AnimNotifies/AnimNotifyState.h"
    #include "CAnimNotifyState_SubAction.generated.h"
    
    UCLASS()
    class U2212_06_API UCAnimNotifyState_SubAction : public UAnimNotifyState
    {
    	GENERATED_BODY()
    
    public:
    	FString GetNotifyName_Implementation() const override;
    
    	virtual void NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration) override;
    	virtual void NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override;
    };

     

     

     

    CAnimNotifyState_SubAction.cpp

    더보기
    #include "Notifies/CAnimNotifyState_SubAction.h"
    #include "Global.h"
    #include "Components/CWeaponComponent.h"
    #include "Weapons/CSubAction.h"
    
    FString UCAnimNotifyState_SubAction::GetNotifyName_Implementation() const
    {
    	return "SubAction";
    }
    
    void UCAnimNotifyState_SubAction::NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration)
    {
    	Super::NotifyBegin(MeshComp, Animation, TotalDuration);
    	CheckNull(MeshComp);
    	CheckNull(MeshComp->GetOwner());
    
    	UCWeaponComponent* weapon = CHelpers::GetComponent<UCWeaponComponent>(MeshComp->GetOwner());
    	CheckNull(weapon);
    	CheckNull(weapon->GetSubAction());
    
    	weapon->GetSubAction()->Begin_SubAction();
    }
    
    void UCAnimNotifyState_SubAction::NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
    {
    	Super::NotifyEnd(MeshComp, Animation);
    
    	CheckNull(MeshComp);
    	CheckNull(MeshComp->GetOwner());
    
    	UCWeaponComponent* weapon = CHelpers::GetComponent<UCWeaponComponent>(MeshComp->GetOwner());
    	CheckNull(weapon);
    	CheckNull(weapon->GetSubAction());
    
    	weapon->GetSubAction()->End_SubAction();
    }

     

     


     

     

     

     

    실행화면