내일배움캠프_언리얼9기

내일배움캠프 56일차 TIL 2026/07/10

begin-play 2026. 7. 10. 23:04

TIL (Today I Learned) - 언리얼 C++ 코드 구조와 데이터 연결 방식 학습

1. 컴포넌트 간 의존성을 줄이는 연결 방식

오늘 배운 핵심은 하나의 클래스가 모든 기능을 처리하지 않고, 필요한 컴포넌트를 찾아 연결하는 방식이다.

예시:

DeckComp = GetOwner() ? GetOwner()->FindComponentByClass<UDeckComponent>() : nullptr;

사용된 방식

GetOwner()

  • 현재 컴포넌트가 붙어있는 액터를 가져온다.

FindComponentByClass<T>()

  • 같은 액터에 붙어있는 특정 컴포넌트를 검색한다.

즉:

PlayerCharacter
    |
    ├── DeckComponent
    |
    └── SkillSlotComponent

구조에서 SkillSlotComponent가 DeckComponent를 직접 생성하거나 관리하지 않고 필요할 때 찾아서 사용한다.

장점:

  • 컴포넌트 간 결합도를 낮출 수 있음
  • 재사용 가능성이 높아짐
  • 하나의 클래스가 너무 많은 책임을 가지는 것을 방지

2. TObjectPtr을 이용한 언리얼 객체 참조

헤더에서:

UPROPERTY(Transient)
TObjectPtr<UDeckComponent> DeckComp;

사용.

TObjectPtr 역할

언리얼 UObject를 안전하게 참조하기 위한 포인터 방식.

기존:

UDeckComponent* DeckComp;

대신:

TObjectPtr<UDeckComponent> DeckComp;

사용.

UPROPERTY()와 함께 사용하면:

  • 가비지 컬렉터 추적 가능
  • 에디터 직렬화 지원
  • 언리얼 객체 생명주기 관리 개선

3. DataTable Row 검색 방식

스킬 데이터를 찾을 때:

CardDataTable->FindRow<FCardDataTableRow>(
    SkillRowName,
    TEXT("SkillSlot")
);

사용.

구조:

DataTable

Row Name
   |
   ↓
FCardDataTableRow

FindRow()는 Row Name을 기준으로 구조체 데이터를 가져온다.

예:

DT_Card

Trueshot
 ├ CardName
 ├ Description
 └ SkillRowName

에서:

FindRow("Trueshot")

하면 해당 Row의 구조체 데이터를 반환한다.


4. const 포인터 반환 방식

사용된 코드:

const FCardDataTableRow* GetSkillDataInSlot(int32 SlotIndex) const;

그리고:

const FCardDataTableRow* SkillToUse =
    GetSkillDataInSlot(SlotIndex);

의미

데이터를 복사하지 않고 원본 데이터를 읽기 전용으로 참조한다.

기존 방식:

FCardDataTableRow Data = ...

이면 구조체 전체 복사 발생.

하지만:

const FCardDataTableRow*

는 주소만 전달.

장점:

  • 불필요한 복사 방지
  • 원본 데이터 변경 방지
  • 큰 구조체 처리 효율 증가

5. 델리게이트(Dynamic Delegate) 이벤트 연결 방식

사용 코드:

BoundPlayerState->OnAvailableSkillSlotsChanged.AddUniqueDynamic(
    this,
    &USkillSlotComponent::HandleAvailableSkillSlotsChanged
);

방식:

PlayerState
      |
      | 값 변경 이벤트 발생
      ↓
SkillSlotComponent 함수 호출

AddUniqueDynamic

의미:

  • 델리게이트에 함수 등록
  • 중복 등록 방지

연결된 함수:

UFUNCTION()
void HandleAvailableSkillSlotsChanged(
    int32 PreviousCount,
    int32 NewCount
);

6. 함수 포인터를 이용한 Timer Callback

사용 코드:

GetWorld()->GetTimerManager().SetTimer(
    SlotCooldownTimers[SlotIndex],
    [this, SlotIndex]()
    {
        this->OnSlotCooldownFinished(SlotIndex);
    },
    SkillCooldown,
    false
);

사용 방식:

람다 함수(Lambda)를 Timer에 전달.

구조:

시간 종료
    ↓
람다 실행
    ↓
OnSlotCooldownFinished 호출

7. TSubclassOf를 이용한 클래스 참조

스킬 데이터:

TSubclassOf<APlayerSkillBase> SkillClass;

투사체:

TSubclassOf<APlayerProjectileBase> ProjectileClass;

역할

특정 부모 클래스를 상속받은 클래스만 선택 가능하도록 제한한다.

예:

가능:

APlayerProjectileBase
        ↑
BP_SonicVoltProjectile

불가능:

ACharacter

같은 관계가 아닌 클래스.


8. SpawnActor를 이용한 런타임 객체 생성

사용 코드:

World->SpawnActor<APlayerProjectileBase>(
    ProjectileClass,
    SpawnLocation,
    FRotator::ZeroRotator,
    SpawnParams
);

동작:

Class 정보
    ↓
SpawnActor()
    ↓
실제 Actor 생성

즉 에디터에서 배치하지 않고 게임 실행 중 객체 생성.


9. Override를 이용한 확장 구조

부모:

virtual void ExecuteSkill(
    const FVector& PlayerLocation,
    const FVector& TargetLocation
);

자식:

virtual void ExecuteSkill(...) override;

구조:

APlayerSkillBase

        ↑

APlayerProjectileSkillBase

부모는 기본 인터페이스만 제공하고,
자식 클래스가 실제 동작을 구현한다.


오늘 사용한 주요 C++ 패턴 정리

방식사용 목적

FindComponentByClass 컴포넌트 검색
TObjectPtr UObject 안전 참조
FindRow DataTable 데이터 검색
const 포인터 읽기 전용 참조 전달
Delegate 이벤트 기반 호출
Lambda Timer Callback 처리
TSubclassOf 클래스 타입 제한
SpawnActor 런타임 객체 생성
Virtual / Override 상속 기반 확장

오늘 구현한 시스템의 핵심은 단순히 스킬을 실행하는 것이 아니라, DataTable → Component → Actor 생성으로 이어지는 언리얼 C++ 객체 연결 구조를 이해한 것이다.