Download

Distance Matching Locomotion : Giving it a shot!

I used strafing animation in this video Paragon locomotion system with Kubold anims - YouTube
It’s the same approach which Epic used in Paragon. Just switching between direction and using root bone rotation + aim offset for turning character when it’s moving diagonally.

Been a long time coming, but here is my distance matching code. The way I used it was I put it in a BP Lib and made calls in the animBP to trigger the functions based of off anim state or state transition notifies. Use it as you will, I am not officially supporting it. The plan was to add this to my AnimWarp+IK plugin but life took a turn and I don’t have time to develop it any further for the foreseeable future.


// Copyright Patrick Stancu 2021


#include "BP_Lib_AnimWarpIK.h"

#include "Engine/Engine.h" 
#include "EngineUtils.h"	


void UBP_Lib_AnimWarpIK::CalcWarpFactorAndPlayRate(float CurrentMovementSpeed, const float AnimAuthordMoveSpeed, float& WarpFactor, float& PlayRate, const float WarpFactorCap, const float PlayRateRatioFaster, const float PlayRateRatioSlower)
{
	float SpeedChangeRatio = FMath::GetMappedRangeValueUnclamped(FVector2D(0.0f, AnimAuthordMoveSpeed), FVector2D(0.0f, 1.0f), CurrentMovementSpeed);
	float SpeedChangeReductionRatio = FMath::Abs(1.0f - SpeedChangeRatio);
	//if (SpeedChangeRatio > 0)
	//{
	//	//SpeedChangeRatio = FMath::Clamp(SpeedChangeRatio, 0.0f, WarpFactorCap);
	//	WarpFactor = FMath::Clamp(FMath::Clamp(SpeedChangeRatio, 0.0f, WarpFactorCap), 0.0f, WarpFactorCap);
	//}
	//else
	//{
	//	//SpeedChangeRatio = FMath::Clamp(1.0f - SpeedChangeRatio - 1.0f, 0.0f, WarpFactorCap);
	//	WarpFactor = FMath::Clamp(1.0f - (FMath::Clamp(1.0f - SpeedChangeRatio - 1.0f, 0.0f, WarpFactorCap)) - 1.0f, 0.0f, WarpFactorCap);
	//}

 //   if (FMath::IsNearlyEqual(WarpFactor, WarpFactorCap, 0.001f))
	//{
	//	PlayRate = (SpeedChangeRatio - WarpFactor + 1.0f);
	//}
	//else
	//{
	//	PlayRate = 1.0f;
	//}
	
	if (SpeedChangeRatio > 1.0f)
	{
		WarpFactor = ((SpeedChangeReductionRatio * (1.0f - PlayRateRatioFaster)) + 1.0f);
		
		if (WarpFactor > WarpFactorCap)
		{
			PlayRate = ((SpeedChangeReductionRatio * PlayRateRatioFaster) + 1.0f) + (WarpFactor - WarpFactorCap);
			WarpFactor = WarpFactorCap;
		}
		else
		{
			PlayRate = (SpeedChangeReductionRatio * PlayRateRatioFaster) + 1.0f;
		}
	}
	else
	{
		
		PlayRate = 1.0f - (SpeedChangeReductionRatio * PlayRateRatioSlower);

		WarpFactor = (1.0f + (SpeedChangeReductionRatio * PlayRateRatioSlower)) * SpeedChangeRatio;

	}

}

void UBP_Lib_AnimWarpIK::DetermineStopLocation(FVector& StopLocation, FVector InitPos, FVector InitVel, float Friction, FVector BrakeAccel, float DeltaTime)
{

	FVector PredicVel = InitVel;
	FVector PredicLoc = InitPos;
	float PredictTime = 0;

	while (PredictTime < 2.0f)
	{
		PredicVel -= (Friction * PredicVel + BrakeAccel) * DeltaTime;
		if (FVector::DotProduct(PredicVel, InitVel) <= 0.0f)
		{
			break;
		}
		PredicLoc += PredicVel * DeltaTime;

		PredictTime += DeltaTime;

	}

	StopLocation = PredicLoc;

}

void UBP_Lib_AnimWarpIK::GetAnimCurveValue(float& FrameTime, UAnimSequence* AnimationSequence, FName CurveName, float CurveValue)
{
	float time = 0;

	checkf(AnimationSequence != nullptr, TEXT("Invalid Animation Sequence ptr"));
	const FName ContainerName = RetrieveContainerNameForCurve(AnimationSequence, CurveName);

	if (ContainerName != NAME_None)
	{
		const FSmartName CurveSmartName = RetrieveSmartNameForCurve(AnimationSequence, CurveName, ContainerName);
		time = AnimationSequence->EvaluateCurveData(CurveSmartName.UID, CurveValue, false);
	}

	FrameTime = time;
}

