초보 코린이의 성장 일지

UE4 Weapon Plugin (10) 본문

언리얼

UE4 Weapon Plugin (10)

코오린이 2023. 6. 15. 17:04

모듈에 접근하여 무언가 기능을 추가하거나 할때 커플링이 일어나게 되면 치명적인 오류가 발생할 수 있다.

근본적인 문제를 해결하기위해 인터페이스나 이벤트로 중간역할을 하게 될텐데, 전혀 영향을 주지 않는 방법으로 모듈에 접근하여 기능들을 더 구현해 나가 보겠다.

더보기
#include "Weapons/CWeaponAsset.h"
#include "Global.h"
#include "CAttachment.h"
#include "CEquipment.h"
#include "CDoAction.h"
#include "GameFramework/Character.h"

#if WITH_EDITOR
void UCWeaponAsset::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent)
{
	// 게임모듈 쪽이므로 Super 사용 가능. PropertyChangedEvent가 중요하다

	//// GetPropertyName 어떤 변수가 바뀌었는지 확인할 수 있다.
	//CLog::Log(PropertyChangedEvent.GetPropertyName().ToString()); 
	//// 어떤 명령에 의해서 바뀌었는지 확인, ChangeType는 직렬화가 안되어있으므로 int로 출력해준다.
	//CLog::Log((int32)PropertyChangedEvent.ChangeType);

	Super::PostEditChangeChainProperty(PropertyChangedEvent);
	CheckTrue(FApp::IsGame()); // 런타임쪽이므로, 현재 게임이 실행중인지 체크

	bool bRefresh = false;
	// 새로고침할 조건, 수정하려는 변수명이 0과 같다면 동일하다는 의미
	bRefresh |= PropertyChangedEvent.GetPropertyName().Compare("DoActionDatas") == 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");
			// IDetailsView 공간에서 찾아오기.
			TSharedPtr<IDetailsView> detailsView = prop.FindDetailView("WeaponAssetEditorDetailsView");

			if (detailsView.IsValid())
				detailsView->ForceRefresh(); // 정상적으로 불려졌다면 새로 그려준다.
		}
	}

}
#endif // WITH_EDITOR

1. 모듈에 기능을 커스텀 하기 위하여 접근하게 될텐데 이벤트를 통해서 처리를 해야한다.2

2. IDetailsView 공간에서 데이터가 일치하다면 데이터 정보들을 그려준다.

SWeaponDetailsView

1. 그려지게 될때 추가를 한다면 배열 공간이 하나 늘게 되므로, return 되는 값이 하나 증가할 것이다.

2. 추가가 되면서 하나가 더 그려지게된다.

1. 테스트로 만든 DataAsset에서도 + 버튼을 눌러 추가를하면, 다른 데이터 값이 들어있는것과 동일하게 정보창이 나오는걸 확인할 수 있다. 


더 처리해야 하는 디테일적인 부분들이 있다. HitData도 동일하게 넣어줘야 한다.

1. HitData 클래스를 생성해준다.

더보기
#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; // 여러개 담을 배열로 선언


};
#include "SWeaponHitData.h"
#include "WeaponStyle.h"
#include "IPropertyUtilities.h"
#include "IDetailPropertyRow.h"
#include "IDetailChildrenBuilder.h"
#include "DetailWidgetRow.h"
#include "SWeaponCheckBoxes.h"
#include "SWeaponHitData.h"
#include "Widgets/Notifications/SNotificationList.h"

TArray<TSharedPtr<class SWeaponCheckBoxes>> SWeaponHitData::CheckBoxes; // Static 변수 초기화.

TSharedRef<IPropertyTypeCustomization> SWeaponHitData::MakeInstance()
{
    return MakeShareable(new SWeaponHitData());
}

