카테고리 없음

언리얼 C++ - 충돌 이벤트로 획득되는 아이템 구현하기

이개 2026. 4. 21. 21:03

충돌 이벤트란?

액터나 컴포넌트가 다른 객체와 상호작용할 때 발생

  • 오버랩 이벤트
    • 객체가 겹칠 때 발생하는 이벤트
  • 히트 이벤트
    • 객체가 닿을 때 발생하는 이벤트

 

BaseItem에 Colision Component를 붙인다.

 

BigCoinItem에다가 블루프린트를 래핑해준다.

 

위와 같은 방법으로 4개의 아이템을 다 블루프린트로 만들어준다.

 

 

블루프린트에 스태틱 메시들을 할당해준다.

CoinItem에는 동전 아이템.

 

 

HealingItem에는 포션
MineItem은 이러한 스태틱 메시를 할당해주었다.
BigCoinItem은 1.5배정도 크기를 수정해준다.

 

Collision Presets 살펴보기

콜리전 컴포넌트의 콜리전 카테코리를 보면 콜리전 프리셋이 있다.

 

콜리전 프리셋의 종류

이 오브젝트가 무엇과 어떻게 충돌할지”를 미리 정해둔 규칙 묶음.

Collision Presets

🔵 NoCollision

👉 아무것과도 충돌하지 않음

  • 트레이스도 무시
  • 물리 충돌도 없음
  • 완전 통과

✔ 용도

  • 이펙트, UI, 장식 오브젝트
  • 충돌 필요 없는 배경

🔵 BlockAll

👉 모든 것을 막음

  • Pawn, WorldStatic, PhysicsBody 등 전부 “Block”

✔ 용도

  • 벽, 바닥, 구조물
  • “여기 못 지나감” 확실히 만들 때

🔵 BlockAllDynamic

👉 동적 오브젝트만 막는 범용 블록

  • Static World는 상황에 따라 다르게 처리
  • 기본적으로 “움직이는 것과 충돌”

✔ 용도

  • 일반 오브젝트 대부분
  • 애매하면 이거 쓰는 경우 많음

🔵 OverlapAll

👉 전부 통과하지만 “겹침 이벤트”는 발생

  • 물리 충돌 없음
  • 대신 BeginOverlap / EndOverlap 발생

✔ 용도

  • 트리거, 감지 구역
  • 아이템 습득 범위, 이벤트 존

🔵 OverlapAllDynamic

👉 동적 대상과만 Overlap

  • Static World는 무시
  • Pawn / PhysicsBody 등에만 반응

✔ 용도

  • 플레이어 감지 영역
  • AI 시야 범위

🔵 Pawn

👉 캐릭터용 프리셋

  • Pawn끼리 충돌하거나 월드와 충돌
  • 트레이스 반응 포함

✔ 용도

  • 플레이어 캐릭터
  • NPC

🔵 Spectator

👉 관전자용

  • 대부분 충돌 없음
  • 다른 Pawn과도 최소 반응

✔ 용도

  • 디버그 카메라
  • 관전 모드

🔵 CharacterMesh / Character

👉 캐릭터 전용 세팅

  • Capsule 기반 충돌
  • Mesh는 보통 NoCollision 또는 QueryOnly

✔ 용도

  • 3인칭/1인칭 캐릭터

🔵 PhysicsActor

👉 물리 시뮬레이션용

  • 모든 물리 충돌 활성
  • 밀리고 튕기고 떨어짐

✔ 용도

  • 박스, 통, 떨어지는 오브젝트
  • RigidBody 기반 오브젝트

🔵 Vehicle

👉 차량용

  • 바퀴/지형 충돌 최적화
  • Pawn과 상호작용 고려됨

✔ 용도

  • 차량 시스템

 

이번 실습에서는 모든 아이템에 OverlapAllDynamic을 사용한다.

 

 

Collision Enabled 

이 오브젝트가 충돌 시스템에 얼마나 참여하는지를 결정

🔵 No Collision

👉 충돌 완전 OFF

  • 물리 충돌 ❌
  • 트레이스 감지 ❌
  • 오버랩 이벤트 ❌

✔ 용도

  • UI, 이펙트, 장식용 메쉬
  • 성능 최적화용 (완전히 무시 가능)

🔵 Query Only (No Physics Collision)

👉 트레이스 / 오버랩만 가능

  • 물리 충돌 ❌ (밀리거나 튕기지 않음)
  • 라인트레이스 / 스윕 / 오버랩 ✔

