개요
- 현재 만들고 있는 격투게임에서는 피격시 공격 타입에 따라 날라가는 정도나 방향이 다름
- 따라서 해당 피격시 적용해야할 정보를 데미지 전달 과정에서 함께 보내주어야 함
- 결과적으로 기존에 사용하던 UGameplayStatics의 ApplyDamage로는 위에서 언급한 기능 구현이 어려움
- 마찬가지로 TakeDamage도 수정이 필요한 상태
ㅡ> 격투게임에 사용할 사용자 정의 ApplyDamage, TakeDamage를 만들기로 결정
FHitBoxData
USTRUCT(BlueprintType)
struct FHitBoxData : public FTableRowBase
{
GENERATED_BODY();
public:
FHitBoxData(): Type(EHitBoxType::Normal), Damage(0.0f), MinimumDamage(0.0f), GuardDamage(0.0f), GuardMeterDamage(0.0f),Hitstun(0.0f), Hitlag(0.0f),
VictimHitlag(0.0f), BlockStun(0.0f), KnockbackForce(0.0f), KnockbackAngle(0.0f), DiModifier(0.0f), GuardPushback(0.0f) {}
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Type")
EHitBoxType Type;
// Damage
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Damage")
float Damage;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Damage")
float MinimumDamage;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Damage")
float GuardDamage;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Damage")
float GuardMeterDamage;
// HitReaction
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HitReaction")
int32 Hitstun;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HitReaction")
int32 Hitlag;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HitReaction")
int32 VictimHitlag;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HitReaction")
int32 BlockStun;
//Knockback
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Knockback")
float KnockbackForce;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Knockback")
FVector KnockbackAngle;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Knockback")
float DiModifier;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Knockback")
float GuardPushback;
};
- 데미지 전달 과정에서 함께 전달할 동작에 따른 피격 관련 정보 구조체
- 넉백 정도, 방향, 데미지, 경직 시간 등의 정보 포함
- 데이터 테이블을 이용하여 초기화
ApplyDamage
DamageHelper.h
#include "CoreMinimal.h"
#include "UObject/Object.h"
#include "DamageHelper.generated.h"
struct FHitBoxData;
UCLASS()
class CCFF_API UDamageHelper : public UObject
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable,Category="DamageHelper")
static float ApplyDamage(
AActor* DamagedActor,
float BaseDamage,
AController* EventInstigator,
AActor* DamageCauser,
TSubclassOf<UDamageType> DamageTypeClass,
FHitBoxData& HitData);
};
DamageHelper.cpp
#include "DamageHelper.h"
#include "Base/DamageAble.h"
#include "Engine/DamageEvents.h"
float UDamageHelper::ApplyDamage(AActor* DamagedActor,
float BaseDamage,
AController* EventInstigator,
AActor* DamageCauser,
TSubclassOf<UDamageType> DamageTypeClass,
FHitBoxData& HitData)
{
if ( DamagedActor && (BaseDamage != 0.f) )
{
// make sure we have a good damage type
TSubclassOf<UDamageType> const ValidDamageTypeClass = DamageTypeClass ? DamageTypeClass : TSubclassOf<UDamageType>(UDamageType::StaticClass());
FDamageEvent DamageEvent(ValidDamageTypeClass);
if (DamagedActor->Implements<UDamageAble>())
{
UE_LOG(LogTemp, Display, TEXT("ApplyDamage"));
return IDamageAble::Execute_TakeDamage(DamagedActor, BaseDamage, DamageEvent, EventInstigator, DamageCauser,HitData);
}
}
return 0.f;
}
- UGameplayStatics 대신 사용할 UDamageHelper 클래스
- 앞으로 ApplyDamage와 비슷하게 데미지 관련 함수가 필요한 경우 해당 클래스에 추가 예정
- 기존 ApplyDamage를 참고하여 FHitBoxData를 매개변수로 추가하여 사용자 정의 ApplyDamage 구현
ㅡ> Execute_TakeDamage의 경우 아래서 설명
TakeDamage
DamageAble (Interface)
#include "CoreMinimal.h"
#include "AttackCollisionData.h"
#include "UObject/Interface.h"
#include "DamageAble.generated.h"
// This class does not need to be modified.
UINTERFACE()
class UDamageAble : public UInterface
{
GENERATED_BODY()
};
class CCFF_API IDamageAble
{
GENERATED_BODY()
// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Damage")
float TakeDamage(float DamageAmount,
const FDamageEvent& DamageEvent,
AController* EventInstigator,
AActor* DamageCauser,
FHitBoxData& HitData);
};
- Interface, 부모 클래스로 생성하여 상속하는 방식 중 Interface를 선택
ㅡ> 추후 몬스터나, 사물 등에서 Interface만 상속하면 TakeDamage를 알맞게 재정의하여 사용 가능 - UFUNCTION(BlueprintNativeEvent)로 캐스팅 없이 인터페이스 기반 호출이 가능하도록 생성
- virtual float TakeDamage() 라는 기반 함수 선언을 자동으로 생성
- TakeDamage_Implementation() 라는 C++ 오버라이드용 함수를 직접 재정의 가능
- 이 덕분에 캐스팅 없이 인터페이스 호출 (Execute_)이 가능
BaseCharacter.h
//Interface Override Functions
virtual float TakeDamage_Implementation(float DamageAmount, const FDamageEvent& DamageEvent, AController* EventInstigator, AActor* DamageCauser,FHitBoxData& HitData) override;
BaseCharacter.cpp
float ABaseCharacter::TakeDamage_Implementation(float DamageAmount, const FDamageEvent& DamageEvent,
AController* EventInstigator, AActor* DamageCauser, FHitBoxData& HitData)
{
const float ActualDamage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
Stats.Health = FMath::Clamp(Stats.Health - DamageAmount, 0.0f, Stats.MaxHealth);
UE_LOG(LogTemp,Warning,TEXT("Damage: %f"),ActualDamage);
return ActualDamage;
}
- _Implementation() 형태의 구현 래퍼를 헤더에 선언 및 cpp에서 구현
//ApplyDamage의 TakeDamage 호출 부분
if (DamagedActor->Implements<UDamageAble>())
{
UE_LOG(LogTemp, Display, TEXT("ApplyDamage"));
return IDamageAble::Execute_TakeDamage(DamagedActor, BaseDamage, DamageEvent, EventInstigator, DamageCauser,HitData);
}
- Implements<UDmagAble>로 인터페이스가 구현되었는지 확인
- 캐스팅 없이 Execute_TakeDamage로 실행 래퍼 함수 호출 ㅡ> 언리얼 리플렉션 시스템에서 자동 생성
정리
선언 방식 | 캐스팅 없이 호출 | Blueprint에서 구현 | Execute_(실행 래퍼) 호출 |
virtual | X | X | X |
UFUNCTION(BlueprintNativeEvent) | O | O | O |
선언 방식 | C++ 구현 | Blueprint 구현 | Execute_(실행 래퍼) 호출 |
BlueprintNativeEvent | X | O | O |
BlueprintImplementableEvent | O | O | O |
- 위 표에 보이듯이 인터페이스 함수에 UFUNCTION(BlueprintNativeEvent) 를 이용하게 되면 C++에서 해당 인터페이스 함수를 구현 래퍼(_Implementation)를 이용해 구현 ㅡ> 실행 래퍼(Execute_)로 호출이 가능해 형변환 없이 호출이 가능해짐
참고자료
https://codakcoo.tistory.com/10
언리얼 interface c++ 호출방법
상속클래스.h void Interact_Implementation() override; //블루프린트에서도 사용가능// virtual void InteractPure() override; //c++에서만 사용함// 상속클래스.cpp void ACharacter::Interact_Implementation() { UE_Log(LogTemp, Warning,
codakcoo.tistory.com
https://iiii4.tistory.com/156?category=1149168
[UE5] 인터페이스
인터페이스 인터페이스 클래스는 (잠재적으로) 무관한 클래스 세트가 공통의 함수 세트를 구현할 수 있도록 하는 데 쓰입니다. 그대로라면 유사성이 없었을 크고 복잡한 클래스들에 어떤 게임
iiii4.tistory.com
https://dev.epicgames.com/documentation/ko-kr/unreal-engine/interfaces?application_version=4.27
https://dev.epicgames.com/documentation/ko-kr/unreal-engine/interfaces-in-unreal-engine
'프로젝트 > CCFF' 카테고리의 다른 글
미친 선인장과 분노의 버섯 - #11 싱글 플레이 -> 멀티 플레이 전환 (네트워크 기반 설계) (0) | 2025.04.09 |
---|---|
미친 선인장과 분노의 버섯 - #10 동작에 알맞은 콜리전, Hit 관련 정보 매핑 구조 설계 (0) | 2025.04.08 |
[Project] 미친 선인장과 분노의 버섯 - #8 애니메이션을 이용한 공격 판정 로직 (0) | 2025.04.04 |
[Project] 미친 선인장과 분노의 버섯 - #7 공격시 콜리전 생성 방식 결정 + 캐릭터별 알맞은 데이터 로딩 로직 (0) | 2025.04.03 |
[Project] 미친 선인장과 분노의 버섯 - #6 구조체 추가 정의 + DataLoader Update (0) | 2025.04.02 |