초보 코린이의 성장 일지

UE4 Weapon Plugin (4) 본문

언리얼

UE4 Weapon Plugin (4)

코오린이 2023. 6. 1. 17:41

Weapon를 관리해주는 Plugin에 검색 기능과 검색을 할때 이름을 입력하면 알맞(연관된)은 이름만 나오도록 만들어볼 것이다.

 

더보기
#pragma once

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


class WEAPON_API SWeaponLeftArea
	: public SCompoundWidget
{

private:
	void OnTextChanged(const FText& InText); // 입력한 Text
	void OnTextCommitted(const FText& InText, ETextCommit::Type InType); // Enter을 눌러 입력이 완료됐을때

private:
	TSharedPtr<class SSearchBox> SearchBox;  // 검색창
	FText SearchText; // 검색 문자열 관리

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

void SWeaponLeftArea::Construct(const FArguments& InArgs)
{
	// 모양, 디자인을 하는 부분
	ChildSlot
		[
			SNew(SVerticalBox)
			+ SVerticalBox::Slot()
			.AutoHeight()
			.Padding(2, 0)
		[
			SAssignNew(SearchBox, SSearchBox) // 동적할당해서 사용할거,
			.SelectAllTextWhenFocused(true) // 검색창 클릭하면 Text 다시 입력해주는 기능
			.OnTextChanged(this, &SWeaponLeftArea::OnTextChanged)
			.OnTextCommitted(this, &SWeaponLeftArea::OnTextCommitted)
		]
}

void SWeaponLeftArea::ReadDataAssetList()
{
	// 콜 될때마다 비워주기
	RowDatas.Empty();

	TArray<UObject*> objects;
	// 데이터를 실제로 찾아올 경로
	// 이 안에서 모든 Asset를 다 찾아서 objects에 return 해줄 것이다
	EngineUtils::FindOrLoadAssetsByPath("/Game/Weapons/", objects, EngineUtils::ATL_Regular);

	// 안에 있는 데이터를 for문으로 확인
	int32 index = 0;
	for (UObject* obj : objects)
	{
		UCWeaponAsset* asset = Cast<UCWeaponAsset>(obj);
		// nullptr이면 Pass 시킨다.
		if (asset == nullptr) continue;

		FString name = asset->GetName();
		if (SearchText.IsEmpty() == false) // 검색 문자열이 비워져있지 않다면, 찾을 Asset에 이름에 SearchText가 포함되어있는지 확인
		{
			// 검색된 이름이 없다면, 보여주지 않기
			if (name.Contains(SearchText.ToString()) == false)
				continue;
		}

		// 이름이 없다면 추가해주면된다.
		RowDatas.Add(FWeaponRowData::Make(++index, name, asset));
	}
}

void SWeaponLeftArea::OnTextChanged(const FText& InText)
{
	// 글자가 바뀔때마다 콜 된다.
	if (SearchText.CompareToCaseIgnored(InText) == 0) // 기존에 있는 문자와 같으면 return
		return;

	SearchText = InText;
	ReadDataAssetList(); // 재검색 하도록,
}

void SWeaponLeftArea::OnTextCommitted(const FText& InText, ETextCommit::Type InType)
{
	OnTextChanged(InText);
}

1. Slot로 영역을 하나 만들어주고, 검색 기능을 넣어준다.

2. 검색할때 검색이 되지 않는 문자라면 숨겨주고, 그게 아니라면 추가해준다.

1. 검색창에 단어를 입력하면, 연관된 Name만 검색되는걸 확인할 수 있다.

 

더보기
#pragma once

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

class WEAPON_API SWeaponLeftArea
	: public SCompoundWidget
{

private:
	void OnSelectionChanged(FWeaponRowDataPtr InDataPtr, ESelectInfo::Type InType);

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

void SWeaponLeftArea::OnSelectionChanged(FWeaponRowDataPtr InDataPtr, ESelectInfo::Type InType)
{
	if (InDataPtr.IsValid())
		GLog->Log(InDataPtr->Asset->GetPathName());
	else
		GLog->Log("Null");

	GLog->Log(StaticEnum<ESelectInfo::Type>()->GetValueAsString(InType));
}

1. 왼쪽에 있는 Name이 선택된거에 따라 오른쪽에 내용들이 보여지도록 만들어 줄 것이다. 

2. 구현은 키가 눌렸을때 이벤트를 넘겨주는 식으로 구현할 것이다.

1. Name에 클릭 후 키보드 상하를 누르거나, 마우스로 클릭할시 왼쪽 출력로그에 검색된게 나오게된다.

2. 마우스로 빈 공간을 찍을때 마지막 Null이 출력되는걸 확인할 수 있다.

 

 

1. 연결해 줄 델리게이 이벤트를 생성해준다.

 

더보기
#pragma once

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

DECLARE_DELEGATE_OneParam(FOnWeaponListViewSelectedItem, FWeaponRowDataPtr);

class WEAPON_API SWeaponLeftArea
	: public SCompoundWidget
{

public:
	SLATE_EVENT(FOnWeaponListViewSelectedItem, OnSelectedItem)

private:
	// 델리게이트 이벤트 사용
	FOnWeaponListViewSelectedItem OnListViewSelectedItem;
    
};
#include "SWeaponLeftArea.h"
#include "Weapons/CWeaponAsset.h"
#include "EngineUtils.h"
#include "Widgets/Input/SSearchBox.h" // 검색창

void SWeaponLeftArea::Construct(const FArguments& InArgs)
{
	// 델리게이트 이벤트 연결, 외부에서 이벤트 넘겨주면 된다.
	OnListViewSelectedItem = InArgs._OnSelectedItem;
    
}

1. 생성한 델리게이트 사용할 것이다.

2. 슬레이트 이벤트도 델리게이트 자료형을 받아서 생성해준다.

3. 창을 생성해주는 함수에서 이벤트를 연결해준다.


더보기
#pragma once

#include "CoreMinimal.h"
#include "SWeaponLeftArea.h"
#include "Toolkits/AssetEditorToolkit.h"

class WEAPON_API FWeaponAssetEditor
	: public FAssetEditorToolkit
{

private:
	void OnListViewSelectedItem(FWeaponRowDataPtr InDataPtr);

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

void FWeaponAssetEditor::Open(FString InAssetName)
{
	// 아래 디자인 되기전에 객체를 생성해 줘야한다.

	LeftArea = SNew(SWeaponLeftArea)
		.OnSelectedItem(this, &FWeaponAssetEditor::OnListViewSelectedItem); // 슬레이트 이벤트 연결

}

1. WeaponLeftArea에서 만든 슬레이트 이벤트를 연결시켜준다.


더보기
#include "SWeaponLeftArea.h"
#include "Weapons/CWeaponAsset.h"
#include "EngineUtils.h"
#include "Widgets/Input/SSearchBox.h" // 검색창

void SWeaponLeftArea::OnSelectionChanged(FWeaponRowDataPtr InDataPtr, ESelectInfo::Type InType)
{
	// 선택된 데이터가 들어온다. 빈 구역을 눌러도 Null이 나오게 되는걸 방지하기 위해 조건 처리
	if (InDataPtr.IsValid() == false) // 선택된게 없으면 끝
		return;

	// 가지고 있는 델리게이트 연결
	OnListViewSelectedItem.ExecuteIfBound(InDataPtr);
}

1. 로그 출력했던 부분 코드를 바꿔준다.


더보기
#pragma once

#include "CoreMinimal.h"
#include "SWeaponLeftArea.h"
#include "Toolkits/AssetEditorToolkit.h"

class WEAPON_API FWeaponAssetEditor
	: public FAssetEditorToolkit
{

private:
	TSharedRef<SDockTab> Spawn_DetailsViewTab(const FSpawnTabArgs& InArgs);

private:
	TSharedPtr<class IDetailsView> DetailsView;

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

void FWeaponAssetEditor::Open(FString InAssetName)
{
	// 아래 디자인 되기전에 객체를 생성해 줘야한다.

	LeftArea = SNew(SWeaponLeftArea)
		.OnSelectedItem(this, &FWeaponAssetEditor::OnListViewSelectedItem); // 슬레이트 이벤트 연결

	FPropertyEditorModule& prop = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");

	// 디테일 View 바꿔주기.
	FDetailsViewArgs args(false, false, true, FDetailsViewArgs::HideNameArea);
	args.ViewIdentifier = "WeaponAssetEditorDetailsView"; // 창이 많아지면 햇갈리므로, 식별자 이름을 설정
	DetailsView = prop.CreateDetailView(args);

}

void FWeaponAssetEditor::RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
{
	// 부모함수를 사용했지만, 에디터 안에서는 Super 사용 불가.
	FAssetEditorToolkit::RegisterTabSpawners(InTabManager);

	FOnSpawnTab tab;
	// 슬레이트 쪽이라 SP를 사용, 
	tab.BindSP(this, &FWeaponAssetEditor::Spawn_LeftAreaTab);
	TabManager->RegisterTabSpawner(LeftAreaTabId, tab); // 구역에 대한 ID,

	FOnSpawnTab tab2;
	tab2.BindSP(this, &FWeaponAssetEditor::Spawn_DetailsViewTab);
	TabManager->RegisterTabSpawner(DetailTabId, tab2);
}

TSharedRef<SDockTab> FWeaponAssetEditor::Spawn_DetailsViewTab(const FSpawnTabArgs& InArgs)
{
	return SNew(SDockTab)
		[
			DetailsView.ToSharedRef() // 만든 디테일 View 재 추가해서 보여준다.
		];
}

1. 디테일 창을 만들어주고, Tab2로 디테일 창이 보여줄수 있도록 만들어준다.

2. 무언가 추가되면 보여지도록 만들어준다.


더보기
#pragma once

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

class WEAPON_API SWeaponLeftArea
	: public SCompoundWidget
{

public:
	// 하나라도 데이터가 있는지 체크
	bool HasRowDataPtr() { return RowDatas.Num() > 0; }
	// 첫번째(맨 위)에 체크된거 return
	FWeaponRowDataPtr GetFirstDataPtr() { return RowDatas[0]; }
    
protected:
	bool OnRequestClose() override; // 창이 닫힐때 콜 될 것이다.

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

void FWeaponAssetEditor::Open(FString InAssetName)
{
	UCWeaponAsset* asset = nullptr;
	asset = LeftArea->GetFirstDataPtr()->Asset; // 첫번째가 선택된다

	DetailsView->SetObject(asset); // 디테일 뷰에도 오브젝트가 세팅된다
}

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

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

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

	return true;
}

1. 첫번째 Asset 체크된걸 체크해주고 선택해준다.

2. 해제되면 알려주고, 나머지 객체도 체크 후 제거해준다.

1. 해제를 안시켜주게 되면 DA 데이터를 눌러서 창에 들어올때 오류가 발생하여 팅길 것이다.


더보기
#pragma once

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

class WEAPON_API SWeaponLeftArea
	: public SCompoundWidget
{
	// 선택된 Asset을 주고, 내부에서 선택하면 디테일이 바뀌게된다.
	void SelectDataPtr(class UCWeaponAsset* InAsset);

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

void SWeaponLeftArea::SelectDataPtr(UCWeaponAsset* InAsset)
{
	// 하나라도 없으면 x
	if (HasRowDataPtr() == false)
		return;

	// 에셋 같은게 있는지 검색 후 선택.
	for (FWeaponRowDataPtr ptr : RowDatas)
	{
		// 들어온게 같다면, ListView한테 알려주기.
		if (ptr->Asset == InAsset)
		{
			ListView->SetSelection(ptr);

			return;
		}
	}
}

1. 내부에서 들어온 데이터를 선택하면 그 데이터에 알맞은게 나오도록 만들어준다.


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

void FWeaponAssetEditor::Open(FString InAssetName)
{
	//DetailsView->SetObject(asset); // 디테일 뷰에도 오브젝트가 세팅된다
	LeftArea->SelectDataPtr(asset);
}

void FWeaponAssetEditor::OnListViewSelectedItem(FWeaponRowDataPtr InDataPtr)
{
	if (InDataPtr == nullptr)
		return;

	// 편집할 객체를 하나 return 받는다
	if (!!GetEditingObject())
		RemoveEditingObject(GetEditingObject()); // 현재 창에서 편집중인 객체 제거

	// 현재 창에다가 우리가 받은 객체를 등록해준다.
	AddEditingObject(InDataPtr->Asset);
	// 창 안에 디테일뷰도 변경하라고 명령해준다.
	DetailsView->SetObject(InDataPtr->Asset);
}

1. LeftArea에 의해서 위에서 만들어 준 SelectDataPtr 함수가 처리되고 -> SelectDataPtr 에 의해서                                      -> OnSelectionChanged 가 콜 -> OnListViewSelectedItem에서 실제로 데이터를 선택하게 되는 과정이다.

2. 창에 객체 등록해주고, 그 객체에 맞게끔 디테일 뷰도 변경해준다.

1. 선택된 객체에 따라 디테일 뷰가 변경되는걸 볼 수 있다.


전체 코드 ↓

더보기
#pragma once

#include "CoreMinimal.h"
#include "SWeaponLeftArea.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);

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;

private:
	static const FName EditorName;
	static const FName LeftAreaTabId;
	static const FName DetailTabId;

private:
	FReply OnClicked();
};
#include "WeaponAssetEditor.h"
#include "SWeaponLeftArea.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)
{
	// 창이 만들어져 있다면
	if (Instance.IsValid())
	{
		// 창이 한번이라도 열려 있었다면, off 해준다.
		Instance->CloseWindow();

		Instance.Reset();
		Instance = nullptr;
	}

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

void FWeaponAssetEditor::Shutdown()
{
	// Shutdown을 OpenWindow가 끝났을때 콜을하면 터진다. 그 이유는 다른곳에서도 사용하기 때문에
	if (Instance.IsValid())
	{
		Instance->CloseWindow();

		Instance.Reset();
		Instance = nullptr;
	}
}

void FWeaponAssetEditor::Open(FString InAssetName)
{
	// 아래 디자인 되기전에 객체를 생성해 줘야한다.

	LeftArea = SNew(SWeaponLeftArea)
		.OnSelectedItem(this, &FWeaponAssetEditor::OnListViewSelectedItem); // 슬레이트 이벤트 연결

	FPropertyEditorModule& prop = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");

	// 디테일 View 바꿔주기.
	FDetailsViewArgs args(false, false, true, FDetailsViewArgs::HideNameArea);
	args.ViewIdentifier = "WeaponAssetEditorDetailsView"; // 창이 많아지면 햇갈리므로, 식별자 이름을 설정
	DetailsView = prop.CreateDetailView(args);

	// 모든 창은 TabManager를 가지고있다. TabManager를 이용하여 레이아웃을 잡아주면 된다.
	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) // Tab 열린상태로 추가
			)
			->Split
			(
				// 한번더 분활하기 위해 Splitter 사용, Splitter는 BP내에서 창을 마우스로 움직일수 있는 창
				FTabManager::NewSplitter()->SetOrientation(Orient_Horizontal)
				->Split // 다시 분활
				(
					FTabManager::NewStack()
					->SetSizeCoefficient(0.175f)
					->AddTab(LeftAreaTabId, ETabState::OpenedTab)
					->SetHideTabWell(true) // 공간을 true 해줘야 펼칠수 있다
				)
				->Split
				(
					FTabManager::NewStack()
					->SetSizeCoefficient(0.725) // 나머지 공간
					->AddTab(DetailTabId, ETabState::OpenedTab)
					->SetHideTabWell(true)
				)
			)
		);

	UCWeaponAsset* asset = nullptr;
	asset = LeftArea->GetFirstDataPtr()->Asset; // 첫번째가 선택된다

	// 독림형 창으로 뛰우기, 부모창 호스트는 없으므로 비워주고, 창에 이름을 설정, 창 추가, 메뉴를 보여줄 것인지, 툴바도 보여줄 것인지, 어떤 오브젝트를 편집할 것인지
	FAssetEditorToolkit::InitAssetEditor(EToolkitMode::Standalone, TSharedPtr<IToolkitHost>(), EditorName, layout, true, true, asset);

	//DetailsView->SetObject(asset); // 디테일 뷰에도 오브젝트가 세팅된다
	LeftArea->SelectDataPtr(asset);
}

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

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

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

	return true;
}

void FWeaponAssetEditor::RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
{
	// 부모함수를 사용했지만, 에디터 안에서는 Super 사용 불가.
	FAssetEditorToolkit::RegisterTabSpawners(InTabManager);

	FOnSpawnTab tab;
	// 슬레이트 쪽이라 SP를 사용, 
	tab.BindSP(this, &FWeaponAssetEditor::Spawn_LeftAreaTab);
	TabManager->RegisterTabSpawner(LeftAreaTabId, tab); // 구역에 대한 ID,

	FOnSpawnTab tab2;
	tab2.BindSP(this, &FWeaponAssetEditor::Spawn_DetailsViewTab);
	TabManager->RegisterTabSpawner(DetailTabId, tab2);
}

TSharedRef<SDockTab> FWeaponAssetEditor::Spawn_LeftAreaTab(const FSpawnTabArgs& InArgs)
{
	// 도킹할수 있는 Tab을 DockTab라 부른다. 마우스로 드래그해서 구역을 왔다갔다 할 수 있는 것
	//TSharedPtr<SDockTab> tab = SNew(SDockTab) // [] 대괄호 사용시 그 영역까지는 Content 영역이며, []로 생략이 가능하다
	//[
	//	SNew(SButton)
	//	.OnClicked(this, &FWeaponAssetEditor::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() // 만든 디테일 View 재 추가해서 보여준다.
		];
}

void FWeaponAssetEditor::OnListViewSelectedItem(FWeaponRowDataPtr InDataPtr)
{
	if (InDataPtr == nullptr)
		return;

	// 편집할 객체를 하나 return 받는다
	if (!!GetEditingObject())
		RemoveEditingObject(GetEditingObject()); // 현재 창에서 편집중인 객체 제거

	// 현재 창에다가 우리가 받은 객체를 등록해준다.
	AddEditingObject(InDataPtr->Asset);
	// 창 안에 디테일뷰도 변경하라고 명령해준다.
	DetailsView->SetObject(InDataPtr->Asset);
}

FName FWeaponAssetEditor::GetToolkitFName() const
{
	// 외부에서 어떠한 이름으로 사용할지
	return EditorName;
}

FText FWeaponAssetEditor::GetBaseToolkitName() const
{
	// 외부에서 어떠한 이름으로 사용할지, 위와 동일한 문맥
	return FText::FromName(EditorName);
}

FString FWeaponAssetEditor::GetWorldCentricTabPrefix() const
{
	// Tab을 표현해주기 위해 식별자가 붙는다 그 이름
	return EditorName.ToString();
}

FLinearColor FWeaponAssetEditor::GetWorldCentricTabColorScale() const
{
	// 색상 파란색으로 설정
	return FLinearColor(0, 0, 1);
}

FReply FWeaponAssetEditor::OnClicked()
{
	// 클릭 이벤트 테스트
	GLog->Log("Test");

	return FReply::Handled();  // Handled 처리하고 끝내고, UnHandled는 패스 시킨다.
}

1. WeaponAssetEditor

 

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

///////////////////////////////////////////////////////////////////////////////

class WEAPON_API SWeaponTableRow
	: public SMultiColumnTableRow<FWeaponRowDataPtr> // 실제로 출력할 자료형 넣어주기, 한줄에 여러칸 표현 가능
{
public:
	// S가 붙었으므로, 사용할때 매크로 선언 안해놓으면, 링크 오류 발생
	SLATE_BEGIN_ARGS(SWeaponTableRow) {	}
	// 일반 변수를 줄땐 SLATE_ARGUMENT 사용, 앞에 자료형, 뒤에 변수명
	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;

private:
	FWeaponRowDataPtr Data; // 실제로 받아서 쓸 자료형
};

///////////////////////////////////////////////////////////////////////////////

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; }
	// 첫번째(맨 위)에 체크된거 return
	FWeaponRowDataPtr GetFirstDataPtr() { return RowDatas[0]; }

	// 선택된 Asset을 주고, 내부에서 선택하면 디테일이 바뀌게된다.
	void SelectDataPtr(class UCWeaponAsset* InAsset);

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); // 입력한 Text
	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; // 검색 문자열 관리
};
#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)
{
	// SLATE_ARGUMENT로 정의한걸 .으로 불러 올 수 있다.
	// 다른곳에서 부를때 ._으로 선언해놓으면 검색하였을때 그냥 버전과, _버전이 두개 검색된다
	// 그 중에 함수로 되어있는걸 사용하면 된다.
	Data = InArgs._RowData;

	// 어떤 형식으로 보여줄건지 스타일을 지정해줘야한다.
	SMultiColumnTableRow<FWeaponRowDataPtr>::Construct
	(
		FSuperRowType::FArguments().Style(FEditorStyle::Get(), "TableView.DarkRow"), InOwnerTable
	);
}