✔ 용도

  • 트리거 존
  • 감지 영역
  • 아이템, 인터랙션 오브젝트
  • “닿았는지 체크만 필요할 때”

🔵 Physics Only

👉 물리 충돌만 가능

  • 물리 시뮬레이션 ✔ (밀리고 떨어짐)
  • 트레이스 / 오버랩 ❌ (감지 안 됨)

✔ 용도

  • 순수 물리 오브젝트
  • 떨어지는 박스, 쓰러지는 물체
  • 충돌은 하지만 “감지 이벤트 필요 없을 때”

🔵 Collision Enabled (Query and Physics)

👉 전부 활성

  • 물리 충돌 ✔
  • 트레이스 ✔
  • 오버랩 ✔

✔ 용도

  • 플레이어 캐릭터
  • 핵심 오브젝트
  • 대부분의 “진짜 게임 오브젝트”

보통 콜리전 오버랩으로 해놓음.

 

기본 콜리전 설정을 코드로 적용하기

	Collision = CreateDefaultSubobject<USphereComponent>(TEXT("Collision"));
	Collision->SetCollisionProfileName(TEXT("OverlapAllDynamic")); // 콜리전 프리셋 설정

 

Collision->SetCollisionProfileName(TEXT("OverlapAllDynamic")); 로 디폴트 설정을 해줄 수 있다.

 

이벤트 바인딩

매개변수 목록을 함수 시그니처라 부르는데, 이를 맞춰줘야 Overlap 이벤트를 발생시킬 수 있다.

 

ItemInterface.h에서 Overlap 관련 선언을 이렇게 바꿔준다.

	virtual void OnItemOverlap(
		UPrimitiveComponent* OverlappedComp,
		AActor* OtherActor,
		UPrimitiveComponent* OtherComp,
		int32 OtherBodyIndex,
		bool bFromSweep,
		const FHitResult& SweepResult
	) = 0; // 캐릭터, 몬스터 등 여러 경우를 생각해서 캐스팅할 수 있도록 AActor 클래스를 받는다.
	virtual void OnItemEndOverlap(
		UPrimitiveComponent* OverlappedComp,
		AActor* OtherActor,
		UPrimitiveComponent* OtherComp,
		int32 OtherBodyIndex
	) = 0;

파라미터 하나씩 의미

1️⃣ UPrimitiveComponent* OverlappedComp

👉 “이쪽(내 오브젝트)의 충돌 컴포넌트”

  • 예: 아이템의 Sphere Collision

2️⃣ AActor* OtherActor

👉 “부딪힌 대상 액터”

  • 예: 플레이어 캐릭터

3️⃣ UPrimitiveComponent* OtherComp

👉 “상대방의 충돌 컴포넌트”

  • 예: 플레이어 캡슐 콜라이더

4️⃣ int32 OtherBodyIndex

👉 “멀티 바디 충돌에서 어느 부분인지”

  • 보통은 0이거나 잘 안 씀
  • Skeletal Mesh처럼 바디 많을 때 사용

5️⃣ bool bFromSweep

👉 “이게 Sweep(이동 중 충돌)인지 여부”

  • true → 이동하면서 감지됨 (예: 캐릭터 이동 중)
  • false → 단순 겹침 발생

6️⃣ const FHitResult& SweepResult

👉 “충돌 상세 정보”

  • 충돌 위치
  • 충돌 법선
  • 충돌 지점 등

 

인터페이스를 상속받은 BaseItem.h, cpp도 수정한다.

virtual void OnItemOverlap(
    UPrimitiveComponent* OverlappedComp,
    AActor* OtherActor,
    UPrimitiveComponent* OtherComp,
    int32 OtherBodyIndex,
    bool bFromSweep,
    const FHitResult& SweepResult
) override; // 캐릭터, 몬스터 등 여러 경우를 생각해서 캐스팅할 수 있도록 AActor 클래스를 받는다.
virtual void OnItemEndOverlap(
    UPrimitiveComponent* OverlappedComp,
    AActor* OtherActor,
    UPrimitiveComponent* OtherComp,
    int32 OtherBodyIndex
) override;
void ABaseItem::OnItemOverlap(
	UPrimitiveComponent* OverlappedComp,
	AActor* OtherActor,
	UPrimitiveComponent* OtherComp,
	int32 OtherBodyIndex,
	bool bFromSweep,
	const FHitResult& SweepResult
)
{

}

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

 

 

 

