1. 멀티플레이어 환경에서 크래시 해결
리슨 서버로 플레이할 때 플레이어가 2명 이상일때 크래시가 발생하는 것은 InitializeDefaultAttributes를 호출할때 AuraAbilitySystemLibrary의 ClassDefaultInfo가 nullptr이기 때문에 발생한다.
이는 해당 클래스가 게임모드를 가져오는데 멀티플레이어에서는 게임모드가 서버 측에서만 실행되기 때문이다.
void AAuraEnemy::BeginPlay()
{
Super::BeginPlay();
GetCharacterMovement()->MaxWalkSpeed = bHitReacting ? 0.f : BaseWalkSpeed;
InitAbilityActorInfo();
if (HasAuthority())
{
UAuraAbilitySystemLibrary::GiveStartupAbilities(this, AbilitySystemComponent);
}
void AAuraEnemy::InitAbilityActorInfo()
{
AbilitySystemComponent->InitAbilityActorInfo(this, this);
Cast<UAuraAbilitySystemComponent>(AbilitySystemComponent)->AbilityActorInfoSet();
if (HasAuthority())
{
InitializeDefaultAttributes();
}
}
2. 클라이언트에서 데미지 텍스트가 발생하지 않는 문제
데미지 텍스트가 클라이언트가 아니라 서버에서 발생하는 문제를 해결한다.
(1) 널 참조 오류 해결
디버그 환경을 클라이언트로 변경해서 전투를 시작하면 투사체를 발사한 후에 LoopingSoundComponent가 nullptr인 것을 볼 수 있다.
이를 해결하기 위해 널 체크를 한다.
void AAuraProjectile::Destroyed()
{
// 클라이언트
if (!bHit && !HasAuthority())
{
UGameplayStatics::PlaySoundAtLocation(this, ImpactSound, GetActorLocation(), FRotator::ZeroRotator);
UNiagaraFunctionLibrary::SpawnSystemAtLocation(this, ImpactEffect, GetActorLocation());
if (LoopingSoundComponent)
LoopingSoundComponent->Stop();
}
Super::Destroyed();
}
void AAuraProjectile::OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
UGameplayStatics::PlaySoundAtLocation(this, ImpactSound, GetActorLocation(), FRotator::ZeroRotator);
UNiagaraFunctionLibrary::SpawnSystemAtLocation(this, ImpactEffect, GetActorLocation());
if (LoopingSoundComponent)
LoopingSoundComponent->Stop();
(2) 메타 속성
속성 세트의 PostGameplayEffectExecute 내의 코드를 살펴본다.
// 데미지 판단
if (Data.EvaluatedData.Attribute == GetIncomingDamageAttribute())
{
const float LocalIncomingDamage = GetIncomingDamage();
SetIncomingDamage(0);
if (LocalIncomingDamage > 0.f)
{
const float NewHealth = GetHealth() - LocalIncomingDamage;
SetHealth(FMath::Clamp(NewHealth, 0, GetMaxHealth()));
const bool bFatal = NewHealth <= 0.f;
if (bFatal)
{
ICombatInterface* CombatInterface = Cast<ICombatInterface>(Props.TargetAvatarActor);
if (CombatInterface)
{
CombatInterface->Die();
}
}
else
{
// HitReact 태그 부여
FGameplayTagContainer TagContainer;
TagContainer.AddTag(FAuraGameplayTags::Get().Effects_HitReact);
// 부여된 어빌리티를 찾아서 활성화
Props.TargetASC->TryActivateAbilitiesByTag(TagContainer);
}
const bool bBlock = UAuraAbilitySystemLibrary::IsBlockedHit(Props.EffectContextHandle);
const bool bCriticalHit = UAuraAbilitySystemLibrary::IsCriticalHit(Props.EffectContextHandle);
ShowFloatingText(Props, LocalIncomingDamage, bBlock, bCriticalHit);
}
}
들어올 데미지를 계산한 뒤 ShowFloatingText를 통하여 텍스트 컴포넌트를 생성하여 붙였다 떼는 작업을 하고 있다.
그런데 메타 속성에 대해 if 체크를 하고 있기 때문에, 복제가 되지 않는 메타 속성의 특징으로 인해 클라이언트에게 텍스트가 발생하지 않고 있는 것을 볼 수 있다.
void UAuraAttributeSet::ShowFloatingText(const FEffectProperties& Props, float Damage, bool bBlockedHit, bool bCriticalHit) const
{
// 데미지 플로팅 위젯
// 자해 불가
if (Props.SourceCharacter != Props.TargetCharacter)
{
if (AAuraPlayerController* PC = Cast<AAuraPlayerController>(Props.SourceController))
{
PC->ShowDamageNumber(Damage, Props.TargetCharacter, bBlockedHit, bCriticalHit);
}
}
}
플레이어 컨트롤러 내의 ShowDamageNumber 함수는 클라이언트 RPC 함수이므로 이 함수를 작동시키면 클라이언트에서도 정상적으로 피해량 수치가 나타날 것이다.
따라서 플레이어 컨트롤러인 PC가 정상적으로 컨트롤러를 가져오고 있는지 확인해야 한다.
(3) 서버에서 클라이언트의 피해량 가리기
이제 서버에서 클라이언트의 피해량 수치가 나타나지 않도록 하면 된다.
void AAuraPlayerController::ShowDamageNumber_Implementation(float DamageAmount, ACharacter* TargetCharacter, bool bBlockedHit, bool bCriticalHit)
{
// 위젯
// IsValid - 사망이 보류 중일 경우를 포함
if (IsValid(TargetCharacter) && DamageTextComponentClass && IsLocalController())
{
PC의 ShowDamageNumber에서 서버로 또 전송하지 않도록 로컬 컨트롤러인지 체크해주면 된다.
3. 구체가 적에게 충돌하지 않는 문제
(1) 시전자에게 부딫히는 투사체 버그
구체가 발생한 이후에 적을 뚫고 나가며 아무런 데미지도 입히지 않는 버그이다.
해당 버그는 생성되는 순간에 해당 게임플레이 이펙트를 발생시킨 액터와 충돌하기 때문에 생기는 현상이다.
(플레이어 캐릭터에 파이어볼트가 겹쳐서 소멸)
void AAuraProjectile::OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if (DamageEffectSpecHandle.Data.IsValid() && DamageEffectSpecHandle.Data.Get()->GetContext().GetEffectCauser() == OtherActor)
return;
파이어볼트가 플레이어 캐릭터와 부딫히지 않게 수정한다.
(2) 투사체의 Z축 각도
void UAuraProjectileSpell::SpawnProjectile(const FVector& ProjectileTargetLocation)
{
ICombatInterface* CombatInterface = Cast<ICombatInterface>(GetAvatarActorFromActorInfo());
if (CombatInterface)
{
const FVector SocketLocation = CombatInterface->GetCombatSocketLocation();
// 소켓에서 타겟까지의 벡터의 각도만 가져옴
FRotator Rotation = (ProjectileTargetLocation - SocketLocation).Rotation();
// 지면과 평행하게 진행, 중력을 적용하려면 값을 올리면 됨
Rotation.Pitch = 0.f;
위의 코드에서 Rotation.Pitch = 0.f; 를 제거한다.
4. 클라이언트에서 발생하는 소리 문제
클라이언트에서 투사체가 충돌할때 1개 이상의 소리가 중첩되는 버그가 있다.
void AAuraProjectile::OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if (DamageEffectSpecHandle.Data.IsValid() && DamageEffectSpecHandle.Data.Get()->GetContext().GetEffectCauser() == OtherActor)
return;
if (!bHit)
{
UGameplayStatics::PlaySoundAtLocation(this, ImpactSound, GetActorLocation(), FRotator::ZeroRotator);
UNiagaraFunctionLibrary::SpawnSystemAtLocation(this, ImpactEffect, GetActorLocation());
if (LoopingSoundComponent)
LoopingSoundComponent->Stop();
}
충돌 여부는 서버에서 판단하고, 클라이언트에서는 bHit 불리언만 관리하고 있다.
bHit이 false일 경우에만 소리를 발생시킨다.
'UE 5 스터디 > Gameplay Ability System(GAS)' 카테고리의 다른 글
13-2. AI - (2) 커스텀 AI 컨트롤러, 비헤이비어 트리와 블랙보드 생성 (0) | 2024.12.27 |
---|---|
13-1. AI - (1) 몬스터 AI 설정 계획 (0) | 2024.12.27 |
12-9. 데미지 타입 - (3) 속성 저항 데미지 계산 (0) | 2024.12.26 |
12-8. 데미지 타입 - (2) 속성 저항 도입 (0) | 2024.12.26 |
12-7. 데미지 타입 - (1) 게임플레이 태그와 게임플레이 어빌리티 생성 (0) | 2024.12.26 |