일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 유니티
- os
- 게임개발
- attribute
- dirty cow
- gameplay ability system
- gameplay effect
- map design
- local prediction
- gravity direction
- Unreal Engine
- ret2libc
- 게임 개발
- 언리얼 엔진
- rpc
- Replication
- unity
- animation
- ability task
- nanite
- Multiplay
- MAC
- listen server
- CTF
- gameplay tag
- UI
- photon fusion2
- Aegis
- 언리얼엔진
- gas
- Today
- Total
Replicated
[Drag Down] Local Prediction 스테미나 자동 회복 시스템 Engine & Bug in Gameplay Effect (비추천) 본문
[Drag Down] Local Prediction 스테미나 자동 회복 시스템 Engine & Bug in Gameplay Effect (비추천)
라구넹 2025. 4. 5. 17:50해결하고 나서 생각한 거 : 주기적으로 변화하는 값, 특히 짧은 주기로 변화하는 값, 시점을 컨트롤하지 못하는 건 예측 실행해선 안된다.
이전 글에서 발생한 문제..
스테미나 깎이는게 UI에서 제대로 안보인다
모아서 보내는 언리얼 리플리케이션 시스템 상 어쩔 수 없는 듯
멀티 개발은 해도 해도 어려운 것 같다
이걸 냅두기엔 나도 불편한데 사용자가 불편하지 않을리가 없다
UX를 챙기자..
그래서 생각한 방법은,
일단 어빌리티를 만들고 ActivateAbility에서 타이머를 설정하는 거다 실패했다.
두번째로 생각한 방법은 UAbilityTask_WaitDelay로 예측 실행을 시도하는 거다 실패했다.
대체 왜 안되는 건지 이해가 안가서 로그를 이리저리 찍어보는데
이펙트 적용은 빠르게 되는데 UI가 그냥 못 따라간다?

void UDDGASStaminaBarUserWidget::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
{
Super::NativeTick(MyGeometry, InDeltaTime);
if ( !Owner->HasAuthority() )
{
UE_LOG(LogDD, Log, TEXT("[NetMode %d] Current Stamina : %f"), GetWorld()->GetNetMode(), ASC->GetSet<UDDAttributeSet>()->GetStamina());
}
}
로그를 찍으니 그냥 이펙트 적용된게??

로그를 늘리거 늘린 결과..

혹시나 맨 처음 발동은 됐을까 스테미나 0으로 시작해보기 -> 안됨
이펙트가 작동한 척만 하고 스테미나 못 건드리고 있는 것 같다
if (!AvatarCharacter->HasAuthority() && ASC->GetSet<UDDAttributeSet>()->GetStamina() < 99.5f)
{
UE_LOG(LogDD, Warning, TEXT("== APPLY ATTEMPT =="));
UE_LOG(LogDD, Warning, TEXT("IsLocallyControlled: %s"), AvatarCharacter->IsLocallyControlled() ? TEXT("YES") : TEXT("NO"));
UE_LOG(LogDD, Warning, TEXT("HasAuthority: %s"), AvatarCharacter->HasAuthority() ? TEXT("YES") : TEXT("NO"));
UE_LOG(LogDD, Warning, TEXT("PredictionKey Valid: %s"), ASC->ScopedPredictionKey.IsValidKey() ? TEXT("YES") : TEXT("NO"));
UE_LOG(LogDD, Warning, TEXT("EffectSpec Valid: %s"), RegenStaminaEffectHandle.IsValid() ? TEXT("YES") : TEXT("NO"));
const float Base = ASC->GetNumericAttributeBase(UDDAttributeSet::GetStaminaAttribute());
const float Current = ASC->GetNumericAttribute(UDDAttributeSet::GetStaminaAttribute());
UE_LOG(LogDD, Log, TEXT("[NetMode : %d] Get Stamina : %f"), GetWorld()->GetNetMode(), ASC->GetSet<UDDAttributeSet>()->GetStamina());
UE_LOG(LogDD, Warning, TEXT("Stamina - Base: %f | Current: %f"), Base, Current);
}

근데 일단 이펙트를 통한 동기화 자체가 한계가 있는 듯 하다

