초보 코린이의 성장 일지
UE4 Weapon Plugin (4) 본문
Weapon를 관리해주는 Plugin에 검색 기능과 검색을 할때 이름을 입력하면 알맞(연관된)은 이름만 나오도록 만들어볼 것이다.
#pragma once
#include "CoreMinimal.h"
#include "Widgets/SCompoundWidget.h"
#include "Widgets/Views/STableRow.h"
class WEAPON_API SWeaponLeftArea
: public SCompoundWidget
{
private:
void OnTextChanged(const FText& InText); // 입력한 Text
void OnTextCommitted(const FText& InText, ETextCommit::Type InType); // Enter을 눌러 입력이 완료됐을때
private:
TSharedPtr<class SSearchBox> SearchBox; // 검색창
FText SearchText; // 검색 문자열 관리
};
#include "SWeaponLeftArea.h"
#include "Weapons/CWeaponAsset.h"
#include "EngineUtils.h"
#include "Widgets/Input/SSearchBox.h" // 검색창
void SWeaponLeftArea::Construct(const FArguments& InArgs)
{
// 모양, 디자인을 하는 부분
ChildSlot
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2, 0)
[
SAssignNew(SearchBox, SSearchBox) // 동적할당해서 사용할거,
.SelectAllTextWhenFocused(true) // 검색창 클릭하면 Text 다시 입력해주는 기능
.OnTextChanged(this, &SWeaponLeftArea::OnTextChanged)
.OnTextCommitted(this, &SWeaponLeftArea::OnTextCommitted)
]
}
void SWeaponLeftArea::ReadDataAssetList()
{
// 콜 될때마다 비워주기
RowDatas.Empty();
TArray<UObject*> objects;
// 데이터를 실제로 찾아올 경로
// 이 안에서 모든 Asset를 다 찾아서 objects에 return 해줄 것이다
EngineUtils::FindOrLoadAssetsByPath("/Game/Weapons/", objects, EngineUtils::ATL_Regular);
// 안에 있는 데이터를 for문으로 확인
int32 index = 0;
for (UObject* obj : objects)
{
UCWeaponAsset* asset = Cast<UCWeaponAsset>(obj);
// nullptr이면 Pass 시킨다.
if (asset == nullptr) continue;
FString name = asset->GetName();
if (SearchText.IsEmpty() == false) // 검색 문자열이 비워져있지 않다면, 찾을 Asset에 이름에 SearchText가 포함되어있는지 확인
{
// 검색된 이름이 없다면, 보여주지 않기
if (name.Contains(SearchText.ToString()) == false)
continue;
}
// 이름이 없다면 추가해주면된다.
RowDatas.Add(FWeaponRowData::Make(++index, name, asset));
}
}
void SWeaponLeftArea::OnTextChanged(const FText& InText)
{
// 글자가 바뀔때마다 콜 된다.
if (SearchText.CompareToCaseIgnored(InText) == 0) // 기존에 있는 문자와 같으면 return
return;
SearchText = InText;
ReadDataAssetList(); // 재검색 하도록,
}
void SWeaponLeftArea::OnTextCommitted(const FText& InText, ETextCommit::Type InType)
{
OnTextChanged(InText);
}
1. Slot로 영역을 하나 만들어주고, 검색 기능을 넣어준다.
2. 검색할때 검색이 되지 않는 문자라면 숨겨주고, 그게 아니라면 추가해준다.
1. 검색창에 단어를 입력하면, 연관된 Name만 검색되는걸 확인할 수 있다.
#pragma once
#include "CoreMinimal.h"
#include "Widgets/SCompoundWidget.h"
#include "Widgets/Views/STableRow.h"
class WEAPON_API SWeaponLeftArea
: public SCompoundWidget
{
private:
void OnSelectionChanged(FWeaponRowDataPtr InDataPtr, ESelectInfo::Type InType);
};
#include "SWeaponLeftArea.h"
#include "Weapons/CWeaponAsset.h"
#include "EngineUtils.h"
#include "Widgets/Input/SSearchBox.h" // 검색창
void SWeaponLeftArea::OnSelectionChanged(FWeaponRowDataPtr InDataPtr, ESelectInfo::Type InType)
{
if (InDataPtr.IsValid())
GLog->Log(InDataPtr->Asset->GetPathName());
else
GLog->Log("Null");
GLog->Log(StaticEnum<ESelectInfo::Type>()->GetValueAsString(InType));
}
1. 왼쪽에 있는 Name이 선택된거에 따라 오른쪽에 내용들이 보여지도록 만들어 줄 것이다.
2. 구현은 키가 눌렸을때 이벤트를 넘겨주는 식으로 구현할 것이다.
1. Name에 클릭 후 키보드 상하를 누르거나, 마우스로 클릭할시 왼쪽 출력로그에 검색된게 나오게된다.
2. 마우스로 빈 공간을 찍을때 마지막 Null이 출력되는걸 확인할 수 있다.
1. 연결해 줄 델리게이 이벤트를 생성해준다.
#pragma once
#include "CoreMinimal.h"
#include "Widgets/SCompoundWidget.h"
#include "Widgets/Views/STableRow.h"
DECLARE_DELEGATE_OneParam(FOnWeaponListViewSelectedItem, FWeaponRowDataPtr);
class WEAPON_API SWeaponLeftArea
: public SCompoundWidget
{
public:
SLATE_EVENT(FOnWeaponListViewSelectedItem, OnSelectedItem)
private:
// 델리게이트 이벤트 사용
FOnWeaponListViewSelectedItem OnListViewSelectedItem;
};
#include "SWeaponLeftArea.h"
#include "Weapons/CWeaponAsset.h"
#include "EngineUtils.h"
#include "Widgets/Input/SSearchBox.h" // 검색창
void SWeaponLeftArea::Construct(const FArguments& InArgs)
{
// 델리게이트 이벤트 연결, 외부에서 이벤트 넘겨주면 된다.
OnListViewSelectedItem = InArgs._OnSelectedItem;
}
1. 생성한 델리게이트 사용할 것이다.
2. 슬레이트 이벤트도 델리게이트 자료형을 받아서 생성해준다.
3. 창을 생성해주는 함수에서 이벤트를 연결해준다.
#pragma once
#include "CoreMinimal.h"
#include "SWeaponLeftArea.h"
#include "Toolkits/AssetEditorToolkit.h"
class WEAPON_API FWeaponAssetEditor
: public FAssetEditorToolkit
{
private:
void OnListViewSelectedItem(FWeaponRowDataPtr InDataPtr);
};
#include "WeaponAssetEditor.h"
#include "SWeaponLeftArea.h"
#include "Weapons/CWeaponAsset.h"
void FWeaponAssetEditor::Open(FString InAssetName)
{
// 아래 디자인 되기전에 객체를 생성해 줘야한다.
LeftArea = SNew(SWeaponLeftArea)
.OnSelectedItem(this, &FWeaponAssetEditor::OnListViewSelectedItem); // 슬레이트 이벤트 연결
}
1. WeaponLeftArea에서 만든 슬레이트 이벤트를 연결시켜준다.
#include "SWeaponLeftArea.h"
#include "Weapons/CWeaponAsset.h"
#include "EngineUtils.h"
#include "Widgets/Input/SSearchBox.h" // 검색창
void SWeaponLeftArea::OnSelectionChanged(FWeaponRowDataPtr InDataPtr, ESelectInfo::Type InType)
{
// 선택된 데이터가 들어온다. 빈 구역을 눌러도 Null이 나오게 되는걸 방지하기 위해 조건 처리
if (InDataPtr.IsValid() == false) // 선택된게 없으면 끝
return;
// 가지고 있는 델리게이트 연결
OnListViewSelectedItem.ExecuteIfBound(InDataPtr);
}
1. 로그 출력했던 부분 코드를 바꿔준다.
#pragma once
#include "CoreMinimal.h"
#include "SWeaponLeftArea.h"
#include "Toolkits/AssetEditorToolkit.h"
class WEAPON_API FWeaponAssetEditor
: public FAssetEditorToolkit
{
private:
TSharedRef<SDockTab> Spawn_DetailsViewTab(const FSpawnTabArgs& InArgs);
private:
TSharedPtr<class IDetailsView> DetailsView;
};
#include "WeaponAssetEditor.h"
#include "SWeaponLeftArea.h"
#include "Weapons/CWeaponAsset.h"
void FWeaponAssetEditor::Open(FString InAssetName)
{
// 아래 디자인 되기전에 객체를 생성해 줘야한다.
LeftArea = SNew(SWeaponLeftArea)
.OnSelectedItem(this, &FWeaponAssetEditor::OnListViewSelectedItem); // 슬레이트 이벤트 연결
FPropertyEditorModule& prop = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
// 디테일 View 바꿔주기.
FDetailsViewArgs args(false, false, true, FDetailsViewArgs::HideNameArea);
args.ViewIdentifier = "WeaponAssetEditorDetailsView"; // 창이 많아지면 햇갈리므로, 식별자 이름을 설정
DetailsView = prop.CreateDetailView(args);
}
void FWeaponAssetEditor::RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
{
// 부모함수를 사용했지만, 에디터 안에서는 Super 사용 불가.
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
FOnSpawnTab tab;
// 슬레이트 쪽이라 SP를 사용,
tab.BindSP(this, &FWeaponAssetEditor::Spawn_LeftAreaTab);
TabManager->RegisterTabSpawner(LeftAreaTabId, tab); // 구역에 대한 ID,
FOnSpawnTab tab2;
tab2.BindSP(this, &FWeaponAssetEditor::Spawn_DetailsViewTab);
TabManager->RegisterTabSpawner(DetailTabId, tab2);
}
TSharedRef<SDockTab> FWeaponAssetEditor::Spawn_DetailsViewTab(const FSpawnTabArgs& InArgs)
{
return SNew(SDockTab)
[
DetailsView.ToSharedRef() // 만든 디테일 View 재 추가해서 보여준다.
];
}
1. 디테일 창을 만들어주고, Tab2로 디테일 창이 보여줄수 있도록 만들어준다.
2. 무언가 추가되면 보여지도록 만들어준다.
#pragma once
#include "CoreMinimal.h"
#include "Widgets/SCompoundWidget.h"
#include "Widgets/Views/STableRow.h"
class WEAPON_API SWeaponLeftArea
: public SCompoundWidget
{
public:
// 하나라도 데이터가 있는지 체크
bool HasRowDataPtr() { return RowDatas.Num() > 0; }
// 첫번째(맨 위)에 체크된거 return
FWeaponRowDataPtr GetFirstDataPtr() { return RowDatas[0]; }
protected:
bool OnRequestClose() override; // 창이 닫힐때 콜 될 것이다.
};
#include "WeaponAssetEditor.h"
#include "SWeaponLeftArea.h"
#include "Weapons/CWeaponAsset.h"
void FWeaponAssetEditor::Open(FString InAssetName)
{
UCWeaponAsset* asset = nullptr;
asset = LeftArea->GetFirstDataPtr()->Asset; // 첫번째가 선택된다
DetailsView->SetObject(asset); // 디테일 뷰에도 오브젝트가 세팅된다
}
bool FWeaponAssetEditor::OnRequestClose()
{
if (!!DetailsView)
{
if (!!GEditor && !!GEditor->GetEditorSubsystem<UAssetEditorSubsystem>())
// 디테일 뷰안에 객체가 등록되어있다면 해제된걸 알려줘라,
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->NotifyAssetClosed(GetEditingObject(), this);
}
// 만든 객체들 다 제거
if (LeftArea.IsValid())
LeftArea.Reset();
if (DetailsView.IsValid())
DetailsView.Reset();
return true;
}
1. 첫번째 Asset 체크된걸 체크해주고 선택해준다.
2. 해제되면 알려주고, 나머지 객체도 체크 후 제거해준다.
1. 해제를 안시켜주게 되면 DA 데이터를 눌러서 창에 들어올때 오류가 발생하여 팅길 것이다.
#pragma once
#include "CoreMinimal.h"
#include "Widgets/SCompoundWidget.h"
#include "Widgets/Views/STableRow.h"
class WEAPON_API SWeaponLeftArea
: public SCompoundWidget
{
// 선택된 Asset을 주고, 내부에서 선택하면 디테일이 바뀌게된다.
void SelectDataPtr(class UCWeaponAsset* InAsset);
};
#include "SWeaponLeftArea.h"
#include "Weapons/CWeaponAsset.h"
#include "EngineUtils.h"
#include "Widgets/Input/SSearchBox.h"
void SWeaponLeftArea::SelectDataPtr(UCWeaponAsset* InAsset)
{
// 하나라도 없으면 x
if (HasRowDataPtr() == false)
return;
// 에셋 같은게 있는지 검색 후 선택.
for (FWeaponRowDataPtr ptr : RowDatas)
{
// 들어온게 같다면, ListView한테 알려주기.
if (ptr->Asset == InAsset)
{
ListView->SetSelection(ptr);
return;
}
}
}
1. 내부에서 들어온 데이터를 선택하면 그 데이터에 알맞은게 나오도록 만들어준다.
#include "WeaponAssetEditor.h"
#include "SWeaponLeftArea.h"
#include "Weapons/CWeaponAsset.h"
void FWeaponAssetEditor::Open(FString InAssetName)
{
//DetailsView->SetObject(asset); // 디테일 뷰에도 오브젝트가 세팅된다
LeftArea->SelectDataPtr(asset);
}
void FWeaponAssetEditor::OnListViewSelectedItem(FWeaponRowDataPtr InDataPtr)
{
if (InDataPtr == nullptr)
return;
// 편집할 객체를 하나 return 받는다
if (!!GetEditingObject())
RemoveEditingObject(GetEditingObject()); // 현재 창에서 편집중인 객체 제거
// 현재 창에다가 우리가 받은 객체를 등록해준다.
AddEditingObject(InDataPtr->Asset);
// 창 안에 디테일뷰도 변경하라고 명령해준다.
DetailsView->SetObject(InDataPtr->Asset);
}
1. LeftArea에 의해서 위에서 만들어 준 SelectDataPtr 함수가 처리되고 -> SelectDataPtr 에 의해서 -> OnSelectionChanged 가 콜 -> OnListViewSelectedItem에서 실제로 데이터를 선택하게 되는 과정이다.
2. 창에 객체 등록해주고, 그 객체에 맞게끔 디테일 뷰도 변경해준다.
1. 선택된 객체에 따라 디테일 뷰가 변경되는걸 볼 수 있다.
전체 코드 ↓
#pragma once
#include "CoreMinimal.h"
#include "SWeaponLeftArea.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);
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;
private:
static const FName EditorName;
static const FName LeftAreaTabId;
static const FName DetailTabId;
private:
FReply OnClicked();
};
#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;
// 이 함수에 못 들어온다면 툴바 버튼을 클릭했다는 의미, 들어왔다면 DataAsset를 클릭했다는 의미
void FWeaponAssetEditor::OpenWindow(FString InAssetName)
{
// 창이 만들어져 있다면
if (Instance.IsValid())
{
// 창이 한번이라도 열려 있었다면, off 해준다.
Instance->CloseWindow();
Instance.Reset();
Instance = nullptr;
}
Instance = MakeShareable(new FWeaponAssetEditor());
Instance->Open(InAssetName);
}
void FWeaponAssetEditor::Shutdown()
{
// Shutdown을 OpenWindow가 끝났을때 콜을하면 터진다. 그 이유는 다른곳에서도 사용하기 때문에
if (Instance.IsValid())
{
Instance->CloseWindow();
Instance.Reset();
Instance = nullptr;
}
}
void FWeaponAssetEditor::Open(FString InAssetName)
{
// 아래 디자인 되기전에 객체를 생성해 줘야한다.
LeftArea = SNew(SWeaponLeftArea)
.OnSelectedItem(this, &FWeaponAssetEditor::OnListViewSelectedItem); // 슬레이트 이벤트 연결
FPropertyEditorModule& prop = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
// 디테일 View 바꿔주기.
FDetailsViewArgs args(false, false, true, FDetailsViewArgs::HideNameArea);
args.ViewIdentifier = "WeaponAssetEditorDetailsView"; // 창이 많아지면 햇갈리므로, 식별자 이름을 설정
DetailsView = prop.CreateDetailView(args);
// 모든 창은 TabManager를 가지고있다. TabManager를 이용하여 레이아웃을 잡아주면 된다.
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) // Tab 열린상태로 추가
)
->Split
(
// 한번더 분활하기 위해 Splitter 사용, Splitter는 BP내에서 창을 마우스로 움직일수 있는 창
FTabManager::NewSplitter()->SetOrientation(Orient_Horizontal)
->Split // 다시 분활
(
FTabManager::NewStack()
->SetSizeCoefficient(0.175f)
->AddTab(LeftAreaTabId, ETabState::OpenedTab)
->SetHideTabWell(true) // 공간을 true 해줘야 펼칠수 있다
)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.725) // 나머지 공간
->AddTab(DetailTabId, ETabState::OpenedTab)
->SetHideTabWell(true)
)
)
);
UCWeaponAsset* asset = nullptr;
asset = LeftArea->GetFirstDataPtr()->Asset; // 첫번째가 선택된다
// 독림형 창으로 뛰우기, 부모창 호스트는 없으므로 비워주고, 창에 이름을 설정, 창 추가, 메뉴를 보여줄 것인지, 툴바도 보여줄 것인지, 어떤 오브젝트를 편집할 것인지
FAssetEditorToolkit::InitAssetEditor(EToolkitMode::Standalone, TSharedPtr<IToolkitHost>(), EditorName, layout, true, true, asset);
//DetailsView->SetObject(asset); // 디테일 뷰에도 오브젝트가 세팅된다
LeftArea->SelectDataPtr(asset);
}
bool FWeaponAssetEditor::OnRequestClose()
{
if (!!DetailsView)
{
if (!!GEditor && !!GEditor->GetEditorSubsystem<UAssetEditorSubsystem>())
// 디테일 뷰안에 객체가 등록되어있다면 해제된걸 알려줘라,
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->NotifyAssetClosed(GetEditingObject(), this);
}
// 만든 객체들 다 제거
if (LeftArea.IsValid())
LeftArea.Reset();
if (DetailsView.IsValid())
DetailsView.Reset();
return true;
}
void FWeaponAssetEditor::RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
{
// 부모함수를 사용했지만, 에디터 안에서는 Super 사용 불가.
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
FOnSpawnTab tab;
// 슬레이트 쪽이라 SP를 사용,
tab.BindSP(this, &FWeaponAssetEditor::Spawn_LeftAreaTab);
TabManager->RegisterTabSpawner(LeftAreaTabId, tab); // 구역에 대한 ID,
FOnSpawnTab tab2;
tab2.BindSP(this, &FWeaponAssetEditor::Spawn_DetailsViewTab);
TabManager->RegisterTabSpawner(DetailTabId, tab2);
}
TSharedRef<SDockTab> FWeaponAssetEditor::Spawn_LeftAreaTab(const FSpawnTabArgs& InArgs)
{
// 도킹할수 있는 Tab을 DockTab라 부른다. 마우스로 드래그해서 구역을 왔다갔다 할 수 있는 것
//TSharedPtr<SDockTab> tab = SNew(SDockTab) // [] 대괄호 사용시 그 영역까지는 Content 영역이며, []로 생략이 가능하다
//[
// SNew(SButton)
// .OnClicked(this, &FWeaponAssetEditor::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() // 만든 디테일 View 재 추가해서 보여준다.
];
}
void FWeaponAssetEditor::OnListViewSelectedItem(FWeaponRowDataPtr InDataPtr)
{
if (InDataPtr == nullptr)
return;
// 편집할 객체를 하나 return 받는다
if (!!GetEditingObject())
RemoveEditingObject(GetEditingObject()); // 현재 창에서 편집중인 객체 제거
// 현재 창에다가 우리가 받은 객체를 등록해준다.
AddEditingObject(InDataPtr->Asset);
// 창 안에 디테일뷰도 변경하라고 명령해준다.
DetailsView->SetObject(InDataPtr->Asset);
}
FName FWeaponAssetEditor::GetToolkitFName() const
{
// 외부에서 어떠한 이름으로 사용할지
return EditorName;
}
FText FWeaponAssetEditor::GetBaseToolkitName() const
{
// 외부에서 어떠한 이름으로 사용할지, 위와 동일한 문맥
return FText::FromName(EditorName);
}
FString FWeaponAssetEditor::GetWorldCentricTabPrefix() const
{
// Tab을 표현해주기 위해 식별자가 붙는다 그 이름
return EditorName.ToString();
}
FLinearColor FWeaponAssetEditor::GetWorldCentricTabColorScale() const
{
// 색상 파란색으로 설정
return FLinearColor(0, 0, 1);
}
FReply FWeaponAssetEditor::OnClicked()
{
// 클릭 이벤트 테스트
GLog->Log("Test");
return FReply::Handled(); // Handled 처리하고 끝내고, UnHandled는 패스 시킨다.
}
1. WeaponAssetEditor
#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;
///////////////////////////////////////////////////////////////////////////////
class WEAPON_API SWeaponTableRow
: public SMultiColumnTableRow<FWeaponRowDataPtr> // 실제로 출력할 자료형 넣어주기, 한줄에 여러칸 표현 가능
{
public:
// S가 붙었으므로, 사용할때 매크로 선언 안해놓으면, 링크 오류 발생
SLATE_BEGIN_ARGS(SWeaponTableRow) { }
// 일반 변수를 줄땐 SLATE_ARGUMENT 사용, 앞에 자료형, 뒤에 변수명
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;
private:
FWeaponRowDataPtr Data; // 실제로 받아서 쓸 자료형
};
///////////////////////////////////////////////////////////////////////////////
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; }
// 첫번째(맨 위)에 체크된거 return
FWeaponRowDataPtr GetFirstDataPtr() { return RowDatas[0]; }
// 선택된 Asset을 주고, 내부에서 선택하면 디테일이 바뀌게된다.
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); // 입력한 Text
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; // 검색 문자열 관리
};
#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)
{
// SLATE_ARGUMENT로 정의한걸 .으로 불러 올 수 있다.
// 다른곳에서 부를때 ._으로 선언해놓으면 검색하였을때 그냥 버전과, _버전이 두개 검색된다
// 그 중에 함수로 되어있는걸 사용하면 된다.
Data = InArgs._RowData;
// 어떤 형식으로 보여줄건지 스타일을 지정해줘야한다.
SMultiColumnTableRow<FWeaponRowDataPtr>::Construct
(
FSuperRowType::FArguments().Style(FEditorStyle::Get(), "TableView.DarkRow"), InOwnerTable
);
}
TSharedRef<SWidget> SWeaponTableRow::GenerateWidgetForColumn(const FName& InColumnName)
{
// 어느 Column이 들어왔는지 구분
FString str;
if (InColumnName == "Number") // 번호 출력
str = FString::FromInt(Data->Number);
else if (InColumnName == "Name") // 이름 출력
str = Data->Name;
return SNew(STextBlock) // 출력 return
.Text(FText::FromString(str));
}
///////////////////////////////////////////////////////////////////////////////
void SWeaponLeftArea::Construct(const FArguments& InArgs)
{
// 델리게이트 이벤트 연결, 외부에서 이벤트 넘겨주면 된다.
OnListViewSelectedItem = InArgs._OnSelectedItem;
// 모양, 디자인을 하는 부분
ChildSlot
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2, 0)
[
SAssignNew(SearchBox, SSearchBox) // 동적할당해서 사용할거,
.SelectAllTextWhenFocused(true) // 검색창 클릭하면 Text 다시 입력해주는 기능
.OnTextChanged(this, &SWeaponLeftArea::OnTextChanged)
.OnTextCommitted(this, &SWeaponLeftArea::OnTextCommitted)
]
+ SVerticalBox::Slot() // 한칸마다 Slot를 가지고있다.
.FillHeight(1) // 전체 꽉 채우기, 1이 100% 를 의미
[
// 이 구역안에 ListView 할당해주기.
SAssignNew(ListView, SListView<FWeaponRowDataPtr>)
.HeaderRow // 머리말
(
SNew(SHeaderRow)
+ SHeaderRow::Column("Number") // 검색할때 찾을 이름
.DefaultLabel(FText::FromString("")) // 보여질 이름
.ManualWidth(40) // 칸에 넓이 40 사용
+ SHeaderRow::Column("Name") // 검색할때 찾을 이름
.DefaultLabel(FText::FromString("Name")) // 보여질 이름
)
.ListItemsSource(&RowDatas) // 어떠한 데이터를 출력할 건지, (포인터이므로)&로 줘야한다
.OnGenerateRow(this, &SWeaponLeftArea::OnGenerateRow) // 한줄 한줄 모양을 정해주기, Row배열이 5개라면 5번 콜이된다.
.OnSelectionChanged(this, &SWeaponLeftArea::OnSelectionChanged)
]
+ SVerticalBox::Slot()
// 크기만큼 높이가 만들어진다. 나머지 영역은 FillHeight가 잡아준다.
// 사용할때 Auto로 특정 영역을 잡아주는 식으로 사용한다.
.AutoHeight()
.VAlign(VAlign_Center) // 수직 정렬
.HAlign(HAlign_Right) // 수평 정렬
.Padding(FMargin(8, 2)) // 마진 영역도 준다, 위아래가 8, 좌우 여백이 2가된다.
[
SNew(STextBlock)
// Text안에 내용이 바뀌면 자동으로 갱신해주는 신기한 일이 일어난다.
// 아래 OnGetAssetCount 함수를 콜한게 아닌, 이벤트로 연결했을 뿐인데
// 원래 가지고 있던 값에서 호출한 내용이 다르다면 자동으로 바꿔서 출력해주는 특징을 가지고있다.
.Text(this, &SWeaponLeftArea::OnGetAssetCount)
]
];
/*RowDatas.Add(FWeaponRowData::Make(1, "kkk", nullptr));
RowDatas.Add(FWeaponRowData::Make(2, "aaa", nullptr));
RowDatas.Add(FWeaponRowData::Make(3, "qqq", nullptr));*/
ReadDataAssetList();
}
void SWeaponLeftArea::SelectDataPtr(UCWeaponAsset* InAsset)
{
// 하나라도 없으면 x
if (HasRowDataPtr() == false)
return;
// 에셋 같은게 있는지 검색 후 선택.
for (FWeaponRowDataPtr ptr : RowDatas)
{
// 들어온게 같다면, ListView한테 알려주기.
if (ptr->Asset == InAsset)
{
ListView->SetSelection(ptr);
return;
}
}
}
TSharedRef<ITableRow> SWeaponLeftArea::OnGenerateRow(FWeaponRowDataPtr InRow, const TSharedRef<STableViewBase>& InTable)
{
// 위에 RowDatas가 만일 5개라면, 5번 return 하면서 콜이 5번 일어난다.
return SNew(SWeaponTableRow, InTable)
.RowData(InRow);
}
void SWeaponLeftArea::ReadDataAssetList()
{
// 콜 될때마다 비워주기
RowDatas.Empty();
TArray<UObject*> objects;
// 데이터를 실제로 찾아올 경로
// 이 안에서 모든 Asset를 다 찾아서 objects에 return 해줄 것이다
EngineUtils::FindOrLoadAssetsByPath("/Game/Weapons/", objects, EngineUtils::ATL_Regular);
// 안에 있는 데이터를 for문으로 확인
int32 index = 0;
for (UObject* obj : objects)
{
UCWeaponAsset* asset = Cast<UCWeaponAsset>(obj);
// nullptr이면 Pass 시킨다.
if (asset == nullptr) continue;
FString name = asset->GetName();
if (SearchText.IsEmpty() == false) // 검색 문자열이 비워져있지 않다면, 찾을 Asset에 이름에 SearchText가 포함되어있는지 확인
{
// 검색된 이름이 없다면, 보여주지 않기
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 다시 그려주기, 다른곳에서도 사용할 것이라서 재갱신
ListView->RequestListRefresh();
}
FText SWeaponLeftArea::OnGetAssetCount() const
{
// 실제 Data 갯수 출력
FString str = FString::Printf(L"%d 에셋", RowDatas.Num());
return FText::FromString(str);
}
void SWeaponLeftArea::OnTextChanged(const FText& InText)
{
// 글자가 바뀔때마다 콜 된다.
if (SearchText.CompareToCaseIgnored(InText) == 0) // 기존에 있는 문자와 같으면 return
return;
SearchText = InText;
ReadDataAssetList(); // 재검색 하도록,
}
void SWeaponLeftArea::OnTextCommitted(const FText& InText, ETextCommit::Type InType)
{
OnTextChanged(InText);
}
void SWeaponLeftArea::OnSelectionChanged(FWeaponRowDataPtr InDataPtr, ESelectInfo::Type InType)
{
// 선택된 데이터가 들어온다. 빈 구역을 눌러도 Null이 나오게 되는걸 방지하기 위해 조건 처리
if (InDataPtr.IsValid() == false) // 선택된게 없으면 끝
return;
// 가지고 있는 델리게이트 연결
OnListViewSelectedItem.ExecuteIfBound(InDataPtr);
}
1. WeaponLeftArea
https://www.youtube.com/watch?v=rwcvrpUkAzM
'언리얼' 카테고리의 다른 글
UE4 Weapon Plugin (6) (0) | 2023.06.09 |
---|---|
UE4 Weapon Plugin (5) (2) | 2023.06.07 |
UE4 Weapon Plugin (3) (0) | 2023.05.31 |
UE4 Weapon Plugin (2) (0) | 2023.05.30 |
UE4 Weapon Plugin (0) | 2023.05.26 |