앞에서부터 돌면서 앞 숫자가 뒷 숫자보다 작고, 버리기 횟수가 목표보다 적다면 해당 숫자를 버리도록 구현했습니다.
#include
#include
using namespace std;
string solution(string number, int k) {
string answer = "";
int removeCnt = 0;
for(int i = 0; i < number.length(); i++)
{
if(removeCnt < k)
{
if(i != number.length() - 1 && number[i + 1] > number[i])
{
removeCnt++;
continue;
}
}
answer += number[i];
}
return answer;
}
이 코드에는 두 가지 문제가 있었습니다.
입력값:
"4177252841", k = 4
기댓값:"775841"
결과값:"4775841"
#include
#include
#include
using namespace std;
string solution(string number, int k) {
string answer = "";
int removeCnt = 0;
stack numSt;
for(int i = 0; i < number.length(); i++)
{
while(removeCnt < k)
{
if (!numSt.empty() && numSt.top() < number[i])
{
removeCnt++;
numSt.pop();
}
else
{
break;
}
}
numSt.push(number[i]);
}
vector numbers(numSt.size());
for(int i = numbers.size() - 1; i >= 0; i--)
{
numbers[i] = numSt.top();
numSt.pop();
}
while(removeCnt != k)
{
int minIdx = 0;
char minChar = numbers[0];
for(int i = 1; i < numbers.size(); i++)
{
if(numbers[i] < minChar)
{
minIdx = i;
minChar = numbers[i];
}
}
numbers.erase(numbers.begin() + minIdx);
removeCnt++;
}
for(char ch : numbers)
{
answer += ch;
}
return answer;
}
핵심 아이디어
while로 반복합니다. 이를 통해 연속된 숫자들도 한 번에 처리할 수 있습니다.removeCnt)가 남아 있다면, 남은 배열에서 가장 작은 수를 찾아 추가로 버힙니다. 이렇게 하면 자릿수 문제도 해결됩니다.MyNBCLog 플러그인 안에 ToDoList 기능을 추가합니다.
.uasset으로 저장기존 MySpartaLog 모듈은 Type이 Runtime이라 게임 실행 중에도 로드됩니다. 에디터 전용 기능(Slate UI, 에디터 메뉴 등)을 여기에 넣으면 게임 패키징 시 빌드가 깨집니다. 때문에 아래처럼 분리해야 합니다.
MyNBCLog 플러그인
├── MySpartaLog (Type: Runtime) → UAsset 데이터, 런타임 UI 위젯
└── MySpartaLogEditor (Type: Editor) → 에디터 패널, 메뉴 등록
UToDoListAsset이라는 커스텀 에셋 클래스를 만들고, 에디터 콘텐츠 브라우저에서 생성할 수 있게 합니다.
ToDoListAsset.h
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "ToDoListAsset.generated.h"
USTRUCT(BlueprintType)
struct FToDoItem
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="ToDo")
FString Text;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="ToDo")
bool bCompleted = false;
};
UCLASS(BlueprintType)
class MYSPARTALOG_API UToDoListAsset : public UDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="ToDo")
TArray Items;
};
ToDoListAsset.cpp
#include "ToDoListAsset.h"
UDataAsset을 상속받기 때문에 별도 팩토리 없이도 콘텐츠 브라우저에서 우클릭 → Miscellaneous → Data Asset으로 바로 생성할 수 있게 됩니다.
Plugins/MyNBCLog/
└── Source/
└── MySpartaLogEditor/
├── MySpartaLogEditor.Build.cs
├── Private/
│ └── MySpartaLogEditor.cpp
└── Public/
└── MySpartaLogEditor.h
MySpartaLogEditor.Build.cs
using UnrealBuildTool;
public class MySpartaLogEditor : ModuleRules
{
public MySpartaLogEditor(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.Add(ModuleDirectory);
PublicDependencyModuleNames.AddRange(new string[]
{
"Core",
"CoreUObject",
"Engine",
"UnrealEd", // 에디터 기능 전반
"Slate", // Slate UI 위젯
"EditorStyle", // 에디터 스타일/폰트/색상
"ToolMenus", // 에디터 메뉴 등록 (핵심)
"MySpartaLog",
});
}
}
Public/MySpartaLogEditor.h
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleInterface.h"
class FMySpartaLogEditorModule : public IModuleInterface
{
public:
virtual void StartupModule() override;
virtual void ShutdownModule() override;
private:
void RegisterMenus();
void OpenToDoListTab();
TSharedRef OnSpawnToDoTab(const FSpawnTabArgs& Args);
};
Private/MySpartaLogEditor.cpp
#include "MySpartaLogEditor.h"
#include "Modules/ModuleManager.h"
#include "ToolMenus.h"
#include "Framework/Docking/TabManager.h"
#include "Widgets/Docking/SDockTab.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Views/SListView.h"
#include "ToDoListAsset.h"
static const FName ToDoTabName("ToDoListTab");
void FMySpartaLogEditorModule::StartupModule()
{
FGlobalTabmanager::Get()->RegisterNomadTabSpawner(
ToDoTabName,
FOnSpawnTab::CreateRaw(this, &FMySpartaLogEditorModule::OnSpawnToDoTab)
)
.SetDisplayName(FText::FromString("ToDo List"))
.SetMenuType(ETabSpawnerMenuType::Hidden);
UToolMenus::RegisterStartupCallback(
FSimpleMulticastDelegate::FDelegate::CreateRaw(
this, &FMySpartaLogEditorModule::RegisterMenus
)
);
}
void FMySpartaLogEditorModule::ShutdownModule()
{
FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(ToDoTabName);
UToolMenus::UnRegisterStartupCallback(this);
UToolMenus::UnregisterOwner(this);
}
void FMySpartaLogEditorModule::RegisterMenus()
{
FToolMenuOwnerScoped OwnerScoped(this);
UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Tools");
FToolMenuSection& Section = Menu->FindOrAddSection("MySpartaTools");
Section.Label = FText::FromString("Sparta Tools");
Section.AddMenuEntry(
"OpenToDoList",
FText::FromString("Open ToDo List"),
FText::FromString("Opens the ToDo List panel"),
FSlateIcon(),
FUIAction(FExecuteAction::CreateRaw(
this, &FMySpartaLogEditorModule::OpenToDoListTab
))
);
}
void FMySpartaLogEditorModule::OpenToDoListTab()
{
FGlobalTabmanager::Get()->TryInvokeTab(ToDoTabName);
}
TSharedRef FMySpartaLogEditorModule::OnSpawnToDoTab(const FSpawnTabArgs& Args)
{
return SNew(SDockTab)
.TabRole(ETabRole::NomadTab)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(10.f)
[
SNew(STextBlock)
.Text(FText::FromString("My ToDo List"))
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(10.f)
[
SNew(SButton)
.Text(FText::FromString("Add Item (WIP)"))
]
];
}
IMPLEMENT_MODULE(FMySpartaLogEditorModule, MySpartaLogEditor);
1. 탭 ID 등록
static const FName ToDoTabName("ToDoListTab");
탭은 언리얼 에디터 안에서 이름으로 구분됩니다. FName은 언리얼의 가볍고 빠른 문자열 타입으로, 탭 ID처럼 식별자 용도로 많이 사용됩니다. static으로 선언한 이유는 이 파일 안에서만 쓰는 상수이기 때문입니다.
2. StartupModule — 탭 스포너 등록
FGlobalTabManager는 언리얼 에디터의 모든 탭을 관리하는 매니저입니다. 위 코드는 "ToDoListTab이라는 ID의 탭이 열릴 때, OnSpawnToDoTab 함수를 호출해서 내용을 만들어라" 는 의미입니다.
CreateRaw: 일반 C++ 객체의 멤버 함수를 델리게이트에 바인딩할 때 사용합니다. IModuleInterface는 UObject가 아니기 때문에 CreateRaw를 써야 합니다. UObject였다면 CreateUObject를 사용해야 합니다.3. StartupModule — 메뉴 등록 콜백
StartupModule에서 바로 등록하지 않고 콜백으로 예약하는 이유는, StartupModule이 호출되는 시점에 에디터 메뉴 시스템(ToolMenus)이 아직 준비되지 않았을 수 있기 때문입니다. "에디터가 완전히 준비되면 그때 RegisterMenus를 호출해줘" 라고 예약해두는 겁니다.
4. ShutdownModule — 정리
FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(ToDoTabName);
UToolMenus::UnRegisterStartupCallback(this);
UToolMenus::UnregisterOwner(this);
플러그인이 언로드될 때 등록했던 것들을 전부 되돌리는 작업입니다. 이 작업을 해주지 않으면 플러그인을 껐다 키거나 에디터를 종료할 때 크래시가 날 수 있습니다. 등록/해제는 항상 쌍으로 작성하는 습관이 중요합니다.
5. RegisterMenus — 메뉴 항목 추가
FToolMenuOwnerScoped OwnerScoped(this) : 이 모듈이 추가하는 메뉴 항목들의 소유권을 this로 설정합니다. ShutdownModule에서 UnregisterOwner(this)를 호출하면 한 번에 전부 제거됩니다.ExtendMenu("LevelEditor.MainMenu.Tools") : 에디터 상단 Tools 메뉴를 이름으로 가져옵니다.FindOrAddSection : 메뉴 안에 섹션(구분선으로 나뉜 그룹)을 추가하거나 기존 것을 가져옵니다.6. OnSpawnToDoTab — Slate UI 구조
SDockTab ← 탭 창 자체
└─ SVerticalBox ← 세로로 쌓는 레이아웃
├─ STextBlock ← 텍스트 표시
└─ SButton ← 버튼
SNew(위젯타입)으로 위젯을 생성하고, [ ] 안에 자식 위젯을 넣는 구조입니다. + SVerticalBox::Slot()은 새 슬롯(칸)을 추가해 세로로 쌓습니다.
StartupModule 호출
│
├─ 탭 스포너 등록 (탭이 열릴 때 쓸 함수 예약)
│
└─ 메뉴 콜백 등록 (에디터 준비되면 RegisterMenus 호출 예약)
│
└─ RegisterMenus 실행
│
└─ Tools 메뉴에 "Open ToDo List" 항목 추가
│
└─ 클릭 시 OpenToDoListTab 호출
│
└─ OnSpawnToDoTab으로 탭 UI 생성
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "My NBC Log Plugin",
"Description": "과제 10 교안용 로그 플러그인",
"Category": "Sparta",
"CreatedBy": "Eunil",
"CanContainContent": true,
"Modules": [
{
"Name": "MySpartaLog",
"Type": "Runtime",
"LoadingPhase": "Default"
},
{
"Name": "MySpartaLogEditor",
"Type": "Editor",
"LoadingPhase": "PostEngineInit"
}
]
}
다음과 같은 패턴의 링크 오류가 발생했습니다.
LNK2019: 확인할 수 없는 외부 기호
"FCurveSequence", "SVerticalBox", "SBoxPanel", "STextBlock",
"SButton", "FSlateIcon", "EVisibility" ...
컴파일 오류 vs 링크 오류의 차이
| 오류 종류 | 원인 | 해결 위치 |
|---|---|---|
| 컴파일 오류 | 헤더를 못 찾음 | #include 문제 |
| 링크 오류 | 헤더는 찾았지만 실제 구현(.dll/.lib)을 못 찾음 | Build.cs 의존성 문제 |
분석된 원인
UE 5.4 이후 Slate 위젯들의 구현체가 여러 모듈에 분산되었고, SDockTab, FGlobalTabmanager 등 에디터 탭 관련 심볼들은 EditorFramework 모듈에 별도로 존재합니다.
Slate → 위젯 선언부 (헤더/인터페이스)
SlateCore → Slate 핵심 타입
AppFramework → 위젯 구현체 상당수
ApplicationCore → EVisibility, FSlateIcon 같은 기반 타입
EditorFramework → SDockTab, TabManager 등 에디터 전용 탭 시스템
시도한 해결 방법
Build.cs에 누락된 모듈들을 추가했습니다.
PublicDependencyModuleNames.AddRange(new string[]
{
"Core", "CoreUObject", "Engine",
"UnrealEd", "Slate", "SlateCore",
"EditorStyle", "ToolMenus", "MySpartaLog",
"EditorFramework", // SDockTab, FGlobalTabmanager
"AppFramework", // SWidget 구현체 일부
"ApplicationCore", // EVisibility, FSlateIcon 등
});
언리얼 엔진은 모듈이 매우 잘게 쪼개져 있고, "필요한 것만 링크" 하도록 설계되어 있습니다. 때문에 사용하는 심볼이 어느 모듈에 속하는지 파악하고 Build.cs에 명시해야 합니다.
게임 플레이 중 UMG 위젯으로 ToDoList를 화면에 표시한다. 내일 이어서 진행할 예정입니다.

