Sparta Unreal 부트캠프 67일차

정찬호·2026년 3월 9일

Challenge 분반 수업

내적

내적은 두 백터가 얼마나 같은 방향인지를 나타내는 값입니다.

현재 블록 애니메이션은 전방을 향해 가드를 하고 있습니다.
만약 적이 뒤에서 공격했는데 블록되는 것은 알맞지 못합니다.

이 때 적의 공격이 뒤에서인지 정면에서인지를 확인하기 위해서 Dot Product(내적)을 해주는 겁니다.

두 액터의 정면 백터 사이의 각도를 직접 계산하는 것은 복잡합니다.
각도를 라디안으로 할지, 도를 사용할지, atan2를 사욯해야 하고 등등 생각할 것이 많아집니다.

대신 더 간단한 방법인 투영을 사용합니다.

투영이란

투영은 한 방향을 다른 방향으로 "눞혔을 때" 길이입니다.
(3, 5) 백터가 존재할 때 이 백터를 X축에 투영하면 3, Y축에 투영하면 5가 됩니다.

투영 길이가 1이면 적을 향한 방햔과 나의 정면이 일치하는 것이고, 음수면 적이 내 뒤, 반대 방향에 있는 겁니다.
0이면 정확히 옆에 있는 겁니다.

이러한 값이 나오는 이유는 백터의 내적은 사실상 cos(θ) (θ - 두 백터 사이의 각)이기 때문입니다.

 cos(θ) = 투영 / |v| (|v| 백터의 크기)
투영 = |v| × cos(θ)

v · w = |v| × |w| × cos(θ)

중요!

두 백터 모두 정규화를 해야 합니다.
정규화하지 않으면 거리에 따라 내적 값이 달라져 각도 판정이 불가능하다고 합니다.

방어 시에 Block 판정은 Dot > 0.5 (약 60도, 넓은 정면, 쉬움)에서 시작하고 플레이 테스트 후 조절하는 것이 추천된다고 합니다.
팀프로젝트에서 AIPerception의 시야를 설정할 때 45도로 설정했었는데 다음에는 60으로 시작해서 서서히 줄이며 테스트를 해봐야겠습니다.

Execution Calculation

여러 Attribute를 참조하는 복잡한 계산을 수행하는 시스템입니다.

Modifer vs Execution Calculation

||Modifier|Execution Calculation|
||||
|용도|단순 계산|복잡한 계산|
|연산|Add, Multiply, Divide, Override|C++ 로직 (조건문, 반복문 등)|
|Attribute 참조|1개|여러 개 가능|
|예시|Health + 10, Stamina 1.5|(Damage - Defense) CriticalMultiplier|
|성능|빠름|상대적으로 느림|

Attribute Capture - 영상 보면서 복습 필요

Cpature

  • Target 또는 Source의 Attribute 값을 읽어오는 것입니다.
  • Snapshot : 계산 시점의 값(true), 실시간 값(false)

좀 더 자세히 알아보겠습니다.
GameplayEffectExecutionCalculation(Execution)에서 Capture는 “계산에 필요한 Attribute 값을 어디서/언제/어떤 방식으로 읽어올지”를 사전에 선언해두는 규칙이라고 합니다.

Execution은 Execute_Implementation 안에서 AttempCalculateCapturedAttributeMagnitude() 같은 함수로 값을 뽑아 쓰는데, 이 때 그 값의 출처(소스/타겟), 어떤 Atrribute인지, 언제의 값인지가 바로 Capture 설정으로 결정됩니다.

AI튜터의 비유

  • ExecutionCalculation = "데미지 최종 계산기"
  • Capture = "계산기 꽂을 센서(HP, 공격력, 방어력 등)와 센서가 바라보는 대상(가해자, 피해자) 설정"
  • Snapsot = "센서가 측정한 값을 그 순간에 사진 찍어 고정할지, 아니면 나중에 다시 측정할지"

Capture 설정의 핵심 요소

보통 FGameplayEffectAttributeCaptureDefinition으로 정의합니다.

  • 어떤 Attribute를 캡쳐할지
  • 누구의 Attribute인지
    - Source(효과를 건 쪽) / Target(효과를 받은 쪽)
  • Snapshot 여부

Snapshot

bSnapshot = true / false는 갭쳐한 값을 "언제 확정"할지를 의미한다고 합니다.

  1. Snapshot = true (스냅샷 캡쳐)
  • GameplayEffect가 적용(스팩 색성/적용)되는 시점의 값을 "한 번 찍어서" 저장해두고,
  • Execution이 나중에 실행되더라도 그때 저장된 값을 사용합니다.

사용처

  • "버프를 건 순간의 공격력"을 기준으로 도트 데미지를 끝까지 유지하고 싶을 때
  • 치명타/랜덤/계수 등을 "발동 순간"에 확정하고 싶을 때
    즉, 효과가 지소되는 동안 수치가 바뀌어도 결과가 흔들리면 안 되는 설계에 잘 맞아요.
  1. Snapshot = false (비스냅샷, 실시가에 가깝게 계산)
  • 값이 "즉시 저장"되지 않고,
  • Execution이 실행되는 시점에 현재의 Attribute/Aggregator 상태를 기반으로 계산되는 성격이 강합니다.

사용처

  • 방어력/저항처럼 "맞는 순간의 현재 상태"를 반영하고 싶을 때
  • 디버프/버프가 중간에 바뀌면 데미지도 같이 바뀌어야 하는 설계

정리

  • Snapshot=true → 당시 값 고정(결과 일관성)
  • Snapshot=false → 실행 시점 값 반영(상태 변화 반영)

강의에서 사용된 코드 분석

