Client-side smoothing for replicated body rotation
Server-only AddActorWorldRotation (HasAuthority guard) prevents client/server tug-of-war. Client interpolates toward replicated rotation via SmoothedBodyYaw (angle-aware FInterpTo at 3x body speed) to eliminate step artifacts from ~30Hz network updates. Cascade (DeltaYaw, head, eyes) uses SmoothedBodyYaw on all machines. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7f92dcab51
commit
b5c52c9236
@ -99,6 +99,7 @@ void UPS_AI_ConvAgent_PostureComponent::BeginPlay()
|
|||||||
// Apply the mesh forward offset so "neutral" aligns with where the face points.
|
// Apply the mesh forward offset so "neutral" aligns with where the face points.
|
||||||
OriginalActorYaw = Owner->GetActorRotation().Yaw + MeshForwardYawOffset;
|
OriginalActorYaw = Owner->GetActorRotation().Yaw + MeshForwardYawOffset;
|
||||||
TargetBodyWorldYaw = Owner->GetActorRotation().Yaw;
|
TargetBodyWorldYaw = Owner->GetActorRotation().Yaw;
|
||||||
|
SmoothedBodyYaw = Owner->GetActorRotation().Yaw;
|
||||||
|
|
||||||
if (bDebug)
|
if (bDebug)
|
||||||
{
|
{
|
||||||
@ -179,6 +180,7 @@ void UPS_AI_ConvAgent_PostureComponent::ResetBodyTarget()
|
|||||||
if (AActor* Owner = GetOwner())
|
if (AActor* Owner = GetOwner())
|
||||||
{
|
{
|
||||||
TargetBodyWorldYaw = Owner->GetActorRotation().Yaw;
|
TargetBodyWorldYaw = Owner->GetActorRotation().Yaw;
|
||||||
|
SmoothedBodyYaw = Owner->GetActorRotation().Yaw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,9 +330,10 @@ void UPS_AI_ConvAgent_PostureComponent::TickComponent(
|
|||||||
TargetWorldYaw = HorizontalDir.Rotation().Yaw;
|
TargetWorldYaw = HorizontalDir.Rotation().Yaw;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Body smoothly interpolates toward its persistent target
|
// Body smoothly interpolates toward its persistent target.
|
||||||
// (only when body tracking is enabled — otherwise only head+eyes move).
|
// Server/standalone: directly rotates the actor.
|
||||||
if (bEnableBodyTracking)
|
// Client: does NOT rotate — accepts replicated rotation from server.
|
||||||
|
if (bEnableBodyTracking && Owner->HasAuthority())
|
||||||
{
|
{
|
||||||
const float BodyDelta = FMath::FindDeltaAngleDegrees(
|
const float BodyDelta = FMath::FindDeltaAngleDegrees(
|
||||||
Owner->GetActorRotation().Yaw, TargetBodyWorldYaw);
|
Owner->GetActorRotation().Yaw, TargetBodyWorldYaw);
|
||||||
@ -341,12 +344,26 @@ void UPS_AI_ConvAgent_PostureComponent::TickComponent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Smoothed body yaw for cascade computations ──────────────────
|
||||||
|
// Server: direct copy (no lag). Client: interpolate toward replicated
|
||||||
|
// rotation to avoid step artifacts from discrete ~30Hz network updates.
|
||||||
|
if (Owner->HasAuthority())
|
||||||
|
{
|
||||||
|
SmoothedBodyYaw = Owner->GetActorRotation().Yaw;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const float ReplicatedYaw = Owner->GetActorRotation().Yaw;
|
||||||
|
const float ClientDelta = FMath::FindDeltaAngleDegrees(SmoothedBodyYaw, ReplicatedYaw);
|
||||||
|
SmoothedBodyYaw += FMath::FInterpTo(0.0f, ClientDelta, SafeDeltaTime, BodyInterpSpeed * 3.0f);
|
||||||
|
}
|
||||||
|
|
||||||
// ── 3. Compute DeltaYaw after body interp ──────────────────────────
|
// ── 3. Compute DeltaYaw after body interp ──────────────────────────
|
||||||
|
|
||||||
float DeltaYaw = 0.0f;
|
float DeltaYaw = 0.0f;
|
||||||
if (!HorizontalDir.IsNearlyZero(1.0f))
|
if (!HorizontalDir.IsNearlyZero(1.0f))
|
||||||
{
|
{
|
||||||
const float CurrentFacingYaw = Owner->GetActorRotation().Yaw + MeshForwardYawOffset;
|
const float CurrentFacingYaw = SmoothedBodyYaw + MeshForwardYawOffset;
|
||||||
DeltaYaw = FMath::FindDeltaAngleDegrees(CurrentFacingYaw, TargetWorldYaw);
|
DeltaYaw = FMath::FindDeltaAngleDegrees(CurrentFacingYaw, TargetWorldYaw);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -432,6 +449,7 @@ void UPS_AI_ConvAgent_PostureComponent::TickComponent(
|
|||||||
|
|
||||||
// Body: keep current orientation — don't rotate back to original facing.
|
// Body: keep current orientation — don't rotate back to original facing.
|
||||||
// The body stays wherever the last tracking left it; only head and eyes reset.
|
// The body stays wherever the last tracking left it; only head and eyes reset.
|
||||||
|
SmoothedBodyYaw = Owner->GetActorRotation().Yaw;
|
||||||
|
|
||||||
// Head + Eyes: return to center
|
// Head + Eyes: return to center
|
||||||
TargetHeadYaw = 0.0f;
|
TargetHeadYaw = 0.0f;
|
||||||
@ -531,20 +549,21 @@ void UPS_AI_ConvAgent_PostureComponent::TickComponent(
|
|||||||
DebugFrameCounter++;
|
DebugFrameCounter++;
|
||||||
if (DebugFrameCounter % 120 == 0)
|
if (DebugFrameCounter % 120 == 0)
|
||||||
{
|
{
|
||||||
const float FacingYaw = Owner->GetActorRotation().Yaw + MeshForwardYawOffset;
|
const float FacingYaw = SmoothedBodyYaw + MeshForwardYawOffset;
|
||||||
const FVector TP = TargetActor->GetActorLocation() + TargetOffset;
|
const FVector TP = TargetActor->GetActorLocation() + TargetOffset;
|
||||||
const FVector Dir = TP - Owner->GetActorLocation();
|
const FVector Dir = TP - Owner->GetActorLocation();
|
||||||
const float TgtYaw = FVector(Dir.X, Dir.Y, 0.0f).Rotation().Yaw;
|
const float TgtYaw = FVector(Dir.X, Dir.Y, 0.0f).Rotation().Yaw;
|
||||||
const float Delta = FMath::FindDeltaAngleDegrees(FacingYaw, TgtYaw);
|
const float Delta = FMath::FindDeltaAngleDegrees(FacingYaw, TgtYaw);
|
||||||
|
|
||||||
UE_LOG(LogPS_AI_ConvAgent_Posture, Log,
|
UE_LOG(LogPS_AI_ConvAgent_Posture, Log,
|
||||||
TEXT("Posture [%s -> %s]: Delta=%.1f | Head=%.1f/%.1f | Eyes=%.1f/%.1f | Body: enabled=%s TargetYaw=%.1f ActorYaw=%.1f"),
|
TEXT("Posture [%s -> %s]: Delta=%.1f | Head=%.1f/%.1f | Eyes=%.1f/%.1f | Body: enabled=%s TargetYaw=%.1f SmoothedYaw=%.1f (raw=%.1f)"),
|
||||||
*Owner->GetName(), *TargetActor->GetName(),
|
*Owner->GetName(), *TargetActor->GetName(),
|
||||||
Delta,
|
Delta,
|
||||||
CurrentHeadYaw, CurrentHeadPitch,
|
CurrentHeadYaw, CurrentHeadPitch,
|
||||||
CurrentEyeYaw, CurrentEyePitch,
|
CurrentEyeYaw, CurrentEyePitch,
|
||||||
bEnableBodyTracking ? TEXT("Y") : TEXT("N"),
|
bEnableBodyTracking ? TEXT("Y") : TEXT("N"),
|
||||||
TargetBodyWorldYaw,
|
TargetBodyWorldYaw,
|
||||||
|
SmoothedBodyYaw,
|
||||||
Owner->GetActorRotation().Yaw);
|
Owner->GetActorRotation().Yaw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -321,6 +321,12 @@ private:
|
|||||||
* Same sticky pattern as TargetHeadYaw but for the body layer. */
|
* Same sticky pattern as TargetHeadYaw but for the body layer. */
|
||||||
float TargetBodyWorldYaw = 0.0f;
|
float TargetBodyWorldYaw = 0.0f;
|
||||||
|
|
||||||
|
/** Smoothed body yaw for cascade computations.
|
||||||
|
* On server/standalone: mirrors Owner->GetActorRotation().Yaw directly.
|
||||||
|
* On client: interpolates toward the replicated rotation to avoid
|
||||||
|
* step artifacts from discrete network updates (~30Hz replication). */
|
||||||
|
float SmoothedBodyYaw = 0.0f;
|
||||||
|
|
||||||
/** Original actor yaw at BeginPlay (for neutral return when TargetActor is null). */
|
/** Original actor yaw at BeginPlay (for neutral return when TargetActor is null). */
|
||||||
float OriginalActorYaw = 0.0f;
|
float OriginalActorYaw = 0.0f;
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user