Mover - LayeredMove_AnimRootMotion not supporting Looped montages

Hey there,

While using Looped montage with `LayeredMove_AnimRootMotion`, I have noticed that the Root motion is not being applied to the character, and the LayeredMove itself has been removed. After some investigation, it seems like this LayeredMove isn’t supporting Looped montages at all.

Even though we can provide `->DurationMs = -1` for a looped animation, the inside of the `FLayeredMove_AnimRootMotion::GenerateMove` won’t wrap `ExtractionStartPosition` and ExtractionEndPosition`, so the LocalRootMotion is always equal to Identity after the first loop.

// Note that Montage 'position' equates to seconds when PlayRate is 1
const double SecondsSinceMontageStarted = (TimeStep.BaseSimTimeMs - StartSimTimeMs) / 1000.0;
const double ScaledSecondsSinceMontageStarted = SecondsSinceMontageStarted * MontageState.PlayRate * MontageRateScale;

const float ExtractionStartPosition = MontageState.StartingMontagePosition + ScaledSecondsSinceMontageStarted;
const float ExtractionEndPosition   = ExtractionStartPosition + (DeltaSeconds * MontageState.PlayRate * MontageRateScale);

// Read the local transform directly from the montage
const FTransform LocalRootMotion = MontageState.Montage ? UMotionWarpingUtilities::ExtractRootMotionFromAnimation(MontageState.Montage, ExtractionStartPosition, ExtractionEndPosition) : FTransform::Identity;

Is there a plan to fix this LayeredMove, or should we fix it locally? I have managed to fix this issue by simply wrapping the extracted positions like so:

  const float ExtractionStartPosition = MontageState.StartingMontagePosition + ScaledSecondsSinceMontageStarted;
  const float ExtractionEndPosition  = ExtractionStartPosition + (DeltaSeconds * MontageState.PlayRate * MontageRateScale);

  const float PlayLength = MontageState.Montage ? MontageState.Montage->GetPlayLength() : 0.f;
  float SampledStartPos = ExtractionStartPosition;
  float SampledEndPos = ExtractionEndPosition;
  if (PlayLength > UE_KINDA_SMALL_NUMBER && ExtractionStartPosition >= PlayLength)
  {
    SampledStartPos = FMath::Fmod(ExtractionStartPosition, PlayLength);
    SampledEndPos = SampledStartPos + (DeltaSeconds * MontageState.PlayRate * MontageRateScale);
  }

  FTransform LocalRootMotion = FTransform::Identity;
  if (MontageState.Montage)
  {
    if (SampledEndPos > PlayLength && PlayLength > UE_KINDA_SMALL_NUMBER)
    {
      // Window straddles the loop boundary: extract each half and accumulate.
      const FTransform Pre = UMotionWarpingUtilities::ExtractRootMotionFromAnimation(MontageState.Montage, SampledStartPos, PlayLength);
      const FTransform Post = UMotionWarpingUtilities::ExtractRootMotionFromAnimation(MontageState.Montage, 0.f, SampledEndPos - PlayLength);
      LocalRootMotion = FTransform(Post.GetRotation() * Pre.GetRotation(), Pre.GetTranslation() + Post.GetTranslation());
    }
    else
    {
      LocalRootMotion = UMotionWarpingUtilities::ExtractRootMotionFromAnimation(MontageState.Montage, SampledStartPos, SampledEndPos);
    }
  }

  FMotionWarpingUpdateContext WarpingContext;
  WarpingContext.Animation = MontageState.Montage;
  WarpingContext.CurrentPosition = SampledEndPos;
  WarpingContext.PreviousPosition = SampledStartPos;
  WarpingContext.PlayRate = MontageState.PlayRate * MontageRateScale;
  WarpingContext.Weight = 1.f;



Hey there,

Yeah, you are correct. 5.8 introduces some changes, like RateScale and respect the starting section, so you can jump to different sections, so your solution may not work well with your setup here. The code you’ve put here seems reasonable, and we recommend keeping it for the time being. I’ve logged an issue with the team, so you’ll want to pay attention to mainline to see how we might address this in the future or not, but I didn’t make it a public-facing one for the moment.

Dustin