Unreal

C++을 사용한 언리얼엔진 인벤토리 시스템 Part.3

story98138 2026. 3. 12. 19:15

1. 오늘의 개발 개요

공간 인벤토리 시스템의 세 번째 파트입니다. 이전 파트에서 인벤토리 그리드 UI의 시각적 요소를 완성한 데 이어, 이번에는 게임 월드에 실제로 존재하는 아이템 클래스를 구현하고 캐릭터와의 충돌(Overlap) 감지 로직을 연결했습니다.

 

*식자는 프로젝트 이름을 설정할 때 오타를 내서 IntentorySystemCpp로 만들어버렸지만 나중에 알아버려서 이는 굳이 수정하지 않겠습니다.


2. 프로젝트 파일 구조

ItemBase.h / .cpp
아이템 부모 클래스. Mesh + SphereComponent 정의
IB_AK47 / Knife / Grenade
ItemBase를 상속한 개별 아이템 C++ 클래스
InventoryComponent.h / .cpp
Columns, Rows, TileSize 보관 컴포넌트
InventoryGridWidget.h / .cpp
그리드 선분 생성 및 NativePaint 드로우
InventoryDataStructs.h
FLines USTRUCT — XLines / YLines 배열
IntentorySystemCppCharacter
OnBeginOverlap, ToggleInventory 구현

 


 3. AItemBase — 아이템 부모 클래스

// ItemBase.h

UCLASS()
class INTENTORYSYSTEMCPP_API AItemBase : public AActor
{
    GENERATED_BODY()

public:
    AItemBase();

    UPROPERTY(EditAnywhere)
    UStaticMeshComponent* Mesh;

    UPROPERTY(EditAnywhere)
    USphereComponent* Sphere;

protected:
    virtual void BeginPlay() override;
};

 

// ItemBase.cpp

AItemBase::AItemBase()
{
    PrimaryActorTick.bCanEverTick = true;

    Mesh   = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
    Sphere = CreateDefaultSubobject<USphereComponent>(TEXT("Sphere"));

    Mesh->SetupAttachment(RootComponent);   // Mesh를 루트에 부착
    Sphere->SetupAttachment(Mesh);           // Sphere는 Mesh에 부착

    Mesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
    // Mesh 자체 충돌은 OFF → Sphere로만 Overlap 처리
}

 

 생성자 vs BeginPlay — 컴포넌트는 반드시 생성자에서
  • CreateDefaultSubobject는 생성자(Constructor)에서만 호출 가능. 엔진이 클래스를 로드할 때 초기화되기 때문.
  • BeginPlay는 게임이 실제로 시작될 때 호출되므로, 이 시점엔 서브오브젝트 생성 불가.
  • 컴포넌트 부착(SetupAttachment), 콜리전 설정 등 구조적 초기화는 모두 생성자에서 처리해야 함.

 4. 아이템 파생 클래스 (AK47 / Knife / Grenade)

각 아이템은 AItemBase를 상속한 독립적인 C++ 클래스로 정의합니다. 현재 단계에서는 클래스 계층 구조를 확립하는 것이 목적이므로, 고유 로직은 향후 추가될 예정입니다. 이 C++ 클래스를 기반으로 에디터에서 Blueprint 자식 클래스를 만들어 메시(Mesh)와 콜리전 크기를 아이템별로 설정합니다.

 

// IB_AK47.h
// IB_Knife.h / IB_Grenade.h (동일 구조)

UCLASS()
class INTENTORYSYSTEMCPP_API AIB_AK47 : public AItemBase
{
    GENERATED_BODY()
};

 

 C++ 클래스 → Blueprint 자식 클래스 워크플로
  • C++로 클래스 계층 구조(공통 로직)를 정의하고, Blueprint에서 에셋(메시, 사운드 등) 을 지정하는 패턴
  • Blueprint 폴더를 별도로 만들어 BP_AK47, BP_Knife, BP_Grenade를 각각 생성
  • 에디터에서 드래그&드롭으로 레벨에 배치 가능 → 빠른 레벨 디자인 이터레이션

