TIL
07.13 TIL
2026. 7. 13. 21:11

일정표

시간 할 일 비고
08:00~10:00 코드카타, 개인프로젝트  
10:00~13:00 개인프로젝트, 팀프로젝트  
14:00~18:00 2시 스크럼, 팀프로젝트  
19:00~21:00 저녁 스크럼, TIL, 개인프로젝트  
21:00~22:00 운동  
22:00~23:30 개인 프로젝트  

*오늘의 코드카타*

 

문제. 덧칠하기

더보기

2016년 1월 1일은 금요일입니다. 2016년 a월 b일은 무슨 요일일까요? 두 수 a ,b를 입력받아 2016년 a월 b일이 무슨 요일인지 리턴하는 함수, solution을 완성하세요. 요일의 이름은 일요일부터 토요일까지 각각 SUN,MON,TUE,WED,THU,FRI,SAT

입니다. 예를 들어 a=5, b=24라면 5월 24일은 화요일이므로 문자열 "TUE"를 반환하세요

2016년은 윤년입니다.

#include <string>
#include <vector>
#include <numeric>

using namespace std;

string solution(int a, int b) {

    int months[] = {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

    string days[] = {"THU", "FRI", "SAT", "SUN", "MON", "TUE", "WED"};
    
    int total_days = 0;

    for (int i = 1; i < a; ++i) {
        total_days += months[i];
    }

    total_days += b;

    return days[total_days % 7];
}

이미 풀었던 문제. 먼저 각 월이 며칠까지 있는지 배열에 저장한 후에, 총 누적일수를 계산하고, 요일을 매핑하여 누적 일수를 7로 나눈 나머지를 인덱스로 활용해 요일을 찾아낸다.


*팀 프로젝트*

1. 작업한 내용들

 

[1. 조준점/크로스헤어]

 

1. 플레이어의 눈 역할을 하는 UParcelInteractionComponent에서 조준 중인 대상(CurrentFocusedActor)이 변경될 때 UI에 신호를 보낼 다이내믹 멀티캐스트 델리게이트를 선언하고 브로드캐스트.

더보기
#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "ParcelHUDWidget.generated.h"

class AParcelGameState;
class AParcelPlayerState;

/**
 *  UParcelWidget
 *  인게임 HUD 요소의 이벤트 바인딩하고 관리하는 베이스 위젯
 *  담당자 : JYW
 */
UCLASS()
class PARCEL_KNIGHT_API UParcelHUDWidget : public UUserWidget
{
	GENERATED_BODY()
	
protected:
	// 위젯의 BeginPlay 함수
	virtual void NativeConstruct() override;
	
	// 로컬 플레이어와 데이터를 바인딩
	void TryBindUIEvents();
	
	// WBP에서 사용할 수 있도록 열어두는 이벤트 (델리게이트 -> 브로드캐스트. K2는 Kismet2 약자입니다)
	UFUNCTION(BlueprintImplementableEvent, Category = "UI")
	void K2_OnHPChanged(float CurrentHP, float MaxHP);
	
	UFUNCTION(BlueprintImplementableEvent, Category = "UI")
	void K2_OnTeamScoreChanged(const FText& DisplayText);

	UFUNCTION(BlueprintImplementableEvent, Category = "UI")
	void K2_OnPersonalScoreChanged(const FText& DisplayText);
	
	UFUNCTION(BlueprintImplementableEvent, Category = "UI")
	void K2_OnRemainingTimeChanged(const FText& DisplayText, float RawRemainingTime);
	
	UFUNCTION(BlueprintImplementableEvent, Category = "UI")
	void K2_OnCrosshairStateChanged(bool bCanInteract, const FText& InteractionPrompt);
	
private:
	
	UFUNCTION()
	void HandleOnTeamScoreChanged(int32 NewTeamScore);

	UFUNCTION()
	void HandleOnPersonalScoreChanged(int32 NewPersonalScore);
	
	// [Timestamp] 핸들러(종료 시각)
	UFUNCTION()
	void HandleOnExpirationTimeChanged(float NewExpirationTime);
	
	// [Interaction] 조준 대상이 바뀔 때 백엔드 신호를 받을 핸들러
	UFUNCTION()
	void HandleOnInteractionFocusChanged(AActor* NewFocusedActor);
	
	// [Timestamp] (남은 시간)
	void UpdateLocalTimer();
	
	// 안전한 접근을 위해 캐싱
	UPROPERTY()
	TWeakObjectPtr<AParcelGameState> CachedGameState;
	
	UPROPERTY()
	TWeakObjectPtr<AParcelPlayerState> CachedPlayerState;
	
	// 안전장치용 타이머 핸들
	FTimerHandle RetryBindTimerHandle;
	
	// [Timestamp] UI 자체 로컬 타이머 핸들 및 종료 시간 저장 변수
	FTimerHandle UILocalTimerHandle;
	float CachedExpirationTime = 0.0f;
};
#include "UI/ParcelHUDWidget.h"
#include "ParcelLog.h"
#include "GameFramework/GameStateBase.h"
#include "Core/ParcelGameState.h"
#include "Core/ParcelPlayerState.h"
#include "Core/TeamScoreComponent.h"
#include "Core/PlayerStatComponent.h"
#include "Character/ParcelInteractionComponent.h" 
#include "GameFramework/Pawn.h"

DEFINE_LOG_CATEGORY(LogInGameHUD);

void UParcelHUDWidget::NativeConstruct()
{
	Super::NativeConstruct();
	TryBindUIEvents();
}

void UParcelHUDWidget::TryBindUIEvents()
{
	bool bInteractionBound = false;
	
	// GameState와 TeamScore 바인딩
	if (!CachedGameState.IsValid())
	{
		CachedGameState = Cast<AParcelGameState>(GetWorld()->GetGameState());
		if (CachedGameState.IsValid())
		{
			// Todo : 디커플링을 위해서 FindComponentByClass를 사용했습니다. 배포 버전을 만들 때 게터로 리팩토링이 필요합니다.
			UTeamScoreComponent* TeamScoreComp = CachedGameState->FindComponentByClass<UTeamScoreComponent>();
			
			if (TeamScoreComp)
			{
				// 이벤트 바인딩
				TeamScoreComp->OnTeamScoreChanged.AddDynamic(this, &UParcelHUDWidget::HandleOnTeamScoreChanged);
				HandleOnTeamScoreChanged(TeamScoreComp->GetTeamScore());

				// 라운드 만료 시간 강제 수신
				TeamScoreComp->OnRemainingTimeChanged.AddDynamic(this, &UParcelHUDWidget::HandleOnExpirationTimeChanged);
				HandleOnExpirationTimeChanged(TeamScoreComp->GetRemainingTime());
			}
		}
	}

	// 2. PlayerState 바인딩
	if (!CachedPlayerState.IsValid())
	{
		CachedPlayerState = Cast<AParcelPlayerState>(GetOwningPlayerState());

		if (CachedPlayerState.IsValid())
		{
			// Todo : 디커플링을 위해서 FindComponentByClass를 사용했습니다. 배포 버전을 만들 때 게터로 리팩토링이 필요합니다.
			UPlayerStatComponent* PlayerStatComp = CachedPlayerState->FindComponentByClass<UPlayerStatComponent>();

			// [이벤트 바인딩]
			PlayerStatComp->OnPersonalScoreChanged.AddDynamic(this, &UParcelHUDWidget::HandleOnPersonalScoreChanged);
			
			// 바인딩 성공시 현재 개인 점수로 UI 텍스트 초기화
			HandleOnPersonalScoreChanged(PlayerStatComp->GetPersonalScore());

			GetWorld()->GetTimerManager().ClearTimer(RetryBindTimerHandle);
		}
		else
		{
			if (!RetryBindTimerHandle.IsValid())
			{
				GetWorld()->GetTimerManager().SetTimer(RetryBindTimerHandle, this, &UParcelHUDWidget::TryBindUIEvents, 0.1f, true);
			}
		}
	}
	
	// 3. 로컬 캐릭터 InteractionComponent 동적 바인딩
	if (APawn* OwningPawn = GetOwningPlayerPawn())
	{
		if (UParcelInteractionComponent* InteractComp = OwningPawn->FindComponentByClass<UParcelInteractionComponent>())
		{
			// 중복 바인딩 방지 추가
			InteractComp->OnFocusChanged.RemoveDynamic(this, &UParcelHUDWidget::HandleOnInteractionFocusChanged);
			InteractComp->OnFocusChanged.AddDynamic(this, &UParcelHUDWidget::HandleOnInteractionFocusChanged);
            
			// UI가 켜지거나 늦게 연결된 순간의 현재 조준 상태 반영
			HandleOnInteractionFocusChanged(InteractComp->GetCurrentFocusedActor());
			bInteractionBound = true;
		}
	}
	
	// 4. 멀티플레이 안전장치
	if (CachedGameState.IsValid() && CachedPlayerState.IsValid() && bInteractionBound)
	{
		GetWorld()->GetTimerManager().ClearTimer(RetryBindTimerHandle);
		INGAMEHUD_LOG(Log, TEXT("[UI] 모든 요소(GameState, PlayerState, Interaction)가 안전하게 바인딩되어 재시도 타이머를 해제."));
	}
	else
	{
		if (!RetryBindTimerHandle.IsValid() && GetWorld())
		{
			GetWorld()->GetTimerManager().SetTimer(RetryBindTimerHandle, this, &UParcelHUDWidget::TryBindUIEvents, 0.1f, true);
			INGAMEHUD_LOG(Warning, TEXT("[UI] 일부 액터가 nullptr입니다. 복제 동기화를 기다리며 0.1초 후 재시도합니다"));
		}
	}
		
}

// 핸들러 함수

void UParcelHUDWidget::HandleOnTeamScoreChanged(int32 NewTeamScore)
{
	FText FormattedText = FText::Format(FText::FromString(TEXT("팀 점수 : {0}")), FText::AsNumber(NewTeamScore));
	K2_OnTeamScoreChanged(FormattedText);
}

void UParcelHUDWidget::HandleOnPersonalScoreChanged(int32 NewPersonalScore)
{
	FText FormattedText = FText::Format(FText::FromString(TEXT("개인 점수 : {0}")), FText::AsNumber(NewPersonalScore));
	K2_OnPersonalScoreChanged(FormattedText);
}

void UParcelHUDWidget::HandleOnInteractionFocusChanged(AActor* NewFocusedActor)
{
	if (NewFocusedActor)
	{
		FText PromptText = FText::FromString(TEXT("E 키를 눌러 상호작용"));
		
		K2_OnCrosshairStateChanged(true, PromptText);
	}
	else
	{
		K2_OnCrosshairStateChanged(false, FText::GetEmpty());
	}
}

void UParcelHUDWidget::HandleOnExpirationTimeChanged(float NewExpirationTime)
{
	if (GetWorld() && NewExpirationTime > 0.0f)
	{
		CachedExpirationTime = GetWorld()->GetTimeSeconds() + NewExpirationTime;

		GetWorld()->GetTimerManager().ClearTimer(UILocalTimerHandle);
		GetWorld()->GetTimerManager().SetTimer(UILocalTimerHandle, this, &UParcelHUDWidget::UpdateLocalTimer, 0.2f, true);
	}
}

void UParcelHUDWidget::UpdateLocalTimer()
{
	float CurrentWorldTime = GetWorld()->GetTimeSeconds();
	float RawRemainingTime = CachedExpirationTime - CurrentWorldTime;

	if (RawRemainingTime <= 0.0f)
	{
		GetWorld()->GetTimerManager().ClearTimer(UILocalTimerHandle);
		RawRemainingTime = 0.0f;
	}
	
	// Min Sec 쪼개기
	int32 TotalSeconds = FMath::FloorToInt(RawRemainingTime);
	int32 Minutes = TotalSeconds / 60;
	int32 Seconds = TotalSeconds % 60;
	
	// 언리얼 내부 함수를 사용해서 2자리 수 맞추기
	FNumberFormattingOptions TwoDigitOptions;
	TwoDigitOptions.MinimumIntegralDigits = 2;
	
	FText MinText = FText::AsNumber(Minutes, &TwoDigitOptions);
	FText SecText = FText::AsNumber(Seconds, &TwoDigitOptions);
	
	// Format Text 문자열 조립
	FText FormattedTime = FText::Format(
		FText::FromString(TEXT("남은시간 {0} : {1}")), 
		MinText, 
		SecText
	);
	
	K2_OnRemainingTimeChanged(FormattedTime, RawRemainingTime);
}

 

2. WBP_InGameHUD 작업 : 조준점과 상호작용 물체에 커서를 갖다대면 텍스트가 화면에 출력되도록 상호작용 프롬프트 텍스트 배치

(+) 에디터 Graph 작업.

 

Event K2 On Crosshair State Changed 노드를 꺼내기.

Interaction Prompt 핀을 끌어서 Txt_InteractPrompt -> Set Text (Text) 노드에 직결.

bCanInteract (조건 불리언) 핀을 뽑아서 Branch(조건 분기) 노드에 연결.

 

[2. 플레이어 체력바 표시]

 

1. 현재 작성된 백엔드 체력 코드를 UI와 연동하기 위해, UHealthComponent에 ReplicatedUsing (RepNotify)과 멀티캐스트 델리게이트를 보완할 필요가 있었음. 따라서 HealthComponent에 다음 코드를 추가함.

 

체력이 바뀔 때 CurrentHP와 MaxHP를 던져줄 다이내믹 델리게이트를 선언하고, HP 변수에 ReplicatedUsing 매크로 속성을 주입.

서버가 값을 바꾸는 시점(AddHP, TakeDamage)과 클라이언트가 복제본을 받는 시점(OnRep_HP)에 모두 델리게이트를 쏴주도록 설계.

DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHPChangedSignature, float, CurrentHP, float, MaxHP);

	// [UI] 체력 변경 신호
	UPROPERTY(BlueprintAssignable, Category = "Health")
	FOnHPChangedSignature OnHPChanged;
    
    // 복제 - 클라이언트에서 체력 갱신 시 실행될 함수
	UFUNCTION()
	void OnRep_HP();
