๐Ÿ“˜TIL - RTS UI Selection ํ™•์žฅ๊ณผ Command Pipeline ๊ตฌ์กฐ ์—ฐ๊ฒฐ

HappyCircleยท2026๋…„ 4์›” 6์ผ

Unreal ๊ฐœ๋ฐœ

๋ชฉ๋ก ๋ณด๊ธฐ
101/163

๋ฌธ์ œ

๊ธฐ์กด UI ๊ตฌ์กฐ์—์„œ Selection๊ณผ Command๋Š” ๊ฐ๊ฐ ์•ˆ์ •ํ™”๋˜์–ด ์žˆ์—ˆ์ง€๋งŒ
์‹ค์ œ RTS ํ๋ฆ„ ๊ธฐ์ค€์—์„œ๋Š” ๋‹ค์Œ ๋ฌธ์ œ๊ฐ€ ๋‚จ์•„ ์žˆ์—ˆ๋‹ค.


๊ตฌ์กฐ์  ํ•œ๊ณ„

๊ตฌ๋ถ„๋ฌธ์ œ
SelectionMulti Selection ํ‘œํ˜„ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ ์‹คํ–‰๊ณผ ์—ฐ๊ฒฐ๋˜์ง€ ์•Š์Œ
CommandUI ํ‘œ์‹œ๊นŒ์ง€๋งŒ ๊ฐ€๋Šฅ, ์‹ค์ œ ์‹คํ–‰ ๊ตฌ์กฐ ์—†์Œ
UI ํ๋ฆ„์ƒํƒœ ํ‘œํ˜„๊ณผ ํ–‰๋™ ์‹คํ–‰์ด ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์Œ
๊ตฌ์กฐ ์—ฐ๊ฒฐUI โ†’ Gameplay๋กœ ์ด์–ด์ง€๋Š” ๊ฒฝ๋กœ ๋ถ€์žฌ

๊ธฐ์กด ์ƒํƒœ ํ๋ฆ„

Selection
โ†’ ViewModel
โ†’ SelectionPanel ํ‘œ์‹œ

CommandPanel
โ†’ ViewModel ํ‘œ์‹œ๋งŒ ์ˆ˜ํ–‰

๋ถ„์„

ํ•ต์‹ฌ ๋ฌธ์ œ๋Š” ๋‹จ์ˆœ ๊ธฐ๋Šฅ์ด ์•„๋‹ˆ๋ผ
โ€œ์ƒํƒœ โ†’ ์‹คํ–‰ โ†’ ์ƒํƒœ ๊ฐฑ์‹ โ€ ํ๋ฆ„์ด ๋Š๊ฒจ ์žˆ๋‹ค๋Š” ์ ์ด์—ˆ๋‹ค.


1. Selection์€ ํ™•์žฅ๋์ง€๋งŒ ์‹คํ–‰์œผ๋กœ ์ด์–ด์ง€์ง€ ์•Š์Œ

์–ด์ œ ๋‹จ๊ณ„์—์„œ Selection์€ ๋‹ค์Œ ๊ตฌ์กฐ๋กœ ํ™•์žฅ๋˜์—ˆ๋‹ค.

UENUM()
enum class ESelectionViewMode : uint8
{
    None,
    Single,
    Multi
};

USTRUCT()
struct FSelectionViewModel
{
    GENERATED_BODY()

    ESelectionViewMode ViewMode;

    // Single
    FName SelectionId;

    // Multi
    int32 TotalSelectedCount;
    TArray<FSelectionSummaryItemViewModel> SummaryItems;
};

ํ•˜์ง€๋งŒ ์ด ์ƒํƒœ์—์„œ๋Š”:

Command ํด๋ฆญ โ†’ ์‹คํ–‰ ๊ฒฝ๋กœ ์—†์Œ

2. ์ž…๋ ฅ ์ฒ˜๋ฆฌ ๊ตฌ์กฐ ๋ถ€์žฌ

๊ธฐ์กด ๋ฐฉ์‹:

// โŒ ์ž˜๋ชป๋œ ๊ตฌ์กฐ (์˜ˆ์‹œ)
void UCommandWidget::OnClick()
{
    // ์ง์ ‘ ์‹คํ–‰ or ์•„๋ฌด ์ฒ˜๋ฆฌ ์—†์Œ
}

