Unreal 개발 본 캠프 44일차

HappyCircle·2026년 1월 29일

Unreal 개발

목록 보기
61/163

📘 챌린지반 4일차 TIL

GAS – Gameplay Effect(GE)로 값 변화 다뤄보기

0. 오늘의 핵심 한 줄 요약

“Attribute 값을 직접 만지지 말고, ‘변화의 규칙’을 Gameplay Effect로 정의하자.”

GAS에서 Gameplay Effect는
👉 값을 바꾸는 ‘결과’가 아니라, ‘방법’을 정의하는 도구였다.

1. 왜 Gameplay Effect로 값 변화를 다룰까?

처음 GAS를 배울 때는 이런 코드가 자연스럽다.

float CurrentHealth = GetHealth();
SetHealth(CurrentHealth - 10);

학습용으로는 충분하지만, 실제 게임 로직에서는 문제가 너무 많다.

❌ 직접 수정 방식의 한계

문제설명
네트워크 동기화수동으로 복제 처리 필요
데미지 계산공격력/방어력/크리티컬 등 직접 계산 필요
재사용성같은 코드를 매번 다시 작성하게 됨
추적어떤 변화a 수치 변화가 언제/왜 일어났는지 확인 어려움
버프/디버프타이머로 수동 관리해야 해서 복잡해짐
디버깅변화 원인을 찾기 힘들고 로그/추적이 번거로움

2. Gameplay Effect란?

Gameplay Effect = Attribute를 어떻게 바꿀지 정의한 규칙

값을 직접 바꾸지 않고,
“이런 변화가 일어난다”는 레시피를 만들어서 적용한다.

예시로 이해하기

Health - 10 → 즉시 데미지 Effect

5초간, 1초마다 Health -5 → 독 Effect

무한히 1초마다 Health +2 → 자동 회복 Effect

👉 같은 Effect를 여러 캐릭터, 여러 상황에서 재사용 가능

3. Gameplay Effect를 쓰면 뭐가 좋아질까?

직접 수정 vs Gameplay Effect

항목직접 수정 (Set)Gameplay Effect
네트워크 복제수동 처리 😓자동 ✅
데미지 계산직접 코딩 😓Modifier로 정의 ✅
재사용매번 코딩 😓한 번 만들면 재사용 ✅
추적어려움 😓Active Effects 확인 ✅
버프/디버프타이머/수동 관리 😓Duration으로 자동 ✅
조건 처리if문 지옥 😓Tag Query로 깔끔 ✅
디버깅힘듦 😓showdebug로 확인 ✅

💡 요리 비유

재료 = Attribute

조리법 = Modifier

레시피 = Gameplay Effect

같은 레시피로 계속 요리 가능 → 같은 Effect로 계속 적용 가능

  1. Gameplay Effect의 3가지 타입 (Duration Policy)
타입Duration자동 제거PeriodActive Effects사용 예시
Instant0초 (즉시)✅ 즉시❌ 불가❌ 남지 않음데미지, 힐, 자원 소모
DurationN초✅ N초 후✅ 가능✅ 남음독, 버프/디버프, 쿨다운
Infinite무한❌ 수동 제거✅ 가능✅ 남음패시브, 장비효과, 자동회복

⚠️ Infinite Effect는 Handle 관리가 핵심

  1. Modifier – Attribute를 어떻게 바꿀까?
    연산 방식 (Modifier Op)
    연산 설명 예시
Operation설명예시자주 쓰는 케이스
Add더하기Health + (-10)데미지/힐/자원 소비
Multiply곱하기AttackPower × 1.5% 버프/디버프
Divide나누기Speed ÷ 2속도 감소 디버프
Override덮어쓰기Health = 100완전 회복/리셋

변화량 계산 방식 (Magnitude)

방식특징예시사용처
Scalable Float고정값 + 레벨 스케일링 가능(커브)-10 / +30대부분의 기본 Effect
Attribute Based다른 Attribute를 참조해서 계산MaxHealth × 0.1비율 데미지/회복
Custom Calculation ClassC++로 복잡 계산(공격-방어)×크리×속성실전 데미지 공식

1️⃣ Scalable Float (가장 많이 씀)

고정 값

CurveTable로 레벨 스케일링 가능

-10 → 항상 10 데미지

2️⃣ Attribute Based

다른 Attribute 기반 계산