void UHealthComponent::AddHP(float Amount)
{
	if (GetOwner()->HasAuthority())
	{
		HP = FMath::Clamp((HP+Amount), 0.0f, MaxHP);
		OnHPChanged.Broadcast(HP, MaxHP);
	}
}

void UHealthComponent::TakeDamage(float Amount)
{
	if (GetOwner()->HasAuthority())
	{
		HP = FMath::Clamp((HP-Amount), 0.0f, MaxHP);
		OnHPChanged.Broadcast(HP, MaxHP);
		if (!bIsDead && HP <= 0.0f)
		OnDeath();
	}
}

void UHealthComponent::OnRep_HP()
{
	OnHPChanged.Broadcast(HP, MaxHP);
}

 

2. HUDWidget 코드 보완

UParcelHUDWidget에 연동하는 작업. 현재 AParcelCharacter 내부에 HealthComponent가 명시적 멤버 변수로 선언되어 있지 않으므로, FindComponentByClass<UHealthComponent>() 방식을 사용해 디커플링 바인딩을 수행.

더보기
#include "Core/HealthComponent.h"

void UParcelHUDWidget::TryBindUIEvents()
{
	bool bInteractionBound = false;
	bool bHealthBound = false;
	
	// GameState와 TeamScore 바인딩
	if (!CachedGameState.IsValid())
	{
		CachedGameState = Cast<AParcelGameState>(GetWorld()->GetGameState());
		if (CachedGameState.IsValid())
		{
			// Todo : 디커플링을 위해서 FindComponentByClass를 사용했습니다. 배포 버전을 만들 때 게터로 리팩토링이 필요합니다.
			UTeamScoreComponent* TeamScoreComp = CachedGameState->FindComponentByClass<UTeamScoreComponent>();
			
			if (TeamScoreComp)
			{
				// 이벤트 바인딩
				TeamScoreComp->OnTeamScoreChanged.AddDynamic(this, &UParcelHUDWidget::HandleOnTeamScoreChanged);
				HandleOnTeamScoreChanged(TeamScoreComp->GetTeamScore());

				// 라운드 만료 시간 강제 수신
				TeamScoreComp->OnRemainingTimeChanged.AddDynamic(this, &UParcelHUDWidget::HandleOnExpirationTimeChanged);
				HandleOnExpirationTimeChanged(TeamScoreComp->GetRemainingTime());
			}
		}
	}

	// 2. PlayerState 바인딩
	if (!CachedPlayerState.IsValid())
	{
		CachedPlayerState = Cast<AParcelPlayerState>(GetOwningPlayerState());

		if (CachedPlayerState.IsValid())
		{
			// Todo : 디커플링을 위해서 FindComponentByClass를 사용했습니다. 배포 버전을 만들 때 게터로 리팩토링이 필요합니다.
			UPlayerStatComponent* PlayerStatComp = CachedPlayerState->FindComponentByClass<UPlayerStatComponent>();

			// [이벤트 바인딩]
			PlayerStatComp->OnPersonalScoreChanged.AddDynamic(this, &UParcelHUDWidget::HandleOnPersonalScoreChanged);
			
			// 바인딩 성공시 현재 개인 점수로 UI 텍스트 초기화
			HandleOnPersonalScoreChanged(PlayerStatComp->GetPersonalScore());

			GetWorld()->GetTimerManager().ClearTimer(RetryBindTimerHandle);
		}
		else
		{
			if (!RetryBindTimerHandle.IsValid())
			{
				GetWorld()->GetTimerManager().SetTimer(RetryBindTimerHandle, this, &UParcelHUDWidget::TryBindUIEvents, 0.1f, true);
			}
		}
	}
	
	// 3. 로컬 캐릭터 InteractionComponent 동적 바인딩
	if (APawn* OwningPawn = GetOwningPlayerPawn())
	{
		// 상호작용
		if (UParcelInteractionComponent* InteractComp = OwningPawn->FindComponentByClass<UParcelInteractionComponent>())
		{
			// 중복 바인딩 방지 추가
			InteractComp->OnFocusChanged.RemoveDynamic(this, &UParcelHUDWidget::HandleOnInteractionFocusChanged);
			InteractComp->OnFocusChanged.AddDynamic(this, &UParcelHUDWidget::HandleOnInteractionFocusChanged);
            
			// 위젯이 처음 켜진 순간의 현재 조준 상태 반영
			HandleOnInteractionFocusChanged(InteractComp->GetCurrentFocusedActor());
			bInteractionBound = true;
		}
		
		// 체력
		if (UHealthComponent* HealthComp = OwningPawn->FindComponentByClass<UHealthComponent>())
		{
			HealthComp->OnHPChanged.RemoveDynamic(this, &UParcelHUDWidget::K2_OnHPChanged);
			HealthComp->OnHPChanged.AddDynamic(this, &UParcelHUDWidget::K2_OnHPChanged);

			// 위젯이 처음 켜진 순간의 체력 바 게이지 강제 동기화
			K2_OnHPChanged(HealthComp->GetHP(), HealthComp->GetMaxHP());
			bHealthBound = true;
		}
	}
	
	// 4. 멀티플레이 안전장치
	if (CachedGameState.IsValid() && CachedPlayerState.IsValid() && bInteractionBound && bHealthBound)
	{
		GetWorld()->GetTimerManager().ClearTimer(RetryBindTimerHandle);
		INGAMEHUD_LOG(Log, TEXT("[UI] 모든 요소(GameState, PlayerState, Interaction, Health) 바인딩 완료. 재시도 핸들 해제."));
	}
	else
	{
		if (!RetryBindTimerHandle.IsValid() && GetWorld())
		{
			GetWorld()->GetTimerManager().SetTimer(RetryBindTimerHandle, this, &UParcelHUDWidget::TryBindUIEvents, 0.1f, true);
			INGAMEHUD_LOG(Warning, TEXT("[UI] 멀티플레이어 네트워크 복제 대기 중... 0.1초 후 UI 바인딩 재시도합니다."));
		}
	}
		
}

