일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- Multiplay
- gravity direction
- listen server
- rpc
- Replication
- dirty cow
- gameplay tag
- gameplay ability system
- 게임 개발
- ability task
- gas
- 언리얼 엔진
- map design
- ret2libc
- unity
- animation
- 게임개발
- CTF
- 메카님
- os
- attribute
- 유니티
- Aegis
- local prediction
- UI
- MAC
- 언리얼엔진
- photon fusion2
- gameplay effect
- Unreal Engine
- Today
- Total
Replicated
** [Drag Down] GAS Local Prediction ** with 밀기 애니메이션 본문
** [Drag Down] GAS Local Prediction ** with 밀기 애니메이션
라구넹 2025. 3. 26. 15:03* 애니메이션 설정 말고 GAS 구조 설계는 좀 더 밑에 있음
애니메이션 인스턴스, 블루프린트 다 만들었는데, 새로 받은 애셋이 구버전 스켈레톤 쓴다
새로 받은 거에 블루프린트 만들어두긴 했는데, 블루프린트 성능은 좀 떨어지고 이미 애니메이션 인스턴스 클래스 만들기도 해서 아깝고, 직접 만들어서 아는게 컨트롤하기 편하니 새로 만들자
저번 글이랑 똑같은 방식으로 쭉 다시 만든다

몽타주 생성
우 스트레이트, 좌 훅, 하이킥, 돌려차기
이제 이걸 어빌리티에서 실행하도록 할 것..

자 이제 문제는 구조이다
입력 -> 어빌리티 발동 -> 애니메이션 실행 -> 애니메이션 특정 타이밍에 Notify를 통해 밀기 어빌리티 발동 -> 밀기 태스크 발동 -> 밀기 타겟 액터 발동 -> 어빌리티로 돌아와서 밀기
이런 구조로 해보자
AnimNotify에서 어빌리티 발동 시 GAS 꼬일 수도 있는 듯
근데 일단 어빌리티 두 개를 쓰는 방식이 마음에 안드는데? 너무 복잡하지 않나
그래서 찾은 방법!
기존 어빌리티에서 WaitGameplayEvent 태스크 사용
Anim Notify에서 Gameplay Event 발생시키면 되는 거 아닌가??
그리고 태스크 하나 더 써서 타겟 액터 소환하면 되는 거다!

일단 태그를 날려야 하니 게임플레이 태그를 키자


저 이벤트를 AnimNotify에서 날리자

몽타주 재생용 슬롯 생성
그리고 이제 밀기 어빌리티를 만들어준다
구조
DDGA_PushingCharacter
In ActivateAbility,
- UAbilityTask_PlayMontageAndWait -> 애니메이션 실행
- UAbilityTask_WaitGameplayEvent -> Event.PushTrigger 발생 시까지 대기
Event.PushTrigger 발생 시,
OnPushingEventReceived 발동
-- UDDAT_MultiTrace -> 타겟팅 태스크 실행
--- ADDTA_MultiTrace -> 멀티 타겟팅 (SweepMultiByChannel 이용)
-- UDDAT_MultiTrace -> OnTargetDataReadyCallback에서 받고 태스크 종료
- DDGA_PushingCharacter에서 데이터 수신 및 처리
DDGA_PushingCharacter는 그리고 애니메이션이 콤보로 나오도록 처리
void UDDGA_PushingCharacter::ActivateAbility(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo,
const FGameplayEventData* TriggerEventData)
{
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
// Montage task
UAbilityTask_PlayMontageAndWait* MontageTask = UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(
this,
NAME_None,
PushingMontage, // UAnimMontage* 타입
1.0f, // 플레이 속도
NAME_None, // Start Section
false // Stop when ability ends
);
MontageTask->OnCompleted.AddDynamic(this, &UDDGA_PushingCharacter::OnMontageCompleted);
MontageTask->ReadyForActivation();
// Wait task
UAbilityTask_WaitGameplayEvent* EventTask = UAbilityTask_WaitGameplayEvent::WaitGameplayEvent(
this,
FGameplayTag::RequestGameplayTag(FName("Event.PushTrigger"))
);
EventTask->EventReceived.AddDynamic(this, &UDDGA_PushingCharacter::OnPushingEventReceived);
EventTask->ReadyForActivation();
// Target & Damage
}
일단 애니메이션 실행 및 태그 대기 코드까지 작성
void UDDGA_PushingCharacter::OnMontageCompleted()
{
bool bReplicatedEndAbility = true;
bool bWasCancelled = false;
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
}
void UDDGA_PushingCharacter::OnPushingEventReceived(FGameplayEventData Payload)
{
UDDAT_MultiTrace* TraceTask = UDDAT_MultiTrace::CreateTask(this, ADDTA_MultiTrace::StaticClass());
TraceTask->OnComplete.AddDynamic(this, &UDDGA_PushingCharacter::OnTraceResultCallback);
TraceTask->ReadyForActivation();
}
각각 처리는 일단 어빌리티 종료되도록 해줌
이제 이걸 Local Prediction이 되도록 설계
UDDGA_PushingCharacter::UDDGA_PushingCharacter()
{
NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted;
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
static ConstructorHelpers::FObjectFinder<UAnimMontage> PushingMontageRef(TEXT("/Script/Engine.AnimMontage'/Game/Animation/Montage/AM_Manny_Pushing.AM_Manny_Pushing'"));
if ( PushingMontageRef.Succeeded() )
{
PushingMontage = PushingMontageRef.Object;
}
}
NetExecutionPolicy를 LocalPredicted로 설정하고
void ADDCharacterPlayer::GASInputPressed(int32 InputId)
{
HandleGASInputPressed(InputId);
if( !HasAuthority() )
{
ServerGASInputPressed(InputId);
}
}
void ADDCharacterPlayer::GASInputReleased(int32 InputId)
{
HandleGASInputReleased(InputId);
if (!HasAuthority())
{
ServerGASInputReleased(InputId);
}
}
클라이언트에서 미리 실행할 수 있도록 함수 콜 구조 변경 (원래 클라 측에선 발동을 아예 못하게 해놓음)
void ADDCharacterPlayer::HandleGASInputPressed(int32 InputId)
{
if (!ASC)
{
return;
}
FGameplayAbilitySpec* Spec = ASC->FindAbilitySpecFromInputID(InputId);
if (Spec)
{
if (Spec->InputPressed) return;
Spec->InputPressed = true;
if (Spec->IsActive())
{
ASC->AbilitySpecInputPressed(*Spec);
}
else
{
ASC->TryActivateAbility(Spec->Handle, true);
}
}
}
또한 TryActivateAbility에서 두번째 인자를 true로 둠으로써 로컬 실행을 허용