이제 BaseItem.cpp의 마지막줄에 콜리전 함수를 연결해준다.

Collision->OnComponentBeginOverlap.AddDynamic(this, &ABaseItem::OnItemOverlap);
Collision->OnComponentEndOverlap.AddDynamic(this, &ABaseItem::OnItemOverlap);

 

함수 시그니처가 다르기 때문에 반드시 이름을 맞춰서 연결해줘야한다.

 

이벤트 바인딩을 하는 이유

언리얼 엔진(Unreal Engine)은 기본적으로 이렇게 동작합니다:

  • 충돌 발생
  • 엔진 내부에서 이벤트 발생
  • 하지만 “무슨 함수를 실행할지”는 모름
  • 엔진은 “무슨 일이 일어났는지”만 알고
    👉 “어떻게 처리할지”는 게임마다 다르기 때문
    • 아이템 → 줍기
    • 문 → 열기
    • 트랩 → 데미지
    • UI → 표시
      • 👉 전부 다 다름
  • 그래서 고정 로직이 아니라
    “연결 구조(바인딩)”로 설계된 것

그래서 개발자가 직접 알려줘야 합니다:

👉 “이 이벤트가 발생하면 이 함수 실행해!”

이걸 하는 게 **Event Binding (델리게이트 연결)**입니다.

  • 엔진은 “신호만 보냄”
  • 개발자는 “원하는 행동 연결”
  • 재사용성 ↑
  • 확장성 ↑
  • 컴포넌트화 가능

 

이벤트 바인딩을 하려면 리플렉션 시스템에 등록해줘야한다.

ItemInterface에 한번만 UFUNCTION을 붙여주면 자식 클래스들도 자동으로 등록된다.

UFUNCTION() // 바인딩하는 함수에는 리플렉션 시스템에 등록하기 위해 UFUNCTION을 붙여준다.
virtual void OnItemOverlap(
    UPrimitiveComponent* OverlappedComp,
    AActor* OtherActor,
    UPrimitiveComponent* OtherComp,
    int32 OtherBodyIndex,
    bool bFromSweep,
    const FHitResult& SweepResult
) = 0; // 캐릭터, 몬스터 등 여러 경우를 생각해서 캐스팅할 수 있도록 AActor 클래스를 받는다.
UFUNCTION()
virtual void OnItemEndOverlap(
    UPrimitiveComponent* OverlappedComp,
    AActor* OtherActor,
    UPrimitiveComponent* OtherComp,
    int32 OtherBodyIndex
) = 0;

 

이후 캐릭터 클래스를 가져와서 Tags에 Player를 추가해준다.

 

로그로 확인하기

GEngine->AddOnScreenDebugMessage로 로그를 확인해준다.

void ABaseItem::OnItemOverlap(
	UPrimitiveComponent* OverlappedComp,
	AActor* OtherActor,
	UPrimitiveComponent* OtherComp,
	int32 OtherBodyIndex,
	bool bFromSweep,
	const FHitResult& SweepResult
	)
{
	if (OtherActor && OtherActor->ActorHasTag("Player")) // OtherActor가 null이 아니고 플레이어 태그가 붙어있을 때 실행
	{
		GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Green, FString::Printf(TEXT("Overlap")));
		ActivateItem(OtherActor);
	}
}

 

 

결과

 

콜리전 오버랩 이벤트가 일어난다.

 

 

아이템에 개별 로직 구현

CoinItem을 초기화하고, ActivateItem 함수를 수정해준다.

// Fill out your copyright notice in the Description page of Project Settings.


#include "CoinItem.h"

ACoinItem::ACoinItem()
{
	// 변수 초기화
	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));
		DestroyItem();
	}
}

 

이후 BigCoin과 SmallCoin의 구현부도 수정해준다.

// Fill out your copyright notice in the Description page of Project Settings.


#include "BigCoinItem.h"

ABigCoinItem::ABigCoinItem()
{
	PointValue = 50;
	ItemType = "BigCoin";
}

void ABigCoinItem::ActivateItem(AActor* Activator)
{
	Super::ActivateItem(Activator);
	// CoinItem에서 구현하였으므로 지워준다.
	// DestroyItem();
}

 

HealingItem의 ActivateItem은 CoinItem과 로그만 다르게 만들어준다.

// Fill out your copyright notice in the Description page of Project Settings.


#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();
	}
}

 

 

MineItem은 충돌 후 5초 뒤에 폭발하고, 폭발 반경에 있는 객체들이 데미지를 입는다.

 

 

