[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
댓글을 사용할 수 없습니다.