UObject는 다중 상속 불가
따라서 액터 아래에서 상속을 해주어야한다.
Interface를 활용하여 불에 타는 것과 불에 타지않는 것을 만들기
C++ 클래스로 언리얼 인터페이스 생성


C++ 클래스로 ItemBase 액터를 만든다.

C++ 클래스로 MyTorchlight 액터를 만든다.


Wood 헤더 파일에 인터페이스의 헤더를 넣어준다.

인터페이스 코드를 구현한다.
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "ItemBase.h"
#include "TestMyInterface.h"
#include "Wood.generated.h"
/**
*
*/
UCLASS()
class MYPROJECT_API AWood : public AItemBase, public ITestMyInterface
{
GENERATED_BODY()
public:
virtual void OnFireDetected(float Temperature, FVector HitLocation) override;
protected:
UPROPERTY(EditAnywhere, Category = "Effects")
TObjectPtr<class UParticleSystem> FireEffect;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "Wood.h"
#include "Kismet/GameplayStatics.h"
#include "Particles/ParticleSystem.h"
void AWood::OnFireDetected(float Temperature, FVector HitLocation)
{
if (FireEffect)
{
UGameplayStatics::SpawnEmitterAtLocation(
GetWorld(),
FireEffect,
GetActorLocation(),
GetActorRotation(),
FVector(1.f)
);
}
}
에디터 개인설정에서 시작시 강제 컴파일 체크하면 에디터와 C++ 클래스가 연동된다.

MyTorchlight 코드를 작성한다.
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyTorchlight.generated.h"
UCLASS()
class MYPROJECT_API AMyTorchlight : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AMyTorchlight();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
public:
UPROPERTY(EditAnywhere)
TArray<TWeakObjectPtr<AActor>> Items;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyTorchlight.h"
#include "TestMyInterface.h"
#include "Kismet/KismetSystemLibrary.h"
// Sets default values
AMyTorchlight::AMyTorchlight()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
}
// Called when the game starts or when spawned
void AMyTorchlight::BeginPlay()
{
Super::BeginPlay();
for (const TWeakObjectPtr<AActor>& Item : Items)
{
ITestMyInterface* MyInterface = Cast<ITestMyInterface>(Item.Get());
if (MyInterface)
{
MyInterface->OnFireDetected(100.f, FVector::ZeroVector);
}
}
}
// Called every frame
void AMyTorchlight::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
뷰포트에서 테스트하기 위해 액터에 도형을 달고 뷰포트에 넣는다.

MyTorchlight를 배치하고 Items에 액터를 추가해준다.


