0. 개요
이전의 실행 계산을 수정하여 Block Chance와 Armor 등을 고려한 데미지를 계산한다.
1. 데미지 실행 계산 수정
(1) 실제 데미지 반영 - ExecCalc_Damage
Set by Caller로 설정된 FireBolt의 데미지 값을 가져와 적용시키도록 수정한다.
FAggregatorEvaluateParameters EvalParams;
EvalParams.SourceTags = SourceTags;
EvalParams.TargetTags = TargetTags;
// Set by Caller Magnitude 가져오기
float Damage = Spec.GetSetByCallerMagnitude(FAuraGameplayTags::Get().Damage);
// 속성, 수정자, 속성 값
const FGameplayModifierEvaluatedData EvalData(UAuraAttributeSet::GetIncomingDamageAttribute(), EGameplayModOp::Additive, Damage);
OutexecutionOutput.AddOutputModifier(EvalData);
}
2. 블록 확룰(Block Chance) 반영
(1) BlockChance - Capture Definition 선언
// 단일 인스턴스 - 정적 구조체
struct AuraDamageStatics
{
// 속성에 대한 Def 생성
DECLARE_ATTRIBUTE_CAPTUREDEF(Armor);
DECLARE_ATTRIBUTE_CAPTUREDEF(BlockChance);
AuraDamageStatics()
{
// 정적 클래스, 속성, 소스 or 타겟, 스냅샷 여부
DEFINE_ATTRIBUTE_CAPTUREDEF(UAuraAttributeSet, Armor, Target, false);
DEFINE_ATTRIBUTE_CAPTUREDEF(UAuraAttributeSet, BlockChance, Target, false);
}
};
static const AuraDamageStatics& DamageStatics()
{
static AuraDamageStatics DStatics;
return DStatics;
}
UExecCalc_Damage::UExecCalc_Damage()
{
RelevantAttributesToCapture.Add(DamageStatics().ArmorDef);
RelevantAttributesToCapture.Add(DamageStatics().BlockChanceDef);
}
(2) 랜덤을 이용한 계산식 반영
// Set by Caller Magnitude 가져오기
float Damage = Spec.GetSetByCallerMagnitude(FAuraGameplayTags::Get().Damage);
// Block Chance 가져와서 블록이 발생했는지 확인
float TargetBlockChance = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().BlockChanceDef, EvalParams, TargetBlockChance);
TargetBlockChance = FMath::Max<float>(TargetBlockChance, 0.f);
const bool bBlocked = FMath::RandRange(1, 100) < TargetBlockChance;
// 발생 시 데미지는 절반
if (bBlocked)
{
Damage = Damage / 2.f;
}
// 속성, 수정자, 속성 값
const FGameplayModifierEvaluatedData EvalData(UAuraAttributeSet::GetIncomingDamageAttribute(), EGameplayModOp::Additive, Damage);
OutexecutionOutput.AddOutputModifier(EvalData);
}
타겟의 블록 확률을 가져와 저장한다.
랜덤 수를 뽑고 확률에 따라 블록 성공을 판단한다.
블록이 성공하면 데미지가 절반으로 감소한다.
테스트를 위해 GE_SecondaryAttribute_Enemy의 속성 값을 변경하면 즉시 적용되는 것을 볼 수 있다.
3. 방어력과 방어 관통(Armor, ArmorPenetration) 반영
(1) ArmorPenetration - Capture Definition 선언
// 단일 인스턴스 - 정적 구조체
struct AuraDamageStatics
{
// 속성에 대한 Def 생성
DECLARE_ATTRIBUTE_CAPTUREDEF(Armor);
DECLARE_ATTRIBUTE_CAPTUREDEF(ArmorPenetration);
DECLARE_ATTRIBUTE_CAPTUREDEF(BlockChance);
AuraDamageStatics()
{
// 정적 클래스, 속성, 소스 or 타겟, 스냅샷 여부
DEFINE_ATTRIBUTE_CAPTUREDEF(UAuraAttributeSet, Armor, Target, false);
DEFINE_ATTRIBUTE_CAPTUREDEF(UAuraAttributeSet, ArmorPenetration, Source, false);
DEFINE_ATTRIBUTE_CAPTUREDEF(UAuraAttributeSet, BlockChance, Target, false);
}
};
static const AuraDamageStatics& DamageStatics()
{
static AuraDamageStatics DStatics;
return DStatics;
}
UExecCalc_Damage::UExecCalc_Damage()
{
RelevantAttributesToCapture.Add(DamageStatics().ArmorDef);
RelevantAttributesToCapture.Add(DamageStatics().ArmorPenetrationDef);
RelevantAttributesToCapture.Add(DamageStatics().BlockChanceDef);
}
(2) 방어력과 방어관통 계산식 반영
// 방어 관통은 타겟의 방어력을 일정 퍼센트 무시함
// 타겟 방어력
float TargetArmor = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().ArmorDef, EvalParams, TargetArmor);
TargetArmor = FMath::Max<float>(0.f, TargetArmor);
// 소스 방어관통
float SourceArmorPenetration = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().ArmorPenetrationDef, EvalParams, SourceArmorPenetration);
SourceArmorPenetration = FMath::Max<float>(0.f, SourceArmorPenetration);
const float EffectiveArmor = TargetArmor * (100 - SourceArmorPenetration * 0.25f) / 100.f;
Damage *= (100 - EffectiveArmor * 0.25f) / 100.f;
// 속성, 수정자, 속성 값
const FGameplayModifierEvaluatedData EvalData(UAuraAttributeSet::GetIncomingDamageAttribute(), EGameplayModOp::Additive, Damage);
OutexecutionOutput.AddOutputModifier(EvalData);
}
임의의 방어력과 방어관통 계산식을 데미지에 반영한다.
4. 계수(Coefficient) 변경
아래의 계산식을 보자.
// 방어 관통은 타겟의 방어력을 일정 퍼센트 무시함
const float EffectiveArmor = TargetArmor * (100 - SourceArmorPenetration * 0.25f) / 100.f;
Damage *= (100 - EffectiveArmor * 0.25f) / 100.f;
소스의 방어 관통에 0.25를 곱하고 100에서 뺀 뒤 타겟 아머에 곱하고 다시 100으로 나누는 방법으로 유효한 방어력을 계산하고 있다.
레벨이 높아짐에 따라 스텟에 의한 방어관통이 매우 높아져 방어력이 의미 없어질 수 있다.
따라서 0.25에 해당하는 계수를 커브 테이블을 이용하여 변경할 수 있도록 해본다.
(1) 커브 테이블 생성
Constant 형태의 커브 테이블을 생성한다.
두 개의 커브를 생성하고 값을 적당히 조절한다.
(2) 커브 테이블을 데이터 에셋에 저장 - CharacterClassInfo
UPROPERTY(EditDefaultsOnly, Category = "Common Class Defaults|Damage")
TObjectPtr<UCurveTable> DamageCalculationCoefficients;
이제 생성한 커브 테이블을 가져올 수 있도록 데이터 에셋에 보관한다.
(3) 데이터 에셋에 쉽게 접근하기 - Aura Ability System Blueprint Library
데이터 에셋에 접근하기 위해서 항상 게임 모드를 캐스팅하여 가져온 뒤 접근하는 방법을 사용했다.
해당 방법을 간소화하기 위해 라이브러리에 함수를 추가한다.
UFUNCTION(BlueprintCallable, category = "AuraAbilitySystemLibrary|CharacterClassDefaults")
static UCharacterClassInfo* GetCharacterClassInfo(const UObject* WorldContextObject);
};
UCharacterClassInfo* UAuraAbilitySystemLibrary::GetCharacterClassInfo(const UObject* WorldContextObject)
{
AAuraGameModeBase* AuraGameMode = Cast<AAuraGameModeBase>(UGameplayStatics::GetGameMode(WorldContextObject));
if (AuraGameMode == nullptr)
return nullptr;
return AuraGameMode->CharacterClassInfo;
}
(4) 전투 인터페이스 가져오기 - ExecCalc_Damage
커브 테이블의 X축 값을 대입하여 Y축 값을 가져오기 위해 레벨을 가져와야 한다.
void UExecCalc_Damage::Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, FGameplayEffectCustomExecutionOutput& OutexecutionOutput) const
{
const UAbilitySystemComponent* SourceASC = ExecutionParams.GetSourceAbilitySystemComponent();
const UAbilitySystemComponent* TargetASC = ExecutionParams.GetTargetAbilitySystemComponent();
AActor* SourceAvatar = SourceASC ? SourceASC->GetAvatarActor() : nullptr;
AActor* TargetAvatar = TargetASC ? TargetASC->GetAvatarActor() : nullptr;
ICombatInterface* SourceCombatInterface = Cast<ICombatInterface>(SourceAvatar);
ICombatInterface* TargetCombatInterface = Cast<ICombatInterface>(TargetAvatar);
GetPlayerLevel로 레벨을 가져오기 위해 소스와 타겟의 전투 인터페이스를 가져온다.
(5) 커브 테이블에서 계수 값을 가져와서 적용하기
// 커브 테이블 방어 관통 계수가져오기
UCharacterClassInfo* CharacterClassInfo = UAuraAbilitySystemLibrary::GetCharacterClassInfo(SourceAvatar);
FRealCurve* ArmorPenetrationCurve = CharacterClassInfo->DamageCalculationCoefficients->FindCurve(FName("ArmorPenetration"), FString());
const float ArmorPenetrationCoefficient = ArmorPenetrationCurve->Eval(SourceCombatInterface->GetPlayerLevel());
// 방어 관통은 방어력의 일부를 무시함
const float EffectiveArmor = TargetArmor * (100 - SourceArmorPenetration * ArmorPenetrationCoefficient) / 100.f;
// 커브 테이블에서 방어력 계수 가져오기
FRealCurve* EffectiveArmorCurve = CharacterClassInfo->DamageCalculationCoefficients->FindCurve(FName("EffectiveArmor"), FString());
const float EffectiveArmorCoefficient = EffectiveArmorCurve->Eval(TargetCombatInterface->GetPlayerLevel());
// 최종 데미지 계산
Damage *= (100 - EffectiveArmor * EffectiveArmorCoefficient) / 100.f;
// 속성, 수정자, 속성 값
const FGameplayModifierEvaluatedData EvalData(UAuraAttributeSet::GetIncomingDamageAttribute(), EGameplayModOp::Additive, Damage);
OutexecutionOutput.AddOutputModifier(EvalData);
}
이제 RealCurve 형태의 커브를 가져와 Eval을 통해 레벨을 전달하여 계수를 가져온다.
가져온 계수를 대입하여 데미지 식을 완성한다.
크래시가 발생하면 null check를 실시한다.
'UE 5 스터디 > Gameplay Ability System(GAS)' 카테고리의 다른 글
12-1. 게임플레이 이펙트 컨텍스트 - (1) 커스텀 게임플레이 이펙트 컨텍스트, 직렬화(Serialize) (0) | 2024.12.24 |
---|---|
11-12. 데미지 실행 계산(Execution Calculation) - (4) 크리티컬 계산 (0) | 2024.12.23 |
11-10. 데미지 실행 계산(Execution Calculation) - (2) 속성 캡쳐(Capturing Attributes) (0) | 2024.12.19 |
11-9. 게임플레이 이펙트 - (8) 실행 계산(Execution Calculation) (0) | 2024.12.19 |
11-8. UI - (8) 플로팅 텍스트 위젯 - 데미지 표현 (0) | 2024.12.19 |