충돌 이벤트로 획득되는 아이템 구현


1️⃣충돌 이벤트 기반 아이템 획득 방식

  • 엔진에서 아이템을 플레이어가 다가가기만 해보 자동으로 회득한다면, 보통 콜리전 이벤트를 사용
  • 충돌 영역을 설정해두면, 플레이어가 그 영역안에 들어오거나 나가면 자동으로 이벤트를 발생시켜 별도의 함수 없이 호출이 가능함
  • 충돌 영역(Collision Volume)
    • 아이템 액터 주변에 SaphereConponent나 BoxComponent 같은 충돌 컴포넌트를 붙여둠
    • 플레이어가 이 영역 안으로 들어오면 Overlap  이벤트가 발생하고, 이를 감지해 아이템 획득 로직을 실행할 수 있다
  • Overlap 이벤트 VS Hit 이벤트돌 이벤트로 획득되는 아이템 구현
    • Overlap 이벤트 : 물리적으로 부딪히는 것 없이 액터들이 서로 겹치기 시작했을 때 발생, 아이템 획득, 트리거 존 감지 같은 경우 주로 사용
    • Hit 이벤트 : 실제 물리 충돌이 일어날 때 발생
  • 아이템 획득
    • 충돌 영역 안에 플레이어가 들어오면(Overlap 되는 시점, 아이템이 즉시사라자거나, 점수를 올리거나, 체력을 회복하는 등 로직을 실행할 수 있음

2️⃣기본 아이템 클래스에 충돌 컴포넌트와 메시 컴포넌트 추가

  • BaseItem 클래스는 모든 아이템에 공통으로 필요한 기능을 담는 부모 클래스 이므로 다음과 같은 컴포넌트와 함수가 포함된다
    • SceneComponent* Scene : 액터의 루트 컴포넌트
    • USpahereComponent : 플레이어가 범위 안에 들어오면 Overlap 이벤트를 발생시킬 출돌 컴포넌트
    • UStaticMeshComponent : 아이템이 월드에 표시될 때, 시각적인 모습을 담당
UCLASS()
class SPARTAPROJECT_API ABaseItem : public AActor, public IItemInterface
{
    GENERATED_BODY()

public:
    ABaseItem();

protected:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
    FName ItemType;
    // 루트 컴포넌트 (씬)
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item|Component")
    USceneComponent* Scene;
    // 충돌 컴포넌트 (플레이어 진입 범위 감지용)
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item|Component")
    USphereComponent* Collision;
    // 아이템 시각 표현용 스태틱 메시
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item|Component")
    UStaticMeshComponent* StaticMesh;
	
    virtual void OnItemOverlap(
		    UPrimitiveComponent* OverlappedComp,
		    AActor* OtherActor,
		    UPrimitiveComponent* OtherComp,
		    int32 OtherBodyIndex,
		    bool bFromSweep,
		    const FHitResult& SweepResult) override;

    virtual void OnItemEndOverlap(
		    UPrimitiveComponent* OverlappedComp,
		    AActor* OtherActor,
		    UPrimitiveComponent* OtherComp,
		    int32 OtherBodyIndex) override;
    virtual void ActivateItem(AActor* Activator) override;
    virtual FName GetItemType() const override;

    void DestroyItem();
};

.cpp
#include "BaseItem.h"
#include "Components/SphereComponent.h"

ABaseItem::ABaseItem()
{
    PrimaryActorTick.bCanEverTick = false;
    
    // 루트 컴포넌트 생성 및 설정
    Scene = CreateDefaultSubobject<USceneComponent>(TEXT("Scene"));
    SetRootComponent(Scene);

    // 충돌 컴포넌트 생성 및 설정
    Collision = CreateDefaultSubobject<USphereComponent>(TEXT("Collision"));
    // 겹침만 감지하는 프로파일 설정
    Collision->SetCollisionProfileName(TEXT("OverlapAllDynamic"));
    // 루트 컴포넌트로 설정
    Collision->SetupAttachment(Scene);
    
    // 스태틱 메시 컴포넌트 생성 및 설정
    StaticMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));
    StaticMesh->SetupAttachment(Collision);
		// 메시가 불필요하게 충돌을 막지 않도록 하기 위해, 별도로 NoCollision 등으로 설정할 수 있음.

    // Overlap 이벤트 바인딩
    Collision->OnComponentBeginOverlap.AddDynamic(this, &ABaseItem::OnItemOverlap);
    Collision->OnComponentEndOverlap.AddDynamic(this, &ABaseItem::OnItemEndOverlap);
}