void UBP_Lib_AnimWarpIK::DetermineStartLocation(FVector& StartLocation, FVector InitPos, FVector InitVel, float Friction, FVector Accel, float DeltaTime)
{
	FVector PredicVel = FVector::ZeroVector;
	FVector CurrentVel = InitVel;
	FVector PredicLoc = FVector::ZeroVector;
	FVector CurrentLoc = InitPos;
	float PredictTime = 0;

	while (PredictTime < 2.0f)
	{

		PredicVel = PredicVel - (PredicVel - Accel.GetSafeNormal() * PredicVel.Size()) * FMath::Min(DeltaTime * Friction, 1.f);
		PredicVel += Accel * DeltaTime;

		if (PredicVel.Size() >= InitVel.Size())
		{
			StartLocation = InitPos + (InitVel.GetSafeNormal() * -1.0f * FVector::Dist(FVector::ZeroVector, PredicLoc));

			return;
		}

		PredicLoc += PredicVel * DeltaTime;

		PredictTime += DeltaTime;

	}

	StartLocation = PredicLoc;
}

void UBP_Lib_AnimWarpIK::DeterminePivotLocation(FVector& PivotLocation, FVector InitPos, FVector InitVel, float Friction, FVector Accel, float DeltaTime)
{
	FVector PredicVel = InitVel;
	FVector PredicLoc = InitPos;
	float PredictTime = 0;

	while (PredictTime < 2.0f)
	{
		/*Velocity = Velocity - (Velocity - AccelDir * VelSize) * FMath::Min(DeltaTime * Friction, 1.f);
		Velocity += Acceleration * DeltaTime;*/

		PredicVel = PredicVel - (PredicVel - Accel.GetSafeNormal() * PredicVel.Size()) * FMath::Min(DeltaTime * Friction, 1.f);
		PredicVel += Accel * DeltaTime;

		if (FVector::DotProduct(PredicVel, InitVel) <= 0.0f)
		{
			break;
		}
		PredicLoc += PredicVel * DeltaTime;

		PredictTime += DeltaTime;

	}

	PivotLocation = PredicLoc;

}

																						
//CODE TAKEN FROM: AnimationBlueprintLibrary.h										

FName UBP_Lib_AnimWarpIK::RetrieveContainerNameForCurve(const UAnimSequence* AnimationSequence, FName CurveName)
{
	checkf(AnimationSequence != nullptr, TEXT("Invalid Animation Sequence ptr"));
	for (int32 Index = 0; Index < (int32)Enum_SmartNameContainerType::SNCT_MAX; ++Index)
	{
		const FSmartNameMapping* CurveMapping = AnimationSequence->GetSkeleton()->GetSmartNameContainer(SmartContainerNames[Index]);
		if (CurveMapping && CurveMapping->Exists(CurveName))
		{
			return SmartContainerNames[Index];
		}
	}

	return NAME_None;
}

const FName UBP_Lib_AnimWarpIK::SmartContainerNames[(int32)Enum_SmartNameContainerType::SNCT_MAX] = { USkeleton::AnimCurveMappingName, USkeleton::AnimTrackCurveMappingName };

bool UBP_Lib_AnimWarpIK::RetrieveSmartNameForCurve(const UAnimSequence* AnimationSequence, FName CurveName, FName ContainerName, FSmartName& SmartName)
{
	checkf(AnimationSequence != nullptr, TEXT("Invalid Animation Sequence ptr"));
	return AnimationSequence->GetSkeleton()->GetSmartNameByName(ContainerName, CurveName, SmartName);
}

FSmartName UBP_Lib_AnimWarpIK::RetrieveSmartNameForCurve(const UAnimSequence* AnimationSequence, FName CurveName, FName ContainerName)
{
	checkf(AnimationSequence != nullptr, TEXT("Invalid Animation Sequence ptr"));
	FSmartName SmartCurveName;
	AnimationSequence->GetSkeleton()->GetSmartNameByName(ContainerName, CurveName, SmartCurveName);
	return SmartCurveName;
}
				
//CODE TAKEN FROM:AnimationBlueprintLibrary.h

Referencing the code @postman09 provided, I was inspired to look more into the physics of the CMC calculations. I’m a physics guy, so the locomotion code doesn’t make sense unless I view it through the lens of physics. Perhaps there are others like me. I’ve attached a pdf document outlining the physics for the stop prediction calculation that @Slavical put into code for our small team. (@Slavical is our code guy. I’m our physics guy.)
DistanceMatchingNotes.pdf (181.5 KB)

Our stop prediction now works for any input parameters. (Our doesn’t yet work for multiplayer yet though.)

I hope you all find the notes helpful!

You don’t need to do anything server side for distance matching since its all cosmetic and not effecting capsule movement. It will work fine networked without having to do anything.