3. WBP 체력 게이지 연동

 

[3. 현재 들고 있는 아이템 / 상자 정보 출력]

1. CharacterCarryComponent에 체인지 델리게이트를 선언하고, 서버/클라이언트 공용 병목 구역인 OnRep_CarriedBox()의 최하단에서 이를 발사하도록 보완.

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCarriedBoxChangedSignature, ADeliveryBox*, NewCarriedBox);

    UPROPERTY(BlueprintAssignable, Category = "Carry")
    FOnCarriedBoxChangedSignature OnCarriedBoxChanged;

 

2. OnRep_CarriedBox() 함수의 맨 마지막 구역에 "내 화면의 로컬 캐릭터일 때만 UI에 신호를 보낸다"는 방어 코드를 곁들여 브로드캐스트를 주입

void UCharacterCarryComponent::OnRep_CarriedBox()
{
(...)

	if (OwnerCharacter->IsLocallyControlled())
    {
        OnCarriedBoxChanged.Broadcast(CarriedBox);
    }
}

 

추가로 박스 2D 이미지를 UI 화면에 출력하기 위해 DeliveryTypes.h 에 다음 코드를 추가

	// [UI] 화면에 출력할 상자 2D 아이콘 이미지
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Delivery Box Data")
	TObjectPtr<UTexture2D> BoxIcon;

 

 

