Skip to content

Commit

Permalink
[feat] 애니메이션 동기화 개선, Interp Component 성능 개선
Browse files Browse the repository at this point in the history
기존 애니메이션 동기화 시에는 퍼펫의 위치 interpolation과 애니메이션 state가 충돌해 부자연스러운 움직임이 보여지는 경우가 있었는데 이를 개선함.
Interp component의 move prediction queue에 트랜스폼을 추가할 때 기존과 동일한 트랜스폼은 무시하는 것으로 처리 속도를 향상함.
  • Loading branch information
hagukin committed Dec 28, 2023
1 parent 80b4233 commit 53e7676
Show file tree
Hide file tree
Showing 11 changed files with 60 additions and 17 deletions.
Binary file modified Content/Blueprints/ABP_PlayerAnimation.uasset
Binary file not shown.
Binary file not shown.
5 changes: 4 additions & 1 deletion Source/IocpGame/PacketCodes/ChatPacketApplier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,17 @@ bool ChatPacketApplier::ApplyPacket(TSharedPtr<RecvBuffer> packet, ANetHandler*
// TODO
// FIXME
// TESTING
// 채팅 패킷을 처리하는 용도로 제작된 함수이지만 현재는 임시로 더미클라이언트들의 가상인풋을 처리하는 용도로 사용중임.
uint16 sessionId = packet->GetHeader()->senderId;
if (sessionId == 1) return true; // 호스트 플레이어 제외

// TestClient 이동 시뮬레이션
APlayerPawn* playerPawn = netHandler->GetRovenhellGameInstance()->GetPlayerOfOwner(sessionId);
if (!playerPawn)
{
UE_LOG(LogTemp, Error, TEXT("세션 %i번 플레이어 폰이 없습니다."), sessionId);
UE_LOG(LogTemp, Error, TEXT("세션 %i번 플레이어 폰이 없습니다."), sessionId);
// NOTE: 테스트 클라이언트에는 굳이 고유 id 할당 기능을 안 만들어뒀기 때문에,
// 한 번 다른 클라이언트 세션의 disconnect 이후 테스트 클라이언트 접속 시 이 부분에서 로그가 떠도 정상이다
return false;
}
int randX = (int)FMath::RandBool() * (FMath::RandBool() ? 1 : -1);
Expand Down
8 changes: 5 additions & 3 deletions Source/IocpGame/PacketCodes/GameStateApplier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,12 @@ void GameStateApplier::ApplyPlayerPhysicsOfHost_UEClient(APlayerPawn* hostPlayer

void GameStateApplier::ApplyPlayerPhysicsOfPuppet_UEClient(APlayerPawn* puppetPlayer, SD_GameState* gameState, const SD_PawnPhysics& playerPhysics, ANetHandler* netHandler)
{
// 최신 트랜스폼 정보 추가
// 트랜스폼이 변경되었을 경우에 한해 최신 트랜스폼 정보 추가
FTransform transform = playerPhysics.GetTransformFromData();
UNetPawnInterpComponent* interpComp = puppetPlayer->GetInterpComp();
if (interpComp)
if (interpComp
&& transform.GetLocation() != puppetPlayer->GetTransform().GetLocation()
&& transform.GetRotation() != puppetPlayer->GetTransform().GetRotation())
{
interpComp->AddNewTransform(transform);
// 추가된 정보를 기반으로 움직임을 interpolate하는 과정은 해당 컴포넌트 틱에서 처리됨
Expand Down Expand Up @@ -187,7 +189,7 @@ void GameStateApplier::ApplyPlayerAnimationOfHost_UEClient(APlayerPawn* hostPlay

void GameStateApplier::ApplyPlayerAnimationOfPuppet_UEClient(APlayerPawn* puppetPlayer, SD_PlayerState* playerState, ANetHandler* netHandler)
{
puppetPlayer->SetAnimTo(AnimStateEnum(playerState->AnimState), playerState->AnimStatus1D);
puppetPlayer->SetAnimTo(AnimStateEnum(playerState->AnimState), playerState->AnimDelta1D);
return;
}

Expand Down
6 changes: 5 additions & 1 deletion Source/IocpGame/PacketCodes/SerializableData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ SD_PlayerState::SD_PlayerState(uint16 sessionId, APlayerPawn* player) : PlayerPh
{
SessionId = sessionId;
AnimState = player->GetCurrentAnimState();
AnimStatus1D = player->GetCurrentAnimStatus();

// AnimState status delta 추출
AnimDelta1D = player->GetCurrentAnimStatus() - player->GetSavedAnimStatus();
player->SaveCurrentAnimStatus(); // 다음번 델타 추출을 위해 현재 값을 기록

// TODO: Hp 등 각종 정보 전달
}
4 changes: 2 additions & 2 deletions Source/IocpGame/PacketCodes/SerializableData.h
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ class SD_PlayerState : SD_Data
{
Archive << Data.SessionId;
Archive << Data.AnimState;
Archive << Data.AnimStatus1D;
Archive << Data.AnimDelta1D;
Archive << Data.PlayerPhysics;
return Archive;
}
Expand All @@ -353,7 +353,7 @@ class SD_PlayerState : SD_Data
public:
uint16 SessionId = 0;
uint8 AnimState = AnimStateEnum::NO_ANIM;
float AnimStatus1D = 0.0f; // blendspace 싱크를 위해 전송하는 값; 2D blendspace 사용 시 하나 더 추가할 것
float AnimDelta1D = 0.0f; // blendspace 싱크를 위해 전송하는 값; 2D blendspace 사용 시 하나 더 추가할 것
SD_PawnPhysics PlayerPhysics;
};

Expand Down
3 changes: 2 additions & 1 deletion Source/IocpGame/Private/NetHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ void ANetHandler::Tick(float DeltaTime)
Super::Tick(DeltaTime);

// DEBUG
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("time %f, tripTime %f"), GetRovenhellGameInstance()->TickCounter->GetTime(GetWorld()), GetRovenhellGameInstance()->TickCounter->GetPacketTripTime()));
// GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("time %f, tripTime %f"), GetRovenhellGameInstance()->TickCounter->GetTime(GetWorld()), GetRovenhellGameInstance()->TickCounter->GetPacketTripTime()));
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("Synced clock time: %f"), GetRovenhellGameInstance()->TickCounter->GetTime(GetWorld())));