void ABaseItem::OnItemOverlap(
			UPrimitiveComponent* OverlappedComp,
			AActor* OtherActor, 
			UPrimitiveComponent* OtherComp, 
			int32 OtherBodyIndex, 
			bool bFromSweep, 
			const FHitResult& SweepResult)
{
	  // OtherActor가 플레이어인지 확인 ("Player" 태그 활용)
    if (OtherActor && OtherActor->ActorHasTag("Player"))
    {
				 GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Green, FString::Printf(TEXT("Overlap!!!")));
		    // 아이템 사용 (획득) 로직 호출
        ActivateItem(OtherActor);
    }
}

void ABaseItem::OnItemEndOverlap(
			UPrimitiveComponent* OverlappedComp, 
			AActor* OtherActor, 
			UPrimitiveComponent* OtherComp, 
			int32 OtherBodyIndex)
{
}

void ABaseItem::ActivateItem(AActor* Activator)
{
		 GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Green, FString::Printf(TEXT("Overlap!!"));
}

FName ABaseItem::GetItemType() const
{
    return ItemType;
}

void ABaseItem::DestroyItem()
{
    Destroy();
}
  • CollisionComponent->InitSphereRadius(float data) : Sphere 의 크기를 지정
  • CollisionComponent->SetCollisionProfileName(L"OverlapAllDynamic")) : SphereComponen가 다른 액터와 겹칠 때만 Overlap 이벤트를 발생시키도록 설정, 이 이름은 엔진의 Collision Preset에 등록된 애들의 이름과 일치해야함
  • OnComponentBeginOverlap.AddDynamic(UObject*, &UObject::Function) : 이벤트 함수를 등록하는방법
    • 이벤트 바인딩은 런타임중에 이벤트 바인딩이 일어나기 때문에 이벤트를 게임 실행중에 동적으로 바인딩을 하여 사용한다

3️⃣인터페이스 함수 시그니처 수정

#pragma once

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "ItemInterface.generated.h"

UINTERFACE(MinimalAPI)
class UItemInterface : public UInterface
{
		GENERATED_BODY()
};

class SPARTAPROJECT_API IItemInterface
{
		GENERATED_BODY()

public:
		UFUNCTION()
		virtual void OnItemOverlap(
				UPrimitiveComponent* OverlappedComp,
				AActor* OtherActor,
				UPrimitiveComponent* OtherComp,
				int32 OtherBodyIndex,
				bool bFromSweep,
				const FHitResult& SweepResult) = 0;
		UFUNCTION()
		virtual void OnItemEndOverlap(
				UPrimitiveComponent* OverlappedComp,
				AActor* OtherActor,
				UPrimitiveComponent* OtherComp,
				int32 OtherBodyIndex) = 0;
		virtual void ActivateItem(AActor* Activator) = 0;
		virtual FName GetItemType() const = 0;
};
  • 이벤트 바인딩을 하는 함수는 동적으로 사용하므로 리플렉션을 통해 엔진이 게임 실행중에 어떤 함수인지 찾을 수 있게 UFUNCTION()을 붙여줘야한다

아이템 Bleuprint 생성 미콜리전/매시 설정

1️⃣아이템 Blueprint 생성 및 StaticMesh 설정

  • C++ 클래스 클래스 기반 블루프린트 생성하여 StaticMesh를 설정해준다

2️⃣콜리전 범위 조정

  • 블루프린트에서 Sphere Radius를 변경하여 충돌 범위를 재설정한다

