컨텐츠 브라우저 편집

컨텐츠 브라우저를 편집 해서 현재 보이는 파일, 폴더를 수정 합니다.
컨텐츠 브라우저를 활용해서 SOverlay를 작성하여 표기한다.
⚠️ 컨텐츠 브라우저는 매우 자주 호출되므로 최대한 부하가 없도록 설계해야 합니다.
구현 설계
네이밍으로 유추 해보니 아래와 같은 아이템으로 예상
- SAssetTileItem Engine\Source\Editor\ContentBrowser\Private\AssetViewWidgets.cpp
- SAssetView Engine\Source\Editor\ContentBrowser\Private\SAssetView.cpp
뭔가 기능이 비슷해 보이는데 코드를 보니 아래와 같다.
| 구분 | SAssetTileItem | SAssetView |
|---|---|---|
| 역할 | 개별 에셋 아이템 UI (타일 한 칸) | 에셋 아이템들의 전체 리스트/타일 뷰 관리 |
| 상위 개념 | 하위 아이템 위젯 | 전체 뷰 위젯 |
| 포함 관계 | SAssetView에 포함되어 여러 개 존재 | 여러 SAssetTileItem(또는 리스트 아이템)을 포함 |
| 담당 기능 | 아이콘, 이름, 상태 표시, 클릭 처리 | 정렬, 필터링, 선택, 스크롤 등 뷰 관리 |
| Slate 기반 | SCompoundWidget 등 |
SListView 또는 STileView 기반 |
생성 순서
- SAssetView 생성
- STileView(또는 SListView) 생성 & 데이터 바인딩
- Slate 뷰에서 화면에 보여질 아이템 개수만큼 OnGenerateWidget 호출 (SAssetView에서 바인딩된 함수)
- SAssetTileItem 생성 & 반환
- SAssetTileItem 초기화 및 UI 렌더링
그렇다면, 구현은 아래와 같은 방법으로 생각 가능하다
SAssetView::MakeTileViewWidget에서 SAssetTileItem을 생성한 뒤에 SOverlay로 감싸기SAssetTileItem::Construct에서 SOverlay로 감싼 뒤에 반환 하기.
1번을 한다고 가정하면
SAssetView::MakeTileViewWidget에서 SOverlay으로 감싼다.
구현 플로우
- SAssetTileItem을 생성한다 (기본 로직)
IsFolder()조건으로 각각 나누던 것을 하나의 변수로 통합- 로직 분기가 끝나는 시점에 Soverlay를 생성하고 SAssetTileItem을 감싼다.
TableRowWidget에 적용한뒤 return해준다
구현 1. 변수 통합
코드에서는 if (AssetItem->IsFolder()) 으로 분기를 쓰고 있어서 같은 코드를 두번 생성해야 하기 떄문에 로직을 통합 합니다.
아래 두 변수가 각각 선언되어 있으므로 하나로 통합
TSharedPtr< STableRow<TSharedPtr<FAssetViewItem>> > TableRowWidget;TSharedRef<SAssetTileItem> Item- ref는 nullptr이 될 수 없으므로
TSharedPtr으로 변경
- ref는 nullptr이 될 수 없으므로
구현 2. overray 생성
Item을 SOverlay로 감싸는 코드 작성
TSharedRef<SWidget> Overlay = SNew(SOverlay)
+ SOverlay::Slot()
[
// Item을 감싼다.
Item.ToSharedRef()
]
+ SOverlay::Slot()
.HAlign(HAlign_Right)
.VAlign(VAlign_Top)
.Padding(FMargin(2.0f)) // 위치 조정
[
// 조건에 따라 보일 이미지
SNew(SImage)
.Image(FEditorStyle::GetBrush("Icons.Cross")) // 커스텀 아이콘
];
이후 TableRowWidget에 Item을 Set하는 코드를 Overlay를 Set하도록 변경
TableRowWidget->SetContent(Overlay);
return TableRowWidget.ToSharedRef();
엔진 콘텐츠를 보면 모든 아이콘에 x자가 표기 되는것을 확인할 수 있음.