switch (HostType)
{
Expand Down
38 changes: 33 additions & 5 deletions Source/IocpGame/Private/PlayerPawn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,15 +168,27 @@ void APlayerPawn::SetAnimBranchToMoving()
bIsMoving = true;
}

void APlayerPawn::SetAnimTo(AnimStateEnum state, float value1D)
void APlayerPawn::SetAnimTo(AnimStateEnum state, float delta1D)
{
SetAnimStateTo(state); // state를 먼저 세팅해야 status가 올바르게 적용된다
SetAnimStatusTo(value1D);
ApplyAnimStatusDelta(delta1D);
}

void APlayerPawn::SetAnimStateTo(AnimStateEnum state)
void APlayerPawn::SetAnimStateTo(AnimStateEnum state, bool isForced)
{
if (state == CurrAnimState) return; // 동일
if (IsPuppet() && CurrAnimState == AnimStateEnum::MOVING)
{
// 퍼펫 애니메이션이 Move state이며
// 현재 Movement interpolation을 처리하는 중일 경우,
// 다른 anim state로 전환하라는 요청을 무시한다
// 그렇게 하지 않을 경우 위치 이동은 일어나지만 애니메이션은 이동 외 애니메이션이 재생되어 부자연스러워 보일 수 있다
UNetPawnInterpComponent* interpComp = GetInterpComp();
if (interpComp != nullptr && !interpComp->IsMovePredictionQueueEmpty() && !isForced)
{
return;
}
}

// FSM 브랜치
switch (state)
Expand All @@ -198,13 +210,19 @@ void APlayerPawn::SetAnimStateTo(AnimStateEnum state)
CurrAnimState = state;
}

void APlayerPawn::SetAnimStatusTo(float value1D)
void APlayerPawn::ApplyAnimStatusDelta(float delta1D)
{
if (!IsPuppet())
{
UE_LOG(LogTemp, Warning, TEXT("플레이어 폰의 경우 MovementComp를 사용해 애니메이션 blendspace의 값을 조정해야 합니다."));
return;
}
switch (CurrAnimState)
{
case AnimStateEnum::MOVING:
{
MovementStatus = value1D;
if (delta1D < 0) return; // 이동 애니메이션의 경우 move interpolation 처리를 고려해 status를 감소시키지 않는다
MovementStatus = FMath::Clamp(MovementStatus + delta1D, 0.f, MaxMovementStatus);
return;
}
}
Expand All @@ -223,6 +241,16 @@ float APlayerPawn::GetCurrentAnimStatus()
}
}

float APlayerPawn::GetSavedAnimStatus()
{
return SavedMovementStatus;
}

void APlayerPawn::SaveCurrentAnimStatus()
{
SavedMovementStatus = MovementStatus;
}