3️⃣아이템 액터의 콜리전 프리셋 설정

  • 콜리전 컴포넌트 설정 확인
    • 생성한 BP를 클릭하여 디테일 패널에 Collision 색션을 찾으면 Collision Presets가 어떠한 값으로 설정되어 있는지 확인할 수 있다
  • 콜리전 프리셋(Collision Preste)
    • 주요 프리셋 옵션
      • No Collison
        • 전혀충돌이 일어나지 않아 단순한 배경등에 이용한다
      • BlockAll
        • 모든 객체와 충돌하여 막게되는데, 물리적 충돌은 하지만, Overlap이벤트는 발생하지 않아 벽이나 바닥 같은 고정된 장치에 이용한다
      • OveralpAll
        • 모듣 객체와 Overlap 이벤트를 발생하며, 트리거존, 감지센서, 투명 오브젝트에 사용한다
      • BlockAllDynamic
        • 움직이는 객체만 충돌을 막으며, 고정된 객체와는 충돌하지 않아 움직이는 플레이어나 물리 오브젝트와 상호작용하는 문,벽, 고정된 환경 요소와는 무관하게 작동한다
      • OverlapAllDynamic
        • 움직이는 객체와 Overlap 이벤트를 발생하여, 플레이어 근처에 있는지 확인하는 아이템, 센서. Overlap이벤트가 필요하지만 물리 충돌이 필요 없는 경우 사용한다
      • Pawn
        • 플레이어나 AI 처럼 Pawn 타입 객체를 대상으로 충돌을 감지하거나 막으며, 플레이어 전용 문, AI전용 센서등에 사용한다
      • Custom
        • 각 채널에 대해 충돌 응답을 객체별로 세부적으로 설정할 수 있다
    •  물리 반응 여부(Collision Enabled)
      • 충돌이 켜져있어도 물리적 반응이 없는 "Query Only"인지, 물리적으로 반응까지 하는 "Query and Physics" 인지 구분해야 한다
        • NoCollision : 충돌 비활성화
        • Query Only : 충돌 이벤트는 감지하지만, 물리적으로 튕기거나 밀리는 반응은 없음
        • Physics Only : 물리 반응만 일어나고 이벤트는 일어나지 않음
        • Query and Physics : 충돌 이벤트 + 물리 반응 모두 일어남

4️⃣캐릭터 태그 부여 및 콜리전 프리셋 설정

  • 플레이어 클래스에서 "Player" 테그부여
    • 캐릭터 BP의 디테일 패널에서 Tag를 찾아 Player를 추가해준다
    • 이를 통해 C++코드에서 Other actor->ActorHasTag("Player")를 통해 플레이어인지 간단히 판별할 수 있다
  • 플레이어 클래스 콜리전 채널 확인
    • 플레이어 BP의 CapsuleCollision이 pawn으로 설정하고, 물리 반응 여부도 OverlapAllDynamic으로 되어있는지 확인한다

아이템 클래스 충돌 처리


1️⃣코인 아이템 공통 부모 클래스 충돌 처리

UCLASS()
class SPARTAPROJECT_API ACoinItem : public ABaseItem
{
    GENERATED_BODY()

public:
    ACoinItem();

protected:
    // 코인 획득 시 플레이어에게 줄 점수
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
    int32 PointValue;

		// 부모 클래스에서 상속받은 ActivateItem 함수를 오버라이드
    virtual void ActivateItem(AActor* Activator) override;
};

.cpp

#include "CoinItem.h"

ACoinItem::ACoinItem()
{
		// 점수 기본값을 0으로 설정
    PointValue = 0;
    ItemType = "DefaultCoin";
}

void ACoinItem::ActivateItem(AActor* Activator)
{
		// 플레이어 태그 확인
    if (Activator && Activator->ActorHasTag("Player"))
    {
        // 점수 획득 디버그 메시지
        GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Green, FString::Printf(TEXT("Player gained %d points!"), PointValue));
        
        // 부모 클래스 (BaseItem)에 정의된 아이템 파괴 함수 호출
			  DestroyItem();
    }
}

2️⃣빅 코인 아이템

#include "BigCoinItem.h"

ABigCoinItem::ABigCoinItem()
{
		// 상위 CoinItem의 PointValue를 50으로 덮어쓰기
    PointValue = 50;
    ItemType = "BigCoin";
}

void ABigCoinItem::ActivateItem(AActor* Activator)
{
    // 부모의 기본 점수 획득 로직 사용
    Super::ActivateItem(Activator);

    // 빅 코인만의 추가 동작(이펙트, 사운드 재생 등)을 여기서 추가할 수 있음
}

3️⃣스몰 코인 아이템

#include "SmallCoinItem.h"

ASmallCoinItem::ASmallCoinItem()
{
    PointValue = 10;
    ItemType = "SmallCoin";
}