Local Preciction 어빌리티에서 Prediction Window가 열린 상태로, 이펙트를 적용시킴에도 PreAtt~가 이펙트 적용 시마다 발생하지 않는다.
테스트하려고 인풋 받으면 회복하는 식으로 만들어봤다
근데 이래도 느리다???
근데 그냥 공격은 동기화가 잘 된다..?
// Fill out your copyright notice in the Description page of Project Settings.
#include "GA/DDGA_RegenStamina.h"
#include "GameFramework/Character.h"
#include "AbilitySystemComponent.h"
#include "AbilitySystemGlobals.h"
#include "GameplayEffect.h"
#include "DragDown.h"
#include "Abilities/Tasks/AbilityTask_WaitDelay.h"
#include "Attribute/DDAttributeSet.h"
UDDGA_RegenStamina::UDDGA_RegenStamina()
{
NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted;
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
static ConstructorHelpers::FClassFinder<UGameplayEffect> RegenStaminaEffectRef(TEXT("/Game/Blueprint/GA/GE/BPGE_DownStamina.BPGE_DownStamina_C"));
if (RegenStaminaEffectRef.Succeeded())
{
RegenStaminaEffect = RegenStaminaEffectRef.Class;
}
}
void UDDGA_RegenStamina::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
ApplyRegenEffect();
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, true, false);
}
void UDDGA_RegenStamina::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)
{
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
}
void UDDGA_RegenStamina::CancelAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateCancel)
{
Super::CancelAbility(Handle, ActorInfo, ActivationInfo, bReplicateCancel);
}
void UDDGA_RegenStamina::OnAvatarSet(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec)
{
Super::OnAvatarSet(ActorInfo, Spec);
AvatarCharacter = Cast<ACharacter>(ActorInfo->AvatarActor.Get());
ASC = ActorInfo->AbilitySystemComponent.Get();
}
void UDDGA_RegenStamina::ApplyRegenEffect()
{
if (ASC == nullptr)
{
UE_LOG(LogDD, Warning, TEXT("UDDGA_RegenStamina - ApplyRegenEffect - No ASC"));
return;
}
if (RegenStaminaEffect == nullptr)
{
UE_LOG(LogDD, Warning, TEXT("UDDGA_RegenStamina - ApplyRegenEffect - No RegenStaminaEffect"));
return;
}
FScopedPredictionWindow ScopedPrediction(ASC, !AvatarCharacter->HasAuthority());
FGameplayEffectSpecHandle EffectSpecHandle = MakeOutgoingGameplayEffectSpec(RegenStaminaEffect);
if (EffectSpecHandle.IsValid())
{
EffectSpecHandle.Data->SetSetByCallerMagnitude(FGameplayTag::RequestGameplayTag(FName("Data.StaminaUsed")), -30.0f);
ApplyGameplayEffectSpecToOwner(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, EffectSpecHandle);
}
}
그래서 소모로 바꿔보니까 소모는 또 작동이 잘 된다??
같은 수치에서 테스트해도 이런다
근데 이펙트 원래 쓰던 건(RegenStamina) 또 느리다..?