MaxHealth × 0.1

3️⃣ Custom Calculation (고급)

C++ 계산 클래스

실전 데미지 공식

Period (주기)

Duration / Infinite에서만 사용

Period > 0 → 반복 적용

5초 / 1초 = 5번 적용

Stacking (중첩)

같은 Effect 여러 번 적용 시 처리 방식

정책의미예시
None중첩 가능(각각 독립)독 2개면 따로 2개가 돌음
Aggregate by Source소스별 1개 유지적 A 독 + 적 B 독은 별개
Aggregate by Target타겟당 1개 유지독 새로 걸면 갱신/덮어쓰기
  1. 실습 요약

✔ Instant Damage

Duration Policy: Instant

Modifier: Health Add -10

✔ Duration Poison

Duration: 5초

Period: 1초

Modifier: Health Add -5

✔ Infinite Regen

Duration: Infinite

Period: 1초

Modifier: Health Add +2

Handle 저장 & Remove 필수

  1. Zone / Trap에 Effect 적용
    핵심 포인트

Apply Gameplay Effect to Target

Infinite → Handle 저장

Duration → EndOverlap 필요 없음 (자동 제거)

💡 이 지점에서
“직접 수치 수정 로직이 완전히 사라진다”는 게 체감됨

  1. Blueprint vs C++
항목BlueprintC++
생성 속도빠름 (클릭)느림 (코드 작성)
수정 편의성빠름재컴파일 필요
버전 관리(diff)어려움(바이너리)쉬움(텍스트)
코드 리뷰어려움쉬움
재사용/확장복사 위주상속/조합 가능
비프로그래머 접근가능어려움
대량 생산(템플릿화)느림빠름

Effect 정의는 C++,
적용은 Blueprint
→ 실무에서 가장 많이 쓰는 패턴

  1. C++에서 Effect 적용 / 제거 핵심
    적용
    ASC->ApplyGameplayEffectSpecToSelf(...)

제거 (Infinite)
ASC->RemoveActiveGameplayEffect(Handle);

노드파라미터의미
Apply Gameplay Effect to TargetTargetEffect를 받을 ASC
Apply Gameplay Effect to TargetGameplay Effect Class적용할 GE
Apply Gameplay Effect to TargetLevelEffect 레벨(기본 1.0)
Apply Gameplay Effect to TargetReturn ValueActive Effect Handle (Instant는 보통 저장 X)
Remove Active Gameplay EffectTargetASC
Remove Active Gameplay EffectHandle저장해둔 ActiveEffectHandle
Remove Active Gameplay EffectStack Count제거할 스택 수 (-1 = 전부)

⚠️ Handle 없으면 제거 못 함

  1. 디버깅 필수 명령어
    showdebug abilitysystem
    AbilitySystem.Effect.ListActive

Instant → 안 보임

Duration / Infinite → Active 상태 확인 가능

👉 “지금 어떤 Effect가 캐릭터에 걸려 있는지”
눈으로 확인 가능해지는 순간 GAS가 이해되기 시작함


📘 게임 루프 / 웨이브 시스템 / UI / 사망 & 게임오버 처리 정리

🎯 오늘 목표

레벨 → 웨이브 구조의 게임 루프 완성

웨이브별 목표 코인 수 / 제한 시간 관리

HUD 실시간 업데이트

캐릭터 사망 시 게임오버 처리

메인 메뉴 / 게임 오버 메뉴 UI 통합

오버헤드 HP UI 개선 (프로그레스바 + 부드러운 감소)

1️⃣ 게임 루프 구조 정리 (Level → Wave)
전체 흐름

Game Start
 → Level Start
   → Wave 1
   → Wave 2
   → Wave 3
 → Next Level
 → ...
 → Game Over

핵심 상태 변수

int32 CurrentLevelIndex;
int32 CurrentWaveIndex;
int32 WavesPerLevel;

int32 SpawnedCoinCount;
int32 CollectedCoinCount;
int32 RequiredCoinCount;

웨이브 시작 로직 핵심

웨이브 시작 시

코인 카운트 초기화

목표 코인 수 설정

웨이브 제한 시간 타이머 설정

아이템 스폰

HUD 즉시 갱신

