[UE] Weapon Plugin 4: 창 내에 데이터 넣어주기, 검색 기능 넣기
목차
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)
실행화면
'⭐ Unreal Engine > UE Plugin - Slate UI' 카테고리의 다른 글
[UE] Weapon Plugin 6: 체크박스 만들기 (0) | 2023.06.08 |
---|---|
[UE] Weapon Plugin 5: 창 내부 Header, Child 구분하기 (0) | 2023.06.07 |
[UE] Weapon Plugin 3: 창 내부 영역 구분하기(SWeaponLeftArea, WeaponAssetEditor) (0) | 2023.05.31 |
[UE] Weapon Plugin 2: 버튼 만들기, 창 띄우기 (0) | 2023.05.30 |
[UE] Weapon Plugin 1: Slate UI Plugin 만들기 (0) | 2023.05.26 |
댓글
이 글 공유하기
다른 글
-
[UE] Weapon Plugin 6: 체크박스 만들기
[UE] Weapon Plugin 6: 체크박스 만들기
2023.06.08 -
[UE] Weapon Plugin 5: 창 내부 Header, Child 구분하기
[UE] Weapon Plugin 5: 창 내부 Header, Child 구분하기
2023.06.07 -
[UE] Weapon Plugin 3: 창 내부 영역 구분하기(SWeaponLeftArea, WeaponAssetEditor)
[UE] Weapon Plugin 3: 창 내부 영역 구분하기(SWeaponLeftArea, WeaponAssetEditor)
2023.05.31 -
[UE] Weapon Plugin 2: 버튼 만들기, 창 띄우기
[UE] Weapon Plugin 2: 버튼 만들기, 창 띄우기
2023.05.30