회복 조차 왼쪽 걸 쓰는게 빠르다?
뭘 해도 왼쪽게 빠르다
설마 오른쪽 거 처음에 Infinite에 Period 설정했던 거 메타데이터가 안지워진 건가??
그럼 당연히 Prediction 안될텐데(Period는 예측 안됨)
저거 지우고 다시 만들어서 해봤다
반응이 너무 잘된다!
아마 엔진에 버그가 있는 것 같다..
Period로 설정하고 다시 Instant로 돌려놔도 Local Prediction이 안되도록 되어 있나 보다
이것 때문에 고생을 한참 했는데.. 안될 이유도 아무리 생각해도 없는데..
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Abilities/GameplayAbility.h"
#include "DDGA_RegenStamina.generated.h"
/**
*
*/
UCLASS()
class DRAGDOWN_API UDDGA_RegenStamina : public UGameplayAbility
{
GENERATED_BODY()
public:
UDDGA_RegenStamina();
virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
protected:
void EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled) override;
void CancelAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateCancel) override;
virtual void OnAvatarSet(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec) override;
UFUNCTION()
void ApplyRegenEffect();
UPROPERTY()
TSubclassOf< class UGameplayEffect > RegenStaminaEffect;
UPROPERTY()
TObjectPtr<class ACharacter> AvatarCharacter;
UPROPERTY()
TObjectPtr<class UAbilitySystemComponent> ASC;
UPROPERTY()
TObjectPtr<class UAbilityTask_WaitDelay> WaitTask;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "GA/DDGA_RegenStamina.h"
#include "GameFramework/Character.h"
#include "AbilitySystemComponent.h"
#include "AbilitySystemGlobals.h"
#include "GameplayEffect.h"
#include "DragDown.h"
#include "Abilities/Tasks/AbilityTask_WaitDelay.h"
#include "Attribute/DDAttributeSet.h"
UDDGA_RegenStamina::UDDGA_RegenStamina()
{
NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted;
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
static ConstructorHelpers::FClassFinder<UGameplayEffect> RegenStaminaEffectRef(TEXT("/Game/Blueprint/GA/GE/BPGE_RegenStamina.BPGE_RegenStamina_C"));
if (RegenStaminaEffectRef.Succeeded())
{
RegenStaminaEffect = RegenStaminaEffectRef.Class;
}
}
void UDDGA_RegenStamina::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
WaitTask = UAbilityTask_WaitDelay::WaitDelay(this, 0.1f);
if (WaitTask)
{
WaitTask->OnFinish.AddDynamic(this, &UDDGA_RegenStamina::ApplyRegenEffect);
WaitTask->ReadyForActivation();
}
}
void UDDGA_RegenStamina::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)
{
if ( WaitTask )
{
WaitTask->EndTask();
}
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
}
void UDDGA_RegenStamina::CancelAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateCancel)
{
Super::CancelAbility(Handle, ActorInfo, ActivationInfo, bReplicateCancel);
}
void UDDGA_RegenStamina::OnAvatarSet(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec)
{
Super::OnAvatarSet(ActorInfo, Spec);
AvatarCharacter = Cast<ACharacter>(ActorInfo->AvatarActor.Get());
ASC = ActorInfo->AbilitySystemComponent.Get();
}
void UDDGA_RegenStamina::ApplyRegenEffect()
{
if (ASC == nullptr)
{
UE_LOG(LogDD, Warning, TEXT("UDDGA_RegenStamina - ApplyRegenEffect - No ASC"));
return;
}
if ( ASC->GetSet<UDDAttributeSet>() && ASC->GetSet<UDDAttributeSet>()->GetStamina() >= 100.0f)
{
WaitTask = UAbilityTask_WaitDelay::WaitDelay(this, 0.1f);
if (WaitTask)
{
WaitTask->OnFinish.AddDynamic(this, &UDDGA_RegenStamina::ApplyRegenEffect);
WaitTask->ReadyForActivation();
}
return;
}
if (RegenStaminaEffect == nullptr)
{
UE_LOG(LogDD, Warning, TEXT("UDDGA_RegenStamina - ApplyRegenEffect - No RegenStaminaEffect"));
return;
}
if ( !AvatarCharacter->HasAuthority() )
{
UE_LOG(LogDD, Log, TEXT("ApplyRegenEffect"));
}
FScopedPredictionWindow ScopedPrediction(ASC, !AvatarCharacter->HasAuthority());
FGameplayEffectSpecHandle EffectSpecHandle = MakeOutgoingGameplayEffectSpec(RegenStaminaEffect);
if (EffectSpecHandle.IsValid())
{
EffectSpecHandle.Data->SetSetByCallerMagnitude(FGameplayTag::RequestGameplayTag(FName("Data.StaminaUsed")), 1.0f);
ApplyGameplayEffectSpecToOwner(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, EffectSpecHandle);
}
WaitTask = UAbilityTask_WaitDelay::WaitDelay(this, 0.1f);
if (WaitTask)
{
WaitTask->OnFinish.AddDynamic(this, &UDDGA_RegenStamina::ApplyRegenEffect);
WaitTask->ReadyForActivation();
}
}
WaitTask에서 시간 간격을 주고 ApplyRegenEffect를 발동시킨다
그리고 ApplyRegenEffect에서 WaitTask 만들어서 다시 시간 간격 뒤에 발동
일반 재귀랑 다르게 태스크로 나눠서 처리하는 거라 스택 터질 걱정도 없긴 하다
시간 간격은 조정할 거다
'언리얼 엔진 > Drag Down (캡스톤 디자인)' 카테고리의 다른 글
** [Drag Down] UI 자체 Local Prediction 구현 ** (0) | 2025.04.05 |
---|---|
[Drag Down] 주기적으로 호출되는 이펙트를 예측 실행하면 안된다.. (0) | 2025.04.05 |
[Drag Down] 스테미나 자동 회복 시스템 (문제.. 동기화가 느림) (0) | 2025.04.03 |
** [Drag Down] Local Prediction시 외부 컴포넌트의 동기화 ** (0) | 2025.04.03 |
[Drag Down] 멀티 환경에서 스테미나 UI 제작 및 GAS 연결 (0) | 2025.04.02 |