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.
|
||||
OriginalActorYaw = Owner->GetActorRotation().Yaw + MeshForwardYawOffset;
|
||||
TargetBodyWorldYaw = Owner->GetActorRotation().Yaw;
|
||||
SmoothedBodyYaw = Owner->GetActorRotation().Yaw;
|
||||
|
||||
if (bDebug)
|
||||
{
|
||||
@ -179,6 +180,7 @@ void UPS_AI_ConvAgent_PostureComponent::ResetBodyTarget()
|
||||
if (AActor* Owner = GetOwner())
|
||||
{
|
||||
TargetBodyWorldYaw = Owner->GetActorRotation().Yaw;
|
||||
SmoothedBodyYaw = Owner->GetActorRotation().Yaw;
|
||||
}
|
||||
}
|
||||
|
||||
@ -328,9 +330,10 @@ void UPS_AI_ConvAgent_PostureComponent::TickComponent(
|
||||
TargetWorldYaw = HorizontalDir.Rotation().Yaw;
|
||||
}
|
||||
|
||||
// Body smoothly interpolates toward its persistent target
|
||||
// (only when body tracking is enabled — otherwise only head+eyes move).
|
||||
if (bEnableBodyTracking)
|
||||
// Body smoothly interpolates toward its persistent target.
|
||||
// Server/standalone: directly rotates the actor.
|
||||
// Client: does NOT rotate — accepts replicated rotation from server.
|
||||
if (bEnableBodyTracking && Owner->HasAuthority())
|
||||
{
|
||||
const float BodyDelta = FMath::FindDeltaAngleDegrees(
|
||||
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 ──────────────────────────
|
||||
|
||||
float DeltaYaw = 0.0f;
|
||||
if (!HorizontalDir.IsNearlyZero(1.0f))
|
||||
{
|
||||
const float CurrentFacingYaw = Owner->GetActorRotation().Yaw + MeshForwardYawOffset;
|
||||
const float CurrentFacingYaw = SmoothedBodyYaw + MeshForwardYawOffset;
|
||||
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.
|
||||
// The body stays wherever the last tracking left it; only head and eyes reset.
|
||||
SmoothedBodyYaw = Owner->GetActorRotation().Yaw;
|
||||
|
||||
// Head + Eyes: return to center
|
||||
TargetHeadYaw = 0.0f;
|
||||
@ -531,20 +549,21 @@ void UPS_AI_ConvAgent_PostureComponent::TickComponent(
|
||||
DebugFrameCounter++;
|
||||
if (DebugFrameCounter % 120 == 0)
|
||||
{
|
||||
const float FacingYaw = Owner->GetActorRotation().Yaw + MeshForwardYawOffset;
|
||||
const float FacingYaw = SmoothedBodyYaw + MeshForwardYawOffset;
|
||||
const FVector TP = TargetActor->GetActorLocation() + TargetOffset;
|
||||
const FVector Dir = TP - Owner->GetActorLocation();
|
||||
const float TgtYaw = FVector(Dir.X, Dir.Y, 0.0f).Rotation().Yaw;
|
||||
const float Delta = FMath::FindDeltaAngleDegrees(FacingYaw, TgtYaw);
|
||||
|
||||
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(),
|
||||
Delta,
|
||||
CurrentHeadYaw, CurrentHeadPitch,
|
||||
CurrentEyeYaw, CurrentEyePitch,
|
||||
bEnableBodyTracking ? TEXT("Y") : TEXT("N"),
|
||||
TargetBodyWorldYaw,
|
||||
SmoothedBodyYaw,
|
||||
Owner->GetActorRotation().Yaw);
|
||||
}
|
||||
}
|
||||
|
||||
@ -321,6 +321,12 @@ private:
|
||||
* Same sticky pattern as TargetHeadYaw but for the body layer. */
|
||||
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). */
|
||||
float OriginalActorYaw = 0.0f;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user