Weapon Plugin 내 항목을 삽입, 삭제, 복제하는 기능을 추가한다. HitData 관련 카테고리를 추가하여 플러그인에서 HitData에서 관리하게 만든다.

 

목차

     

     


     

     

     
    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
        Characters
        CAnimInstance.h .cpp
    CEnemy.h .cpp 
    CPlayer.h .cpp
    ICharacter.h .cpp
        Components
        CMontagesComponent.h .cpp 
    CMovementComponent.h .cpp 
    CStateComponent.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
        Utilities
        CHelper.h
    CLog.h .cpp
        Weapons
        CDoAction_Combo.h .cpp
    CAttachment.h .cpp
    CDoAction.h .cpp
    CEquipment.h .cpp
    CWeaponAsset.h .cpp
    CWeaponStructures.h .cpp
        Global.h
    CGameMode.h .cpp
    .Build.cs
        .uproject
     

     

     

     

    삽입, 삭제, 복제 기능 추가하기

     


     

     

    Plugin의 참조 방식

     

    Plugin는 서로를 직접 참조하지 않고 중앙 Editor를 통해서 참조한다.

     

    CWeaponAsset.h .cpp 내의 

    if WITH_EDITOR ~ #endif 구동방식

    void PostEditChangeChainProperty() 

     

    Plugin과 Editor의 연관성을 줄인다.

    커플링 방지.

     

     

     


     

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

    변경사항 없음.

     

     

     

    CWeaponAsset.cpp

    더보기
    #include "Weapons/CWeaponAsset.h"
    #include "Global.h"
    #include "CAttachment.h"
    #include "CEquipment.h"
    #include "CDoAction.h"
    #include "GameFramework/Character.h"
    
    UCWeaponAsset::UCWeaponAsset()
    {
    	AttachmentClass = ACAttachment::StaticClass();//기본값
    	EquipmentClass = UCEquipment::StaticClass();//기본값
    	DoActionClass = UCDoAction::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 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

    Editor 내에서만 수행하는 함수

    • #if WITH_EDITOR 
      void UCWeaponAsset::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent)
      #endif

     

     


     

    실행화면

     

     


     

     

     

     

    Hit Data 만들기

     


     

    SWeaponHitData 생성

     

    새 C++ 클래스 - 없음 - SWeaponHitData 생성

     

     

    SWeaponHitData.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "IPropertyTypeCustomization.h"
    
    class WEAPON_API SWeaponHitData
    	: public IPropertyTypeCustomization
    {
    public:
    	static TSharedRef<IPropertyTypeCustomization> MakeInstance();
    	static TSharedPtr<class SWeaponCheckBoxes> AddCheckBoxes();
    	static void EmptyCheckBoxes();//한번에 비워주는 역할의 함수
    
    	void CustomizeHeader(TSharedRef<IPropertyHandle> InPropertyHandle, FDetailWidgetRow& InHeaderRow, IPropertyTypeCustomizationUtils& InCustomizationUtils) override;
    	void CustomizeChildren(TSharedRef<IPropertyHandle> InPropertyHandle, IDetailChildrenBuilder& InChildBuilder, IPropertyTypeCustomizationUtils& InCustomizationUtils) override;
    
    private:
    	static TArray<TSharedPtr<class SWeaponCheckBoxes>> CheckBoxes;
    };

     

     

     

    SWeaponHitData.cpp

    더보기
    #include "SWeaponHitData.h"
    #include "WeaponStyle.h"
    #include "IPropertyUtilities.h"
    #include "IDetailPropertyRow.h"
    #include "IDetailChildrenBuilder.h"
    #include "SWeaponCheckBoxes.h"
    #include "DetailWidgetRow.h"
    
    TArray<TSharedPtr<SWeaponCheckBoxes>> SWeaponHitData::CheckBoxes;
    
    TSharedRef<IPropertyTypeCustomization> SWeaponHitData::MakeInstance()
    {
    	//자신의 클래스 타입을 만들어서 return해준다.
    	return MakeShareable(new SWeaponHitData());
    }
    
    TSharedPtr<SWeaponCheckBoxes> SWeaponHitData::AddCheckBoxes()
    {
    	TSharedPtr<SWeaponCheckBoxes> checkBoxes = MakeShareable(new SWeaponCheckBoxes());
    	int32 index = CheckBoxes.Add(checkBoxes);
    
    	return CheckBoxes[index];
    }
    
    void SWeaponHitData::EmptyCheckBoxes()
    {
    	for (TSharedPtr<SWeaponCheckBoxes> ptr : CheckBoxes)
    	{
    		if (ptr.IsValid())
    			ptr.Reset();
    	}
    
    	CheckBoxes.Empty();
    }
    
    void SWeaponHitData::CustomizeHeader(TSharedRef<IPropertyHandle> InPropertyHandle, FDetailWidgetRow& InHeaderRow,
    	IPropertyTypeCustomizationUtils& InCustomizationUtils)
    {
    	if (SWeaponCheckBoxes::CanDraw(InPropertyHandle, CheckBoxes.Num()) == false)
    		return;//CanDraw가 false면 그리지 않고 리턴.
    
    	int32 index = InPropertyHandle->GetIndexInArray();//GetIndexInArray()는 Array 안에서의 번호를 리턴.
    	CheckBoxes[index]->SetUtilities(InCustomizationUtils.GetPropertyUtilities());//Header,Children,Header,Children..순서로 콜된다.
    
    	FString name = InPropertyHandle->GetPropertyDisplayName().ToString();//0,1,2..표시하는 name
    	name = "Hit Data - " + name;
    
    	InHeaderRow
    	.NameContent()
    	[
    		SNew(SBorder)//SBorder는 한 줄을 출력할 때 사용.
    		.BorderImage(FWeaponStyle::Get()->Array_Image.Get())
    		[
    			InPropertyHandle->CreatePropertyNameWidget(FText::FromString(name))
    		]
    	]
    	.ValueContent()
    	.MinDesiredWidth(FWeaponStyle::Get()->DesiredWidth.X)
    	.MaxDesiredWidth(FWeaponStyle::Get()->DesiredWidth.Y)
    	[
    		CheckBoxes[index]->Draw(true)//CheckBoxes를 그려준다(=생성한다).bBackground에 true값을 넣어주어 alpha값이 0.1f인 흰색 이미지가 겹쳐저 해당 줄이 띠처럼 보이게 만들어준다.
    	];
    }
    
    void SWeaponHitData::CustomizeChildren(TSharedRef<IPropertyHandle> InPropertyHandle,
    	IDetailChildrenBuilder& InChildBuilder, IPropertyTypeCustomizationUtils& InCustomizationUtils)
    {
    	if (SWeaponCheckBoxes::CanDraw(InPropertyHandle, CheckBoxes.Num()) == false)
    		return;//CanDraw가 false면 그리지 않고 리턴.
    
    	int32 index = InPropertyHandle->GetIndexInArray();//GetIndexInArray()는 Array 안에서의 번호를 리턴.
    	CheckBoxes[index]->DrawProperties(InPropertyHandle, &InChildBuilder);
    }

     

     


     

    SWeaponDetailsView

     

     

    SWeaponDetailsView.cpp

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "IDetailCustomization.h"
    
    class WEAPON_API SWeaponDetailsView
    	: public IDetailCustomization
    {
    public:
    	static TSharedRef<IDetailCustomization> MakeInstance();
    	void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override;
    
    public:
    	//외부에서 CheckBoxes 바꿔주는 역할
    	static void OnRefreshByCheckBoxes() { bRefreshByCheckBoxes = true; }
    	static void OffRefreshByCheckBoxes() { bRefreshByCheckBoxes = false; }
    
    private:
    	static bool bRefreshByCheckBoxes;
    };

     

     

     

    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를 카테고리에 추가.
    	}
    
    	//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);//전체 개수를 구한다.
    
    			SWeaponHitData::EmptyCheckBoxes();//비워놓고 시작.
    
    			FDoActionData 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.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)
    	}
    }

    헤더 추가

    • #include "SWeaponHitData.h"
    • #include "Sound/SoundWave.h"

     

     


     

    WeaponAssetEditor

     

     

    WeaponAssetEditor.cpp

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Toolkits/AssetEditorToolkit.h"
    
    class WEAPON_API FWeaponAssetEditor
    	: public FAssetEditorToolkit
    {
    public:
    	static void OpenWindow(FString InAssetName = "");
    	static void Shutdown();
    
    private:
    	static TSharedPtr<FWeaponAssetEditor> Instance;
    
    private:
    	void Open(FString InAssetName);
    
    protected:
    	bool OnRequestClose() override;//창이 닫힐 때 콜이 되게 설계.
    
    public:
    	void RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager) override;
    
    private:
    	TSharedRef<SDockTab> Spawn_LeftAreaTab(const FSpawnTabArgs& InArgs);
    	TSharedRef<SDockTab> Spawn_DetailsViewTab(const FSpawnTabArgs& InArgs);
    
    private:
    	void OnListViewSelectedItem(FWeaponRowDataPtr InDataPtr);//LeftArea에서 선택한 FWeaponRowDataPtr
    
    public:
    	FName GetToolkitFName() const override;
    	FText GetBaseToolkitName() const override;
    	FString GetWorldCentricTabPrefix() const override;
    	FLinearColor GetWorldCentricTabColorScale() const override;
    
    private:
    	TSharedPtr<class SWeaponLeftArea> LeftArea;
    	TSharedPtr<class IDetailsView> DetailsView;//IDetailsView는 DetailView의 최상위 자료형.
    
    private:
    	static const FName EditorName;
    	static const FName LeftAreaTabId;
    	static const FName DetailTabId;
    
    private:
    	FReply OnClicked();
    };

     

     

     

    WeaponAssetEditor.cpp

    더보기
    #include "WeaponAssetEditor.h"
    #include "SWeaponLeftArea.h"
    #include "SWeaponDetailsView.h"
    #include "SWeaponEquipmentData.h"
    #include "SWeaponDoActionData.h"
    #include "SWeaponHitData.h"
    #include "Weapons/CWeaponAsset.h"
    
    //설정한 이름들
    const FName FWeaponAssetEditor::EditorName = "WeaponAssetEditor";
    const FName FWeaponAssetEditor::LeftAreaTabId = "LeftArea";
    const FName FWeaponAssetEditor::DetailTabId = "Details";
    
    TSharedPtr<FWeaponAssetEditor> FWeaponAssetEditor::Instance = nullptr;//헤더에서 선언한 static 외부 초기화.
    
    void FWeaponAssetEditor::OpenWindow(FString InAssetName)
    {
    	//아래의 코드 대신에 Shutdown()을 사용하면 안 된다! Shutdown()은 다른 곳에서도 콜이 되기 때문에 아래의 코드 대신에 사용하면 문제가 된다.
    	//창이 한번이라도 열리면 인스턴스가 할당된다.
    	if (Instance.IsValid()) //창이 만들어졌다면 
    	{
    		if (Instance->LeftArea.IsValid())//LeftArea가 존재한다면
    		{
    			FWeaponRowDataPtr ptr = nullptr;
    
    			if (InAssetName.Len() > 0)//콘텐츠 브라우저를 더블클릭 했다면
    				ptr = Instance->LeftArea->GetRowDataPtrByName(InAssetName);//InAssetName으로 ptr
    
    			if (ptr.IsValid() == false)//더블클릭 안 했다면 또는 InAssetName을 못 찾았다면
    				ptr = Instance->LeftArea->GetFirstDataPtr();//첫번째꺼
    
    			Instance->LeftArea->SelectDataPtr(ptr->Asset);
    
    			return;
    		}
    
    		Instance->CloseWindow();//창을 닫는다.
    
    		Instance.Reset();
    		Instance = nullptr;
    	}
    
    	Instance = MakeShareable(new FWeaponAssetEditor());
    	Instance->Open(InAssetName);
    }
    
    void FWeaponAssetEditor::Shutdown()
    {
    	if (Instance.IsValid())
    	{
    		Instance->CloseWindow();
    
    		Instance.Reset();
    		Instance = nullptr;
    	}
    }
    
    void FWeaponAssetEditor::Open(FString InAssetName)
    {
    	LeftArea = SNew(SWeaponLeftArea)//SWeaponLeftArea에서 받은 자료형을 생성하여 넣어준다.
    		.OnSelectedItem(this, &FWeaponAssetEditor::OnListViewSelectedItem);//LeftArea에서 선택한 데이터
    
    	FPropertyEditorModule& prop = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
    
    	//DetailsView
    	{
    		FDetailsViewArgs args(false, false, true, FDetailsViewArgs::HideNameArea);//기본값 설정. ActorsUserNameArea, ObjectsUserNameArea, HideNameArea
    		args.ViewIdentifier = "WeaponAssetEditorDetailsView";//식별자 설정. 게임 Editor쪽에서 DetailView 접근시 이 식별자로 찾을 수 있다.
    		DetailsView = prop.CreateDetailView(args);//Detail 창 띄우기.
    
    		FOnGetDetailCustomizationInstance detailView;
    		detailView.BindStatic(&SWeaponDetailsView::MakeInstance);//Static은 객체가 필요없다. 그래서 함수 주소로 바로 연결한다.
    		DetailsView->SetGenericLayoutDetailsDelegate(detailView);//Delegate를 연결해준다.
    	}
    
    	//EquipmentData
    	{
    		FOnGetPropertyTypeCustomizationInstance instance;
    		instance.BindStatic(&SWeaponEquipmentData::MakeInstance);
    		prop.RegisterCustomPropertyTypeLayout("EquipmentData", instance);//instance를 delegate 등록
    	}
    
    	//DoActionData
    	{
    		FOnGetPropertyTypeCustomizationInstance instance;
    		instance.BindStatic(&SWeaponDoActionData::MakeInstance);
    		prop.RegisterCustomPropertyTypeLayout("DoActionData", instance);//instance를 delegate 등록
    	}
    
    	//HitData
    	{
    		FOnGetPropertyTypeCustomizationInstance instance;
    		instance.BindStatic(&SWeaponHitData::MakeInstance);
    		prop.RegisterCustomPropertyTypeLayout("HitData", instance);//instance를 delegate 등록
    	}
    
    	//Layout 설정
    	TSharedRef<FTabManager::FLayout> layout = FTabManager::NewLayout("WeaponAssetEditor_Layout")
    		->AddArea //전체화면의 메인 영역
    		(
    			FTabManager::NewPrimaryArea()->SetOrientation(Orient_Vertical)
    			->Split
    			(
    				FTabManager::NewStack()
    				->SetSizeCoefficient(0.1f)//10%만 사용하겠다.
    				->AddTab(GetToolbarTabId(), ETabState::OpenedTab)
    			)
    			->Split
    			(
    				FTabManager::NewSplitter()->SetOrientation(Orient_Horizontal)
    				->Split
    				(
    					FTabManager::NewStack()
    					->SetSizeCoefficient(0.175f)//왼쪽 17.5% 사용
    					->AddTab(LeftAreaTabId, ETabState::OpenedTab)//ListViewTabId
    					->SetHideTabWell(true)
    				)
    				->Split
    				(
    					FTabManager::NewStack()
    					->SetSizeCoefficient(0.725f)//오른쪽 72.5% 사용
    					->AddTab(DetailTabId, ETabState::OpenedTab)//DetailTabId
    					->SetHideTabWell(true)
    				)
    			)
    		);
    
    	UCWeaponAsset* asset = nullptr;
    	if (InAssetName.Len() > 0)//받은 InAssetName의 문자열이 0보다 크다면(=에셋이 있다는 의미)
    	{
    		FWeaponRowDataPtr ptr = LeftArea->GetRowDataPtrByName(InAssetName);
    
    		if (LeftArea->SelectedRowDataPtrName() == InAssetName)//LeftArea의 선택된 이름과 InAssetName이 같다면
    			return;
    
    		if (ptr.IsValid())
    			asset = ptr->Asset;//ptr의 Asset을 넣어준다.
    	}
    	if (asset == nullptr)
    		asset = LeftArea->GetFirstDataPtr()->Asset;//LeftArea의 첫번째 데이터 선택
    
    	FAssetEditorToolkit::InitAssetEditor(EToolkitMode::Standalone, TSharedPtr<IToolkitHost>(), EditorName, layout, true, true, asset);
    
    	//DetailsView->SetObject(asset);//어느 DetailView든 asset객체가 세팅된다. 창은 여러개지만 실제 관리는 내부적으로 하나로 관리한다. 그래서 창이 종료될 때 DetailsView가 해제 안 되어 있으면 터진다.
    	LeftArea->SelectDataPtr(asset);
    }
    
    bool FWeaponAssetEditor::OnRequestClose()
    {
    	if (!!DetailsView)
    	{
    		//AssetEditorSubsystem안에(=DetailView 안에)
    		//GetEditingObject()가 등록되어 있었다면 해제하고 Editor에 알린다.
    		if (!!GEditor && !!GEditor->GetEditorSubsystem<UAssetEditorSubsystem>())
    			GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->NotifyAssetClosed(GetEditingObject(), this);
    
    		//해당 모듈이 읽힌적이 있다면 해제.
    		if (FModuleManager::Get().IsModuleLoaded("PropertyEditor"))
    		{
    			FPropertyEditorModule& prop = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");//해당모듈을 가져온다.
    			prop.UnregisterCustomClassLayout("EquipmentData");//등록 해제
    			prop.UnregisterCustomClassLayout("DoActionData");//등록 해제
    			prop.UnregisterCustomClassLayout("HitData");//등록 해제
    		}
    	}
    
    	if (LeftArea.IsValid())
    		LeftArea.Reset();
    
    	if (DetailsView.IsValid())
    		DetailsView.Reset();
    
    	return true;
    	//false 리턴인 경우 창이 닫힐 수 없다는 것을 의미한다.
    }
    
    void FWeaponAssetEditor::RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
    {
    	FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
    
    	FOnSpawnTab tab;
    	tab.BindSP(this, &FWeaponAssetEditor::Spawn_LeftAreaTab);
    	TabManager->RegisterTabSpawner(LeftAreaTabId, tab);
    
    	FOnSpawnTab tab2;
    	tab2.BindSP(this, &FWeaponAssetEditor::Spawn_DetailsViewTab);
    	TabManager->RegisterTabSpawner(DetailTabId, tab2);
    }
    
    TSharedRef<SDockTab> FWeaponAssetEditor::Spawn_LeftAreaTab(const FSpawnTabArgs& InArgs)
    {
    	//TSharedPtr<SDockTab> tab = SNew(SDockTab)
    	//[
    	//	SNew(SButton)
    	//	.OnClicked(this, &FWeaponAssetEditor::OnClicked)//OnClicked 함수 연결
    	//	[
    	//		SNew(STextBlock)
    	//		.Text(FText::FromString("Test"))
    	//	]
    	//];
    
    	//return tab.ToSharedRef();
    	
    	return SNew(SDockTab)
    	[
    		LeftArea.ToSharedRef()
    	];
    }
    
    TSharedRef<SDockTab> FWeaponAssetEditor::Spawn_DetailsViewTab(const FSpawnTabArgs& InArgs)
    {
    	return SNew(SDockTab)
    	[
    		DetailsView.ToSharedRef()
    	];
    }
    
    void FWeaponAssetEditor::OnListViewSelectedItem(FWeaponRowDataPtr InDataPtr)
    {
    	if (InDataPtr == nullptr)//LeftArea에서 선택한게 없다면(또는 빈 공간을 선택했다면)
    		return;
    
    	if (!!GetEditingObject())//편집하는 객체가 있다면
    		RemoveEditingObject(GetEditingObject());//현재 창에서 편집중인 애들 제거한다.
    
    	AddEditingObject(InDataPtr->Asset);//현재 창에 편집해줄 객체를 등록해준다.
    	DetailsView->SetObject(InDataPtr->Asset);//창 안의 DetailsView도 변경해준다.
    }
    
    FName FWeaponAssetEditor::GetToolkitFName() const
    {
    	return EditorName;//외부에서 어떤 에디터 이름을 쓸 지 정해준다.
    }
    
    FText FWeaponAssetEditor::GetBaseToolkitName() const
    {
    	return FText::FromName(EditorName);//외부에서 어떤 에디터 이름을 쓸 지 정해준다.
    }
    
    FString FWeaponAssetEditor::GetWorldCentricTabPrefix() const
    {
    	return EditorName.ToString();////외부에서 어떤 식별자를 쓸 지 정해준다.
    }
    
    FLinearColor FWeaponAssetEditor::GetWorldCentricTabColorScale() const
    {
    	return FLinearColor(0, 0, 1);//파란색으로 설정.
    }
    
    FReply FWeaponAssetEditor::OnClicked()
    {
    	GLog->Log("Test");
    
    	return FReply::Handled();//Handled()는 처리하고 끝낸다.
    }

    헤더 추가

    • #include "SWeaponHitData.h"

     

    HitData 등록: instance를 delegate 등록

    • FOnGetPropertyTypeCustomizationInstance instance;
      instance.BindStatic(&SWeaponHitData::MakeInstance);
      prop.RegisterCustomPropertyTypeLayout("HitData", instance);

     

    bool FWeaponAssetEditor::OnRequestClose()에 HItData 해제

    • if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) {
      FPropertyEditorModule& prop = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
      prop.UnregisterCustomClassLayout("HitData"); //등록 해제 }

     

    void FWeaponAssetEditor::OpenWindow(FString InAssetName)

    •  

     


     

     

    WeaponContextMenu

     

    WeaponContextMenu.cpp

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "AssetTypeActions_Base.h"
    #include "AssetTypeCategories.h"
    
    //_Base 붙는것은 Abstract 지칭하는 네이밍컨벤션
    class WEAPON_API FWeaponContextMenu
    	: public FAssetTypeActions_Base 
    {
    public:
    	FWeaponContextMenu(EAssetTypeCategories::Type InCategory);
    
    public:
    	virtual FText GetName() const override;
    	virtual UClass* GetSupportedClass() const override;
    	virtual FColor GetTypeColor() const override;
    	uint32 GetCategories() override;
    
    	void OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<class IToolkitHost> EditWithinLevelEditor = TSharedPtr<IToolkitHost>()) override;
    
    private:
    	EAssetTypeCategories::Type Category;
    };

    함수 추가

    • void FWeaponContextMenu::OpenAssetEditor(const TArray<UObject*>& InObjects,
      TSharedPtr<IToolkitHost> EditWithinLevelEditor)

     

     

     

    WeaponContextMenu.cpp

    더보기
    #include "WeaponContextMenu.h"
    #include "WeaponAssetEditor.h"
    #include "Weapons/CWeaponAsset.h"
    
    FWeaponContextMenu::FWeaponContextMenu(EAssetTypeCategories::Type InCategory)
    {
    	Category = InCategory;
    }
    
    FText FWeaponContextMenu::GetName() const
    {
    	return FText::FromString("DataAsset");
    }
    
    UClass* FWeaponContextMenu::GetSupportedClass() const
    {
    	return UCWeaponAsset::StaticClass();//factory가 동작할 클래스로 리턴.
    }
    
    FColor FWeaponContextMenu::GetTypeColor() const
    {
    	return FColor::Blue;
    }
    
    uint32 FWeaponContextMenu::GetCategories()
    {
    	return Category;//만든 것이 소속될 (우클릭해서 나오는)카테고리를 리턴.
    }
    
    void FWeaponContextMenu::OpenAssetEditor(const TArray<UObject*>& InObjects,
    	TSharedPtr<IToolkitHost> EditWithinLevelEditor)
    {
    	/*for (UObject* obj : InObjects)
    	{
    		if (!!obj)
    			GLog->Log(obj->GetName());
    	}*/
    	//위에서 타입을 "DataAsset"으로만 적용해서 DataAsset만 작동한다.
    
    	if (InObjects.Num() < 1)//1 미만으로 선택되었다면(=선택된게 없다면)
    		return;
    
    	FWeaponAssetEditor::OpenWindow(InObjects[0]->GetName());//선택된것에 이름 설정해준다. 하나만 선택되었다는 전제(더블클릭해서 열기 때문). 
    }

    헤더 추가

    • #include "WeaponAssetEditor.h"

     

    함수 정의

    • void FWeaponContextMenu::OpenAssetEditor(const TArray<UObject*>& InObjects,
      TSharedPtr<IToolkitHost> EditWithinLevelEditor)

     


     

     

    SWeaponLeftArea

     

    SWeaponLeftArea.h

    더보기
    #pragma once
    
    #include "CoreMinimal.h"
    #include "Widgets/SCompoundWidget.h"
    #include "Widgets/Views/STableRow.h"
    
    struct FWeaponRowData
    {
    	int Number;
    	FString Name;
    	class UCWeaponAsset* Asset;
    
    	FWeaponRowData()
    	{
    		
    	}
    
    	FWeaponRowData(int32 InNumber, FString InName, class UCWeaponAsset* InAsset)
    		: Number(InNumber), Name(InName), Asset(InAsset)
    	{
    
    	}
    
    	static TSharedPtr<FWeaponRowData> Make(int32 InNumber, FString InName, class UCWeaponAsset* InAsset)
    	{
    		return MakeShareable(new FWeaponRowData(InNumber, InName, InAsset));
    	}
    };
    typedef TSharedPtr<FWeaponRowData> FWeaponRowDataPtr;//실제 출력할 자료형
    
    ///////////////////////////////////////////////////////////////////////////////
    //한줄에 여러개 SMultiColumnTableRow
    
    class WEAPON_API SWeaponTableRow
    	: public SMultiColumnTableRow<FWeaponRowDataPtr>
    {
    public:
    	SLATE_BEGIN_ARGS(SWeaponTableRow) { }//{ }는 초기화영역
    	SLATE_ARGUMENT(FWeaponRowDataPtr, RowData)
    	SLATE_END_ARGS()
    
    public:
    	void Construct(const FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTable);
    
    protected:
    	TSharedRef<SWidget> GenerateWidgetForColumn(const FName& InColumnName) override;//STableRow.h에서 상속받아 오버라이드.
    
    private:
    	FWeaponRowDataPtr Data;//실제로 받아서 쓸 자료형 FWeaponRowDataPtr의 변수 Data.
    
    };
    
    
    //이벤트용 델리게이트. FWeaponRowDataPtr이 눌렸을때 호출.
    DECLARE_DELEGATE_OneParam(FOnWeaponListViewSelectedItem, FWeaponRowDataPtr)
    
    class WEAPON_API SWeaponLeftArea
    	: public SCompoundWidget
    {
    public:
    	SLATE_BEGIN_ARGS(SWeaponLeftArea) {}
    	SLATE_EVENT(FOnWeaponListViewSelectedItem, OnSelectedItem)
    	SLATE_END_ARGS()
    
    public:
    	void Construct(const FArguments& InArgs);
    
    public:
    	bool HasRowDataPtr() { return  RowDatas.Num() > 0; }//하나라도 데이터 있는지 체크
    	FWeaponRowDataPtr GetFirstDataPtr() { return RowDatas[0]; }//첫번째 데이터 리턴
    
    	void SelectDataPtr(class UCWeaponAsset* InAsset);
    
    	FWeaponRowDataPtr GetRowDataPtrByName(FString InAssetName);
    	FString SelectedRowDataPtrName();
    
    private:
    	TSharedRef<ITableRow> OnGenerateRow(FWeaponRowDataPtr InRow, const TSharedRef<STableViewBase>& InTable);
    	void OnSelectionChanged(FWeaponRowDataPtr InDataPtr, ESelectInfo::Type InType);
    
    	FText OnGetAssetCount() const;
    
    	void OnTextChanged(const FText& InText);//입력한 텍스트가 들어와 바뀐다.
    	void OnTextCommitted(const FText& InText, ETextCommit::Type InType);//입력완료(enter 등)시켰을 때 호출되는 이벤트.
    
    private:
    	void ReadDataAssetList();
    
    private:
    	FOnWeaponListViewSelectedItem OnListViewSelectedItem;
    
    private:
    	TArray<FWeaponRowDataPtr> RowDatas;//데이터 자료형을 받는 배열변수 
    	TSharedPtr<SListView<FWeaponRowDataPtr>> ListView;
    
    private:
    	TSharedPtr<class SSearchBox> SearchBox;
    	FText SearchText;//검색 문자열을 관리할 변수
    };

    함수 추가

    • FWeaponRowDataPtr GetRowDataPtrByName(FString InAssetName);
      • TArray<FWeaponRowDataPtr> RowDataPtr 변수를 사용하기 위해 만든 함수.
    • FString SelectedRowDataPtrName();
      • 선택된 이름을 반환하는 함수를 만든다.

     

     

     

    SWeaponLeftArea.cpp

    더보기
    #include "SWeaponLeftArea.h"
    #include "Weapons/CWeaponAsset.h"
    #include "EngineUtils.h"
    #include "Widgets/Input/SSearchBox.h"
    
    void SWeaponTableRow::Construct(const FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTable)
    {
    	Data = InArgs._RowData;//InArgs를 통해서 들어온다.
    
    	SMultiColumnTableRow<FWeaponRowDataPtr>::Construct
    	(
    		FSuperRowType::FArguments().Style(FEditorStyle::Get(), "TableView.DarkRow"), InOwnerTable
    	);
    }
    
    TSharedRef<SWidget> SWeaponTableRow::GenerateWidgetForColumn(const FName& InColumnName)
    {	//InColumnName에 아래 Construct 내부의 SHeaderRow::Column("이름, 번호 등")이 들어간다.
    	FString str;
    	if (InColumnName == "Number")//InColumnName이 숫자면
    		str = FString::FromInt(Data->Number);//str에 숫자를 넣어준다.
    	else if (InColumnName == "Name")//InColumnName이 이름이면
    		str = Data->Name;//str에 이름을 넣어준다.
    
    	return SNew(STextBlock)
    		.Text(FText::FromString(str));//출력할 문자str를 생성하여 리턴해준다.
    }
    
    
    /*모양을 디자인 해주는 역할*/
    void SWeaponLeftArea::Construct(const FArguments& InArgs)
    {
    	OnListViewSelectedItem = InArgs._OnSelectedItem;//InArgs안의 OnSelectionItem 사용.
    
    	ChildSlot
    	[
    		SNew(SVerticalBox)
    		+ SVerticalBox::Slot()
    		.AutoHeight()
    		.Padding(2, 0)
    		[
    			SAssignNew(SearchBox, SSearchBox)//SearchBox를 가져와 동적할당하여 사용한다.
    			.SelectAllTextWhenFocused(true)//검색창 클릭 시 텍스트 입력되있어도 자동으로 입력되게 해주는 기능.
    			.OnTextChanged(this, &SWeaponLeftArea::OnTextChanged)
    			.OnTextCommitted(this, &SWeaponLeftArea::OnTextCommitted)
    		]
    		+ SVerticalBox::Slot()
    		.FillHeight(1)//1이 100%를 의미한다. 한줄을 다 채우겠다는 의미.
    		[
    			SAssignNew(ListView, SListView<FWeaponRowDataPtr>)//ListView를 가져와 동적할당하여 사용한다. 자료형은 FWeaponRowDataPtr
    			.HeaderRow
    			(
    				SNew(SHeaderRow)
    				+ SHeaderRow::Column("Number")
    				.DefaultLabel(FText::FromString(""))
    				.ManualWidth(40)//칸의 너비
    				+ SHeaderRow::Column("Name")
    				.DefaultLabel(FText::FromString("Name"))
    			)
    			.ListItemsSource(&RowDatas)
    			.OnGenerateRow(this, &SWeaponLeftArea::OnGenerateRow)//한줄한줄 어떻게 표현할지 모양을 정해달라는 의미.
    			.OnSelectionChanged(this, &SWeaponLeftArea::OnSelectionChanged)
    			.SelectionMode(ESelectionMode::Single)
    		]
    		+SVerticalBox::Slot()
    		.AutoHeight()
    		.VAlign(VAlign_Center)
    		.HAlign(HAlign_Right)
    		.Padding(FMargin(8, 2))
    		[
    			SNew(STextBlock)
    			.Text(this, &SWeaponLeftArea::OnGetAssetCount)//const TAttribute<FText>. 여기의 text 내용이 바뀌면 자동으로 갱신해준다. Attribute의 특성 때문이다. Attribute를 함수로 줄 수 있다. 여기서는 &SWeaponLeftArea::OnGetAssetCount.
    		]
    	];
    
    	ReadDataAssetList();
    }
    
    void SWeaponLeftArea::SelectDataPtr(UCWeaponAsset* InAsset)
    {
    	if (HasRowDataPtr() == false)
    		return;
    
    	for (FWeaponRowDataPtr ptr : RowDatas)
    	{
    		if (ptr->Asset == InAsset)
    		{
    			ListView->SetSelection(ptr);
    
    			return;
    		}
    	}
    }
    
    FWeaponRowDataPtr SWeaponLeftArea::GetRowDataPtrByName(FString InAssetName)
    {
    	for (FWeaponRowDataPtr ptr : RowDatas)//RowDatas를 모두 탐색했을때
    	{
    		if (ptr->Name == InAssetName)//ptr의 이름이 받은 InAssetName이 같다면
    			return ptr;//그 데이터인 ptr를 리턴.
    	}
    
    	return nullptr;
    }
    
    FString SWeaponLeftArea::SelectedRowDataPtrName()
    {
    	TArray<FWeaponRowDataPtr> ptrs = ListView->GetSelectedItems();
    
    	if (ptrs.Num() > 0)//선택된게 있을때
    		return ptrs[0]->Asset->GetName();//선택된 것의 이름을 리턴
    
    	return "";//빈 문자열 리턴
    }
    
    TSharedRef<ITableRow> SWeaponLeftArea::OnGenerateRow(FWeaponRowDataPtr InRow, const TSharedRef<STableViewBase>& InTable)
    {
    	/*실제모양을 만들어 리턴*/
    	return SNew(SWeaponTableRow, InTable)
    		.RowData(InRow);
    }
    
    void SWeaponLeftArea::ReadDataAssetList()
    {
    	RowDatas.Empty();//데이터를 불러오기 전에 비워주고 시작.
    
    	//"/Game"은 콘텐츠 폴더. 모든 에셋들을 찾아 objects에 리턴하겠다.
    	///ATL_Class는 클래스 타입, ATL_Regular는 나머지 타입(에셋을 불러와야 하므로 이것 사용).
    	TArray<UObject*> objects;
    	EngineUtils::FindOrLoadAssetsByPath("/Game/Weapons/", objects, EngineUtils::ATL_Regular);
    
    	int32 index = 0;
    	for (UObject* obj : objects)
    	{
    		UCWeaponAsset* asset = Cast<UCWeaponAsset>(obj);
    		if (asset == nullptr) continue;
    
    		FString name = asset->GetName();
    		if (SearchText.IsEmpty() == false)//검색하는 문자열(=SearchText)이 비워져있지 않다면(=있다면)
    		{
    			//에셋에 SearchText 문자열이 포함되는지 확인. 포함되지 않는다면 보여줄게 아니기 때문에 continue; 
    			if (name.Contains(SearchText.ToString()) == false)
    				continue;
    		}
    		
    		RowDatas.Add(FWeaponRowData::Make(++index, name, asset));//데이터를 하나씩 추가해준다.
    	}
    
    	RowDatas.Sort([](const FWeaponRowDataPtr& A, const FWeaponRowDataPtr& B)
    	{
    		return A->Number < B->Number;//오름차순 정렬
    	});
    
    	ListView->RequestListRefresh();//다른곳에서 사용하기 때문에 재갱신를 해준다.
    }
    
    FText SWeaponLeftArea::OnGetAssetCount() const
    {
    	FString str = FString::Printf(L"%d 에셋", RowDatas.Num());
    
    	return FText::FromString(str);
    }
    
    void SWeaponLeftArea::OnTextChanged(const FText& InText)
    {
    	if (SearchText.CompareToCaseIgnored(InText) == 0)//기존 문자열과 현재 입력된 문자열이 같다면
    		return;//리턴으로 끝내버린다.
    
    	SearchText = InText;
    	ReadDataAssetList();//재검색하도록 ReadDataAssetList()를 콜 한다.
    }
    
    void SWeaponLeftArea::OnTextCommitted(const FText& InText, ETextCommit::Type InType)
    {	
    	OnTextChanged(InText);
    }
    
    void SWeaponLeftArea::OnSelectionChanged(FWeaponRowDataPtr InDataPtr, ESelectInfo::Type InType)
    {
    	//InDataPtr로 선택된 데이터가 들어온다.
    	//주의사항: 빈 구역을 클릭해도 InDataPtr의 데이터가 들어온다. 빈 구역 클릭 시 null로 값이 들어온다.
    	if (InDataPtr.IsValid() == false)
    		return;
    
    	OnListViewSelectedItem.ExecuteIfBound(InDataPtr);
    }

    함수 정의

    • FWeaponRowDataPtr GetRowDataPtrByName(FString InAssetName);
      • TArray<FWeaponRowDataPtr> RowDataPtr 변수를 사용하기 위해 만든 함수.
      •  
    • FString SelectedRowDataPtrName();

     

     

    실행화면

     

     

     


     

     

     

    플러그인 내 Effect Location 정보가 제대로 나오지 않는 문제해결

     


     

    Effect Location 정보가 나오지 않음.

     

    Effect Location 정보가 나오지 않음.


     

     

    SWeaponCheckBoxes

     

    SWeaponCheckBoxes.h

    변동사항 없음.

     

     

     

    SWeaponCheckBoxes.cpp

    더보기
    #include "SWeaponCheckBoxes.h"
    #include "WeaponStyle.h"
    #include "SWeaponDetailsView.h"
    #include "Widgets/Layout/SUniformGridPanel.h"
    #include "IPropertyUtilities.h"
    #include "IDetailPropertyRow.h"
    #include "IDetailChildrenBuilder.h"
    #include "DetailWidgetRow.h"
    
    void SWeaponCheckBoxes::AddProperties(TSharedPtr<IPropertyHandle> InHandle)
    {
        uint32 number = 0;
        InHandle->GetNumChildren(number);//핸들 내의 자식 property가 몇 개 있는지 가져온다.
    
        for (uint32 i = 0; i < number; i++)
            InternalDatas.Add(FInternalData(InHandle->GetChildHandle(i)));
    }
    
    TSharedRef<SWidget> SWeaponCheckBoxes::Draw(bool bBackground)
    {
        TSharedPtr<SUniformGridPanel> panel;
        SAssignNew(panel, SUniformGridPanel);//SNew와 다르게 변수를 선언해 놓는 SAssignNew
        panel->SetMinDesiredSlotWidth(150);//최소폭 150으로 설정.
    
        for (int32 i = 0; i < InternalDatas.Num(); i++)
        {
            panel->AddSlot(i, 0)//한줄만 사용, 여러줄사용하려면 ex) i % 5
            [
                SNew(SCheckBox)//체크박스는 컨텐츠 영역을 가지고 있다.
                .IsChecked(InternalDatas[i].bChecked)//체크 여부 출력, 0과 1로만 판단
    	        .OnCheckStateChanged(this, &SWeaponCheckBoxes::OnCheckStateChanged, i)//체크 했는지 판단
    	        [
    	            SNew(STextBlock)
    	            .Text(FText::FromString(InternalDatas[i].Name))//InternalDatas의 이름들 출력
    	        ]
            ];
        }
    
        if(bBackground == false)
    		return panel.ToSharedRef();//추가를 하게되면, 추가된 그자체를 return
    
    
        TSharedPtr<SBorder> border = SNew(SBorder)//한 줄 생성.
        .BorderImage(FWeaponStyle::Get()->Array_Image.Get())
        [
            panel.ToSharedRef()//panel를 컨텐츠 영역으로 넣어준다.
        ];
    
        return border.ToSharedRef();//border를 리턴.
    }
    
    void SWeaponCheckBoxes::DrawProperties(TSharedRef<IPropertyHandle> InPropertyHandle, IDetailChildrenBuilder* InChildrenBuilder)
    {
        for (int32 i = 0; i < InternalDatas.Num(); i++)
        {
            // 체크박스에 체크를 바꾸게 되는지 확인, 바꾸지 안았다면 그릴 필요가 없다.
            if (InternalDatas[i].bChecked == false)//그릴 필요가 없는 경우
                continue;
            // 각 줄에 식별자
            TSharedPtr<IPropertyHandle> handle = InPropertyHandle->GetChildHandle(i);
            // 자식부분을 담당, 이 Handle이 기본모양을 추가해서 만들어준다. 커스텀 마이징도 가능하다
            IDetailPropertyRow& row = InChildrenBuilder->AddProperty(handle.ToSharedRef());
    
            //초기세팅1//FString name = FString("Name ") + FString::FromInt(i + 1);
            TSharedPtr<SWidget> name;
            TSharedPtr<SWidget> value;
    
            row.GetDefaultWidgets(name, value);
            
            row.CustomWidget()
            .NameContent()
            [
                //초기세팅1//handle->CreatePropertyNameWidget()
                name.ToSharedRef()
            ]
            //줄이거나 늘렸을 때 Min 이하로는 고정. Max 이상으로는 고정.
            .ValueContent()
            .MinDesiredWidth(FWeaponStyle::Get()->DesiredWidth.X)
            .MaxDesiredWidth(FWeaponStyle::Get()->DesiredWidth.Y)
            [
                //초기세팅1//handle->CreatePropertyValueWidget()
                value.ToSharedRef()
            ];
        }
    }
    
    void SWeaponCheckBoxes::SetUtilities(TSharedPtr<IPropertyUtilities> InUtilities)
    {
        //외부 객체 받아오기
        Utilities = InUtilities;
    }
    
    void SWeaponCheckBoxes::OnCheckStateChanged(ECheckBoxState InState, int32 InIndex)
    {
        //상태를 바뀌는걸 확인했기때문에, 값을 뒤집어 줘서 다시 안그려지게 만들어준다.
        InternalDatas[InIndex].bChecked = !InternalDatas[InIndex].bChecked;
    
        SWeaponDetailsView::OnRefreshByCheckBoxes();
        {
            // ForceRefresh이 콜이되면 새로고침이 되면서 다시 그려진다.
            Utilities->ForceRefresh();
        }
        SWeaponDetailsView::OffRefreshByCheckBoxes();
    
    }
    
    bool SWeaponCheckBoxes::CanDraw(TSharedPtr<IPropertyHandle> InHandle, int InCount)
    {
        bool bCheck = true;
        bCheck &= InCount > 0;//배열의 개수 InCount가 0보다 큰지
    
        int32 index = InHandle->GetIndexInArray();//InHandle에서 배열 인덱스번호를 가져온다.
        bCheck &= index >= 0;
        bCheck &= index < InCount;//인덱스가 InCount 배열의 개수보다 작은지//크면 범위를 벗어나서 그릴 수 없다.
    
        return bCheck;
    }
    
    void SWeaponCheckBoxes::CheckDefaultObject(int32 InIndex, UObject* InValue)
    {
        UObject* val = nullptr;//기본값
        InternalDatas[InIndex].Handle->GetValue(val);//해당 Property에 선택한 값
    
        if (!!val && InValue != val)//nullptr이거나 선택한 값이랑 기본값이 다르다면
            InternalDatas[InIndex].bChecked = true;
    }
    
    void SWeaponCheckBoxes::CheckDefaultValue(int32 InIndex, float InValue)
    {
        float val = 0.0f;//기본값
        InternalDatas[InIndex].Handle->GetValue(val);//해당 Property에 선택한 값
    
        if (InValue != val)//선택한 값이랑 기본값이 다르다면
            InternalDatas[InIndex].bChecked = true;
    }
    
    void SWeaponCheckBoxes::CheckDefaultValue(int32 InIndex, bool InValue)
    {
        bool val = false;//기본값
        InternalDatas[InIndex].Handle->GetValue(val);//해당 Property에 선택한 값
    
        if (InValue != val)//선택한 값이랑 기본값이 다르다면
            InternalDatas[InIndex].bChecked = true;
    }
    
    void SWeaponCheckBoxes::CheckDefaultValue(int32 InIndex, const FVector& InValue)
    {
        FVector val = FVector::ZeroVector;//기본값
        InternalDatas[InIndex].Handle->GetValue(val);//해당 Property에 선택한 값
    
        if (InValue != val)//선택한 값이랑 기본값이 다르다면
            InternalDatas[InIndex].bChecked = true;
    }

    초기세팅: 테스팅용

    • FString name = FString("Name ") + FString::FromInt(i + 1);
    • .NameContent()
              [
                 handle->CreatePropertyNameWidget()
              ]
              .ValueContent()
              .MinDesiredWidth(FWeaponStyle::Get()->DesiredWidth.X)
              .MaxDesiredWidth(FWeaponStyle::Get()->DesiredWidth.Y)
              [
                 handle->CreatePropertyValueWidget()
              ];
    • CreatePropertyNameWidget()은 기본 자료형만 지원할뿐 다른 자료형은 지원하지 않음.

     

     

    수정 후

    • TSharedPtr<SWidget> name;
    • TSharedPtr<SWidget> value;
    • row.GetDefaultWidgets(namevalue);
      • GetDefaultWidgets는 언리얼이 띄워주는 자료형에 대한 기본모양을 리턴해준다.
                
    • row.CustomWidget()
              .NameContent()
              [
                  name.ToSharedRef()
              ]
              //줄이거나 늘렸을 때 Min 이하로는 고정. Max 이상으로는 고정.
              .ValueContent()
              .MinDesiredWidth(FWeaponStyle::Get()->DesiredWidth.X)
              .MaxDesiredWidth(FWeaponStyle::Get()->DesiredWidth.Y)
              [
                  value.ToSharedRef()
              ];

     


     

     

    실행화면