TSharedRef<SWidget> SWeaponTableRow::GenerateWidgetForColumn(const FName& InColumnName)
{
	// 어느 Column이 들어왔는지 구분
	FString str;
	if (InColumnName == "Number") // 번호 출력
		str = FString::FromInt(Data->Number);
	else if (InColumnName == "Name") // 이름 출력
		str = Data->Name;

	return SNew(STextBlock) // 출력 return
		.Text(FText::FromString(str));
}

///////////////////////////////////////////////////////////////////////////////

void SWeaponLeftArea::Construct(const FArguments& InArgs)
{
	// 델리게이트 이벤트 연결, 외부에서 이벤트 넘겨주면 된다.
	OnListViewSelectedItem = InArgs._OnSelectedItem;

	// 모양, 디자인을 하는 부분
	ChildSlot
		[
			SNew(SVerticalBox)
			+ SVerticalBox::Slot()
			.AutoHeight()
			.Padding(2, 0)
		[
			SAssignNew(SearchBox, SSearchBox) // 동적할당해서 사용할거,
			.SelectAllTextWhenFocused(true) // 검색창 클릭하면 Text 다시 입력해주는 기능
			.OnTextChanged(this, &SWeaponLeftArea::OnTextChanged)
			.OnTextCommitted(this, &SWeaponLeftArea::OnTextCommitted)
		]
	+ SVerticalBox::Slot() // 한칸마다 Slot를 가지고있다.
		.FillHeight(1) // 전체 꽉 채우기, 1이 100% 를 의미
		[
			// 이 구역안에 ListView 할당해주기.
			SAssignNew(ListView, SListView<FWeaponRowDataPtr>)
			.HeaderRow // 머리말
			(
				SNew(SHeaderRow)
				+ SHeaderRow::Column("Number") // 검색할때 찾을 이름
				.DefaultLabel(FText::FromString("")) // 보여질 이름
				.ManualWidth(40) // 칸에 넓이 40 사용
				+ SHeaderRow::Column("Name") // 검색할때 찾을 이름
				.DefaultLabel(FText::FromString("Name")) // 보여질 이름
			)
		.ListItemsSource(&RowDatas) // 어떠한 데이터를 출력할 건지, (포인터이므로)&로 줘야한다
		.OnGenerateRow(this, &SWeaponLeftArea::OnGenerateRow) // 한줄 한줄 모양을 정해주기, Row배열이 5개라면 5번 콜이된다.
		.OnSelectionChanged(this, &SWeaponLeftArea::OnSelectionChanged)
		]
	+ SVerticalBox::Slot()
		// 크기만큼 높이가 만들어진다. 나머지 영역은 FillHeight가 잡아준다.
		// 사용할때 Auto로 특정 영역을 잡아주는 식으로 사용한다.
		.AutoHeight()
		.VAlign(VAlign_Center) // 수직 정렬
		.HAlign(HAlign_Right) // 수평 정렬
		.Padding(FMargin(8, 2)) // 마진 영역도 준다, 위아래가 8, 좌우 여백이 2가된다.
		[
			SNew(STextBlock)
			// Text안에 내용이 바뀌면 자동으로 갱신해주는 신기한 일이 일어난다.
			// 아래 OnGetAssetCount 함수를 콜한게 아닌, 이벤트로 연결했을 뿐인데
			// 원래 가지고 있던 값에서 호출한 내용이 다르다면 자동으로 바꿔서 출력해주는 특징을 가지고있다.
		.Text(this, &SWeaponLeftArea::OnGetAssetCount)
		]
		];

	/*RowDatas.Add(FWeaponRowData::Make(1, "kkk", nullptr));
	RowDatas.Add(FWeaponRowData::Make(2, "aaa", nullptr));
	RowDatas.Add(FWeaponRowData::Make(3, "qqq", nullptr));*/

	ReadDataAssetList();
}

