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를 실시한다.