블루프린트와 연동하여 사용하기
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "TestMyInterface.generated.h"
// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UTestMyInterface : public UInterface
{
GENERATED_BODY()
};
/**
*
*/
class MYPROJECT_API ITestMyInterface
{
GENERATED_BODY()
// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Interface")
void OnFireDetected(float Temperature, FVector HitLocation);
};
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "ItemBase.h"
#include "TestMyInterface.h"
#include "Wood.generated.h"
/**
*
*/
UCLASS()
class MYPROJECT_API AWood : public AItemBase, public ITestMyInterface
{
GENERATED_BODY()
public:
virtual void OnFireDetected_Implementation(float Temperature, FVector HitLocation);
protected:
UPROPERTY(EditAnywhere, Category = "Effects")
TObjectPtr<class UParticleSystem> FireEffect;
};
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "ItemBase.h"
#include "TestMyInterface.h"
#include "Cloth.generated.h"
/**
*
*/
UCLASS()
class MYPROJECT_API ACloth : public AItemBase, public ITestMyInterface
{
GENERATED_BODY()
public:
virtual void OnFireDetected_Implementation(float Temperature, FVector HitLocation);
protected:
UPROPERTY(EditAnywhere, Category = "Effects")
TObjectPtr<class UParticleSystem> FireEffect;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyTorchlight.h"
#include "TestMyInterface.h"
#include "Kismet/KismetSystemLibrary.h"
// Sets default values
AMyTorchlight::AMyTorchlight()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
}
// Called when the game starts or when spawned
void AMyTorchlight::BeginPlay()
{
Super::BeginPlay();
for (const TWeakObjectPtr<AActor>& Item : Items)
{
if (UKismetSystemLibrary::DoesImplementInterface(Item.Get(), UTestMyInterface::StaticClass())) // ITextMyInterface가 아니고 UTextMyInterface의 StaticClass를 가져와야한다.
{
ITestMyInterface::Execute_OnFireDetected(Item.Get(), 100.f, FVector::ZeroVector);
}
}
}
// Called every frame
void AMyTorchlight::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
하지만 이방식은 호출 접근성을 보장하지 않고, Item.Get()할 때 타입 검사를 하지 않으며 크래시가 날 수 있다는 문제점이 있다.
Delegate
옵저버 패턴을 구현할 수 있게 해준다.
// C++ 형태로 1대1만 지원한다
// DECLARE_DELEGATE
// C++ 1대 다수
// DECLARE_MULTICAST
// 1대1 형태 블루프린트까지 호출
// DECLARE_DYNAMIC
// 1대 다수로 블루프린트까지 지원
// DECLARE_DYNAMIC_MULTICAST
// 바인드 되기 전까지 1바이트도 차지하지 않는다. 사용하면 느려진다. 거의 바인딩 되지 않으면 효율적이다.
// DECLARE_SPARSE
// 반환값 / 매개변수
// 파라미터 하나
// DECLARE_DELEGATE_OneParam
// 반환값이 있고, 파라미터가 세 개
// DECLARE_DELEGATE_RetVal_ThreeParams
// MULTICAST는 반환값을 지원하지 않는다.
// 바인딩
// C++ 전용이랑 블루프린트용이랑 방식이 다르다.
// C++ 전용은 AddUObject로 바인딩
// 블루프린트까지 허용 -> AddDynamic 블루프린트 이벤트 디스패처와 연동이 된다.
// 싱글 -> Bind로 시작
// 멀티 -> Add로 시작
// 앞부분 Bind/Add
// 뒷부분 UObejct/Dynamic
// 오브젝트, SharedPtr, Lamd, 전역함수, Static, UFUNCTION
// 오브젝트 = 델리게이트.BindUObject(객체, &UMyObject::함수);
// 스마트 포인터 = 델리게이트.BindSP(객체, &UMyObject::함수);
// 람다 = 델리게이트.BindLamda([]() {});
// 스태틱 = 델리게이트.BindStatic(객체, &UMyObject::함수);
// UFUNCTION = 델리게이트.BindUFunction(객체, TEXT("함수이름"));
// 싱글 C++ -> BindUObject
// 멀티 C++ -> AddUObject
// 싱글 Dynamic -> BindDynamic
// 멀티 Dynamic -> AddDynamic
// 블루프린트와 연동하려면 리플렉션 UFUNCTION 매크로를 넣어야한다.
// 싱글 1대1 대응 = Execute();
// 싱글은 바인딩이 안 되어 있으면 크래시가 난다.
// 그래서 사용 전에 반드시 bool값인 MySingleDelegate.IsBound();로 검사해주고 진행한다.
// 멀티 1대 다수 = BroadCast();
// 블루프린트로 받아올 경우
// 이쪽에서 객체로 만들고 그 객체를 통해서 블루프린트 델리게이트를 만들어준다.
// UPROPERTY(BlueprintAssignable)
// FDeath OnDeath
액터컴포넌트를 하나 만든다.

// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "MyActorComponent.generated.h"
// 1대 다수로 블루프린트까지 지원하는 죽었을 때
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FHealthDeadSignature, AController*, Instigator);// 델리게이트 선언.
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FHealthDamagedSignature, float, NewHealth, float, MaxHealth, float, HealthChange);
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class MYPROJECT_API UMyActorComponent : public UActorComponent
{
GENERATED_BODY()
public:
// Sets default values for this component's properties
UMyActorComponent();
virtual void TickComponent(float DeltaTime,ELevelTick TickType,FActorComponentTickFunction* ThisTickFunction) override;
UPROPERTY(BlueprintAssignable)
FHealthDeadSignature OnHealthDead;
UPROPERTY(BlueprintAssignable)
FHealthDamagedSignature OnHealthDamaged;
protected:
// Called when the game starts
virtual void BeginPlay() override;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float CurrentHealth;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
float MaxHealth;
private:
// TakeDamage 바인딩
UFUNCTION()
void DamageTake(AActor* DamagedActor, float Damage, const UDamageType* DamageType, AController* Instigator, AActor* Causer);
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyActorComponent.h"
// Sets default values for this component's properties
UMyActorComponent::UMyActorComponent()
{
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
// off to improve performance if you don't need them.
PrimaryComponentTick.bCanEverTick = true;
MaxHealth = 100.f;
}
// Called when the game starts
void UMyActorComponent::BeginPlay()
{
Super::BeginPlay();
CurrentHealth = MaxHealth;
GetOwner()->OnTakeAnyDamage.AddDynamic(this, &UMyActorComponent::DamageTake);
}
void UMyActorComponent::DamageTake(AActor* DamagedActor, float Damage, const UDamageType* DamageType, AController* Instigator, AActor* Causer)
{
float FinalDamage = FMath::Min(Damage, CurrentHealth); // 0 이하로 떨어지지 않도록 처리
CurrentHealth -= FinalDamage;
OnHealthDamaged.Broadcast(CurrentHealth, MaxHealth, FinalDamage);
if (CurrentHealth <= 0.f)
{
OnHealthDead.Broadcast(Instigator);
}
}
// Called every frame
void UMyActorComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
GEngine->AddOnScreenDebugMessage(-1, 0.f, FColor::Green, FString::Printf(TEXT("HP : %f"), CurrentHealth));
}
캐릭터 블루프린트 창에서 MyActorComponent를 추가해서 확인한다.


