Unreal - 가드 (Guard) / 체간 (Posture) 시스템

2026. 1. 12. 07:45·Unreal 프로젝트 다이어리/두번째 프로젝트

미리보기

구현내용

플레이어와 적이 상호작용하며 서로 영향을 주고받는

이른바 제 2의 체력인 세키로의 체간 및 가드 시스템을 구현했습니다

 

  • 플레이어의 체간 시스템
  • 적의 체간 시스템
  • 플레이어의 가드 & 패링

가드 와 패링 의 차이

가드 적의 공격을 막을 수 있음
하지만 체간 손상이 쌓임
패링 공격 직전에 타이밍을 맞춰 적의 공격을 무효화
체간 게이지가 쌓이지 않음

 


사용 클래스

사용 클래스 사용 목적
HitInterface(인터페이스) 플레이어와 적이 공격 / 방어 상호작용을 할 때
공격 이벤트를 전달하는 중간다리 역할
StatComponent(액터컴포넌트) 플레이어의 체력, 체간, 상태, 데미지 계산을 담당하는
가드나 패링 여부에 따라 받는 피해량을 계산하는 두뇌
MainCharacter(캐릭터) 가드 & 패링 을 실행하는 플레이어
PostureWidget(위젯) 플레이어의 체간 게이지 시각화를 담당하는 위젯
EnemyStatusWidget(위젯) 적의 체력 & 체간 게이지 시각화를 담당하는 위젯

구현C++

우선 적과 플레이어는 HitInterface를 상속받아 Hit이벤트를 주고받습니다

HitInterface를 상속받으면 상속받은 대상은 여러가지 Hit이벤트를 담당하는 데이터를 가진

ReceiveHit함수를 사용 할 수 있습니다

USTRUCT(BlueprintType)
struct FGameHitSystemData
{
	GENERATED_BODY()

	//공격 데미지
	UPROPERTY(BlueprintReadWrite)
	float damageAmount;

	//공격 방향 (-180 ~ 180)
	UPROPERTY(BlueprintReadWrite)
	float hitDirection;

	//패링가능 여부
	UPROPERTY(BlueprintReadWrite)
	bool bCanParry;

	//넉백강도
	UPROPERTY(BlueprintReadWrite)
	float knockBackStrength;

	//맞은곳
	UPROPERTY(BlueprintReadWrite)
	FVector hitLocation;

	UPROPERTY(BlueprintReadWrite)
	EDamageType damageType;

	
};

//interface
UINTERFACE(MinimalAPI)
class UHitInterface : public UInterface
{
	GENERATED_BODY()
};

class IHitInterface
{
	GENERATED_BODY()
public:
	virtual void ReceiveHit(const FGameHitSystemData& damageData, AActor* attacker) = 0;
};

 

적과 플레이어 는 이와같이 인터페이스를 상속합니다

 

HitInterface의 ReceiveHit함수를 오버라이드 하여 사용하였습니다

virtual void ReceiveHit(const FGameHitSystemData& damageData, AActor* attacker) override;

 

 

우선 적의 체간 시스템입니다

체간 시스템의 계산은 데미지를 받는 피격자 에서 실행됩니다

각각 적은 외부에서 수정할수 있는 BreakHitLevel이 존재하며

해당 레벨만큼 히트 하게되면 가드브레이크가 실행됩니다

 

구현C++

더보기
.h
UPROPERTY(EditAnywhere, Category = "Posture")
int32 guardBreakHitLevel = 7;

UPROPERTY(EditAnywhere, Category = "Posture")
int32 currentPostureHit = 0;

.cpp
void AEnemyBaseCharacter::ApplyPosture()
{
	//무적인경우 x
	if (superArmorType == ESuperArmorType::Invincible || superArmorType == ESuperArmorType::SuperArmor)
		return;

	currentPostureHit++;

	currentPosture = (float)currentPostureHit / guardBreakHitLevel * maxPosture;
	currentPosture = FMath::Clamp(currentPosture, 0.f, maxPosture);

	
	if (currentPosture >= maxPosture)
		OnPostureBreak();

	BroadCastPosture();
}

플레이어의 체간 시스템입니다

플레이어의 체간 StatComponent에서 계산된 Modifier에서 계산되어

가드 인지 패링 인지 아니면 그냥 히트 인지 에 따라서 계산됩니다

EGuardType::None -> 히트

EGuardType::Normal -> 가드

EGuardType::Perfect -> 패링

 

 

구현C++

더보기
struct FGuardModifier
{
	float damageRate;
	float postureRate;
};

