[UE] Weapon Plugin 3: 창 내부 영역 구분하기(SWeaponLeftArea, WeaponAssetEditor)
목차
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()는 처리하고 끝낸다.
}
실행화면