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을 호출하여 아이템의 기능을 동작하도록 하였다.
- 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 |