static const TMap<EGuardType, FGuardModifier> guard_Modifiers =
{
	//hp p / posture
	{EGuardType::None, {1.f, 0.3f}},
	{EGuardType::Normal, {0.f, 0.1f}},
	{EGuardType::Perfect,{0.f, 0.f}}
};

void UStatComponent::ApplyGetDamage(float damageAmount, EGuardType guardType)
{
	const FGuardModifier* guardModifier = guard_Modifiers.Find(guardType);
	if (!guardModifier)
		return;

	float finalHpDamage = damageAmount * guardModifier->damageRate;
	float finalPostureDamage = damageAmount * guardModifier->postureRate;

	currentHp -= finalHpDamage;

	bool bPostureIncreased = finalPostureDamage > 0.f;

	if (bPostureIncreased)
		currentPosture += finalPostureDamage;

	currentHp = FMath::Clamp(currentHp, 0.f, GetMaxHp());
	currentPosture = FMath::Clamp(currentPosture, 0.f, GetMaxPosture());

	if (bPostureIncreased)
		StartPostureRecover();

	UpdateCurrentStats();
}

void UStatComponent::StartPostureRecover()
{
	GetWorld()->GetTimerManager().ClearTimer(TH_PostureRecoverDelayTimer);
	GetWorld()->GetTimerManager().ClearTimer(TH_PostureRecoverTimer);

	float maxPosture = GetMaxPosture();
	float delay = (currentPosture >= maxPosture) ? 3.f : postureRecoverDelay;

	GetWorld()->GetTimerManager().SetTimer(TH_PostureRecoverDelayTimer, this, &UStatComponent::RecoverPostureTick, delay, false);
}

void UStatComponent::RecoverPostureTick()
{
	if (currentPosture <= 0.f)
	{
		currentPosture = 0.f;
		UpdateCurrentStats();
		return;
	}

	float maxPosture = GetMaxPosture();
	float postureRatio = currentPosture / maxPosture;

	float recoverMultiplier;

	if (currentPosture >= maxPosture)
		recoverMultiplier = 300.f;
	else
		recoverMultiplier = FMath::Lerp(0.6f, 1.4f, postureRatio);

	float recoverAmount = postureRecoverAmount * recoverMultiplier;

	currentPosture -= recoverAmount;
	currentPosture = FMath::Clamp(currentPosture, 0.f, maxPosture);

	UpdateCurrentStats();

	float interval = (currentPosture >= maxPosture) ? 0.01f : postureRecoverInterval;

	GetWorld()->GetTimerManager().SetTimer(TH_PostureRecoverTimer,this,&UStatComponent::RecoverPostureTick,interval,false);
}

bool UStatComponent::IsPostureBroken() const
{
	return currentPosture >= GetMaxPosture();
}

 

가드브레이크가 터지면 플레이어는 휘청이며 잠시동안 공격 및 가드실행이 불가능해집니다


플레이어의 가드 로직입니다

 

가드할때 가드 입력시간을 기록하여

해당 입력시간으로 구별됩니다

lastGuardInputTime = GetWorld()->GetTimeSeconds();

 

구현C++

더보기
EGuardType AMainCharacter::CheckGuardResult(float attackTime)
{
    if (statComp->currentPosture >= statComp->GetMaxPosture())
        return EGuardType::None;

    if (!guardMode)
        return EGuardType::None;

    if (!IsFrontAttack(135.f))
        return EGuardType::None;

    const float perfectGuardWindow = 0.2f;

    if (FMath::Abs(attackTime - lastGuardInputTime) <= perfectGuardWindow)
        return EGuardType::Perfect;

    return EGuardType::Normal;

}

bool AMainCharacter::IsFrontAttack(float allowedAngleDegress) const
{
    float halfAngle = allowedAngleDegress * 0.5f;
    return (hitDirection >= -halfAngle && hitDirection <= halfAngle);
}

플레이어가 가드를 하게되면 넉백되는 로직입니다

당연히 애니메이션으로 루트모션이 적용된 애니메이션이 있다면 편리하겠지만

1인개발이라 그런모션따위는 존재하지않았습니다

그래서 모션은 따로 구현해오고 밀리는건 직접 구현하였습니다

 

구현C++

