일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- dirty cow
- attribute
- Multiplay
- rpc
- Unreal Engine
- animation
- listen server
- map design
- ability task
- stride
- Aegis
- ret2libc
- unity
- 게임 개발
- gameplay effect
- 언리얼엔진
- MAC
- gas
- os
- CTF
- 메카님
- 게임개발
- gameplay ability system
- gravity direction
- 유니티
- Replication
- gameplay tag
- photon fusion2
- 언리얼 엔진
- local prediction
- Today
- Total
Replicated
** [Drag Down] RPC In Local Prediction (feat. Root Motion) / (서버->클라이언트 애니메이션 동기화 문제) ** 본문
** [Drag Down] RPC In Local Prediction (feat. Root Motion) / (서버->클라이언트 애니메이션 동기화 문제) **
라구넹 2025. 3. 31. 01:36클라이언트가 자기 자신에 대해 쓰는 건 문제 없다
애초에 예측 실행도 잘 되고 있다
문제는 리슨 서버에서 실행한 애니메이션이 클라이언트에서 느리게 반응한다
정확힌, '자신이 아닌 클라이언트'의 애니메이션이 늦게 동기화된다.
이런 느낌이다
서버는 애초에 자기가 실행하니 당연히 잘 보인다
클라는 결국 복제받는 것이니 너무 느리다
찾아보면, 애초에 GAS의 UAbilityTask_PlayMontageAndWait한테 동기화 맡기면 느린 듯하다
복제 방식의 한계인듯
그래서 아 복제 방식의 한계구나 하고 넘어갈 것이냐? 하면 그래선 안된다
UX 관점에서, 다른 플레이어의 공격이 제때 보여야 한다.
그래서 어떻게 해결할 것이냐?
RPC를 쓰는 거다
RPC는 Replicated 같은 거보다 훨씬 빠르다
근데 GA는 UObject 기반이라 RPC 못쓴다고 생각할 수 있다
그러면 애니메이션 실행할 Actor 기반 클래스에 RPC NetMulticast로 선언하면 된다
(일단 누가 실행했든 나머지도 다 봐야 하니까 멀티캐스트가 맞다)
UCLASS()
class DRAGDOWN_API ADDCharacterBase : public ACharacter
{
GENERATED_BODY()
public:
ADDCharacterBase();
UFUNCTION(NetMulticast, Reliable)
void NetMulticastPlayAnimMontage(UAnimMontage* Montage, FName SectionName);
void ADDCharacterBase::NetMulticastPlayAnimMontage_Implementation(UAnimMontage* Montage, FName SectionName)
{
if ( GetMesh() && GetMesh()->GetAnimInstance() )
{
GetMesh()->GetAnimInstance()->Montage_Play(Montage, 1.0f);
GetMesh()->GetAnimInstance()->Montage_JumpToSection(SectionName, Montage);
}
}
굉장히 간단하다
인자로 몽타주랑 섹션 이름만 받아주는 걸 RPC로 캐릭터에 선언해주고
void UDDGA_PushingCharacter::ActivateAbility(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo,
const FGameplayEventData* TriggerEventData)
{
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
bIsTraced = false;
AvatarCharacter = Cast<ACharacter>(ActorInfo->AvatarActor.Get());
if ( AvatarCharacter )
{
if (AvatarCharacter->GetCharacterMovement()->IsFalling())
{
if (UAbilitySystemComponent* ASC = ActorInfo->AbilitySystemComponent.Get())
{
ASC->TryActivateAbilityByClass(UDDGA_JumpPushingCharacter::StaticClass());
}
bool bReplicatedEndAbility = true;
bool bWasCancelled = false;
EndAbility(Handle, ActorInfo, ActivationInfo, true, false);
return;
}
AvatarCharacter->GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_None);
AttackStateComponent = AvatarCharacter->GetComponentByClass<UDDAttackStateComponent>();
}
if (AttackStateComponent == nullptr) return;
FString SectionString = AttackStateComponent->GetSectionPrefix() + FString::FromInt(AttackStateComponent->GetAttackState());
FName SectionName(*SectionString);
ADDCharacterBase* Character = Cast<ADDCharacterBase>(AvatarCharacter);
if (Character)
{
UAbilitySystemComponent* ASC = CurrentActorInfo->AbilitySystemComponent.Get();
if (true)
{
FScopedPredictionWindow ScopedPrediction(ASC, !AvatarCharacter->HasAuthority());
Character->NetMulticastPlayAnimMontage(PushingMontage, SectionName);
}
}
// Wait task
EventTask = UAbilityTask_WaitGameplayEvent::WaitGameplayEvent(
this,
FGameplayTag::RequestGameplayTag(FName("Event.PushTrigger"))
);
EventTask->EventReceived.AddDynamic(this, &UDDGA_PushingCharacter::OnPushingEventReceived);
EventTask->ReadyForActivation();
}
GA에선 그걸 발동해주면 된다
점프 공격도 마찬가지로 변경해주려는데
동기화 과정에서 부들부들 떤다
차이점은, 점프 공격은 중간에 캐릭터에 AvatarCharacter->LaunchCharacter(FVector(0.0f, 0.0f, 300.0f), false, true);를 해줬다
아니나 다를까, 이거 제거하니 멀쩡히 실행된다.
동기화 과정에서 위치 안맞아서 알아서 동기화하다가 값이 많이 꼬였을 가능성이 크다
애초에 LaunchCharacter가 순간적인 힘이라 CharacterMovementComponent가 알아서 예측하고 보정하다가 문제가 생겼을 가능성도 높고
로컬 프레딕션이라 롤백하다가 더 문제가 생겼을 수 있다
애초에 그냥 넣는게 좋은가 해서 테스트하다가 냅둔 코드라 제거한다
근데 이래도 좀 더 떨린다
근데 애니메이션에 수상한 설정이 있다
저 Enable Root Motion을 끄니까 이제 안 떨린다
Enable Root Motion?
루트 모션(Root Motion)은 말 그대로, 애니메이션 클립 안에 포함된 "루트 본"의 이동/회전 값을 실제 캐릭터 이동에 반영하도록 하는 기능
이게 문제였다
지금 내 프로젝트와 완전히 안맞는 옵션이다
이유
1. 서버와 클라이언트의 루트본 해석이 다르면?
- 특히 프레임 단위로 몽타주 시작 시점, 속도, 회전 방향이 미묘하게 달라지면
-> 위치가 달라지고 서버가 보정 시도
2. CharacterMovementComponent는 Root Motion같은 거 고려 안함
- LaunchCharacter 쓰면 당연히 더 충돌
3. 예측 시스템과는 궁합이 최악
- 클라는 애니메이션을 미리 재생하고 움직임
- 버는 나중에 승인하면서 루트 모션 처리 위치가 다름
-> 부들부들..
저거 해제하고
다시 LaunchCharacter 넣어보고 최종 테스트
정말 완벽하게 내가 원하는대로다!
플레이어는
- 자신의 입력은 미리 실행해서 불편함을 느끼지 않고
- 다른 플레이어의 공격을 RPC로 받아 공격 애니메이션을 제대로 볼 수 있다
- 거기에 부들부들 떨리는 것도 없다
한 가지 굉장히 흥미로운 점은 OnTraceResultCallback이 이제 클라이언트에선 발생하기도, 안하기도 한다는 것
일단 RPC로 애니메이션 실행을 날리는데, 애니메이션의 AnimNotify에서 태그를 날린다.
이럼 이제 예측 실행보다 권위 실행이 빨라지는 것도 가능해져 버린다.
이 경우, 클라이언트의 예측 실행은 무의미하여 실행이 아예 안되는 것이 가능하다.
'언리얼 엔진 > Drag Down (캡스톤 디자인)' 카테고리의 다른 글
[Drag Down] Gameplay Tag로 어빌리티 꼬임 방지 (0) | 2025.04.02 |
---|---|
[Drag Down] 회피 만들기 (0) | 2025.04.01 |
[Drag Down] 점프 공격하기 (0) | 2025.03.30 |
** [Drag Down] Local Prediction으로 인한 리슨 서버 이중 처리 문제 with 캐릭터 밀기 물리 처리, 상태 기반 연속 공격 시스템 ** (0) | 2025.03.29 |
** [Drag Down] GAS Prediction 기반 AnimNotify 처리 ** (1) | 2025.03.27 |