TSharedPtr<SWeaponCheckBoxes> SWeaponHitData::AddCheckBoxes()
{
    TSharedPtr<SWeaponCheckBoxes> checkBoxes = MakeShareable(new SWeaponCheckBoxes()); // 체크박스 생성
    int32 index = CheckBoxes.Add(checkBoxes);

    return CheckBoxes[index]; // 현재 번호 return 

}

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)
{
    // false면 안그리고 끝내버린다, true면 그려준다.
    if (SWeaponCheckBoxes::CanDraw(InPropertyHandle, CheckBoxes.Num()) == false)
        return;

    // GLog->Log("CustomizeHeader");
    int32 index = InPropertyHandle->GetIndexInArray(); // 배열안에 번호를 return 해달라,
    // 자동으로 Header, Children이 콜이되므로 for문으로 돌리지 않아도된다.
    CheckBoxes[index]->SetUtilities(InCustomizationUtils.GetPropertyUtilities());

    FString name = InPropertyHandle->GetPropertyDisplayName().ToString(); // 이름으로 출력
    name = "Hit Data - " + name;

    // 접히는 라인
    InHeaderRow
        .NameContent()
        [
            SNew(SBorder) // SBorder 한 줄을 출력해줄때 사용
            .BorderImage(FWeaponStyle::Get()->Array_Image.Get()) // 만들어 놓은 Image 넣어주기, 스마터포인터므로 Get로 return
        [
            // Handle에는 기본 모양이 존재하지만, 커스텀 한 모양대로 만들어주기
            InPropertyHandle->CreatePropertyNameWidget(FText::FromString(name)) // 문자열로 변경
        ]
        ]
    .ValueContent() // 슬레이트 UI 다루는 문법과 동일
     // 사이즈 조절, 마우스로 끌어서 크기를 변경할때 적정선을 못넘어 가도록 고정 (최대, 최소 사이즈)
        .MinDesiredWidth(FWeaponStyle::Get()->DesiredWidth.X)
        .MaxDesiredWidth(FWeaponStyle::Get()->DesiredWidth.Y)
        [
            // InPropertyHandle->CreatePropertyValueWidget() // 접히는 부분
            CheckBoxes[index]->Draw(true) // 바로 그려준다. true일때는 배경색이 칠해지도록 설정.
        ];
}

void SWeaponHitData::CustomizeChildren(TSharedRef<IPropertyHandle> InPropertyHandle, IDetailChildrenBuilder& InChildBuilder, IPropertyTypeCustomizationUtils& InCustomizationUtils)
{
    // GLog->Log("CustomizeChildren");

    if (SWeaponCheckBoxes::CanDraw(InPropertyHandle, CheckBoxes.Num()) == false)
        return;

    int32 index = InPropertyHandle->GetIndexInArray(); // 배열안에 번호를 return 해달라,
    CheckBoxes[index]->DrawProperties(InPropertyHandle, &InChildBuilder); // index 배열안에 그려주기
}

1. DoActionData 내용을 그대로 가져와서 이름만 변경해준다. 이유는 당연하게도 데이터를 보여주는 부분이 동일하므로 안에 내용물만 바뀌면된다.


더보기
#include "Weapons/CWeaponAsset.h"
#include "Global.h"
#include "CAttachment.h"
#include "CEquipment.h"
#include "CDoAction.h"
#include "GameFramework/Character.h"

#if WITH_EDITOR
void UCWeaponAsset::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent)
{
	// 게임모듈 쪽이므로 Super 사용 가능. PropertyChangedEvent가 중요하다

	//// GetPropertyName 어떤 변수가 바뀌었는지 확인할 수 있다.
	//CLog::Log(PropertyChangedEvent.GetPropertyName().ToString()); 
	//// 어떤 명령에 의해서 바뀌었는지 확인, ChangeType는 직렬화가 안되어있으므로 int로 출력해준다.
	//CLog::Log((int32)PropertyChangedEvent.ChangeType);

	Super::PostEditChangeChainProperty(PropertyChangedEvent);
	CheckTrue(FApp::IsGame()); // 런타임쪽이므로, 현재 게임이 실행중인지 체크

	bool bRefresh = false;
	// 새로고침할 조건, 수정하려는 변수명이 0과 같다면 동일하다는 의미
	bRefresh |= PropertyChangedEvent.GetPropertyName().Compare("DoActionDatas") == 0;
	bRefresh |= PropertyChangedEvent.GetPropertyName().Compare("HitDatas") == 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");
			// IDetailsView 공간에서 찾아오기.
			TSharedPtr<IDetailsView> detailsView = prop.FindDetailView("WeaponAssetEditorDetailsView");

			if (detailsView.IsValid())
				detailsView->ForceRefresh(); // 정상적으로 불려졌다면 새로 그려준다.
		}
	}

}
#endif // WITH_EDITOR

1. bRefresh |= PropertyChangedEvent.GetPropertyName().Compare("HitDatas") == 0; 을 추가해주고, 새로고침할 조건을 만들어준다.

 