void AGLGamceState::StartWave()
{
    CollectedCoinCount = 0;

    RequiredCoinCount = DefaultRequiredCoins;
    if (RequiredCoinsPerWave.IsValidIndex(CurrentWaveIndex))
    {
        RequiredCoinCount = RequiredCoinsPerWave[CurrentWaveIndex];
    }

    float WaveTime = DefaultWaveDuration;
    if (WaveDurations.IsValidIndex(CurrentWaveIndex))
    {
        WaveTime = WaveDurations[CurrentWaveIndex];
    }

    GetWorldTimerManager().SetTimer(
        WaveTimerHandle,
        this,
        &AGLGamceState::OnWaveTimeUp,
        WaveTime,
        false
    );

    UpdateHUD();
}

2️⃣ 웨이브 클리어 조건 개선
❌ 기존

모든 코인을 먹어야 웨이브 종료

✅ 개선

웨이브마다 “필요한 개수만” 먹으면 클리어

void AGLGamceState::OnCoinCollected()
{
    CollectedCoinCount++;
    UpdateHUD();

    if (CollectedCoinCount >= RequiredCoinCount)
    {
        EndWave();
    }
}

3️⃣ HUD 업데이트 구조 정리
HUD에 표시하는 정보

Level

Wave (현재 / 전체)

Goal (웨이브 목표 코인 수)

Collected (현재 획득 수)

Time (웨이브 남은 시간)

HUD는 GameState에서 단일 책임으로 갱신

void AGLGamceState::UpdateHUD()
{
    float RemainingTime = GetWorldTimerManager().GetTimerRemaining(WaveTimerHandle);
    if (RemainingTime < 0.f) RemainingTime = 0.f;

    // TextBlock 이름:
    // Level / Wave / Goal / Collected / Time
}

배운 점

HUD는 Tick이 아니라 Timer 기반 주기 업데이트가 훨씬 안정적

웨이브 전환 직후, 코인 획득 직후에는 즉시 UpdateHUD() 호출 필요

4️⃣ GameInstance를 “점수의 단일 소스”로 통일
문제

게임 중 점수는 오르는데

게임 오버 화면에서 TotalScore가 항상 0

원인

GameState의 Score만 증가

GameInstance의 TotalScore는 갱신 안 됨

해결

void AGLGamceState::AddScore(int32 Amount)
{
    Score += Amount;

    if (UGLGameInstance* GI = GetGameInstance<UGLGameInstance>())
    {
        GI->AddToScore(Amount);
    }
}

5️⃣ 메인 메뉴 / 게임 오버 메뉴 통합
구조

WBP_MainMenu 하나로 관리

내부에 패널 2개:

Panel_Main (Start / Exit)

Panel_GameOver (Restart / BackToMainMenu)

장점

위젯 중복 생성 X

UI 관리 단순

입력 문제 최소화

6️⃣ 게임 오버 시 버튼 클릭 안 되던 문제
증상

버튼 Hover / Click 안 됨

커서는 보이지만 반응 없음

원인

HUD 위젯이 화면에 남아 입력을 가로챔

InputMode / 포커스 미설정

해결

HUDWidgetInstance->RemoveFromParent();

bShowMouseCursor = true;
FInputModeUIOnly Mode;
Mode.SetWidgetToFocus(MainMenuWidgetInstance->TakeWidget());
SetInputMode(Mode);

배운 점

UI 입력 문제의 90%는
“보이지 않는 위젯이 히트테스트를 먹고 있는 것”

7️⃣ 캐릭터 낙사(Kill Z) & 사망 처리
방법 1 – 엔진 기본 훅 사용

void AGLCharacter::FellOutOfWorld(const UDamageType& dmgType)
{
    Health = 0.f;
    UpdateOverheadHP(false);
    OnDeath();
}

사망 처리에서 GameOver 호출

void AGLCharacter::OnDeath()
{
    if (AGLGamceState* GS = GetWorld()->GetGameState<AGLGamceState>())
    {
        GS->OnGameOver();
    }
}

8️⃣ 오버헤드 HP UI 개선
요구사항

ProgressBar 기반 HP 표시

데미지 시 깜빡임

HP 감소 부드럽게 보간

구조

오버헤드 위젯을 C++ 기반 위젯 클래스로 분리

표시용 HPPercent를 따로 두고 Interp

DisplayPercent = FMath::FInterpTo(
    DisplayPercent,
    TargetPercent,
    DeltaTime,
    InterpSpeed
);
profile
개발합시다!

0개의 댓글