이렇게 해두고 로그를 찍어보자

클라이언트에서 어빌리티 발동 시 프레딕션이 두 개가 뜬다?
뭔가 이상한데

로그를 더 자세히 찍어보자

일단 넷모드2는 서버고,
if (ActivationInfo.GetActivationPredictionKey().IsValidKey())
{
UE_LOG(LogDD, Warning, TEXT("[NetMode %d] Client-side(%s) prediction running"),
GetWorld()->GetNetMode(), *ActorInfo->AvatarActor.Get()->GetName());
}
if (HasAuthority(&ActivationInfo))
{
UE_LOG(LogDD, Warning, TEXT("[NetMode %d] Server-side(%s) authority running"),
GetWorld()->GetNetMode(), *ActorInfo->AvatarActor.Get()->GetName());
}
일단 로그를 이렇게 찍었는데

3명 만들어보자

마지막 두 줄이 서버에서 어빌리티 실행한 거다
모든 클라이언트가 아니라, 실행한 클라이언트만 서버에서 보인다
근데 로그 조건은?

키가 서버에 없을 수가 있나?
클라이언트 -> 서버로 프레딕션 키를 보내는데 당연히 있을 것이다.
정상이다
이제 성능 테스트를 해보자

테스트 결과, 로컬에서 작동하여 레이턴시가 별로 없을 것임에도
클라이언트의 입력을 반영하여 서버 작동보다 0.02초 가량 미리 발동된다
프레딕션 설정은 됐고, 다음은 콤보 액션을 구현해야 한다
그런데 GAS 프레딕션 이용하려면 콤보 하나 하나 어빌리티로 만들어야 하지 않나?
구조는 좀 더 생각해보고, 다음 글에서 작업하자
'언리얼 엔진 > Drag Down (캡스톤 디자인)' 카테고리의 다른 글
** [Drag Down] Local Prediction으로 인한 리슨 서버 이중 처리 문제 with 캐릭터 밀기 물리 처리, 상태 기반 연속 공격 시스템 ** (0) | 2025.03.29 |
---|---|
** [Drag Down] GAS Prediction 기반 AnimNotify 처리 ** (1) | 2025.03.27 |
[Drag Down] 애니메이션 애셋 (0) | 2025.03.25 |
[Drag Down] Idle, Locomotion, Jump (0) | 2025.03.25 |
[Drag Down] 기본세팅 #2 : Attribute, PlayerState (0) | 2025.03.24 |