더보기
#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 "Particles/ParticleSystem.h"
#include "NiagaraSystem.h"
#include "Animation/AnimMontage.h"
#include "Sound/SoundWave.h"

bool SWeaponDetailsView::bRefreshByCheckBoxes = false;

void SWeaponDetailsView::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
        // HitData
        {
            IDetailCategoryBuilder& category = DetailBuilder.EditCategory("HitData", FText::FromString("Hit Data"));
            IDetailPropertyRow& row = category.AddProperty("HitDatas", type); // 변수명, WeaponAsset 있는 변수명과 일치 시켜주면 된다.

            if (bRefreshByCheckBoxes == false) // 새로고침 됐는지 체크
            {
                uint32 count = 0;
                row.GetPropertyHandle()->GetNumChildren(count); // 배열의 개수 return,  ChildrenHandle는 배열에 담아져있다.

                SWeaponHitData::EmptyCheckBoxes(); // 처음추가 하는 것이므로 새롭게 열릴때 이전에 있는거 다 비워주고 추가.

                // 몇개인지 확인,
                FHitData data;
                for (uint32 i = 0; i < count; i++)
                {
                    // 현재 HeaderHandle에서 자식꺼 Handle 가져오기
                    TSharedPtr<IPropertyHandle> handle = row.GetPropertyHandle()->GetChildHandle(i);

                    TSharedPtr<SWeaponCheckBoxes> checkBoxes = SWeaponHitData::AddCheckBoxes(); // checkBoxes 추가
                    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)
    }
}

1. 그려지는 부분인 DetailsView로 와서 동일하게 구조체 데이터를 받아서 넣어준다.


더보기
#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;

void FWeaponAssetEditor::Open(FString InAssetName)
{
	// DoActionData
	{
		FOnGetPropertyTypeCustomizationInstance instance;
		instance.BindStatic(&SWeaponDoActionData::MakeInstance); // Type를 만들어서 return 해주기 때문에, BindStatic 사용
		// Editor에 등록 해준다. 변수화된 Type을 다룰것이기 때문에 RegisterCustomPropertyTypeLayout 사용,
		prop.RegisterCustomPropertyTypeLayout("DoActionData", instance); // 편집할 변수

	}

	// HitData
	{
		FOnGetPropertyTypeCustomizationInstance instance;
		instance.BindStatic(&SWeaponHitData::MakeInstance); // Type를 만들어서 return 해주기 때문에, BindStatic 사용
		// Editor에 등록 해준다. 변수화된 Type을 다룰것이기 때문에 RegisterCustomPropertyTypeLayout 사용,
		prop.RegisterCustomPropertyTypeLayout("HitData", instance); // 편집할 변수

	}
}

bool FWeaponAssetEditor::OnRequestClose()
{
	if (!!DetailsView)
	{
		if (!!GEditor && !!GEditor->GetEditorSubsystem<UAssetEditorSubsystem>())
			// 디테일 뷰안에 객체가 등록되어있다면 해제된걸 알려줘라,
			GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->NotifyAssetClosed(GetEditingObject(), this);

		// 해당 모듈이 읽힌적(불러진적)이 있는지 체크
		if (FModuleManager::Get().IsModuleLoaded("PropertyEditor"))
		{
			// 등록해제를 안해주면, 다른곳에 등록된 곳에서도 유지되어 버린다. 그래서 창이 열려져서 닫힐 동안만큼은 내가 만든걸 화면에 띄어줄 것이다.
			FPropertyEditorModule& prop = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
			prop.UnregisterCustomPropertyTypeLayout("EquipmentData");
			prop.UnregisterCustomPropertyTypeLayout("DoActionData");
			prop.UnregisterCustomPropertyTypeLayout("HitData");
		}
	}

	// 만든 객체들 다 제거
	if (LeftArea.IsValid())
		LeftArea.Reset();

	if (DetailsView.IsValid())
		DetailsView.Reset();

	return true;
}

1. WeaponAssetEditor에서도 동일하게 HitData를 에디터에 등록해주고, 사용했으면 해지해준다.


1. WeaponDetailsView에서도 이제는 모든 카테고리에 등록된 정보를 다 기입했으므로, 카테고리를 숨겨줄 이유가 사라져서 주석처리 해준다.


1. 실행해보면 HitData도 잘 등록되었으며, 정보도 잘 나오는걸 알 수 있다.


