인터페이스 기반 아이템 클래스 구현_1_인터페이스 설계

2025. 1. 27. 18:08·내배캠/Unreal Engine
Item 인터페이스
// ItemInterface.h
#pragma once

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

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

class STRIKEZONE_API IItemInterface
{
	GENERATED_BODY()

public:
	UFUNCTION()
	virtual void OnItemOverlap(
		UPrimitiveComponent* OverlappedComponent, 
		AActor* OtherActor, 
		UPrimitiveComponent* OtherComp, 
		int32 OtherBodyIndex, 
		bool bFromSweep, 
		const FHitResult& SweepResult) = 0;
	UFUNCTION()
	virtual void OnItemEndOverlap(
		UPrimitiveComponent* OverlappedComponent,
		AActor* OtherActor,
		UPrimitiveComponent* OtherComp,
		int32 OtherBodyIndex) = 0;

	virtual void ActivateItem(AActor* Activator) = 0;
	virtual FName GetItemType() const = 0;
};
  • UInterface를 상속받은 ItemInterface를 선언
  • UINTERFACE(MinimalAPI): 리플렉션 시스템을 위해 사용되는 매크로
  • 기본적으로 Class가 UItemInterface와 IItemInterface로 나뉘게 되는데, 구현해서 사용할 함수는 IItemInterface에서 정의한다.
  • OnItemOverlap과 OnItemEndOverlap 함수는 Collision Component의 OnComponentBeginOverlap.AddDynamic으로 런타임 중 동적으로 바인딩될 함수들이기 때문에, UFUNCION( ) 매크로를 사용해서 리플렉션 시스템에 등록해주었다.
  • UInterface를 상속받은 Interface Class이기 때문에, 엔진에서 내부적으로 관리하여 순수 가상함수에 PURE_VIRTURE를 사용하지 않아도 CDO와의 충돌 문제가 발생하지 않는다. 하지만 UObject를 상속받은 클래스에서 순수 가상함수를 선언한다면 CDO관련 오류를 방지하기 위해 매크로가 필요하다.

 

 


 

 

 

Item 인터페이스를 상속받은 BaseItem 클래스
// BaseItem.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ItemInterface.h"
#include "BaseItem.generated.h"

class USphereComponent;

UCLASS()
class STRIKEZONE_API ABaseItem : public AActor, public IItemInterface
{
	GENERATED_BODY()
	
public:	
	ABaseItem();

protected:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
	FName ItemType;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item|Component")
	TObjectPtr<USceneComponent> Scene;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item|Component")
	TObjectPtr<USphereComponent> Collision;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item|Component")
	TObjectPtr<UStaticMeshComponent> StaticMesh;

	virtual void OnItemOverlap(
		UPrimitiveComponent* OverlappedComponent,
		AActor* OtherActor,
		UPrimitiveComponent* OtherComp,
		int32 OtherBodyIndex,
		bool bFromSweep,
		const FHitResult& SweepResult) override;
	virtual void OnItemEndOverlap(
		UPrimitiveComponent* OverlappedComponent,
		AActor* OtherActor,
		UPrimitiveComponent* OtherComp,
		int32 OtherBodyIndex) override;

	virtual void ActivateItem(AActor* Activator) override;
	virtual FName GetItemType() const override;

	virtual void DestroyItem();

};
// BaseItem.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);

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

void ABaseItem::OnItemOverlap(
	UPrimitiveComponent* OverlappedComponent,
	AActor* OtherActor,
	UPrimitiveComponent* OtherComp,
	int32 OtherBodyIndex,
	bool bFromSweep,
	const FHitResult& SweepResult)
{
	if (OtherActor && OtherActor->ActorHasTag("Player"))
	{
		GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Cyan, FString::Printf(TEXT("Overlap!!!")));
		ActivateItem(OtherActor);
	}
}

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

void ABaseItem::ActivateItem(AActor* Activator)
{
}

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

void ABaseItem::DestroyItem()
{
	Destroy();
}
  • IItemInterface에서 요구하는 함수들을 선언 및 정의해주었고, 정의가 비어있는 부분은 필요하다면 파생클래스에서 재정의해 사용할 수 있다.
  • OtherActor->ActorHasTag: Actor의 Tag를 확인하고 Player라면 ActivateItem을 호출하여 아이템의 기능을 동작하도록 하였다.

BP_PlayerCharacter의 Tags에 "Player" 등록

  • GEngine->AddOnScreenDebugMessage( ): 로그 출력이 아닌, 에디터 뷰포트 상에 Debug Message 출력함수

 

 

 


 

 

 

BaseItem을 상속 받는 CoinItem Class를 만들고, 이 CoinItem Class를 상속 받는 BigCoinItem Class 만들기
// CoinItem.h
#pragma once

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