5. OnBeginOverlap — 아이템 획득 감지

// IntentorySystemCppCharacter.h — UFUNCTION 선언

UFUNCTION()
void OnBeginOverlap(
    class UPrimitiveComponent* HitComp,
    class AActor*              OtherActor,
    class UPrimitiveComponent* OtherComp,
    int32                      OtherBodyIndex,
    bool                       bFromSweep,
    const FHitResult&          SweepResult
);

 

// IntentorySystemCppCharacter.cpp — 생성자에서 바인딩

// 생성자에서 CapsuleComponent에 Overlap 이벤트 연결
GetCapsuleComponent()->OnComponentBeginOverlap.AddDynamic(
    this,
    &AIntentorySystemCppCharacter::OnBeginOverlap
);

 

// IntentorySystemCppCharacter.cpp OnBeginOverlap 구현

void AIntentorySystemCppCharacter::OnBeginOverlap(
    UPrimitiveComponent* HitComp,
    AActor*              OtherActor,
    UPrimitiveComponent* OtherComp,
    int32                OtherBodyIndex,
    bool                 bFromSweep,
    const FHitResult&    SweepResult)
{
    AItemBase* Item = Cast<AItemBase>(OtherActor);

    if (Item)
    {
        GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red,
            TEXT("Item is picked up"));
        // 다음 파트: Item을 인벤토리 배열에 추가하는 로직 연결 예정
    }
}

 

 포인트 정리
  • UFUNCTION() 매크로가 없으면 AddDynamic 바인딩이 불가 → 반드시 선언 필요
  • Cast<AItemBase>(OtherActor)가 성공하면 파생 클래스(AK47, Knife, Grenade) 모두 감지 가능 → 상속의 힘
  • SphereComponent를 Mesh에 부착했으므로 감지 범위는 메시 주변의 구체 영역

6. 개발 중 직면한 문제 & 해결

 

① 물리 시뮬레이션 시 아이템이 바닥을 뚫고 떨어지는 문제

 

문제: Mesh에 Physics Simulation을 활성화했을 때, Mesh 자체 콜리전이 없어 다른 오브젝트를 통과하여 자유 낙하.

 

해결: Mesh->SetCollisionEnabled(ECollisionEnabled::NoCollision)로 Mesh 충돌을 OFF하고, 충돌 감지는 SphereComponent에만 위임. 물리 시뮬레이션은 Mesh가 아닌 SphereComponent에 적용하거나, 바닥 배치 아이템은 시뮬레이션을 비활성화.

 

② OnBeginOverlap이 호출되지 않는 문제

 

문제: UFUNCTION() 매크로 없이 AddDynamic으로 바인딩 시도 → 컴파일 에러 또는 무반응.

 

해결: 헤더에 UFUNCTION() 매크로를 반드시 선언. UE 리플렉션 시스템이 이 매크로를 통해 함수를 동적으로 등록하기 때문에 없으면 바인딩 자체가 불가능.

 


기술적 인사이트
  • UE5의 컴포넌트는 생성자에서만 CreateDefaultSubobject로 생성 — BeginPlay와 혼동 금지
  • Overlap 이벤트 함수는 반드시 UFUNCTION() 매크로와 함께 선언해야 AddDynamic 바인딩 가능
  • Cast<AItemBase> 한 줄로 모든 아이템 파생 클래스를 공통 처리 — C++ 다형성 + UE Cast의 조합
  • Mesh 충돌 OFF + SphereComponent만 활성화 = 물리 간섭 없이 수집 감지만 정확하게

 

핵심 교훈
"UE5에서 아이템 클래스를 설계할 때는 C++로 계층 구조와 공통 로직을 잡고, Blueprint에서 에셋과 수치를 지정하는 역할 분리가 핵심이다. 특히 충돌 감지는 Mesh와 SphereComponent의 역할을 명확히 나눠야 물리 시뮬레이션과 Overlap이 서로 간섭하지 않는다."