목차

     

     


     

     
    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 
     
       

     

     

     

    Weapon Plugin 3

     

     


     

    SCompoundWidget

     

    SCompoundWidget

    • 공간 분할해주는 역할
    • 연관된 것을 묶어줄 때 사용.
    • 요소들
      • SAssetSearchBox
      • SListView
      • STextBlock

     

    https://docs.unrealengine.com/4.27/en-US/API/Runtime/SlateCore/Widgets/SCompoundWidget/

     

    SCompoundWidget

    A CompoundWidget is the base from which most non-primitive widgets should be built.

    docs.unrealengine.com


     

     

    Weapon.Build.cs

     

    Weapon.Build.cs

    더보기
    using UnrealBuildTool;
    
    public class Weapon : ModuleRules
    {
    	public Weapon(ReadOnlyTargetRules Target) : base(Target)
    	{
    		PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
    
            PrivateIncludePaths.Add(ModuleDirectory);
    
    		PublicDependencyModuleNames.Add("Core");
    
    		PrivateDependencyModuleNames.Add("U2212_06");//프로젝트명
    
    		PrivateDependencyModuleNames.Add("CoreUObject");
            PrivateDependencyModuleNames.Add("Engine");
            PrivateDependencyModuleNames.Add("Slate");
            PrivateDependencyModuleNames.Add("SlateCore");
    
            PrivateDependencyModuleNames.Add("UnrealEd");//UFactory 
            PrivateDependencyModuleNames.Add("EditorStyle");
            PrivateDependencyModuleNames.Add("InputCore");
        }
    }

     

     

    PrivateDependencyModuleNames.Add("InputCore"); 추가

     


     

     

     

    SWeaponLeftArea 생성

     

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

    CWeaponAsset의 왼쪽에 해당되는 부분을 작업하기 위해  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.
    
    };
    
    
    class WEAPON_API SWeaponLeftArea
    	: public SCompoundWidget
    {
    public:
    	SLATE_BEGIN_ARGS(SWeaponLeftArea) {}
    	SLATE_END_ARGS()
    
    public:
    	void Construct(const FArguments& InArgs);
    
    private:
    	TSharedRef<ITableRow> OnGenerateRow(FWeaponRowDataPtr InRow, const TSharedRef<STableViewBase>& InTable);
    
    	FText OnGetAssetCount() const;
    
    private:
    	void ReadDataAssetList();
    
    private:
    	TArray<FWeaponRowDataPtr> RowDatas;//데이터 자료형을 받는 배열변수 
    	TSharedPtr<SListView<FWeaponRowDataPtr>> ListView;
    };

    SMultiColumnTableRow을 상속받아 클래스 생성

    • class WEAPON_API SWeaponTableRow
      : public SMultiColumnTableRow<FWeaponRowDataPtr>

     

    SCompoundWidget을 상속받아 클래스 생성

    • class WEAPON_API SWeaponLeftArea : public SCompoundWidget

     

     

     

    SWeaponLeftArea.cpp

    더보기
    #include "SWeaponLeftArea.h"
    #include "Weapons/CWeaponAsset.h"
    #include "EngineUtils.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)
    {
    	ChildSlot
    	[
    		SNew(SVerticalBox)
    		+ SVerticalBox::Slot()
    		.FillHeight(1)//1이 100%를 의미한다. 한줄을 다 채우겠다는 의미.
    		[
    			SAssignNew(ListView, SListView<FWeaponRowDataPtr>)//자료형은 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)//한줄한줄 어떻게 표현할지 모양을 정해달라는 의미.
    		]
    		+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.
    		]
    	];
    
    	//테스트용 출력
    	//RowDatas.Add(FWeaponRowData::Make(1, "aaa", nullptr));
    	//RowDatas.Add(FWeaponRowData::Make(2, "bbb", nullptr));
    	//RowDatas.Add(FWeaponRowData::Make(3, "ccc", nullptr));
    
    	ReadDataAssetList();
    }
    
    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();
    
    		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);
    }

     

     

     

    ※ 참고 - STableRow.h

     

     

    SLATE_ARGUMENT | Unreal Engine Documentation

     

    SLATE_ARGUMENT

    Overload list

    docs.unrealengine.com

     

    SCustomDialog::SLATE_EVENT | Unreal Engine Documentation

     

    SCustomDialog::SLATE_EVENT

    Event triggered when the dialog is closed, either because one of the buttons is pressed, or the windows is closed.

    docs.unrealengine.com

     

     


     

    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);
    
    public:
    	void RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager) override;
    
    private:
    	TSharedRef<SDockTab> Spawn_ListViewTab(const FSpawnTabArgs& InArgs);
    
    public:
    	FName GetToolkitFName() const override;
    	FText GetBaseToolkitName() const override;
    	FString GetWorldCentricTabPrefix() const override;
    	FLinearColor GetWorldCentricTabColorScale() const override;
    
    private:
    	TSharedPtr<class SWeaponLeftArea> LeftArea;
    
    private:
    	static const FName EditorName;
    	static const FName ListViewTabId;
    	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::ListViewTabId = "ListView";
    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에서 받은 자료형을 생성하여 넣어준다.
    
    	//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(ListViewTabId, ETabState::OpenedTab)//ListViewTabId
    					->SetHideTabWell(true)
    				)
    				->Split
    				(
    					FTabManager::NewStack()
    					->SetSizeCoefficient(0.725f)//오른쪽 72.5% 사용
    					->AddTab(DetailTabId, ETabState::OpenedTab)//DetailTabId
    					->SetHideTabWell(true)
    				)
    			)
    		);
    
    	UCWeaponAsset* asset = NewObject<UCWeaponAsset>();//현재 없는 상태이므로 일단 빈 상태로 만들어준다.
    	FAssetEditorToolkit::InitAssetEditor(EToolkitMode::Standalone, TSharedPtr<IToolkitHost>(), EditorName, layout, true, true, asset);
    
    }
    
    void FWeaponAssetEditor::RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
    {
    	FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
    
    	FOnSpawnTab tab;
    	tab.BindSP(this, &FWeaponAssetEditor::Spawn_ListViewTab);
    	TabManager->RegisterTabSpawner(ListViewTabId, tab);
    }
    
    TSharedRef<SDockTab> FWeaponAssetEditor::Spawn_ListViewTab(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);
    
    	return SNew(SDockTab)
    	[
    		LeftArea.ToSharedRef()
    	];
    }
    
    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()는 처리하고 끝낸다.
    }

     

     

     

     

    좌: 적용 전
    RowDatas.Add로 테스트

     


     

    실행화면