목차

     

     


     

     

     

     
    Plugins
     
      Weapon
     
        Resource
     
          Icon128.png
    weapon_thumbnail_icon.png
     
        Source
     
          Weapon  
          SWeaponLeftArea.h .cpp
    Weapon.Build.cs
    WeaponAssetEditor.h .cpp
    WeaponAssetFactory.h .cpp
    WeaponCommand.h .cpp 
    WeaponContextMenu.h .cpp
    WeaponModule.h .cpp
    WeaponStyle.h .cpp 
     
       

     

     

     

    무기 플러그인 만들기

     

     


     

     

    SWeaponLeftArea

     

    지난 시간에 네이밍 잘못 지어준 부분 수정.
     static consst FName ListViewTabId;을 LeftAreaTabId;로 변경.
    TSharedRef<SDockTab> Spawn_ListViewTab(const FSpawnTabArgs & InArgs);을 Spawn_LeftAreaTab로 변경.

     

     

    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);
    
    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;//검색 문자열을 관리할 변수
    };

    변수 추가

    • TSharedPtr<class SSearchBox> SearchBox;
    • FText SearchText;
      • 검색 문자열을 관리할 변수

     

    델리게이션 추가

    • DECLARE_DELEGATE_OneParam(FOnWeaponListViewSelectedItem, FWeaponRowDataPtr)
    • .
    • SLATE_EVENT(FOnWeaponListViewSelectedItem, OnSelectedItem)
    •  

     

    변수 추가

    • TSharedPtr<class SSearchBox> SearchBox;
    • FText SearchText;

     

     

     

    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)
    		]
    		+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;
    		}
    	}
    }
    
    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);
    }

    헤더 추가

    • #include "Widgets/Input/SSearchBox.h"

     

     

     

    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();//다른곳에서 사용하기 때문에 재갱신를 해준다.
    }

    위의 코드가 적용되는 부분.

    검색 창에서 문자를 기입하면 해당 문자열을 포함하는 부분만 보여준다.

     


     

    WeaponAssetEditor

     

    WeaponAssetEditor.h

    더보기
    #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 "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()) //창이 만들어졌다면
    	{
    		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");
    
    	FDetailsViewArgs args(false, false, true, FDetailsViewArgs::HideNameArea);//기본값 설정. ActorsUserNameArea, ObjectsUserNameArea, HideNameArea
    	args.ViewIdentifier = "WeaponAssetEditorDetailsView";//식별자 설정. 게임 Editor쪽에서 DetailView 접근시 이 식별자로 찾을 수 있다.
    	DetailsView = prop.CreateDetailView(args);
    
    	//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; 
    	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 (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()는 처리하고 끝낸다.
    }

     

     

    void SWeaponLeftArea::SelectDataPtr(UCWeaponAsset* InAsset)

                                               ↓

    void SWeaponLeftArea::OnSelectionChanged(FWeaponRowDataPtr InDataPtr, ESelectInfo::Type InType)

                                               

    void FWeaponAssetEditor::OnListViewSelectedItem(FWeaponRowDataPtr InDataPtr)

     


     

     

    실행화면