구현 사항
콤보 공격 히트시 뒤로 갈수록 전달하는 데미지가 증가하도록 설정
레벨에 따라 스폰된 캐릭터가 다른 체력 값을 가지도록 설정
게임플레이 이펙트(GE, Gameplay Effect)
GAS는 게임에 영향을 주는(게임 데이터 변경) 객체(GE)를 별도로 분리해서 관리하므로 대부분 게임플레이 이펙트와 어트리뷰트는 함께 동작하도록 구성됨
Instant : 어트리뷰트에 즉각적으로 적용되는 게임플레이 이펙트, 한 프레임에 실행됨
Duration : 지정한 시간 동안 동작하는 게임플레이 이펙트
Infinite : 명시적으로 종료하지 않으면 계속 동작하는 게임플레이 이펙트
다양하게 많은 옵션을 제공하여 다양하고 복잡한 작업의 수행이 가능함
게임플레이 이펙트 모디파이어(Modifier)
GE에서 어트리뷰트의 변경 방법을 설정
변경 방법
더하기, 곱하기, 나누기, 덮어쓰기
계산 방법
ScalableFloat : 실수(대입, 데이터테이블과 연동 가능)
AttributeBased : 특정 어트리뷰트 기반
CustomCalculationClass : 계산을 담당하는 전용 클래스 활용
SetByCaller : 데이터 태그를 활용한 데이터 전달
모디파이어 없이 GameplayEffectExecutionCalculation을 상속받아 자체 계산 로직을 만드는 것도 가능
블루프린트로 제작하는 것을 권장하는 이유
간단한 작업을 C++보다 블루프린트가 간단하고 생산성이 좋음
이펙트에서 어트리뷰트의 프로퍼티에 접근하기 위해 프렌드를 설정해야 함
void UABGA_AttackHitCheck::OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
{
if (UAbilitySystemBlueprintLibrary::TargetDataHasHitResult(TargetDataHandle, 0))
{
FGameplayEffectSpecHandle EffectSpecHandle = MakeOutgoingGameplayEffectSpec(AttackDamageEffect);
if (EffectSpecHandle.IsValid())
{
ApplyGameplayEffectSpecToTarget(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, EffectSpecHandle, TargetDataHandle);
}
}
bool bReplicateEndAbility = true;
bool bWasCancelled = false;
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicateEndAbility, bWasCancelled);
}
메타 어트리뷰트
어트리뷰트의 설정을 위해 사전에 미리 설정하는 임시 어트리뷰트
- 체력을 바로 깎지 않고, 데미지를 통해 체력을 감소하도록 설정
체력은 일반 어트리뷰트, 데미지는 메타 어트리뷰트
기획 추가에 유연한 대처가 가능
- 무적 기능 추가 : 데미지를 0으로 처리
- 실드 기능 추가 : 실드 값을 토대로 실드 값만큼 데미지를 처리
- 콤보 진행시 공격력 보정 추가 : 콤보 증가시 데미지를 보정
메타 어트리뷰트는 적용 후 바로 0으로 값을 초기화되도록 설정
클라이언트에는 최종 체력 정보만 보내주면 되기 때문에 리플리케이션에서 제외시키는 것이 일반적
void UABCharacterAttributeSet::PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData& Data)
{
constexpr float MinimumHealth = 0.0f;
if (Data.EvaluatedData.Attribute == GetDamageAttribute())
{
SetHealth(FMath::Clamp(GetHealth() - GetDamage(), MinimumHealth, GetMaxHealth()));
SetDamage(0.0f);
}
}
레벨과 커브테이블
게임플레이 이펙트에 추가적으로 레벨 정보를 지정하여 커브테이블에서 특정 값을 가져올 수 있음
ScalableFloat 모디파이어 타입을 사용하여 가능
void UAnimNotify_GASAttackHitCheck::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference)
{
Super::Notify(MeshComp, Animation, EventReference);
if (MeshComp)
{
if (AActor* Owner = MeshComp->GetOwner())
{
FGameplayEventData PayloadData;
PayloadData.EventMagnitude = ComboAttackLevel;
UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(Owner, TriggerGameplayTag, PayloadData);
}
}
}
void UABGA_AttackHitCheck::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
CurrentComboLevel = TriggerEventData->EventMagnitude;
}
게임플레이 이펙트 생성 과정
게임플레이 이펙트 컨텍스트와 게임플레이 이펙트 스펙을 통해 생성할 수 있음
게임플레이 이펙트 컨텍스트
GE에서 계산에 필요한 데이터를 담은 객체
Instigator(가해자), Causer(가해수단), HitResult(판정정보) 등
게임플레이 이펙트 스펙
GE에 관련된 정보를 담고 있는 객체
레벨, 모디파이어, 태그에 대한 정보, 게임플레이 이펙트 컨텍스트 핸들
ASC는 각 데이터를 핸들 객체를 통해 간접적으로 관리하므로 이펙트 컨텍스트 핸들 -> 이펙트 스펙 핸들 순으로 생성해야 함
void AABGASCharacterNonPlayer::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
AbilitySystemComponent->InitAbilityActorInfo(this, this);
FGameplayEffectContextHandle EffectContextHandle = AbilitySystemComponent->MakeEffectContext();
EffectContextHandle.AddSourceObject(this);
FGameplayEffectSpecHandle EffectSpecHandle = AbilitySystemComponent->MakeOutgoingSpec(InitStatEffect, Level, EffectContextHandle);
if (EffectSpecHandle.IsValid())
{
AbilitySystemComponent->ApplyGameplayEffectSpecToSelf(*EffectSpecHandle.Data.Get());
}
}
기간형 게임플레이 이펙트
인스턴트 타입이 아닌 게임플레이 이펙트
유효기간 동안 태그와 같은 상태를 가질 수 있음
중첩(Stack)이 가능하도록 설정이 가능
모디파이어를 설정하지 않아도 다양하게 활용 가능
인스턴트는 Base값을 변경하지만 기간형은 Current값을 변경하고 원래대로 돌려놓음
bool UABCharacterAttributeSet::PreGameplayEffectExecute(struct FGameplayEffectModCallbackData& Data)
{
if (Data.EvaluatedData.Attribute == GetDamageAttribute())
{
if (Data.Target.HasMatchingGameplayTag(ABTAG_CHARCTER_INVINSIBLE))
{
return false;
}
}
return true;
}