๋ฌธ์ œ:

  • ์ž…๋ ฅ ๊ฒฝ๋กœ ํ†ต์ผ ์•ˆ๋จ
  • ์„œ๋ฒ„ ์—ฐ๊ฒฐ ๋ถˆ๊ฐ€
  • ์œ ์ง€๋ณด์ˆ˜ ์–ด๋ ค์›€

ํ•ด๊ฒฐ


1. Command ์‹คํ–‰ ํ๋ฆ„ ์—ฐ๊ฒฐ


์ „์ฒด ํ๋ฆ„

Selection ์ƒํƒœ
โ†’ Command ์ƒ์„ฑ
โ†’ ์ž…๋ ฅ ๋ฐœ์ƒ
โ†’ ์‹คํ–‰
โ†’ ์ƒํƒœ ๋ณ€๊ฒฝ
โ†’ UI ๊ฐฑ์‹ 

์‹ค์ œ ์—ฐ๊ฒฐ ์ฝ”๋“œ


1-1. UI โ†’ PlayerController

// CommandSlotWidget.cpp
void URTSCommandSlotWidget::OnClicked()
{
    if (OwningPC)
    {
        OwningPC->ExecuteCommandById(CommandId);
    }
}

1-2. ๋‹จ์ผ ์ง„์ž…์ 

// PlayerController.cpp
bool ARTSUIPrototypePlayerController::ExecuteCommandById(FName CommandId)
{
    if (!CommandDispatcher || CommandId.IsNone())
    {
        return false;
    }

    return CommandDispatcher->DispatchCommandById(CommandId);
}

1-3. Dispatcher ๊ณ„์ธต

// CommandDispatcher.cpp
bool URTSCommandDispatcher::DispatchCommandById(FName CommandId)
{
    // 1. UI Context ์ฒ˜๋ฆฌ
    if (OwnerPC->HandleLocalOnlyCommand(CommandId))
    {
        return true;
    }

    // 2. ์„œ๋ฒ„ ์‹คํ–‰ ์š”์ฒญ ์ƒ์„ฑ
    FRTSCommandRequest Request = BuildCommandRequest(CommandId);

    return OwnerPC->IssueCommandRequest(Request);
}

1-4. CommandRequest ์ƒ์„ฑ

FRTSCommandRequest URTSCommandDispatcher::BuildCommandRequest(FName CommandId)
{
    FRTSCommandRequest Request;

    Request.CommandId = CommandId;
    Request.SelectedEntityIds = SelectionProvider->GetSelectedIds();
    Request.TargetLocation = OwnerPC->GetCachedMouseHitLocation();

    return Request;
}

### 1-5. Server RPC ์ง„์ž…

UFUNCTION(Server, Reliable)
void ARTSUIPrototypePlayerController::ServerIssueCommand(
    const FRTSCommandRequest& Request)
{
    OrderService->ExecuteCommandOnServer(Request);
}

1-6. ์‹ค์ œ ์‹คํ–‰ ๊ณ„์ธต

// OrderService.cpp
bool URTSOrderService::ExecuteCommandOnServer(
    const FRTSCommandRequest& Request)
{
    if (Request.CommandId == "Move")
    {
        ExecuteMove(Request);
    }
    else if (Request.CommandId == "Attack")
    {
        ExecuteAttack(Request);
    }

    return true;
}

2. Multi Selection + Command ์—ฐ๊ฒฐ


๊ต์ง‘ํ•ฉ ๊ณ„์‚ฐ

TArray<FName> URTSUIHUDCoordinator::BuildCommonCommandIds()
{
    TArray<TArray<FName>> AllCommands;

    for (const FName& EntityId : SelectedEntities)
    {
        AllCommands.Add(CommandTable->GetCommands(EntityId));
    }

    return Intersect(AllCommands);
}

๊ฒฐ๊ณผ ์˜ˆ์‹œ

Marine: Move, Attack, Stop
SCV:    Move, Attack, Stop, Build

โ†’ ๊ฒฐ๊ณผ
Move, Attack, Stop

3. Context ์‹œ์Šคํ…œ


Provider ๋‚ด๋ถ€

void USelectionProvider::PushContext(FName NewContext)
{
    ContextStack.Push(CurrentContext);
    CurrentContext = NewContext;
}

void USelectionProvider::PopContext()
{
    if (ContextStack.Num() > 0)
    {
        CurrentContext = ContextStack.Pop();
    }
}

Command ์ฒ˜๋ฆฌ

bool APlayerController::HandleLocalOnlyCommand(FName CommandId)
{
    if (CommandId == "BuildMenu")
    {
        SelectionProvider->PushContext("BuildMenu");
        return true;
    }

    if (CommandId == "Back")
    {
        SelectionProvider->PopContext();
        return true;
    }

    return false;
}