이제 남은건 만들어 놓은 플러그인 아이콘을 눌러서 정보가 나오는게 아닌, 생성한 DataAsset를 클릭했을시에도 동일하게 위에 정보창이 나올 수 있도록, 또 한 Sword를 눌렀으면 동일한 Sword이 바로 선택되도록 만들어 줄 것이다.


1. WeaponContextMenu에서 상속 받은 부모인 AssetTypeActions_Base로 들어가보면, OpenAssetEditor 함수가 존재한다.

2. OpenAssetEditor를 가져다가 사용할 것이다. 에디터안에 무언가 클릭했을때 열리는 기본창을 관여하는 역할을한다.


더보기
#pragma once

#include "CoreMinimal.h"
#include "AssetTypeActions_Base.h"
#include "AssetTypeCategories.h"

class WEAPON_API FWeaponContextMenu
	// 에디터 메뉴 부모헤더, Base가 붙으면 이걸 Base로 선택해서 구현하라는 의미
	: public FAssetTypeActions_Base 
{
public:
	// FAssetTypeActions_Base 부모에서 가져온 함수
	virtual void OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<class IToolkitHost> EditWithinLevelEditor = TSharedPtr<IToolkitHost>()) override;

};
#include "WeaponContextMenu.h"
#include "WeaponAssetEditor.h"
#include "Weapons/CWeaponAsset.h"

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

	for(UObject* obj : InObjects)
	{
		if (!!obj)
			GLog->Log(obj->GetName());
	}
}

1. 어떠한 객체를 클릭했을때 어떠한걸 받았는지 로그로 출력해서 확인해 볼 것이다.

1. 한개를 눌러서 클릭하거나, 2개를 동시에 지정 후 클릭해도 출력창에 무엇을 클릭했는지 로그가 찍혀 나오는걸 확인할 수 있다.

더보기
#include "WeaponContextMenu.h"
#include "WeaponAssetEditor.h"
#include "Weapons/CWeaponAsset.h"

void FWeaponContextMenu::OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<IToolkitHost> EditWithinLevelEditor)
{
	/*FAssetTypeActions_Base::OpenAssetEditor(InObjects, EditWithinLevelEditor);

	for(UObject* obj : InObjects)
	{
		if (!!obj)
			GLog->Log(obj->GetName());
	}*/

	// 하나라도 선택되지 않았다면, 만들어준 WeaponAssetData를 이용할 것이다.
	if (InObjects.Num() < 1)
		return;

	FWeaponAssetEditor::OpenWindow(InObjects[0]->GetName()); // 하나만 선택됐다고 판단. 어차피 더블클릭으로 열게되니까, 선택된 이름정보
}

1. 무조건 클릭시 열게된다는 조건을 추가하여, 창이 뜨게 만들어준다. 

1. 이제 창은 열렸다. 하지만 처리를 해야할게 남아있다. Sword를 눌러서 열었지만 Fist의 정보창이 열리게된다.

Sword를 눌렀으면 Sword로 가게끔 마지막 처리를해 줄 것이다.


더보기
#pragma once

#include "CoreMinimal.h"
#include "Widgets/SCompoundWidget.h"
#include "Widgets/Views/STableRow.h"

class WEAPON_API SWeaponLeftArea
	: public SCompoundWidget
{
public:
	FWeaponRowDataPtr GetRowDataPtrByName(FString InAssetName);
	FString SelectedRowDataPtrName();

};
#include "SWeaponLeftArea.h"
#include "Weapons/CWeaponAsset.h"
#include "EngineUtils.h"
#include "Widgets/Input/SSearchBox.h" // 검색창

FWeaponRowDataPtr SWeaponLeftArea::GetRowDataPtrByName(FString InAssetName)
{
	// RowDatas를 전부 확인
	for (FWeaponRowDataPtr ptr : RowDatas)
	{
		// 받은 이름과 동일하다면 같다는 의미로 return
		if (ptr->Name == InAssetName)
			return ptr;

	}

	// 동일하지 않다면 null return
	return nullptr;
}

FString SWeaponLeftArea::SelectedRowDataPtrName()
{
	// 선택
	TArray<FWeaponRowDataPtr> ptrs = ListView->GetSelectedItems();

	// 하나만 선택되게 된다.
	if (ptrs.Num() > 0)
		return ptrs[0]->Asset->GetName();

	return ""; // 선택이 안된다면 빈 문자열 return

}