void SWeaponLeftArea::SelectDataPtr(UCWeaponAsset* InAsset)
{
	// 하나라도 없으면 x
	if (HasRowDataPtr() == false)
		return;

	// 에셋 같은게 있는지 검색 후 선택.
	for (FWeaponRowDataPtr ptr : RowDatas)
	{
		// 들어온게 같다면, ListView한테 알려주기.
		if (ptr->Asset == InAsset)
		{
			ListView->SetSelection(ptr);

			return;
		}
	}
}

TSharedRef<ITableRow> SWeaponLeftArea::OnGenerateRow(FWeaponRowDataPtr InRow, const TSharedRef<STableViewBase>& InTable)
{
	// 위에 RowDatas가 만일 5개라면, 5번 return 하면서 콜이 5번 일어난다.
	return SNew(SWeaponTableRow, InTable)
		.RowData(InRow);
}

void SWeaponLeftArea::ReadDataAssetList()
{
	// 콜 될때마다 비워주기
	RowDatas.Empty();

	TArray<UObject*> objects;
	// 데이터를 실제로 찾아올 경로
	// 이 안에서 모든 Asset를 다 찾아서 objects에 return 해줄 것이다
	EngineUtils::FindOrLoadAssetsByPath("/Game/Weapons/", objects, EngineUtils::ATL_Regular);

	// 안에 있는 데이터를 for문으로 확인
	int32 index = 0;
	for (UObject* obj : objects)
	{
		UCWeaponAsset* asset = Cast<UCWeaponAsset>(obj);
		// nullptr이면 Pass 시킨다.
		if (asset == nullptr) continue;

		FString name = asset->GetName();
		if (SearchText.IsEmpty() == false) // 검색 문자열이 비워져있지 않다면, 찾을 Asset에 이름에 SearchText가 포함되어있는지 확인
		{
			// 검색된 이름이 없다면, 보여주지 않기
			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 다시 그려주기, 다른곳에서도 사용할 것이라서 재갱신
	ListView->RequestListRefresh();
}

FText SWeaponLeftArea::OnGetAssetCount() const
{
	// 실제 Data 갯수 출력
	FString str = FString::Printf(L"%d 에셋", RowDatas.Num());

	return FText::FromString(str);
}

void SWeaponLeftArea::OnTextChanged(const FText& InText)
{
	// 글자가 바뀔때마다 콜 된다.
	if (SearchText.CompareToCaseIgnored(InText) == 0) // 기존에 있는 문자와 같으면 return
		return;

	SearchText = InText;
	ReadDataAssetList(); // 재검색 하도록,
}

void SWeaponLeftArea::OnTextCommitted(const FText& InText, ETextCommit::Type InType)
{
	OnTextChanged(InText);
}

void SWeaponLeftArea::OnSelectionChanged(FWeaponRowDataPtr InDataPtr, ESelectInfo::Type InType)
{
	// 선택된 데이터가 들어온다. 빈 구역을 눌러도 Null이 나오게 되는걸 방지하기 위해 조건 처리
	if (InDataPtr.IsValid() == false) // 선택된게 없으면 끝
		return;

	// 가지고 있는 델리게이트 연결
	OnListViewSelectedItem.ExecuteIfBound(InDataPtr);
}

1. WeaponLeftArea


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

 

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

UE4 Weapon Plugin (6)  (0) 2023.06.09
UE4 Weapon Plugin (5)  (2) 2023.06.07
UE4 Weapon Plugin (3)  (0) 2023.05.31
UE4 Weapon Plugin (2)  (0) 2023.05.30
UE4 Weapon Plugin  (0) 2023.05.26
Comments