강의에서는 Capture 구조체를 만들어 어떤 Attribute를 읽을 지 정의했습니다.

// Capture 구조체 - 어떤 Attribute를 읽을 지 정의
struct FDamageStatics
{
    // Target의 Defense를 Capture
    DECLARE_ATTRIBUTE_CAPTUREDEF(Defense);
    // Target의 Health를 Capture (최종 데미지 적용용)
    DECLARE_ATTRIBUTE_CAPTUREDEF(Health);

    FDamageStatics()
    {
        // UMyAttributeSet의 Defense, Target에서, Snapshot 안 함(실시간 값)
        DEFINE_ATTRIBUTE_CAPTUREDEF(UMyAttributeSet, Defense, Target, false);
        DEFINE_ATTRIBUTE_CAPTUREDEF(UMyAttributeSet, Health, Target, false);
    }

};

앞서 말한 함수명들을 매크로가 대신해주고 있는 것으로 보입니다.

  1. DECLARE_ATTRIBUTE_CAPTUREDEF(Defense)가 하는 일
    이 매크로는 보통 "캡처 정의 변수(Definition)를 구조체 멤버로 선언"해줍니다.

개념적으로는 다음과 같은 맴버가 생긴다고 보면 됩니다.

// 대략 이런 멤버가 선언됨(개념 스켈레톤)
FGameplayEffectAttributeCaptureDefinition DefenseDef;

즉, “나중에 Defense를 어떤 방식으로 캡처할지”를 담는 그릇(정의 객체) 를 구조체에 만들어두는 단계입니다.

  1. DEFINE_ATTRIBUTE_CAPTUREDEF(UMyAttributeSet, Defense, Target, false)가 하는 일
    이 매크로는 위에서 선언한 "그릇(DefenseDef)"에다가, 캡처 규칙을 채춰 넣는 초기화 코드를 만들어줍니다.
  • UMyAttributeSet : 어떤 AttributeSet에 들어있는 속성인가
  • Defense : 그 AttributeSet 안의 어떤 Attribute인가
  • Target : Source/Target 중 누구의 값을 읽나(여기선 Target)
  • false : Snapshot 여부 (false면 스냅샷 안 함)

개념적으로는 이런 코드가 들어간다고 생각하시면 됩니다(스켈레톤):

// 핵심은 "UMyAttributeSet의 Defense 속성을 Target에서, Snapshot=false로 캡처"
DefenseDef = FGameplayEffectAttributeCaptureDefinition(
    /* UMyAttributeSet::GetDefenseAttribute() 같은 getter로 속성 지정 */,
    EGameplayEffectAttributeCaptureSource::Target,
    /*bSnapshot=*/false
);

정리하면, DECLARE = “변수 만들기”, DEFINE = “그 변수에 규칙 세팅하기” 입니다.

구조체로 묶어두는 이유

ExecutionCalculation에서 캡처 정의는 보통 “한 번 만들어서 계속 재사용”하는 쪽이 안전하고 편해요.

  • 같은 Attribute를 여러 Execution에서 캡처할 때 정의가 일관됨
  • Source/Target, Snapshot 같은 옵션을 한 군데서 관리
  • 오타(특히 Source/Target 뒤바뀜)로 인한 버그를 줄임
    그래서 흔히 static FDamageStatics& DamageStatics() 같은 형태로 싱글톤처럼 접근해서 사용한다고 합니다.

RelevantAttributesToCapture

생성자에서는 RelevantAttributesToCapture에 Capture를 등록했습니다.

UExecCalc_Damage::UExecCalc_Damage()
{
    // Capture할 Attribute 등록
    RelevantAttributesToCapture.Add(DamageStatics().DefenseDef);
    RelevantAttributesToCapture.Add(DamageStatics().HealthDef);
}

RelevantAttributesToCapture는 정확히 말하자면 CaptureDefinition들을 담는 배열/리스트라고 합니다.

Execution 안에서는 보통 다음과 같은 흐름이라고 합니다.
1. 생성자에서 RelevantAttributesToCapture에 필요한 캡처 정의를 등록
2. Execute_Implementation에서 AttemptCalculateCapturedAttributeMagnitude(...) 같은 함수로 값 계산 시도
3. 등록된 캡처 정의 기준으로 Source/Target + Snapshot 여부까지 반영해서 값을 가져옴

만약 RelevantAttributesToCapture에 등록을 안 해두면, 실행 단계에서 "캡처 데이터가 없다/준비가 안 됐다"로 인식되어 값 계산이 실패하거나 0으로 처리되는 식의 문제가 나기 쉬어진다고 합니다.

Execution 함수의 매개변수

  • FGameplayEffectCustomExecutionParameters는 Execution(계산기)에게 “입력/컨텍스트”를 전달하는 묶음
  • FGameplayEffectCustomExecutionOutput은 Execution이 계산한 결과를 GameplayEffect 쪽으로 “반영하라고 내보내는 출력 그릇"
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(
    DamageStatics().DefenseDef,
    EvalParams,
    Defense
);

RelevantAttributesToCapture에 등록해둔 Capture 정의(DefenseDef)를 실제 값으로 평가해주는 “계산 요청”입니다.

ExecCalc에서 방향 판정을 하는 이유
ExecCalc은 Source(공격자)와 Target(방어자) ASC에 모두 접근 가능합니다.
데미지 계산과 방향 판정을 한 곳에서 처리하면

  • ApplyDamageToActor()는 GE 적용만 담당 (단순)
  • 데미지 관련 로직이 ExecCalc에 집중 (응집도 높음)
  • 태그를 주고받을 필요 없음 (ExecCalc 안에서 직접 판단)
profile
게임 개발 지망생입니다.

0개의 댓글