원래 이 부분은 아트분들께 애니메이션을 받으면 구현하려고 했는데, 그냥 아무 애니메이션이라도 넣어서 미리 구현해놓고 추후에 애니메이션만 교체하면 되게 하기로 했다.
먼저 시전하는 것 같은 동작을 임포트 한후 애니메이션 몽타주로 설정해주었다. 그 다음으로는 애니메이션 블루프린트를 건들여주었는데,
솔루나 시프트의 랜딩 동작이 따로 있다고 하여 메인 애니메이션 스테이트를 변경해준 후, Layered blend per bone 노드로 만들어준 몽타주를 상체만 출력하도록 블렌딩 해줬다. 시전 동작에 블렌딩이 필요 없을 시, 이부분은 삭제하고 교체해줄 예정이다.
void ATrapperPlayer::Interact(const FInputActionValue& Value)
{
float Data = Value.Get<float>();
if(!Movement->CanMagneticMoving() || !Data)
return;
if (HasAuthority())
{
MulticastRPCMagneticMoveStart();
}
else
{
ServerRPCMagneticMoveStart();
}
}
솔루나 시프트가 가능할 때 Shift를 눌러 활성화해주면, MulticastRPC가 호출된다.
void ATrapperPlayer::ServerRPCMagneticMoveStart_Implementation()
{
MulticastRPCMagneticMoveStart();
}
void ATrapperPlayer::MulticastRPCMagneticMoveStart_Implementation()
{
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && MagneticMoveMontage)
{
AnimInstance->Montage_Play(MagneticMoveMontage, 1.0);
Movement->bIsMagneticMovingCast = true;
bUseControllerRotationYaw = true;
Movement->GravityScale *= 0.5f;
FOnMontageEnded EndDelegate;
EndDelegate.BindUObject(this, &ATrapperPlayer::MagneticMoveCastEnd);
AnimInstance->Montage_SetEndDelegate(EndDelegate, MagneticMoveMontage);
}
}
여기서 몽타주를 실행해주고, 몽타주 종료 델리게이트를 설정해준다. 중간에 GravityScale 변수를 건드는 이유는, 공중에서 시전 동작을 실행했을 경우 캐릭터가 천천히 낙하하게끔 해주기 위해서다. 중력을 조절해주지 않으면 애니메이션 실행 도중에 바닥까지 아예 떨어져버려서 어색한 느낌이 강했다.
bIsMagneticMovingCast
변수를 추가해 애니메이션 블루프린트에서 이 변수가 true
일 때 애니메이션을 실행하도록 해주었다.
void ATrapperPlayer::MagneticMoveCastEnd(UAnimMontage* Montage, bool bInterrupted)
{
Movement->GravityScale = Movement->DefaultGravityScale;
Movement->bIsMagneticMovingCast = false;
Movement->StartMagneticMove();
CharacterControlTypeCheck();
}
애니메이션 몽타주가 끝나면 솔루나 시프트를 시작하게끔 해주었고, 중력도 처음 값으로 돌려주었다 :)
이렇게 변경해주고 나니, 서버가 솔루나 시프트를 한 후 도착했을 때 클라이언트 쪽에서 서버 캐릭터가 제자리에서 계속 걷는 애니메이션이 출력된다. 서버쪽에서 클라이언트의 움직임은 제대로 적용되는 것을 보니 뭔가가 클라이언트로 리플리케이션이 안되고 있다는 뜻인데, 찍어보니 역시나 벨로시티가 계속 들어가고 있었다.
클라이언트 쪽에서 서버의 솔루나 시프트가 끝났다는 것을 제대로 모르는 것 같아서, StopMagneticMove()
를 RPC로 바꿔주었다.
UFUNCTION(Server, Reliable)
void ServerRPCStopMagneticMove();
UFUNCTION(NetMulticast, Reliable)
void MulticastRPCStopMagneticMove();
void UTrapperPlayerMovementComponent::MagneticMove(float DeltaSeconds)
{
FVector CurrentLocation = GetActorLocation();
FVector2D DeleteZCurrentLocation(CurrentLocation.X, CurrentLocation.Y);
FVector2D DeleteZTargetLocation(TargetPosition.X, TargetPosition.Y);
if ((DeleteZTargetLocation - DeleteZCurrentLocation).Size() < 150.f)
{
if (PlayerRef->HasAuthority())
{
MulticastRPCStopMagneticMove();
}
else if (PlayerRef->IsLocallyControlled())
{
ServerRPCStopMagneticMove();
}
}
FVector Direction = TargetPosition - CurrentLocation;
CharacterOwner->AddMovementInput(Direction, 1.f);
SetMaxSpeed();
}
무조건 서버쪽에서 솔루나 시프트가 끝났다는 것을 클라이언트쪽에 알리도록 했더니 해결되었다.
이렇게 고치고 나니, 클라이언트쪽에서 도착했을 때 점프를 하지 않는 현상이 발생했다.. Jump()
함수 호출 자체는 잘 되고 있는 것 같은데, 왜 동작하지 않는걸까?
[[ 클라이언트 로컬 플레이어의 Velocity.Z ]]
LogTemp: Warning: 1943.885719
LogTemp: Warning: 0.000000
LogTemp: Warning: 1943.885840
LogTemp: Warning: 0.000000
LogTemp: Warning: 1943.886521
LogTemp: Warning: 0.000000
LogTemp: Warning: 1779.867717
LogTemp: Warning: 0.000000
LogTemp: Warning: 672.829285
LogTemp: Warning: 0.000000
LogTemp: Warning: -83.265559
LogTemp: Warning: 0.000000
LogTemp: Warning: -116.507442
LogTemp: Warning: 0.000000
LogTemp: Warning: -150.286866
LogTemp: Warning: 0.000000
LogTemp: Warning: -179.943619
LogTemp: Warning: 0.000000
LogTemp: Warning: -208.699417
LogTemp: Warning: 0.000000
[[ 서버 로컬 플레이어의 Velocity.Z ]]
LogTemp: Warning: 1237.912159
LogTemp: Warning: 0.000000
LogTemp: Warning: 1237.912159
LogTemp: Warning: 0.000000
LogTemp: Warning: 173.307702
LogTemp: Warning: 0.000000
LogTemp: Warning: 667.772108
LogTemp: Warning: 0.000000
LogTemp: Warning: 634.162643
LogTemp: Warning: 0.000000
LogTemp: Warning: 605.848678
LogTemp: Warning: 0.000000
LogTemp: Warning: 577.208762
LogTemp: Warning: 0.000000
LogTemp: Warning: 540.744924
LogTemp: Warning: 0.000000
LogTemp: Warning: 512.774942
LogTemp: Warning: 0.000000
LogTemp: Warning: 483.984111
LogTemp: Warning: 0.000000
LogTemp: Warning: 455.111743
LogTemp: Warning: 0.000000
LogTemp: Warning: 424.542797
LogTemp: Warning: 0.000000
무브먼트, 함수 반환 값 이것저것 값을 찍어보다, Velocity.Z
의 값이 다르게 동작하는걸 확인했다. 서버와 다르게 클라이언트 로컬 플레이어의 벨로시티 값은, 갑자기 -로 뚝 떨어져버린다.
점프를 실행하고 함수 호출을 쭉 따라갔을 땐 이상이 없어보였는데, 두세번째 점프 루프를 실행할 때 빠져나가면서 떨어지는 것 같다. 그럼 어디서, 왜 빠져나가는지 확인해봐야지..
이것저것 확인해봐도 답이 없어서, 조금 다른 방향으로 틀기로 했다. 우선, 지금까지의 네트워크 로직 중에 잘못된게 없나 보다가, 서버에만 적용하게끔 되어있는 등 이리저리 이상하게 짜진 코드들이 많아서, 전체적인 코드 정리를 한번 해주기로 했다.
코드를 일일이 설명하기엔 너무 길어져서, 정리한 코드를 간단히 도식화하면 이런식의 구조가 된다. 당연하게도.. 코드를 정리해도 문제는 동일하다. 플래그를 설정해 OnMovementUpdated()
함수에서 점프를 하게 해도 똑같다. 도대체 왜 점프 상태를 끝까지 인지하지 못하고 빠져나가버리는걸까??
디버깅 하다보니 뭔가 느낌이 오는게 있다.. 뭔가 어떤 값들이 클라이언트에 덮혀씌워지고 있는 것 같음. 내일 이상한 값이 있는지 꼼꼼히 찾아봐야겠다. 서버에서 해줘야 하는 일인지, 클라이언트에서 해줘야 하는 일인지 좀 더 세세하게 구분이 필요할 것 같음.
어제 예상한게 맞았다. Velocity
값은 계속 리플리케이트 되고 있으니, Velocity
와 관련된 값들은 서버에서만 변경하도록 해주고 점프를 멀티캐스트로 호출했더니 멀쩡하게 작동한다! 계속 이상하게 작동했던 시나리오를 추측해보자면..
클라이언트와 서버 모두에서 점프 -> 먼저 동작해서 계산되고 있는 클라이언트의 벨로시티 값에 뒤늦게 바뀐 서버의 값이 덮어써짐 -> 그대로 랜딩
아마 이런게 아닐까..
아무튼 최종 구조는 이렇다! 지연을 아예 고려하지 않고 작성했고, 리플리케이션에 대한 이해가 떨어지다보니 확실히 코드에 대한 만족도가 그리 높지는 않지만.. 추후에 좀 더 학습하고 코드를 짜보다 보면 더 나은 구조로 개선할 수 있지 않을까? 앞으로 더 나아질 길만 있으니까 화이팅하자!!