트리거 박스 설치

레벨 블루프린트를 연다.



실습
캐릭터가 대미지를 받아서 죽을 때
interface 실습 때 마든 레벨에 배치되어있는 모든 아이템들이 Destory 되도록 만들기
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyActorComponent.h"
// Sets default values for this component's properties
UMyActorComponent::UMyActorComponent()
{
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
// off to improve performance if you don't need them.
PrimaryComponentTick.bCanEverTick = true;
MaxHealth = 100.f;
}
// Called when the game starts
void UMyActorComponent::BeginPlay()
{
Super::BeginPlay();
CurrentHealth = MaxHealth;
GetOwner()->OnTakeAnyDamage.AddDynamic(this, &UMyActorComponent::DamageTake);
}
void UMyActorComponent::DamageTake(AActor* DamagedActor, float Damage, const UDamageType* DamageType, AController* Instigator, AActor* Causer)
{
float FinalDamage = FMath::Min(Damage, CurrentHealth); // 0 이하로 떨어지지 않도록 처리
CurrentHealth -= FinalDamage;
UE_LOG(LogTemp, Warning, TEXT("DamageTake Called! Damage: %f"), Damage);
OnHealthDamaged.Broadcast(CurrentHealth, MaxHealth, FinalDamage);
if (CurrentHealth <= 0.f)
{
OnHealthDead.Broadcast(Instigator);
}
}
// Called every frame
void UMyActorComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
GEngine->AddOnScreenDebugMessage(-1, 0.f, FColor::Green, FString::Printf(TEXT("HP : %f"), CurrentHealth));
}
// Fill out your copyright notice in the Description page of Project Settings.
#include "ItemBase.h"
#include "Kismet/GameplayStatics.h"
#include "GameFramework/Character.h"
#include "MyActorComponent.h"
// Sets default values
AItemBase::AItemBase()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
}
// Called when the game starts or when spawned
void AItemBase::BeginPlay()
{
Super::BeginPlay();
ACharacter* PlayerCharacter =
UGameplayStatics::GetPlayerCharacter(GetWorld(), 0);
UMyActorComponent* HealthComp = PlayerCharacter->FindComponentByClass<UMyActorComponent>();
if (HealthComp)
{
UE_LOG(LogTemp, Error, TEXT("Success: HealthComp Found and Binding..."));
HealthComp->OnHealthDead.AddDynamic(this, &AItemBase::ItemDestroyed);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Failure: HealthComp NOT Found on this Actor!"));
}
}
// Called every frame
void AItemBase::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void AItemBase::ItemDestroyed(AController* Inst)
{
GEngine->AddOnScreenDebugMessage(-1, 3.f, FColor::Green, FString::Printf(TEXT("Dead!")));
this->Destroy();
}

'게임 개발 > 학습 일지' 카테고리의 다른 글
| 언리얼 내배캠 TA코스 5주차 - 리깅 (0) | 2026.05.26 |
|---|---|
| 언리얼 TA 코스 4주차 정리 (0) | 2026.05.15 |
| 언리얼 큐브 그리드로 맵 디자인하기 (0) | 2026.05.12 |
| 언리얼 마스터 머티리얼 만들기 (0) | 2026.05.10 |
| 언리얼 내배캠 마스터반 수업 과제 - 샌드박스 패턴으로 기존 과제 구조 변경 / 우클릭 조준 구현 (0) | 2026.05.07 |