내적은 두 백터가 얼마나 같은 방향인지를 나타내는 값입니다.
현재 블록 애니메이션은 전방을 향해 가드를 하고 있습니다.
만약 적이 뒤에서 공격했는데 블록되는 것은 알맞지 못합니다.
이 때 적의 공격이 뒤에서인지 정면에서인지를 확인하기 위해서 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으로 시작해서 서서히 줄이며 테스트를 해봐야겠습니다.
여러 Attribute를 참조하는 복잡한 계산을 수행하는 시스템입니다.
||Modifier|Execution Calculation|
||||
|용도|단순 계산|복잡한 계산|
|연산|Add, Multiply, Divide, Override|C++ 로직 (조건문, 반복문 등)|
|Attribute 참조|1개|여러 개 가능|
|예시|Health + 10, Stamina 1.5|(Damage - Defense) CriticalMultiplier|
|성능|빠름|상대적으로 느림|
Cpature
좀 더 자세히 알아보겠습니다.
GameplayEffectExecutionCalculation(Execution)에서 Capture는 “계산에 필요한 Attribute 값을 어디서/언제/어떤 방식으로 읽어올지”를 사전에 선언해두는 규칙이라고 합니다.
Execution은 Execute_Implementation 안에서 AttempCalculateCapturedAttributeMagnitude() 같은 함수로 값을 뽑아 쓰는데, 이 때 그 값의 출처(소스/타겟), 어떤 Atrribute인지, 언제의 값인지가 바로 Capture 설정으로 결정됩니다.
보통 FGameplayEffectAttributeCaptureDefinition으로 정의합니다.
bSnapshot = true / 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);
}
};
앞서 말한 함수명들을 매크로가 대신해주고 있는 것으로 보입니다.
개념적으로는 다음과 같은 맴버가 생긴다고 보면 됩니다.
// 대략 이런 멤버가 선언됨(개념 스켈레톤)
FGameplayEffectAttributeCaptureDefinition DefenseDef;
즉, “나중에 Defense를 어떤 방식으로 캡처할지”를 담는 그릇(정의 객체) 를 구조체에 만들어두는 단계입니다.
개념적으로는 이런 코드가 들어간다고 생각하시면 됩니다(스켈레톤):
// 핵심은 "UMyAttributeSet의 Defense 속성을 Target에서, Snapshot=false로 캡처"
DefenseDef = FGameplayEffectAttributeCaptureDefinition(
/* UMyAttributeSet::GetDefenseAttribute() 같은 getter로 속성 지정 */,
EGameplayEffectAttributeCaptureSource::Target,
/*bSnapshot=*/false
);
정리하면, DECLARE = “변수 만들기”, DEFINE = “그 변수에 규칙 세팅하기” 입니다.
ExecutionCalculation에서 캡처 정의는 보통 “한 번 만들어서 계속 재사용”하는 쪽이 안전하고 편해요.
생성자에서는 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 함수의 매개변수
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(
DamageStatics().DefenseDef,
EvalParams,
Defense
);
RelevantAttributesToCapture에 등록해둔 Capture 정의(DefenseDef)를 실제 값으로 평가해주는 “계산 요청”입니다.
ExecCalc에서 방향 판정을 하는 이유
ExecCalc은 Source(공격자)와 Target(방어자) ASC에 모두 접근 가능합니다.
데미지 계산과 방향 판정을 한 곳에서 처리하면
ApplyDamageToActor()는 GE 적용만 담당 (단순)