타이머 핸들러

특정 시간 뒤에 함수를 실행하거나, 일정 간격으로 반복 호출할 때 사용하는 기능이다.

 

구조

GetWorld()->GetTimerManager().SetTimer(
    FTimerHandle& InOutHandle,      // 1. 관리할 핸들 변수
    UserObject* InObj,              // 2. 함수를 가진 객체 (보통 this)
    FunctionName InTimerMethod,      // 3. 실행할 함수 주소 (&클래스::함수)
    float InRate,                   // 4. 주기/지연 시간 (초 단위)
    bool InbLoop,                   // 5. 반복 여부 (true면 반복, false면 1회)
    float InFirstDelay              // 6. (선택) 첫 실행까지의 추가 지연 시간
);

 

주의점

1. 타이머를 걸어만 두고 제어하지 못하면 메모리나 로직에 문제가 생길 수 있다. 이때 아까 만든 FTimerHandle 변수를 사용한다.

  • 타이머 취소 (중지): GetWorld()->GetTimerManager().ClearTimer(MyTimerHandle);
  • 타이머 일시정지 / 재개: GetWorld()->GetTimerManager().PauseTimer(MyTimerHandle); GetWorld()->GetTimerManager().UnPauseTimer(MyTimerHandle);
  • 남은 시간 확인: float Remaining = GetWorld()->GetTimerManager().GetTimerRemaining(MyTimerHandle);

2. 인자가 없는 함수만 받을 수 있다. 인자를 전달하고 싶다면 FTimerDeligate를 사용해야한다.

FTimerDelegate TimerDel;
// 인자가 있는 함수 바인딩 (예: void MyFunction(int32 Value);)
TimerDel.BindUObject(this, &APawnDrone::MyFunction, 10); 

GetWorld()->GetTimerManager().SetTimer(MyTimerHandle, TimerDel, 1.0f, false);

 

또는 람다 함수 내부에서 함수에 인자를 넣어 호출해줄 수도 있다.

int32 ScoreAmount = 100;

// 델리게이트 설정 없이 람다의 '캡처' 기능을 통해 인자처럼 전달 가능
GetWorld()->GetTimerManager().SetTimer(TimerHandle, [this, ScoreAmount]()
{
    AddScore(ScoreAmount); // 람다 외부의 변수를 그대로 가져다 씀
}, 2.0f, false);

 

예시

MineItem.h에 변수로 FTimerHandle 추가

FTimerHandle ExplosionTimeHandle;

 

MineItem.cpp 

GetWorld()->GetTimerManager().SetTimer(ExplosionTimeHandle, this, &AMineItem::Explode, 5.0f, false);

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "BaseItem.h"
#include "MineItem.generated.h"

/**
 * 
 */
UCLASS()
class SPARTAPROJECT_API AMineItem : public ABaseItem
{
	GENERATED_BODY()

public:
	AMineItem();

	USphereComponent* ExplosionCollision;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
	float ExplosionDelay; // 폭발 지연시간
	// 콜리전 컴포넌트를 이용할 것이므로 삭제 
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
	float ExplosionRadius; // 폭발 반경
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
	int32 ExplosionDamage; // 폭발 대미지

	// 폭발 시간을 조정하는 타이머 핸들
	FTimerHandle ExplosionTimeHandle;

	virtual void ActivateItem(AActor* Activator) override;

	void Explode(); // 폭발하는 함수
};

 

 

폭발 로직을 함수로 빼고, 타이머 핸들러를 사용하여, 5초 뒤에 폭발을 실행하도록 변경한다.

void AMineItem::ActivateItem(AActor* Activator)
{
	// 게임 월드 -> 타이머 매니저
	// 5초 뒤를 계산하는 타이머 핸들러
	GetWorld()->GetTimerManager().SetTimer(ExplosionTimeHandle, this, &AMineItem::Explode, 5.0f, false); // 타이머


	// Explode로 이동
	//TArray<AActor*> OverlappingActors;
	//ExplosionCollision->GetOverlappingActors(OverlappingActors);

	//for (AActor* Actor : OverlappingActors)
	//{
	//	if (Actor && Actor->ActorHasTag("Player")) // 액터인지와 플레이어 태그 확인
	//	{
	//		// 데미지 구현
	//		GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Green, FString::Printf(TEXT("Player damaged %f by MineItem"), ExplosionDamage));
	//	}
	//}

	//DestroyItem();
}