UCLASS()
class STRIKEZONE_API ACoinItem : public ABaseItem
{
	GENERATED_BODY()
	
public:
	ACoinItem();

protected:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
	int32 PointValue;

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

// CoinItem.cpp
#include "CoinItem.h"

ACoinItem::ACoinItem()
{
	PointValue = 0;
	ItemType = "DefaultCoin";
}

void ACoinItem::ActivateItem(AActor* Activator)
{
	if (Activator && Activator->ActorHasTag("Player"))
	{
		DestroyItem();
	}
}

// BigCoinItem.h
#pragma once

#include "CoreMinimal.h"
#include "CoinItem.h"
#include "BigCoinItem.generated.h"

UCLASS()
class STRIKEZONE_API ABigCoinItem : public ACoinItem
{
	GENERATED_BODY()
	
public:
	ABigCoinItem();

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

// BigCoinItem.cpp
#include "BigCoinItem.h"

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

void ABigCoinItem::ActivateItem(AActor* Activator)
{
	Super::ActivateItem(Activator);
}
  • UInterface에서 선언된 순수 가상함수들을 BaseItem에서 상속받아 필수적으로 구현을 하였고, BaseItem을 상속 받은 CoinItem과 BigCoinItem 클래스에서 ActivateItem 함수를 코인을 획득하였을 때의 동작으로 재정의하여 사용할 수있도록 정의해주었다.
  • 기본적으로 Coin을 획득했을 때의 동작을 CoinItem의 ActivateItem에서 정의해주고, 자식 클래스인 BigCoinItem에서  Super매크로로 부모의 ActivateItem을 호출하고 BigCoinItem 고유의 추가적인 동작도 구현 할 수 있게 하였다.

 

 


 

 

 

BaseItem을 상속 받는 MineItem 클래스 만들기
// MineItem.h
#pragma once

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

UCLASS()
class STRIKEZONE_API AMineItem : public ABaseItem
{
	GENERATED_BODY()

public:
	AMineItem();

	TObjectPtr<USphereComponent> ExplosionCollision;
	
	virtual void ActivateItem(AActor* Activator) override;

	void Explode();

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
	float ExplosionDelay;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
	float ExplosionRadius;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
	int32 ExplosionDamage;
	
	FTimerHandle ExplosionTimerHandle;

};

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

AMineItem::AMineItem()
{
	ExplosionDelay = 5.0f;
	ExplosionRadius = 300.0f;
	ExplosionDamage = 30;
	ItemType = "Mine";

	ExplosionCollision = CreateDefaultSubobject<USphereComponent>(TEXT("ExplosionCollision"));
	ExplosionCollision->InitSphereRadius(ExplosionRadius);
	ExplosionCollision->SetCollisionProfileName(TEXT("OverlapAllDynamic"));
	ExplosionCollision->SetupAttachment(Scene);
}

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::Cyan,
				FString::Printf(TEXT("Player Damaged %d by MineItem"), ExplosionDamage));
		}
	}
	DestroyItem();
}
  • MineItem의 경우, 캐릭터가 Mine Collision에 오버랩되면 일정시간 이후 폭발 반경에 Overlap 되어있는 Actor들에게 데미지를 가할 수 있도록 폭발 반경을 감지하는 Collision Component를 추가로 붙혀주었다.
  • InitSphereRadius( ): Sphere Collision의 반지름을 설정
  • UPrimitiveComponent::SetCollisionprofileName( ):  엔진의 콜리전 프리셋을 가져와 설정
  • Mine의 ActivateItem 함수는 Timer를 사용해 ExplosionDelay만큼 시간이 지난 후에 Explode 함수를 호출하도록 재정의 하였다.
  • UPrimitiveComponent::GetOverlappingActors(TArray<AAcor*>): 현재 오버랩된 액터를 TArray<Aactor*> 타입 레퍼런스에 저장한다. ClassFilter(옵션) 는 특정 클래스나 해당 클래스의 자식 클래스와 곂치는 액터만 받환받고 싶을 때 사용하고 기본은 nullptr로 설정되며, 이는 모든 액터를 가져옴을 의미한다.
// 함수 원형
void GetOverlappingActors(
TArray<AActor*>& OverlappingActors, 
TSubclassOf<AActor> ClassFilter = nullptr) const;

 

'내배캠 > Unreal Engine' 카테고리의 다른 글

데이터 테이블에 사용할 구조체 만들기  (0) 2025.01.27
DataTable 만들기(CSV, Unreal)  (0) 2025.01.27
Delegate  (0) 2025.01.23
UPrimitiveComponent  (0) 2025.01.23
Animation Blueprint로 캐릭터 애니메이션 구현  (0) 2025.01.23
'내배캠/Unreal Engine' 카테고리의 다른 글
  • 데이터 테이블에 사용할 구조체 만들기
  • DataTable 만들기(CSV, Unreal)
  • Delegate
  • UPrimitiveComponent
동그래님
동그래님
  • 동그래님
    개발자 동그래
    동그래님
  • 전체
    오늘
    어제
    • 분류 전체보기 (210)
      • 공부 (51)
        • Code Cata (50)
      • 내배캠 (151)
        • TIL (50)
        • C++ (37)
        • Unreal Engine (48)
        • GAS(Gameplay Ability System.. (16)
      • Project (7)
        • Gunfire Paragon (5)
        • Arena Fighters (1)
  • 블로그 메뉴

    • 링크

    • 공지사항

    • 인기 글

    • 태그

    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.3
    동그래님
    인터페이스 기반 아이템 클래스 구현_1_인터페이스 설계
    상단으로

    티스토리툴바

    단축키

    내 블로그

    내 블로그 - 관리자 홈 전환
    Q
    Q
    새 글 쓰기
    W
    W

    블로그 게시글

    글 수정 (권한 있는 경우)
    E
    E
    댓글 영역으로 이동
    C
    C

    모든 영역

    이 페이지의 URL 복사
    S
    S
    맨 위로 이동
    T
    T
    티스토리 홈 이동
    H
    H
    단축키 안내
    Shift + /
    ⇧ + /

    * 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.