1. 하나만 선택할 수 있게끔 SelectionMode를 추가해준다.

2. 선택된게 동일하다면 그대로 return, 안되었다면 빈 문자열을 return 해주게 만들어준다.


더보기
#include "WeaponAssetEditor.h"
#include "SWeaponLeftArea.h"
#include "SWeaponDetailsView.h"
#include "SWeaponEquipmentData.h"
#include "SWeaponDoActionData.h"
#include "SWeaponHitData.h"
#include "Weapons/CWeaponAsset.h"

void FWeaponAssetEditor::Open(FString InAssetName)
{
    UCWeaponAsset* asset = nullptr;
	// 문자열 길이가 0보다 크다는건, 존재하다는 의미.
	if (InAssetName.Len() > 0) // 왼쪽 창에서, InAssetName와 동일한 이름을 가져온다
	{
		// 동일한 이름을 찾아온다.
		FWeaponRowDataPtr ptr = LeftArea->GetRowDataPtrByName(InAssetName);

		// 왼쪽창 LeftArea, 선택된 이름과 동일하다면 다시해줄 이유가 없다.
		if (LeftArea->SelectedRowDataPtrName() == InAssetName)
			return;

		if (ptr.IsValid())
			asset = ptr->Asset;
	}

	// 위에 조건을 통과했다는건 Asset이 없다는 의미
	if (asset == nullptr)
		asset = LeftArea->GetFirstDataPtr()->Asset; // 첫번째가 선택된다
}

 

1. 선택한 Asset로 선택되게끔 설정.

1. Sword를 클릭해서 창이 열리게되면, Sword에 데이터 정보가 나오게 바뀐걸 알 수 있다.


이제 마지막 단계로 만일 Sword 창을 열은 상태에서 Sword를 다시 누르면 그 창 그대로 유지될 것이지만, Fist Data를 클릭하게되면 Fist 정보 창이 열리게 될 것이다. 또 창이 열리는거 보다는 같은 창에서 Fist로 변경만 되게끔 바꿔서 보기 편하게 만들어 줄 것이다. (창이 껏다 켜지는걸 막을 것이다.)


더보기
#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;

// 이 함수에 못 들어온다면 툴바 버튼을 클릭했다는 의미, 들어왔다면 DataAsset를 클릭했다는 의미
void FWeaponAssetEditor::OpenWindow(FString InAssetName)
{
	// 창이 만들어져 있다면, Instance는 한번이라도 창이 열린다면 할당이된다, off로 만들기 전까지
	if (Instance.IsValid())
	{
		// 창이 열려있다는 조건 판단은, Left창이 살아있다는 뜻, 결국 창이 떠 있다는 의미.
		if (Instance->LeftArea.IsValid())
		{
			FWeaponRowDataPtr ptr = nullptr;

			if (InAssetName.Len() > 0)
				// 컨텐츠 브라우저를 더블 클릭했다면, AssetName으로 ptr을 찾아온다.
				ptr = Instance->LeftArea->GetRowDataPtrByName(InAssetName);

			// 못찾았다면 카테고리 첫번째 목록을 선택
			if (ptr.IsValid() == false)
				ptr = Instance->LeftArea->GetFirstDataPtr();

			// 찾았다면 데이터 정보를 변환시켜서 보여준다.
			Instance->LeftArea->SelectDataPtr(ptr->Asset);

			// 창이 열리고 변경되었다면, 더이상 수행할 필요가 없다.
			return;
		}

		// 창이 한번이라도 열려 있었다면, off 해준다.
		Instance->CloseWindow();

		Instance.Reset();
		Instance = nullptr;
	}

	Instance = MakeShareable(new FWeaponAssetEditor());
	Instance->Open(InAssetName);
}

1. 왼쪽 LeftArea창이 열려있다는 조건을 찾아주고, 있는지 없는지로 조건을 줘서 창에 내용을 변경해주거나 유지시켜준다.


https://www.youtube.com/watch?v=ZwikiQ7lHDc 

 

'언리얼' 카테고리의 다른 글

UE4 SubAction Fist  (0) 2023.06.20
UE4 SubAction Fist  (0) 2023.06.20
UE4 Weapon Plugin (9)  (0) 2023.06.14
UE4 Weapon Plugin (8)  (2) 2023.06.13
UE4 Weapon Plugin (7)  (0) 2023.06.12
Comments