void ASmallCoinItem::ActivateItem(AActor* Activator)
{
    // 부모의 기본 점수 획득 로직 사용
    Super::ActivateItem(Activator);

    // 스몰 코인만의 별도 동작은 여기에 추가
}

4️⃣힐링 아이템 충돌 처리

UCLASS()
class SPARTAPROJECT_API AHealingItem : public ABaseItem
{
    GENERATED_BODY()

public
    AHealingItem();

protected:
		// 회복량
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Healing")
    int32 HealAmount;

    virtual void ActivateItem(AActor* Activator) override;
};

.cpp
#include "HealingItem.h"

AHealingItem::AHealingItem()
{
    HealAmount = 20.0f;
    ItemType = "Healing";
}

void AHealingItem::ActivateItem(AActor* Activator)
{
    if (Activator && Activator->ActorHasTag("Player"))
    {
		    // 회복 디버그 메시지
        GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Green, FString::Printf(TEXT("Player gained %d HP!"), HealAmount));
        
        DestroyItem();
    }
}
  • 이후 Activator가 캐릭터라는 전제하에, 플레이어의 체력 변수 혹은 함수를 호출해 직접 체력 회복을 처리하는 구현을 해야(PlayerCharacter->Heal(HealAmount))

5️⃣지뢰 아이템 충돌

  • 지뢰는 플레이어가 범위안에 들어오면 일전 시간이 흐른뒤 폭발하고, 그때 범위 내 액터에게 데미지를 주는 로직을 구현할 것이다
UCLASS()
class SPARTAPROJECT_API AMineItem : public ABaseItem
{
    GENERATED_BODY()

public:
    AMineItem();

protected:
		UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item|Component")
		USphereComponent* ExplosionCollision;

    // 폭발까지 걸리는 시간 (5초)
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mine")
    float ExplosionDelay;
    // 폭발 범위
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mine")
    float ExplosionRadius;
    // 폭발 데미지
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mine")
    float ExplosionDamage;
    
    // 지뢰 발동 여부
    FTimerHandle ExplosionTimerHandle;

    virtual void ActivateItem(AActor* Activator) override;

    void Explode();
};

.cpp

AMineItem::AMineItem()
{
    ExplosionDelay = 5.0f;
    ExplosionRadius = 300.0f;
    ExplosionDamage = 30.0f;
    ItemType = "Mine";
    
    ExplosionCollision = CreateDefaultSubobject<USphereComponent>(TEXT("ExplosionCollision"));
		ExplosionCollision->InitSphereRadius(ExplosionRadius);
		ExplosionCollision->SetCollisionProfileName(TEXT("OverlapAllDynamic"));
		ExplosionCollision->SetupAttachment(Scene);
}

void AMineItem::ActivateItem(AActor* Activator)
{
    // 5초 후 폭발 실행
    GetWorld()->GetTimerManager().SetTimer(ExplosionTimerHandle, this, &AMineItem::Explode, ExplosionDelay);
}

void AMineItem::Explode()
{
    TArray<AActor*> OverlappingActors;
    ExplosionCollision->GetOverlappingActors(OverlappingActors);

    for (AActor* Actor : OverlappingActors)
    {
        if (Actor && Actor->ActorHasTag("Player"))
        {
            GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red, FString::Printf(TEXT("Player damaged %d by MineItem"), ExplosionDamage));
        }
    }

    // 지뢰 제거
    DestroyItem();
}
  •  ActivateItem
    • GetWorld()->GetTimerManager().SetTimer()를 사용해 5초 후 Explode()가 자동 실행되도록 지연 호출한다
  • Explode() 
    • 폭발이 일어날 주변 액터를 검사해, 지뢰 범위안에 있고 지뢰 콜리전 컴포넌트와 여전히 Overlap 상태인 플레이얼에세 데미지를 주는 예시이다
    • 폭발 처리 후  DestroyItem()을 호출하여 지뢰 아이템을 제거한다
  • ExplosionCollision-> GetOverlappingActors(TArray<AActor*>))
    • GetOverlappingActors를 사용하면 현재 collision에 충돌한 모든 액터를 가져와 저장할 수 있다
get
 
미국∙영국
[ɡet]
1. 받다
2. 얻다, 구하다, 마련하다
3. (무엇을 팔고 돈을) 받다[보다]