더보기
void AMainCharacter::ReceiveKnockBack(float knockBackStrength, AActor* attacker)
{
    FVector knockBackDir = (GetActorLocation() - attacker->GetActorLocation());
    knockBackDir.Z = 0.f;
    knockBackDir.Normalize();

    knockBackStart = GetActorLocation();
    knockBackEnd = knockBackStart + knockBackDir * knockBackStrength;

    knockBackDuration = 0.25f;
    knockBackElapsed = 0.f;

    GetWorldTimerManager().SetTimer(TH_KnockBackTimerHandle, this, &AMainCharacter::UpdateKnockBack, 0.01f, true);
}
void AMainCharacter::UpdateKnockBack()
{
    knockBackElapsed += 0.005f;
    float alpha = FMath::Clamp(knockBackElapsed / knockBackDuration, 0.f, 1.f);

    float knockAlpha = FMath::Pow(alpha, 0.5f);

    FVector knockNewLoc = FMath::Lerp(knockBackStart, knockBackEnd, knockAlpha);
    SetActorLocation(knockNewLoc, true);

    if (alpha >= 1.f)
        GetWorldTimerManager().ClearTimer(TH_KnockBackTimerHandle);
}
//적이 플레이어를 밀때 사용되는함수

void AEnemyBaseCharacter::ReceiveKnockBack(float knockBackStrength)
{
	mainCharacter->ReceiveKnockBack(knockBackStrength, this);
}

void UEnemyBaseAnimInstance::AnimNotify_KnockBackMed()
{
    if (!enemyCharacter)
        return;

    for (AActor* hitActor : enemyCharacter->weapon->alreadyHitActors)
        enemyCharacter->ReceiveKnockBack(120.f);
}

 

넉백을 원하는 프레임구간에 추가하여 쉽게 사용 할 수 있습니다

 


 

결과

가드 (일반가드 - 체간게이지 쌓임 / 적의 공격을 막을 수 있음)

 

패링 (퍼펙트가드 - 체간게이지 쌓이지않음 / 적의 공격 무효화)

 

가드를 못했을 경우 ( 논가드 - 체간게이지 많이 쌓임 / 데미지를 받음)

 

1,2타는 가드 3타는 패링인 경우

적도 가드상태일때 플레이어의 공격을 방어할 수 있습니다

 

적을 때렸을 경우 적의 체간 게이지가 상승합니다

영상

 

 

저작자표시 비영리 변경금지 (새창열림)

'Unreal 프로젝트 다이어리 > 두번째 프로젝트' 카테고리의 다른 글

Unreal - 죽음 / 리스폰  (0) 2026.01.20
Unreal - 적 공격 간파하기  (2) 2026.01.16
Unreal - 그래플링 훅  (0) 2026.01.08
Unreal - 처형( Execution )  (0) 2026.01.05
Unreal - 회피(Dodge)  (0) 2026.01.03
'Unreal 프로젝트 다이어리/두번째 프로젝트' 카테고리의 다른 글
  • Unreal - 죽음 / 리스폰
  • Unreal - 적 공격 간파하기
  • Unreal - 그래플링 훅
  • Unreal - 처형( Execution )
lucodev
lucodev
언리얼 포폴개발 일기
  • lucodev
    루코 개발테이블
    lucodev
  • 전체
    오늘
    어제
    • 분류 전체보기 (236)
      • Unreal 프로젝트 다이어리 (132)
        • 첫번째 프로젝트 (73)
        • 두번째 프로젝트 (59)
      • Unreal 팁 (8)
      • Unreal 디버깅 (8)
      • C++ 프로그래머스 (52)
        • Stack,Queue (7)
        • Hash (4)
        • Heap (2)
        • Sort (5)
        • Exhaustive search (5)
        • Greedy (2)
        • BFS , DFS (7)
        • Graph (2)
        • Dynamic Programming (1)
        • C++ Math (2)
        • 기타 문제 (14)
      • C++ 백준 (5)
      • C++ 팁 (1)
      • 개인 코테 & 스타디 <비공개> (29)
        • 코드 개인보관함 (9)
        • 코딩테스트+@ (11)
        • 알고리즘 스타디 (6)
        • 알고리즘 스타디 과제 (3)
        • 비공개 (0)
  • 인기 글

  • 최근 글

  • 최근 댓글

  • 링크

  • 공지사항

  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 태그

    언리얼 behaviortree
    unreal inventory
    언리얼 상호작용
    언리얼 ui
    unreal npc
    언리얼 비헤이비어트리
    언리얼 인벤토리
    언리얼
    언리얼 파쿠르
    언리얼 시퀀스
    unreal 상호작용
    언리얼 인터렉션
    언리얼 세키로
    언리얼 컷씬
    unreal 인벤토리
    unreal
    unreal 세키로
    unreal 파쿠르
    언리얼 behavior tree
    언리얼 parkour
  • hELLO· Designed By정상우.v4.10.3
lucodev
Unreal - 가드 (Guard) / 체간 (Posture) 시스템
상단으로

티스토리툴바