구현 3. 조건에 맞게 보이게 설정
여러가지 방법이 있음.
당장 생각나는건
- SOverlay로 감싸고, 조건을 검사한 뒤에 SOverlay의 Visibiliry제어
- 코드가 한곳에 위치하여 가독성이 좋아 보임.
- 조건을 검사한 뒤에 SOverlay를 감싸서 반환 하거나, 감싸지 않는 방법
- Slate갯수가 최적화 되는 장점이 있음
콘텐츠 브라우저에서 동작하기 때문에 성능에 직결되어 2로 구현
-
NeverCook 리스트를 얻어와 Asset이 들어가는지 파악 NeverCook은 DefaultGame.ini의 [/Script/UnrealEd.ProjectPackagingSettings] 섹션에 존재. ```cpp bool ContainNeverCook = false; TArray
CachedNeverCookDirs; const FString SectionName = TEXT("/Script/UnrealEd.ProjectPackagingSettings"); if (AssetItem.IsValid()) { const FString ItemPath = AssetItem->GetItem().GetVirtualPath().ToString(); // 예: "/Game/Assets/MyAsset" const UProjectPackagingSettings* PackagingSettings = GetDefault<UProjectPackagingSettings>(); const TArray<FDirectoryPath>& NeverCookDirs = PackagingSettings->DirectoriesToNeverCook; for (const FDirectoryPath& DirectoryPath : PackagingSettings->DirectoriesToNeverCook) { if (ItemPath.StartsWith(DirectoryPath.Path)) { ContainNevercook = true; break; } } } ``` -
해당 조건에 맞게 SOverlay 생성 제어
if (ContainNevercook) { TSharedRef<SWidget> Overlay = SNew(SOverlay) + SOverlay::Slot() [ Item.ToSharedRef() ] + SOverlay::Slot() .HAlign(HAlign_Right) .VAlign(VAlign_Top) .Padding(FMargin(2.0f)) // 위치 조정 [ // 조건에 따라 보일 이미지 SNew(SImage) .Image(FEditorStyle::GetBrush("Icons.Cross")) // 커스텀 아이콘 ]; TableRowWidget->SetContent(Overlay); } else { TableRowWidget->SetContent(Item.ToSharedRef()); } return TableRowWidget.ToSharedRef();
이렇게 하면 아래와 같이 보인다.

최적화
Nevercook을 캐싱하고 사용한다면 성능 최적화를 기대할 수 있다.
- static변수를 통한 캐싱 사용
bool ContainNevercook = false;
// TArray를 static변수로 추가, bIsInitialized도 추가
static TArray<FString> CachedNeverCookDirs;
static bool bIsInitialized = false;
const FString SectionName = TEXT("/Script/UnrealEd.ProjectPackagingSettings");
// 초기화를 한번 하면 UProjectPackagingSettings을 참조하지 않아 성능상 이점이 생길 것.
if (bIsInitialized == false)
{
const UProjectPackagingSettings* PackagingSettings = GetDefault<UProjectPackagingSettings>();
const TArray<FDirectoryPath>& NeverCookDirs = PackagingSettings->DirectoriesToNeverCook;
for (const FDirectoryPath& DirectoryPath : PackagingSettings->DirectoriesToNeverCook)
{
CachedNeverCookDirs.Add(DirectoryPath.Path);
}
bIsInitialized = true;
}
if (AssetItem.IsValid())
{
const FString ItemPath = AssetItem->GetItem().GetVirtualPath().ToString();
for (const FString& NeverCookDir : CachedNeverCookDirs)
{
if (ItemPath.StartsWith(NeverCookDir))
{
ContainNevercook = true;
break;
}
}
}
- 단. 런타임 도중 추가 하는건 어려울 것 같다.
- 런타임 도중에 추가하고 싶다는 니즈가 생긴다면 고민 해봐야 하겠지만 PackagingSettings을 GetMutableDefault으로 받아와서 수정하지 않을까 싶다.
번외
Visibility제어
TSharedRef<SWidget> Overlay = SNew(SOverlay)
+ SOverlay::Slot()
[
Item.ToSharedRef()
]
+ SOverlay::Slot()
.HAlign(HAlign_Right)
.VAlign(VAlign_Top)
.Padding(FMargin(2.0f)) // 위치 조정
[
// 조건에 따라 보일 이미지
SNew(SImage)
.Image(FEditorStyle::GetBrush("Icons.Cross")) // 커스텀 아이콘
.Visibility(ContainNevercook ? EVisibility::Visible : EVisibility::Collapsed)
];
TableRowWidget->SetContent(Overlay);
- SOverlay를 만들고 Visibility를 조절하면 된다.
Visibility_Lambda
람다식을 사용하여 Visibility를 제어 하는 방법
- 코드가 SOverlay생성 하는 곳에 있어서 (개인적으로)가독성이 높아 보인다.
TSharedRef<SWidget> Overlay = SNew(SOverlay)
+ SOverlay::Slot()
[
Item.ToSharedRef()
]
+ SOverlay::Slot()
.HAlign(HAlign_Right)
.VAlign(VAlign_Top)
.Padding(FMargin(2.0f)) // 위치 조정
[
// 조건에 따라 보일 이미지
SNew(SImage)
.Image(FEditorStyle::GetBrush("Icons.Cross")) // 커스텀 아이콘
.Visibility_Lambda([AssetItem]()
{
static TArray<FString> CachedNeverCookDirs;
static bool bIsInitialized = false;
if (!bIsInitialized)
{
const FString SectionName = TEXT("/Script/UnrealEd.ProjectPackagingSettings");
const UProjectPackagingSettings* PackagingSettings = GetDefault<UProjectPackagingSettings>();
const TArray<FDirectoryPath>& NeverCookDirs = PackagingSettings->DirectoriesToNeverCook;
for (const FDirectoryPath& DirectoryPath : PackagingSettings->DirectoriesToNeverCook)
{
CachedNeverCookDirs.Add(DirectoryPath.Path);
}
bIsInitialized = true;
}
if (AssetItem.IsValid())
{
const FString ItemPath = AssetItem->GetItem().GetVirtualPath().ToString(); // 예: "/Game/Assets/MyAsset"
for (const FString& NeverCookDir : CachedNeverCookDirs)
{
if (ItemPath.StartsWith(NeverCookDir))
{
return EVisibility::Visible;
}
}
}
return EVisibility::Collapsed;
})
];
TableRowWidget->SetContent(Overlay);