3. HUDWidget 보완

    UFUNCTION(BlueprintImplementableEvent, Category = "ParcelUI")
    void K2_OnCarriedBoxInfoChanged(bool bIsCarrying, const FText& BoxTypeName, const FText& DestinationText, UTexture2D* BoxIcon);
    
    UFUNCTION()
    void HandleOnCarriedBoxChanged(ADeliveryBox* NewCarriedBox);
    
    bool bCarryBound = false;
    
void UParcelHUDWidget::HandleOnCarriedBoxChanged(ADeliveryBox* NewCarriedBox)
{
	if (!NewCarriedBox)
	{
		K2_OnCarriedBoxInfoChanged(false, FText::GetEmpty(), FText::GetEmpty());
		return;
	}
	
	FBoxData CarriedBoxData = NewCarriedBox->GetBoxData();
	
	FText BoxNameText = FText::FromString(CarriedBoxData.DisplayName);
	
	FText FormattedName = FText::Format(
		FText::FromString(TEXT("{0} ({1}kg)")), 
		BoxNameText, 
		FText::AsNumber(CarriedBoxData.Weight)
	);
	
	FText DestinationText = FText::FromString(TEXT("목적지 : 미지정 구역"));
    
	if (CarriedBoxData.TargetZoneTag.IsValid())
	{
		FString ZoneString = CarriedBoxData.TargetZoneTag.ToString();
		
		ZoneString.ReplaceInline(TEXT("Delivery."), TEXT(""));
		ZoneString.ReplaceInline(TEXT("Zone."), TEXT(""));
		
		DestinationText = FText::Format(
			FText::FromString(TEXT("목적지 : {0} 구역")), 
			FText::FromString(ZoneString)
		);
	}
	K2_OnCarriedBoxInfoChanged(true, FormattedName, DestinationText);
}

 