void APlayerPawn::AddMovementStatus(float value)
{
MovementStatus = FMath::Clamp(MovementStatus + value, 0.0f, MaxMovementStatus);
Expand Down
2 changes: 1 addition & 1 deletion Source/IocpGame/Public/GameTickCounter.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,6 @@ class IOCPGAME_API GameTickCounter : public FTickableGameObject
float LocalTimePassed = 0.0f; // 마지막 서버 시간 수신 이후 경과한 시간
float LastServerTimestamp = 0.0f; // 마지막으로 수신한 서버 시간; 0.0f일 경우 아직 한 번도 수신받지 않았음을 의미함
float LastClientTimestamp = 0.0f; // 마지막으로 서버 시간을 수신했을 때의 클라이언트 시간
float PacketTripTime = 0.0f; // 서버에서 클라이언트까지 패킷이 도달하는데 걸리는 시간의 추측값
float PacketTripTime = 0.0f; // 서버에서 클라이언트까지 패킷이 도달하는데 걸리는 시간의 추측값; 클라이언트와 서버의 지연시간이 거의 없으면서 동시에 클라이언트의 게임 프레임이 서버보다 빠르면 음수가 나올 수 있다
bool CanGetTripTime = false; // 서버로부터 최소 1회 패킷을 수신받았는지 여부
};
3 changes: 2 additions & 1 deletion Source/IocpGame/Public/NetPawnInterpComponent.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class IOCPGAME_API UNetPawnInterpComponent : public UActorComponent
public:
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
void AddNewTransform(FTransform transform); // 새 최신 transform 정보를 수신받았을 경우 이 함수를 호출해 추가해준다
const int IsMovePredictionQueueEmpty() { return MovePredictionQueue.IsEmpty(); }

private:
float ApplyMovePrediction(float DeltaTime); // 입력받은 Move prediction 객체를 DeltaTime만큼 처리한다. 처리가 완료되었다면 소비하고 남은 DeltaTime을 반환한다.
Expand All @@ -63,6 +64,6 @@ class IOCPGAME_API UNetPawnInterpComponent : public UActorComponent
uint32 QueueSize = 0; // 단일 스레드에서만 접근하므로 Atomic 없이 사용 가능
float DeltaTimeMultiplier = 1.0f; // 큐가 적정치를 초과했을 경우 단일 틱에 여러 MovePrediction을 처리하기 위해 수신받은 델타타임의 크기를 증폭시켜서 여러 MovePrediction에 대한 처리를 가능케 한다
float TimeSinceLastNewTransform = 0.0f; // 마지막으로 새 트랜스폼을 등록한 이후 경과한 시간 (일반적인 경우 서버로부터 마지막으로 수신받은지 얼마나 시간이 흘렀는지를 의미)
bool bHasBegunMovePrediction = false; // interpolation을 시작했는지 (지금까지 서버로부터 1개 이상의 transform을 수신받았는지)
bool bHasBegunMovePrediction = false; // interpolation을 시작했는지 (게임을 실행한 이후 지금까지 서버로부터 1개 이상의 transform을 수신받았는지)
FTransform pendingTransform; // 마지막으로 서버로부터 수신받은 정보
};
8 changes: 6 additions & 2 deletions Source/IocpGame/Public/PlayerPawn.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,18 @@ class IOCPGAME_API APlayerPawn : public ANetSyncPawn
void SetAnimTo(AnimStateEnum state, float value1D);
AnimStateEnum GetCurrentAnimState() { return CurrAnimState; }
float GetCurrentAnimStatus(); // 현재 AnimState에서 사용중인 blendspace 값을 반환한다
float GetSavedAnimStatus(); // 마지막으로 save한 AnimState blendspace 값을 반환한다, 없을 경우 0을 반환한다
const float GetMaxAnimStatus() { return MaxMovementStatus; }
void SaveCurrentAnimStatus(); // 현재 AnimState blendspace 값을 save한다
void AddMovementStatus(float value); // 음수 더해 뺄셈도 가능

protected:
virtual void BeginPlay();
virtual void Tick(float DeltaTime) override;

// 애니메이션
void SetAnimStateTo(AnimStateEnum state);
void SetAnimStatusTo(float value1D); // NOTE: 추후 2D blendspace 사용 시 추가
void SetAnimStateTo(AnimStateEnum state, bool isForced = false); // 퍼펫의 위치 interp 중이더라도 애니메이션을 강제로 변경하기 위해 isForced를 true로 설정할 수 있다
void ApplyAnimStatusDelta(float value1D); // NOTE: 추후 2D blendspace 사용 시 추가

public:
//// 컴포넌트
Expand Down Expand Up @@ -110,6 +113,7 @@ class IOCPGAME_API APlayerPawn : public ANetSyncPawn
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Animation", meta = (AllowPrivateAccess = "true"))
float MovementStatus = 0.0f; // 0 초과일 경우 해당 캐릭터가 이동중임을 나타낸다. 이동 애니메이션 blendspace에 사용됨
float MaxMovementStatus = 100.0f;
float SavedMovementStatus = 0.0f;

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Animation", meta = (AllowPrivateAccess = "true"))
bool bIsMoving = false;
Expand Down

0 comments on commit 53e7676

Please sign in to comment.