일정표
| 시간 | 할 일 | 비고 |
| 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 생성
*오늘의 총평*
팀프로젝트에서 게임에 대한 애정이나 일정관리 등이 얼마나 중요한지 다시 한번 깨닫는 순간이었다. 열심히 배우고, 필요한 것을 미리미리 파악한 다음에 다음 프로젝트에서는 팀장을 맡아서 좋은 결과물을 만들 수 있도록 도전해보기로 했다.