4. WBP_InGameHUD 블루프린트 작업


*개인 프로젝트 및 개인 공부*

 

1. DGALocationTags 구축(네이티브 태그 선언부 소스 코드).
2. 라이라 스타일 폴더 구조로 리팩토링(같은 기능별 단일 폴더 안에 .h와 .cpp를 나란히 모아두어 생산성, 코드 가시성 개선)

더보기
Source/DGA/
│
├── System/ (코어 인프라 및 전역 통제 시스템)
│   ├── DgaLog.h
│   ├── DgaLocationTags.h / .cpp
│   ├── UDgaGameDeveloperSettings.h / .cpp
│   └── UDgaLevelManagerSubsystem.h / .cpp
│
├── Data/ (정적 데이터 명세서 및 데이터 자산)
│   ├── UDgaLevelDataAsset.h / .cpp
│   └── DgaQuestTypes.h (FDgaQuestRow 구조체 포함)
│
├── Interfaces/ (클래스 간 비종속 계약서 모음)
│   └── DgaInteractableInterface.h
│
├── GameModes/ (레벨별 게임 규칙 및 코어 제어 장치)
│   ├── ADgaGameModeBase.h / .cpp
│   └── ADgaPlayerController.h / .cpp
│
├── Character/ (월드에 배치되는 생명체 액터 본체 Shell)
│   ├── ADgaPlayerCharacter.h / .cpp
│   └── ADgaNPCBase.h / .cpp
│
├── Components/ (캐릭터들에게 탈부착되는 모듈형 부품 로직)
│   ├── UDgaHeroComponent.h / .cpp
│   ├── UDgaCameraComponent.h / .cpp
│   ├── UDgaTriggerComponent.h / .cpp
│   ├── UDgaNameplateComponent.h / .cpp
│   └── UDgaQuestComponent.h / .cpp
│
└── UI/ (화면 표현 및 연출 전담 위젯 라인)
    ├── UDgaDialogueWidget.h / .cpp
    ├── UDgaMainMenuWidget.h / .cpp
    └── UDgaQuestTrackerWidget.h / .cpp

