컨텐츠 검색
[UE C++] 지뢰의 폭발 범위 시각화와 다이내믹 머티리얼

2026. 2. 4. 14:53Unreal Engine/개념

단순한 '밟으면 터지는 지뢰'는 플레이어에게 불합리한 경험을 줄 수 있습니다. 이번 프로젝트에서는 데칼(Decal)을 활용한 점진적 경고 시스템과 Gameplay Ability System(GAS)을 결합하여, 시각적 정보와 정교한 데미지 메카닉이 공존하는 지뢰 시스템을 구축했습니다.

1. 문제 구현 및 해결 과정

  • 원인: 정적인 지뢰 모델은 폭발 시점과 범위를 알 수 없어 플레이어에게 불합리한 피해를 줄 수 있음.
  • 분석: 폭발까지의 남은 시간과 실제 물리적 판정 범위를 시각적으로 일치시켜야 함.
  • 예측: 지형 굴곡에 구애받지 않는 Deferred Decal과 런타임 제어가 가능한 DMI(Dynamic Material Instance)를 사용하면 해결될 것임.
  • 수정: UDecalComponent를 활용해 지면에 범위를 투영하고, C++ Tick에서 계산된 진행률을 머티리얼의 Progress 파라미터로 전달.
  • 결과: 지뢰 활성화 시 원형 경고 장판이 시간에 따라 차오르며 폭발 임박을 알리는 긴박한 연출 완성.

2. 머티리얼 로직 분석: 시각적 피드백의 원리

노드 구성 역할 및 분석
RadialGradientExponential 중심에서 외곽으로 갈수록 수치가 변하는 원형 그라디언트를 생성합니다.
Progress (Scalar Param) C++에서 계산된 0.0 ~ 1.0 사이의 값을 받아옵니다.
Step Node RadialGradient 값과 Progress를 비교하여, 진행률에 따라 원이 점점 커지는 '이진 마스크'를 만듭니다.
Emissive (Red * 5.0) Constant(1,0,0)에 강도 5.0을 곱해, 단순한 빨간색이 아닌 빛나는(Glow) 효과를 부여합니다.

3. C++ 및 GAS 시스템 통합 분석

[1] 난이도 동적 조절

BeginPlay에서 HighScoreGameState를 참조하여 스테이지와 웨이브에 맞는 물리적 수치를 계산합니다.

  • 폭발 지연 시간: 스테이지가 올라갈수록 빨라짐.
  • 폭발 반경: 웨이브가 진행될수록 넓어짐.
void AMineItem::BeginPlay()
{
    Super::BeginPlay();

    if (AHighScoreGameState* GS = GetWorld()->GetGameState<AHighScoreGameState>())
    {
        // 레벨이 올라갈수록 지연 시간은 짧아짐 (최소값 보정 필요)
        int32 Level = GS->GetCurrentLevelIndex();
        ExplosionDelay = FMath::Min(ExplosionDelay, ExplosionDelay - (Level * 0.3f));

        // 웨이브가 올라갈수록 폭발 반경은 넓어짐
        int32 Wave = GS->GetCurrentWaveIndex();
        ExplosionRadius = FMath::Max(ExplosionRadius, ExplosionRadius + ((Wave - 1) * 100));
        ExplosionCollision->SetSphereRadius(ExplosionRadius);
    }
    // ... DMI 설정 로직
}

[2] 비주얼 동기화 ($Tick$)

폭발 진행률($Progress$)은 다음 공식을 통해 머티리얼에 실시간 전달됩니다.

$$Progress = \text{clamp}\left(\frac{ElapsedTime}{ExplosionDelay}, 0, 1\right)$$

void AMineItem::UpdateWarningVisual(float DeltaTime)
{
    // 1. 다이내믹 머티리얼과 폭발 지연 시간이 유효한지 확인
    if (DynamicWarningMat && ExplosionDelay > 0.0f)
    {
        // 2. 경과 시간 누적
        ElapsedTime += DeltaTime;

        // 3. 진행률 계산 (0.0 ~ 1.0)
        float ProgressValue = FMath::Clamp(ElapsedTime / ExplosionDelay, 0.0f, 1.0f);

        // 4. 머티리얼의 'Progress' 파라미터 값 설정
        DynamicWarningMat->SetScalarParameterValue(TEXT("Progress"), ProgressValue);
    }
}

[3] GAS 데미지 메카닉

기존 ApplyDamage 대신 GAS의 GameplayEffect를 통해 타겟의 ASC에 데미지를 전달하며, 스테이지 정보를 레벨로 활용합니다.

  • Effect Level 계산: 스테이지와 웨이브를 조합한 수식을 통해 데미지 위력을 결정합니다.
  • Context 전달: AddInstigator를 통해 지뢰가 데미지의 주체임을 명확히 하여 로그 추적이 가능하게 설계했습니다.
// 스테이지와 웨이브 정보를 조합해 Level 계산 (1~9)
float EffectLevel = 1.0f;
if (AHighScoreGameState* GS = GetWorld()->GetGameState<AHighScoreGameState>())
{
    EffectLevel = static_cast<float>(GS->GetCurrentLevelIndex() * 3 + GS->GetCurrentWaveIndex());
}

FGameplayEffectContextHandle ContextHandle = TargetASC->MakeEffectContext();
ContextHandle.AddInstigator(this, this);

FGameplayEffectSpecHandle SpecHandle = TargetASC->MakeOutgoingSpec(DamageEffectClass, EffectLevel, ContextHandle);
if (SpecHandle.IsValid())
{
    TargetASC->ApplyGameplayEffectSpecToSelf(*SpecHandle.Data.Get());
}

4. 현재 구조의 장단점 및 개선사항

장점

  • 시각적 정합성: 데칼을 사용하여 미로 바닥이 평평하지 않아도 경고 범위가 지면을 따라 정확히 투영됩니다.
  • 직관적인 연출: 머티리얼의 Step 노드와 C++의 시간 계산이 맞물려 플레이어가 폭발 시점을 직관적으로 인지할 수 있습니다.

단점 및 개선

  • Tick 의존성: 현재 모든 활성 지뢰가 Tick에서 UpdateWarningVisual을 호출합니다. 지뢰가 100개 이상 스폰되는 상황에서는 CPU 비용이 발생할 수 있습니다.
    • 개선: 머티리얼 내부에 Time 노드를 활용한 애니메이션을 넣거나, FTimeline을 사용하여 Tick 의존성을 제거할 예정입니다.
void AMineItem::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);


    if (bHasExploded)
    {
        // 경고 장판 비주얼 업데이트 호출
        UpdateWarningVisual(DeltaTime);
    }

}
  • 데칼 투영 깊이: DecalSize.X가 100으로 고정되어 있어, 지뢰가 공중에 살짝 뜰 경우 바닥에 닿지 않을 수 있습니다.
    • 개선: 스폰 시 LineTrace 거리만큼 DecalSize.X를 동적으로 설정하면 어떤 높이에서도 안정적인 투영이 가능하도록 수정할 예정입니다.
WarningDecal->DecalSize = FVector(100.0f, ExplosionRadius, ExplosionRadius);