4. Provider โ†’ UI ๊ฐฑ์‹  ํ๋ฆ„


์ƒํƒœ ๋ณ€๊ฒฝ ํ›„ ํ๋ฆ„

// Provider ๋‚ด๋ถ€
void USelectionProvider::NotifySelectionChanged()
{
    OnSelectionChanged.Broadcast();
}

Coordinator

void URTSUIHUDCoordinator::Initialize(...)
{
    SelectionProvider->OnSelectionChanged.AddUObject(
        this,
        &URTSUIHUDCoordinator::RefreshAll
    );
}

UI ๊ฐฑ์‹ 

void URTSUIHUDCoordinator::RefreshAll()
{
    RefreshSelectionPanel();
    RefreshCommandPanel();
}

5. ์ „์ฒด ๊ตฌ์กฐ ์š”์•ฝ


๊ณ„์ธต ๊ตฌ์กฐ

UI (Widget)
โ†’ PlayerController
โ†’ CommandDispatcher
โ†’ Server RPC
โ†’ OrderService
โ†’ Provider
โ†’ Coordinator
โ†’ UI

์ฝ”๋“œ ๊ธฐ์ค€ ํ๋ฆ„

// 1. ํด๋ฆญ
Widget โ†’ ExecuteCommandById

// 2. ๋ถ„๊ธฐ
Dispatcher โ†’ Local or Server

// 3. ์‹คํ–‰
Server โ†’ OrderService

// 4. ์ƒํƒœ ๋ณ€๊ฒฝ
Provider

// 5. UI ๊ฐฑ์‹ 
Coordinator โ†’ Widget

๊ฒฐ๊ณผ


๊ตฌ์กฐ ๋ณ€ํ™”

ํ•ญ๋ชฉ๋ณ€๊ฒฝ
SelectionSingle โ†’ Multi ํ™•์žฅ
Commandํ‘œ์‹œ โ†’ ์‹คํ–‰ ๊ฐ€๋Šฅ
์ž…๋ ฅ๋‹จ์ผ ์ง„์ž…์ 
์ƒํƒœ ๊ด€๋ฆฌProvider ์ง‘์ค‘
์‹คํ–‰ ๊ตฌ์กฐDispatcher ๋ถ„๋ฆฌ

ํ•ต์‹ฌ ์„ฑ๊ณผ

  • Selection โ†’ Command โ†’ ์‹คํ–‰ ํ๋ฆ„ ์™„์„ฑ
  • UI์™€ Gameplay ์™„์ „ ๋ถ„๋ฆฌ
  • ๋ฉ€ํ‹ฐํ”Œ๋ ˆ์ด ๋Œ€์‘ ๊ฐ€๋Šฅ ๊ตฌ์กฐ ํ™•๋ณด

์ข…ํ•ฉ


ํ•ต์‹ฌ ํ๋ฆ„

Selection ํ™•์žฅ
โ†’ Command ์ƒ์„ฑ
โ†’ ExecuteCommandById
โ†’ Dispatcher
โ†’ Server
โ†’ ์ƒํƒœ ๋ณ€๊ฒฝ
โ†’ UI ๊ฐฑ์‹ 

์˜ค๋Š˜์˜ ์ •๋ฆฌ

  • Multi Selection ๊ตฌ์กฐ๋ฅผ ์‹ค์ œ Command ์‹คํ–‰๊นŒ์ง€ ์—ฐ๊ฒฐํ–ˆ๋‹ค
  • ์ž…๋ ฅ โ†’ ์‹คํ–‰ โ†’ ์ƒํƒœ ๊ฐฑ์‹  โ†’ UI ๋ฐ˜์˜ ํ๋ฆ„์ด ์™„์„ฑ๋๋‹ค
  • UI๋Š” ๋๊นŒ์ง€ ๋ฐ์ดํ„ฐ ์†Œ๋น„๋งŒ ํ•˜๊ณ , ์‹คํ–‰๊ณผ ์ƒํƒœ๋Š” ์™ธ๋ถ€ ๊ณ„์ธต์—์„œ ์ฒ˜๋ฆฌํ•˜๋„๋ก ์œ ์ง€ํ–ˆ๋‹ค
profile
๊ฐœ๋ฐœํ•ฉ์‹œ๋‹ค!

0๊ฐœ์˜ ๋Œ“๊ธ€