feat(client): snapshot interpolation no proxy remoto para suavizar movimento
Implementa o padrao Source/Quake/Unreal: o proxy nao aplica mais a posicao recebida diretamente — empurra para um buffer ordenado de snapshots e o Tick interpola entre os dois snapshots que cercam (server_now - InterpolationDelayMs). - Novo USTRUCT FZMMOProxySnapshot guardado em TArray ordenado por ServerTimeMs (insercao defensiva + dedupe; UDP pode trocar ordem). - ApplyEntitySnapshot vira "push to buffer"; estabelece o offset de relogio na primeira amostra (local_now - server_time). Mudanca de bGrounded continua aplicada imediatamente (MovementMode) para o pulo nao herdar o delay de interpolacao. - Tick acha o par (A,B), faz Lerp em pos/vel/yaw; extrapolacao curta clampada por MaxExtrapolationSeconds quando faltam snapshots futuros. - Aceleracao derivada agora vem da curva interpolada (mais estavel que a derivada snapshot-a-snapshot anterior). Fallback sintetico de accel para o AnimBP do Quinn preservado. - Removido o line-trace de Z (causava oscilacao apos a interpolacao; a Z autoritativa do dono ja vem no snapshot, ADR 0041). - Separacao explicita AuthoritativePosCm vs InterpolatedPosCm, com LastSnapshotPosition mantido como alias para compat de Blueprint. - Defaults: InterpolationDelayMs=100, SnapshotBufferCapacity=8, MaxExtrapolationSeconds=0.1 (UPROPERTY EditAnywhere para tunar em PIE). - Log Verbose 1x/s com renderLagMs, interpAlpha, flag de extrapolacao. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -97,7 +97,121 @@ void AZMMOPlayerProxy::BeginPlay()
|
|||||||
void AZMMOPlayerProxy::Tick(const float DeltaSeconds)
|
void AZMMOPlayerProxy::Tick(const float DeltaSeconds)
|
||||||
{
|
{
|
||||||
Super::Tick(DeltaSeconds);
|
Super::Tick(DeltaSeconds);
|
||||||
|
|
||||||
|
if (SnapshotBuffer.Num() == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tempo do servidor estimado: relogio local em ms - offset estabelecido na
|
||||||
|
// primeira amostra. RenderMs = tempo "atrasado" usado para procurar o par
|
||||||
|
// de snapshots a interpolar (snapshot interpolation a la Source/Quake).
|
||||||
|
const int64 LocalNowMs = static_cast<int64>(FPlatformTime::Seconds() * 1000.0);
|
||||||
|
const int64 ServerNowMs = LocalNowMs - ServerClockOffsetMs;
|
||||||
|
const int64 RenderMs = ServerNowMs - static_cast<int64>(InterpolationDelayMs);
|
||||||
|
|
||||||
|
// Procura o snapshot B tal que A.t <= RenderMs <= B.t (A = B-1).
|
||||||
|
int32 IdxB = INDEX_NONE;
|
||||||
|
for (int32 i = 1; i < SnapshotBuffer.Num(); ++i)
|
||||||
|
{
|
||||||
|
if (SnapshotBuffer[i].ServerTimeMs >= RenderMs)
|
||||||
|
{
|
||||||
|
IdxB = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FVector NewPos;
|
||||||
|
FVector NewVel;
|
||||||
|
float InterpAlpha = 0.0f;
|
||||||
|
bool bExtrapolating = false;
|
||||||
|
|
||||||
|
if (IdxB == INDEX_NONE)
|
||||||
|
{
|
||||||
|
// Sem snapshot futuro: extrapolacao curta com a ultima velocidade
|
||||||
|
// conhecida, clampada por `MaxExtrapolationSeconds` para nao deixar
|
||||||
|
// o proxy fugir se a rede ficar muda.
|
||||||
|
const FZMMOProxySnapshot& Last = SnapshotBuffer.Last();
|
||||||
|
const float ExtrapSec = FMath::Clamp(
|
||||||
|
static_cast<float>(RenderMs - Last.ServerTimeMs) / 1000.0f,
|
||||||
|
0.0f, MaxExtrapolationSeconds);
|
||||||
|
NewPos = Last.PosCm + Last.VelCmS * ExtrapSec;
|
||||||
|
NewVel = Last.VelCmS;
|
||||||
|
bExtrapolating = ExtrapSec > KINDA_SMALL_NUMBER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const FZMMOProxySnapshot& A = SnapshotBuffer[IdxB - 1];
|
||||||
|
const FZMMOProxySnapshot& B = SnapshotBuffer[IdxB];
|
||||||
|
const double Span = FMath::Max<double>(1.0, static_cast<double>(B.ServerTimeMs - A.ServerTimeMs));
|
||||||
|
InterpAlpha = static_cast<float>(FMath::Clamp(
|
||||||
|
static_cast<double>(RenderMs - A.ServerTimeMs) / Span, 0.0, 1.0));
|
||||||
|
NewPos = FMath::Lerp(A.PosCm, B.PosCm, InterpAlpha);
|
||||||
|
NewVel = FMath::Lerp(A.VelCmS, B.VelCmS, InterpAlpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Yaw deriva da velocidade interpolada: locomotion mantem face na direcao
|
||||||
|
// do movimento; em idle preserva o yaw atual para nao "snappar".
|
||||||
|
FRotator NewRot = GetActorRotation();
|
||||||
|
if (FVector(NewVel.X, NewVel.Y, 0.0f).SizeSquared() > 1.0f)
|
||||||
|
{
|
||||||
|
NewRot.Yaw = FMath::RadiansToDegrees(FMath::Atan2(NewVel.Y, NewVel.X));
|
||||||
|
}
|
||||||
|
NewRot.Pitch = 0.0f;
|
||||||
|
NewRot.Roll = 0.0f;
|
||||||
|
|
||||||
|
// Aceleracao derivada da curva interpolada (mais estavel que a derivada
|
||||||
|
// snapshot-a-snapshot que tinhamos antes; AnimBP do Quinn precisa de
|
||||||
|
// `Acceleration != 0` para sair de Idle).
|
||||||
|
FVector DerivedAccel = FVector::ZeroVector;
|
||||||
|
if (bHasPreviousSnapshot && DeltaSeconds > KINDA_SMALL_NUMBER)
|
||||||
|
{
|
||||||
|
DerivedAccel = (NewVel - PreviousVisualVelocity) / DeltaSeconds;
|
||||||
|
DerivedAccel = DerivedAccel.GetClampedToMaxSize(8000.0f);
|
||||||
|
}
|
||||||
|
constexpr float AccelSmoothingAlpha = 0.35f;
|
||||||
|
LastDerivedAccelerationCmS2 = bHasPreviousSnapshot
|
||||||
|
? FMath::Lerp(LastDerivedAccelerationCmS2, DerivedAccel, AccelSmoothingAlpha)
|
||||||
|
: DerivedAccel;
|
||||||
|
|
||||||
|
// Fallback sintetico: locomotion a velocidade constante => derivada ~ 0.
|
||||||
|
// Sem este nudge o AnimBP voltaria para Idle.
|
||||||
|
const FVector2D Vel2D(NewVel.X, NewVel.Y);
|
||||||
|
const float Speed2D = Vel2D.Size();
|
||||||
|
const FVector2D Accel2D(LastDerivedAccelerationCmS2.X, LastDerivedAccelerationCmS2.Y);
|
||||||
|
constexpr float MinSpeedForFallbackCmS = 50.0f;
|
||||||
|
constexpr float MinAccelFallbackCmS2 = 200.0f;
|
||||||
|
const bool bGroundedNow = SnapshotBuffer.Last().bGrounded;
|
||||||
|
if (bGroundedNow && Speed2D >= MinSpeedForFallbackCmS && Accel2D.Size() < MinAccelFallbackCmS2)
|
||||||
|
{
|
||||||
|
const FVector2D VelDir = Vel2D.GetSafeNormal();
|
||||||
|
const FVector2D FallbackAccel2D = VelDir * MinAccelFallbackCmS2;
|
||||||
|
LastDerivedAccelerationCmS2.X = FallbackAccel2D.X;
|
||||||
|
LastDerivedAccelerationCmS2.Y = FallbackAccel2D.Y;
|
||||||
|
LastDerivedAccelerationCmS2.Z = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
PreviousVisualVelocity = NewVel;
|
||||||
|
bHasPreviousSnapshot = true;
|
||||||
|
|
||||||
|
InterpolatedPosCm = NewPos;
|
||||||
|
VisualVelocity = NewVel;
|
||||||
|
SetActorLocationAndRotation(NewPos, NewRot);
|
||||||
|
|
||||||
RefreshAnimationDrivers();
|
RefreshAnimationDrivers();
|
||||||
|
|
||||||
|
// Diagnostico 1x/s: render lag, buffer, alpha, extrapolating?
|
||||||
|
const double NowSec = FPlatformTime::Seconds();
|
||||||
|
if (NowSec - LastDiagLogSec >= 1.0)
|
||||||
|
{
|
||||||
|
LastDiagLogSec = NowSec;
|
||||||
|
const int64 NewestMs = SnapshotBuffer.Last().ServerTimeMs;
|
||||||
|
const int64 RenderLagMs = ServerNowMs - NewestMs;
|
||||||
|
UE_LOG(LogZMMO, Verbose,
|
||||||
|
TEXT("ZMMOPlayerProxy[%lld] buffer=%d renderLagMs=%lld interpAlpha=%.2f extrap=%d delayMs=%.0f"),
|
||||||
|
EntityId, SnapshotBuffer.Num(), RenderLagMs, InterpAlpha,
|
||||||
|
bExtrapolating ? 1 : 0, InterpolationDelayMs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AZMMOPlayerProxy::SetZMMOIdentity(const int64 InEntityId, const EZMMOEntityType InEntityType)
|
void AZMMOPlayerProxy::SetZMMOIdentity(const int64 InEntityId, const EZMMOEntityType InEntityType)
|
||||||
@@ -108,84 +222,54 @@ void AZMMOPlayerProxy::SetZMMOIdentity(const int64 InEntityId, const EZMMOEntity
|
|||||||
|
|
||||||
void AZMMOPlayerProxy::ApplyEntitySnapshot(const FZMMOEntitySnapshot& Snapshot)
|
void AZMMOPlayerProxy::ApplyEntitySnapshot(const FZMMOEntitySnapshot& Snapshot)
|
||||||
{
|
{
|
||||||
const FRotator NewRotation(0.0f, Snapshot.YawDeg, 0.0f);
|
// Estabiliza o offset de relogio na primeira amostra. Subsequente nao
|
||||||
|
// re-sincroniza para evitar saltos visuais; um esquema mais sofisticado
|
||||||
// V0: o servidor envia Z=spawnZ fixo (ADR 0039). ADR 0041 passou a Z a
|
// (ema do offset) cabe quando tivermos ping/RTT estabilizado por sessao.
|
||||||
// vir do cliente do dono (`GetActorLocation().Z`), pelo que o snapshot
|
if (SnapshotBuffer.Num() == 0)
|
||||||
// ja contem a Z autoritativa. Mantemos o line trace local como fallback
|
|
||||||
// gracioso para os casos em que o snapshot Z fica estranho (terreno
|
|
||||||
// receiver com buracos, drift entre clientes, packet loss). Sem hit, o
|
|
||||||
// AdjustedPos.Z fica igual a Snapshot.PositionCm.Z.
|
|
||||||
FVector AdjustedPos = Snapshot.PositionCm;
|
|
||||||
if (UWorld* World = GetWorld())
|
|
||||||
{
|
{
|
||||||
const FVector TraceStart = AdjustedPos + FVector(0.0f, 0.0f, 1000.0f);
|
const int64 LocalNowMs = static_cast<int64>(FPlatformTime::Seconds() * 1000.0);
|
||||||
const FVector TraceEnd = AdjustedPos - FVector(0.0f, 0.0f, 4000.0f);
|
ServerClockOffsetMs = LocalNowMs - Snapshot.ServerTimeMs;
|
||||||
FHitResult Hit;
|
}
|
||||||
FCollisionQueryParams Params(TEXT("ZMMOProxyGround"), false, this);
|
|
||||||
if (World->LineTraceSingleByChannel(Hit, TraceStart, TraceEnd, ECC_Visibility, Params))
|
FZMMOProxySnapshot S;
|
||||||
|
S.PosCm = Snapshot.PositionCm;
|
||||||
|
S.VelCmS = Snapshot.VelocityCmS;
|
||||||
|
S.bGrounded = Snapshot.bGrounded;
|
||||||
|
S.ServerTimeMs = Snapshot.ServerTimeMs;
|
||||||
|
|
||||||
|
// Insercao ordenada defensiva (UDP pode trocar a ordem de pacotes).
|
||||||
|
int32 InsertAt = SnapshotBuffer.Num();
|
||||||
|
for (int32 i = SnapshotBuffer.Num() - 1; i >= 0; --i)
|
||||||
|
{
|
||||||
|
if (SnapshotBuffer[i].ServerTimeMs < S.ServerTimeMs)
|
||||||
{
|
{
|
||||||
const float HalfHeight = GetCapsuleComponent()
|
InsertAt = i + 1;
|
||||||
? GetCapsuleComponent()->GetScaledCapsuleHalfHeight()
|
break;
|
||||||
: 96.0f;
|
|
||||||
AdjustedPos.Z = Hit.Location.Z + HalfHeight;
|
|
||||||
}
|
}
|
||||||
}
|
if (SnapshotBuffer[i].ServerTimeMs == S.ServerTimeMs)
|
||||||
|
|
||||||
const FVector NewVel = Snapshot.VelocityCmS;
|
|
||||||
|
|
||||||
// Aceleração derivada entre snapshots (espelha ZeusInputBridgeComponent.cpp:443-486).
|
|
||||||
// O AnimBP do Quinn usa Acceleration!=0 nas formulas de ShouldMove e
|
|
||||||
// Jump_Start. Como proxies não têm input, derivamos a accel a partir da
|
|
||||||
// variação de velocidade entre snapshots consecutivos.
|
|
||||||
FVector DerivedAccel = FVector::ZeroVector;
|
|
||||||
if (bHasPreviousSnapshot && PreviousSnapshotServerTimeMs > 0)
|
|
||||||
{
|
|
||||||
const double DeltaSec = static_cast<double>(
|
|
||||||
Snapshot.ServerTimeMs - PreviousSnapshotServerTimeMs) / 1000.0;
|
|
||||||
if (DeltaSec > KINDA_SMALL_NUMBER)
|
|
||||||
{
|
{
|
||||||
DerivedAccel = (NewVel - PreviousVisualVelocity) / static_cast<float>(DeltaSec);
|
// Dedupe: substitui pelo mais recente (este).
|
||||||
DerivedAccel = DerivedAccel.GetClampedToMaxSize(8000.0f);
|
SnapshotBuffer[i] = S;
|
||||||
|
InsertAt = INDEX_NONE;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
InsertAt = i;
|
||||||
}
|
}
|
||||||
constexpr float AccelSmoothingAlpha = 0.35f;
|
if (InsertAt != INDEX_NONE)
|
||||||
LastDerivedAccelerationCmS2 = bHasPreviousSnapshot
|
|
||||||
? FMath::Lerp(LastDerivedAccelerationCmS2, DerivedAccel, AccelSmoothingAlpha)
|
|
||||||
: DerivedAccel;
|
|
||||||
|
|
||||||
// Fallback visual: se o proxy está em locomotion (grounded + Speed alto)
|
|
||||||
// mas a accel derivada caiu para ~0 (velocidade constante entre snapshots),
|
|
||||||
// injetar accel sintética na direção da velocidade. Sem isto, andar em
|
|
||||||
// linha reta a velocidade constante deixaria Acceleration=0 e o AnimBP
|
|
||||||
// voltaria para Idle.
|
|
||||||
const FVector2D Vel2D(NewVel.X, NewVel.Y);
|
|
||||||
const float Speed2D = Vel2D.Size();
|
|
||||||
const FVector2D Accel2D(LastDerivedAccelerationCmS2.X, LastDerivedAccelerationCmS2.Y);
|
|
||||||
constexpr float MinSpeedForFallbackCmS = 50.0f;
|
|
||||||
constexpr float MinAccelFallbackCmS2 = 200.0f;
|
|
||||||
if (Snapshot.bGrounded && Speed2D >= MinSpeedForFallbackCmS && Accel2D.Size() < MinAccelFallbackCmS2)
|
|
||||||
{
|
{
|
||||||
const FVector2D VelDir = Vel2D.GetSafeNormal();
|
SnapshotBuffer.Insert(S, InsertAt);
|
||||||
const FVector2D FallbackAccel2D = VelDir * MinAccelFallbackCmS2;
|
}
|
||||||
LastDerivedAccelerationCmS2.X = FallbackAccel2D.X;
|
while (SnapshotBuffer.Num() > SnapshotBufferCapacity)
|
||||||
LastDerivedAccelerationCmS2.Y = FallbackAccel2D.Y;
|
{
|
||||||
LastDerivedAccelerationCmS2.Z = 0.0f;
|
SnapshotBuffer.RemoveAt(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
PreviousVisualVelocity = NewVel;
|
AuthoritativePosCm = Snapshot.PositionCm;
|
||||||
PreviousSnapshotServerTimeMs = Snapshot.ServerTimeMs;
|
LastSnapshotPosition = Snapshot.PositionCm;
|
||||||
bHasPreviousSnapshot = true;
|
|
||||||
|
|
||||||
SetActorLocationAndRotation(AdjustedPos, NewRotation);
|
|
||||||
LastSnapshotPosition = AdjustedPos;
|
|
||||||
VisualVelocity = NewVel;
|
|
||||||
bHasFirstSnapshot = true;
|
bHasFirstSnapshot = true;
|
||||||
|
|
||||||
// Estado de queda/pulo replicado via `bGrounded` (eco do bit IsFalling do
|
// `bGrounded` e evento — aplicar `MovementMode` imediatamente para que o
|
||||||
// cliente local — ver `EInputAxisFlags::IsFalling` e CoreServerApp). Sem
|
// pulo nao espere `InterpolationDelayMs` para mudar a state machine do AnimBP.
|
||||||
// este passo o `MovementMode` do proxy ficaria sempre em MOVE_Walking
|
|
||||||
// (definido no construtor) e o AnimBP nunca tocaria a animação de queda.
|
|
||||||
if (UCharacterMovementComponent* CMC = GetCharacterMovement())
|
if (UCharacterMovementComponent* CMC = GetCharacterMovement())
|
||||||
{
|
{
|
||||||
const EMovementMode DesiredMode = Snapshot.bGrounded ? MOVE_Walking : MOVE_Falling;
|
const EMovementMode DesiredMode = Snapshot.bGrounded ? MOVE_Walking : MOVE_Falling;
|
||||||
@@ -195,7 +279,6 @@ void AZMMOPlayerProxy::ApplyEntitySnapshot(const FZMMOEntitySnapshot& Snapshot)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RefreshAnimationDrivers();
|
|
||||||
ApplyCollisionPolicy();
|
ApplyCollisionPolicy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,29 @@
|
|||||||
* Velocity+Acceleration estejam populados quando o `NativeUpdateAnimation`
|
* Velocity+Acceleration estejam populados quando o `NativeUpdateAnimation`
|
||||||
* do AnimBP os ler.
|
* do AnimBP os ler.
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* Snapshot bruto guardado no buffer de interpolacao. Mantido como struct privada
|
||||||
|
* ao .h porque so o proxy (e debugging local) precisa olhar para ele.
|
||||||
|
*/
|
||||||
|
USTRUCT()
|
||||||
|
struct FZMMOProxySnapshot
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
FVector PosCm = FVector::ZeroVector;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
FVector VelCmS = FVector::ZeroVector;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
bool bGrounded = true;
|
||||||
|
|
||||||
|
/** Timestamp do servidor que produziu o snapshot (ms desde Unix epoch). */
|
||||||
|
UPROPERTY()
|
||||||
|
int64 ServerTimeMs = 0;
|
||||||
|
};
|
||||||
|
|
||||||
UCLASS(Blueprintable, BlueprintType)
|
UCLASS(Blueprintable, BlueprintType)
|
||||||
class ZMMO_API AZMMOPlayerProxy : public ACharacter, public IZMMOEntityInterface
|
class ZMMO_API AZMMOPlayerProxy : public ACharacter, public IZMMOEntityInterface
|
||||||
{
|
{
|
||||||
@@ -63,30 +86,74 @@ protected:
|
|||||||
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Entity")
|
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Entity")
|
||||||
bool bHasFirstSnapshot = false;
|
bool bHasFirstSnapshot = false;
|
||||||
|
|
||||||
/** Ultima posicao recebida por snapshot (mundo, cm). */
|
/**
|
||||||
|
* Ultima posicao **autoritativa** recebida do servidor (cm, mundo). Difere
|
||||||
|
* de `InterpolatedPosCm` (visual) — separar os dois e essencial para
|
||||||
|
* comparar drift autoritativo vs visual via debug overlay/logs.
|
||||||
|
*/
|
||||||
|
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Entity")
|
||||||
|
FVector AuthoritativePosCm = FVector::ZeroVector;
|
||||||
|
|
||||||
|
/** Posicao **visual** atual (resultado da interpolacao do tick). */
|
||||||
|
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Entity")
|
||||||
|
FVector InterpolatedPosCm = FVector::ZeroVector;
|
||||||
|
|
||||||
|
/** Compat: alias para `AuthoritativePosCm` (preserva binding de Blueprint, se houver). */
|
||||||
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Entity")
|
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Entity")
|
||||||
FVector LastSnapshotPosition = FVector::ZeroVector;
|
FVector LastSnapshotPosition = FVector::ZeroVector;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Velocidade visual derivada para o AnimBP. Espelha `Snapshot.VelocityCmS`
|
* Velocidade visual derivada para o AnimBP. Espelha a velocidade
|
||||||
* e e re-imposta no `CMC->Velocity` em cada `Tick` para manter os drivers
|
* *interpolada* (nao o snapshot bruto) e e re-imposta no `CMC->Velocity`
|
||||||
* de animacao alimentados entre updates de rede.
|
* em cada `Tick` para manter os drivers de animacao alimentados entre
|
||||||
|
* updates de rede.
|
||||||
*/
|
*/
|
||||||
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Entity")
|
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Entity")
|
||||||
FVector VisualVelocity = FVector::ZeroVector;
|
FVector VisualVelocity = FVector::ZeroVector;
|
||||||
|
|
||||||
/** Velocidade do snapshot anterior — base para derivar `Acceleration`. */
|
/**
|
||||||
FVector PreviousVisualVelocity = FVector::ZeroVector;
|
* Atraso de interpolacao em ms. Render-time = wall_now - InterpolationDelayMs,
|
||||||
|
* expresso no dominio do tempo do servidor. Padrao 100 ms (~3 snapshots a 30 Hz)
|
||||||
|
* cobre 1 perda + 1 jitter sem extrapolar.
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ZMMO|Network",
|
||||||
|
meta = (ClampMin = "0.0", ClampMax = "500.0"))
|
||||||
|
double InterpolationDelayMs = 100.0;
|
||||||
|
|
||||||
/** ServerTimeMs do snapshot anterior — `dt` da derivada de Acceleration. */
|
/** Tamanho maximo do buffer (em snapshots). Cobre ~270 ms a 30 Hz. */
|
||||||
int64 PreviousSnapshotServerTimeMs = 0;
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ZMMO|Network",
|
||||||
|
meta = (ClampMin = "2", ClampMax = "32"))
|
||||||
|
int32 SnapshotBufferCapacity = 8;
|
||||||
|
|
||||||
|
/** Limite duro de extrapolacao (s) quando nao ha snapshot futuro. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ZMMO|Network",
|
||||||
|
meta = (ClampMin = "0.0", ClampMax = "0.5"))
|
||||||
|
float MaxExtrapolationSeconds = 0.1f;
|
||||||
|
|
||||||
|
/** Buffer ordenado por `ServerTimeMs` ascendente. */
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<FZMMOProxySnapshot> SnapshotBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offset entre o relogio local (`FPlatformTime::Seconds * 1000`) e o
|
||||||
|
* relogio do servidor (`ServerTimeMs` do snapshot). Estimado pela primeira
|
||||||
|
* amostra como `local_now_ms - server_time_ms`. Subtraindo este offset do
|
||||||
|
* relogio local obtemos o tempo do servidor estimado.
|
||||||
|
*/
|
||||||
|
int64 ServerClockOffsetMs = 0;
|
||||||
|
|
||||||
/** Aceleracao derivada (com smoothing) escrita no CMC custom para alimentar
|
/** Aceleracao derivada (com smoothing) escrita no CMC custom para alimentar
|
||||||
* o AnimBP (`ShouldMove`/`Jump_Start` exigem Acceleration!=0). */
|
* o AnimBP (`ShouldMove`/`Jump_Start` exigem Acceleration!=0). */
|
||||||
FVector LastDerivedAccelerationCmS2 = FVector::ZeroVector;
|
FVector LastDerivedAccelerationCmS2 = FVector::ZeroVector;
|
||||||
|
|
||||||
|
/** Velocidade visual do tick anterior (para a derivada `Acceleration = dV/dt`). */
|
||||||
|
FVector PreviousVisualVelocity = FVector::ZeroVector;
|
||||||
|
|
||||||
bool bHasPreviousSnapshot = false;
|
bool bHasPreviousSnapshot = false;
|
||||||
|
|
||||||
|
/** Ultimo log de diagnostico (s, monotonic). Log emitido 1x/s no Tick. */
|
||||||
|
double LastDiagLogSec = 0.0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/** Reaplica `CMC->Velocity` (com clamp de Z em walking) e injeta a
|
/** Reaplica `CMC->Velocity` (com clamp de Z em walking) e injeta a
|
||||||
* aceleracao derivada no CMC custom. Chamado em `Tick` (pre-mesh) e em
|
* aceleracao derivada no CMC custom. Chamado em `Tick` (pre-mesh) e em
|
||||||
|
|||||||
Reference in New Issue
Block a user