Unreal: Gameplay Ability System(GAS) - Gameplay Effect (GE) 2
Unreal: Gameplay Ability System(GAS) - Gameplay Effect (GE) 2
Gameplay Effect (GE) 내용 보충
- Set By Caller를 이용하면 외부에서 전해주는 값에 따라 어트리뷰트를 변경하는 것도 가능하다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// 예: 트랩, 투사체, 스킬 등 외부 객체가 데미지 값을 정해서 GE에 전달하는 경우 FGameplayEffectContextHandle Context = ASC->MakeEffectContext(); FGameplayEffectSpecHandle SpecHandle = ASC->MakeOutgoingSpec(DamageGEClass, 1.f, Context); if (SpecHandle.IsValid()) { // GE 안에서 같은 GameplayTag를 Set By Caller Magnitude로 사용해야 한다. SpecHandle.Data->SetSetByCallerMagnitude( FGameplayTag::RequestGameplayTag(TEXT("Data.Damage")), DamageAmount ); ASC->ApplyGameplayEffectSpecToTarget( *SpecHandle.Data.Get(), TargetASC ); }
- 더 어렵거나 복잡한 계산을 필요로 할경우 ExecutionCalculator를 생성해 사용할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
// 예: 공격력, 방어력, 치명타, 태그 상태 등을 조합해 최종 데미지를 계산하는 구조 void UDamageExecution::Execute_Implementation(const FGameplayEffectCustomExecutionParameters& Params, FGameplayEffectCustomExecutionOutput& OutOutput) const { float AttackPower = 0.f; float Defense = 0.f; // 실제 구현에서는 CaptureDef를 통해 Source/Target Attribute를 가져온다. // 여기서는 계산 흐름만 예시로 둔다. float RawDamage = AttackPower - Defense; float FinalDamage = FMath::Max(RawDamage, 0.f); OutOutput.AddOutputModifier( FGameplayModifierEvaluatedData( UMyAttributeSet::GetHealthAttribute(), EGameplayModOp::Additive, -FinalDamage ) ); }
- 더 어렵거나 복잡한 계산을 필요로 할경우 ExecutionCalculator를 생성해 사용할 수 있다.
- 클램핑을 이용해 어트리뷰트를 제한/보호 가능하다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
void UMyAttributeSet::PreAttributeChange( const FGameplayAttribute& Attribute, float& NewValue ) { Super::PreAttributeChange(Attribute, NewValue); if (Attribute == GetHealthAttribute()) { NewValue = FMath::Clamp(NewValue, 0.f, GetMaxHealth()); } else if (Attribute == GetStaminaAttribute()) { NewValue = FMath::Clamp(NewValue, 0.f, GetMaxStamina()); } }
- 필요시에는 PostGameplayEffectExecute()에서 SetHealth(GetHealth());같은 방식으로 CurrentValue(버프등에 의해 변화한 보정값)가 아닌 BaseValue(원본 기준 값)까지 변화시켜줄 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
void UMyAttributeSet::PostGameplayEffectExecute( const FGameplayEffectModCallbackData& Data ) { Super::PostGameplayEffectExecute(Data); if (Data.EvaluatedData.Attribute == GetHealthAttribute()) { // GetHealth()로 현재 계산된 값을 가져오고, // SetHealth()로 BaseValue 쪽에도 보정된 값을 다시 반영한다. SetHealth(FMath::Clamp(GetHealth(), 0.f, GetMaxHealth())); } if (Data.EvaluatedData.Attribute == GetStaminaAttribute()) { SetStamina(FMath::Clamp(GetStamina(), 0.f, GetMaxStamina())); } }
- ASC를 별도로 만들어서 사용할 수도 있다.
- 블루프린트에서 구현하던 것 대신, ApplyInitEffects(), ApplyGameplayEffectSpecToSelf()등을 코드상에서 구현할 수 있다.
- AttributeSet내부 생성자에서 직접 초기화하지 않고, ASC를 통해 GE를 호출해 초기화할 수 있다. ```c++ // Character BeginPlay 등에서 호출
void AMyCharacter::BeginPlay() { Super::BeginPlay();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
if (MyASC) { // GE_Init, GE_FullStat 같은 초기화용 GE가 InitEffects에 들어있다고 가정 MyASC->ApplyInitEffects(); } } ``` * 어빌리티의 초기화 및 ID를 통한 관리도 ASC에서 진행할 수 있다. map구조로 열거형과 어빌리티를 조합할 수 있는 구조로 만들면, 에디터에서도 간단하게 어빌리티를 여러종류 할당할 수 있는 구조로 만들 수 있다. ```c++ void UMyAbilitySystemComponent::GiveInitAbilities() { if (!GetOwner()) { return; } //열거열 EAbilityID와 어빌리티 클래스에 따른 어빌리티 부여 for (const TPair<EAbilityID, TSubclassOf<UGameplayAbility>>& Pair : InitAbilities) { const EAbilityID InputID = Pair.Key; TSubclassOf<UGameplayAbility> AbilityClass = Pair.Value; if (!AbilityClass) { continue; } FGameplayAbilitySpec AbilitySpec( AbilityClass, 1, // 어빌리티 레벨 InputID, // 입력ID nullptr ); GiveAbility(AbilitySpec); } } ```
This post is licensed under CC BY 4.0 by the author.