3. Rider의 Refactor -> Move 기능으로 폴더 이전. (인클루드하고 있던 다른 모든 코드들의 주소를 라이더가 자동으로 고쳐줌)

4. 매번 헤더를 불러올 때마다 DGA/UI/..., DGA/System/... 이라고 모듈 이름을 적는 것은 실무적으로 매우 번거롭고 가독성도 떨어지므로, UBT의 장부를 직접 수정하는 명령을 내렸다.

PublicIncludePaths.Add(ModuleDirectory);

 

ModuleDirectory란? : 현재 Build.cs 파일이 위치한 가리키는 주소, 즉 Source/DGA/ 폴더 자체를 뜻하는 언리얼 빌드 내장 변수.

 

이 코드의 진짜 의미 : UBT에게 수동으로 세금 계산서를 끊듯 선언하는 것. Source/DGA/ 폴더 자체를 컴파일러의 '기본 검색 시작 주소(Include Root)'로 강제 등록하는 과정이다.

 

이 한 줄이 Build.cs에 장착되는 순간, 컴파일러의 검색 시작점이 다시 Source/DGA/로 전진하게 된다. 이제 "UI/DgaDialogueWidget.h""System/DgaLog.h" 처럼 깔끔하고 직관적인 경로로 코딩을 진행할 수 있게 된 것이다.

 

5. 하드코딩을 배제하는 전역 설정 시스템 UDgaGameDeveloperSettings 생성

 

 



*오늘의 총평*

팀프로젝트에서 게임에 대한 애정이나 일정관리 등이 얼마나 중요한지 다시 한번 깨닫는 순간이었다. 열심히 배우고, 필요한 것을 미리미리 파악한 다음에 다음 프로젝트에서는 팀장을 맡아서 좋은 결과물을 만들 수 있도록 도전해보기로 했다.

'TIL' 카테고리의 다른 글

07.15 TIL  (0) 2026.07.15
07.14 TIL  (0) 2026.07.14
07.10 TIL  (0) 2026.07.10
07.09 TIL  (0) 2026.07.09
07.08 TIL  (0) 2026.07.08