본문 바로가기

언리얼 + cpp

언리얼C++ 8일차(Collision, 이벤트 바인딩, 타이머 핸들)

충돌(Collision)

아이템 액터 주변에 Collision 컴포넌트를 붙여서 Overlap 또는 Hit이벤트를 활용해서 다양한 아이템 로직을 실행할 수 있다.

Overlap 이벤트 : 물리적으로 부딪히는 것 없이 액터들이 서로 겹치기 시작 했을 때 이벤트 발생

Hit 이벤트 : 물리적으로 충돌이 일어날 때 이벤트 발생

기본 아이템 클래스(BaseItem)에 충돌 컴포넌트와 메시 컴포넌트 추가하기

BaseItem 클래스는 모든 아이템(코인, 포션, 무기 등)에 공통으로 필요한 기능을 담는 부모 클래스이므로 여기에 충돌 컴포넌트와 메시 컴포넌트를 추가하면 모든 아이템에 추가가 된다.
// BaseItem.h
class USphereComponent; // 전방 선언
    
    // 루트 컴포넌트 (씬)
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item|Component")
    USceneComponent* Scene;
    // 충돌 컴포넌트 (플레이어 진입 범위 감지용)
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item|Component")
    USphereComponent* Collision;
    // 아이템 시각 표현용 스태틱 메시
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item|Component")
    UStaticMeshComponent* StaticMesh;
// BaseItem.cpp
// 생성자
	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);
	PrimaryActorTick.bCanEverTick = false;

콜리전 프리셋은 OverlapAllDynamic으로 지정했다.

 

에디터에서 각 아이템을 블루프린트로 파생시킨 뒤 아이템에 맞게 스태틱 메시를 설정해준다.

이벤트 바인딩

이제 아이템에 붙인 SphereComponent에 겹쳐졌을 때 발생될 이벤트를 구현해야 하는데 겹쳐졌을 때 OnComponentBeginOverlap 이벤트가 호출이 된다.

 

OnComponentBeginOverlap 함수와 직접 만든 ItemInterface의 OnItemOverlap 함수를 연결을 시켜야 겹쳐졌을 때 OnItemOverlap에서 구현한 로직들이 실행이 되는데 연결하기 위해 두 함수의 매개변수를 똑같이 작성해야한다.

매개변수가 똑같아야 OnComponentBeginOverlap에서 받은 정보들을 OnItemOverlap으로 전달할 수 있기 때문이다.
매개변수가 하나라도 다르거나 누락이 된다면 에러가 발생한다.

ItemInterface의 OnItemOverlap과 OnItemEndOverlap의 매개변수(함수 시그니처)를 수정한다.

// ItemInterface.h
	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;

OnItemOverlap 매개변수를 위에서부터 순서대로

OverlappedComp = Overlap이 발생한 자기 자신(여기서는 SphereComponent)

OtherActor = 겹쳐진 상대 액터(Player가 됨)

OtherComp = OtherActor의 붙어있던, 가장 1차적으로 충돌을 일으킨 컴포넌트(Player의 캡슐 컴포넌트)

나머지 3개의 매개변수는 물리시뮬레이션과 관련된 내용으로 다음에 다룬다.

 

OnItemEndOverlap 매개변수는 OnItemOverlap의 매개변수에서 가장 뒤에 2개만 지우면 된다.

OnItemOverlap과 OnItemEndOverlap을 구현했던 클래스들로 가서 매개변수들을 모두 수정해준다.(헤더와 소스 모두)

 

이제 두 함수를 연결시키기 위해 AddDynamic으로 연결을 할 수 있다. AddDynamic(대상객체, &대상클래스::호출할함수);

런타임에 “이벤트(겹침)가 발생하면 저 함수를 불러라” 하고 연결하는 것이다.

// BaseItem.cpp
// 생성자
	Collision->OnComponentBeginOverlap.AddDynamic(this, &ABaseItem::OnItemOverlap);
	Collision->OnComponentEndOverlap.AddDynamic(this, &ABaseItem::OnItemEndOverlap);

 

이제 각 아이템의 ActivateItem 함수에서 어떤 기능이 활성화 될건지 구현하면 되는데 우선 캐릭터의 블루프린트에서 Detail - Actor - Tags에서 태그를 추가하고 Player를 입력한다. 이는 캐릭터에 Player 태그를 붙여준것이다.

이제 다시 C++로 가서 CoinItem.cpp에 코드를 입력한다.

// CoinItem.cpp
#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 Point"), PointValue));
		DestroyItem();
	}
}

ActorHasTag로 이 액터가 해당 태그를 가지고 있는지 판단하여 로직을 실행한다.

디버그 목적으로 메세지를 출력하는 함수를 사용했다. 이후에 점수를 증가시키는 로직을 구현할 예정

 

빅 코인과 스몰 코인에서는 부모의 ActivateItem만 호출하고 다른 추가 동작을 구현하고싶다면 추가할 수 있다.

// BigCoin.cpp
void ABigCoinItem::ActivateItem(AActor* Activator)
{
	Super::ActivateItem(Activator);
}
// SmallCoin.cpp
void ASmallCoinItem::ActivateItem(AActor* Activator)
{
	Super::ActivateItem(Activator);
}

HealingItem도 똑같이 구현을 하고 지뢰 아이템은 조금 다르다.

지뢰 아이템에는 겹쳐진 후 타이머를 시작하고 5초후 폭발범위 내 액터에게 데미지를 주는 로직이 있기 때문에 폭발 범위를 나타낼 컴포넌트를 추가한다. 그리고 타이머와 실제 폭발 로직을 구현할 함수를 추가한다.

// MineItem.h
	UPROPERTY(VisibleAnywhere, Category = "Item|Component")
	USphereComponent* ExplosionCollision;
	FTimerHandle ExplosionTimerHandle;
    
	void Explode();
  • ActivateItem: 발동 이후 타이머를 설정하고, 폭발 함수를 스케줄링합니다.
  • Explode: 타이머가 만료되었을 때 실제 폭발 처리를 담당합니다.

ActivateItem에서는 겹쳐질 때 타이머가 발동하도록하고 실제 폭발 로직은 Explode 함수가 담당한다.

void AMineItem::ActivateItem(AActor* Activator)
{
	GetWorld()->GetTimerManager().SetTimer(
		ExplosionTimerHandle,
		this,
		&AMineItem::Explode,
		ExplosionDelay,
		false);
}

void AMineItem::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 %d by MineItem"), ExplosionDamage));
		}
	}
	DestroyItem();
}
  • ActivateItem
    • GetWorld()->GetTimerManager().SetTimer를 사용해 5초 후 Explode()가 자동 실행되도록 지연 호출 (Delayed Call)합니다.
  • Explode()
    • 폭발이 일어날 때 주변 액터를 검사해, 지뢰 범위(ExplosionRadius) 안에 있고 지뢰 콜리전 컴포넌트와 여전히 Overlap 상태인 플레이어에게 데미지를 주는 예시입니다.
    • 폭발 처리 후 DestroyItem()을 호출해 지뢰 아이템 자체를 제거합니다.

AActor*를 담는 OverlappingActors 배열에 ExplosionCollision에 겹쳐져 있는 액터들을 담고 배열을 순회하면서 액터면서 Player태그를 가지고 있는 액터에게 데미지를 주고 DestroyItem이 호출되어 지뢰 아이템을 제거하는 로직이다.