Need Help: Custom Locomotion System - Up Sweep Tracing Issue

I’m struggling with my standing up trace, a Sphere/Capsule swept from my character’s location to their default height, used to determine if character can get up to duck or fully.

Thing is - the “distance between” value doesn’t seem right.
Is something wrong with the trace or the object, that makes my stand up function go haywire, ticking between ducking and default desired stances?

namespace CndELS_Func
{

	FHitResult Get_Hit_Space_StandUp(ACndCharacter_Master* CharacterTarget, FVector OwnerLocation, float DesiredHeight)
	{

		FHitResult HR_StandUp_Space;

		if (!CharacterTarget)
		{
			return HR_StandUp_Space;  // No valid hit
		}

		FVector CndOwnerLocation = CndFunc_Owner::GetLocation(CharacterTarget);

		FVector TraceStart = CndOwnerLocation;
		FVector TraceEnd = TraceStart + FVector(0.0f, 0.0f, DesiredHeight);



		if (CharacterTarget->GetWorld()->SweepSingleByObjectType(
			HR_StandUp_Space,
			TraceStart,
			TraceEnd,
			FQuat::Identity,
			CharacterTarget->CndSys_ODS.Params.CollisionObjectQueryParams,
			CharacterTarget->CndSys_ODS.Params.UpTrace.Shape,
			CharacterTarget->CndSys_ODS.Params.CollisionQueryParams
		))
		{


			if (CharacterTarget->CO_ELS->ELS_DebugEnabled)
			{
				// Draw the capsule trace path
				FColor TraceColor = HR_StandUp_Space.bBlockingHit ? FColor::Green : FColor::Red;

				DrawDebugCapsule(CharacterTarget->GetWorld(),
					TraceEnd,
					CharacterTarget->CndSys_ODS.Params.UpTrace.Height,
					CharacterTarget->CndSys_ODS.Params.UpTrace.Radius,
					FQuat::Identity,
					TraceColor,
					false,
					0.0f
				);

				DrawDebugLine(CharacterTarget->GetWorld(),
					TraceStart,
					TraceEnd,
					TraceColor,
					false,
					0.0f
				);

			}

			return HR_StandUp_Space;

		}

		return HR_StandUp_Space;


	}

	float Get_StandUpSpace(ACndCharacter_Master* CharacterTarget, float DesiredHeight)
	{

		FVector CndOwnerLocation = CndFunc_Owner::GetLocation(CharacterTarget);

		FVector ImpactPoint = CndELS_Func::Get_Hit_Space_StandUp(CharacterTarget, CndOwnerLocation, DesiredHeight).ImpactPoint;

		// Get Distance Between Desired Height and Impact Point
		float Distance = CndFunc_Global::Get_DistanceBetween(CndOwnerLocation, ImpactPoint);

		return Distance;

	}

}
void UCndComp_LocomotionSystem::BPF_UEF_CndCharacter_Desired_MVRecovery(ECndCharacter_Movement_Recovery Recovery)
{

	float Height_Default = CndCharacter_Data_Params::Get_Height_Map(CndCharacter_Target, H_Default);

	switch (Recovery)
	{

	case MVRecovery_StandUp:
	{

		float Space_Stand = CndELS_Func::Get_StandUpSpace(CndCharacter_Target, Height_Default);

	break;
	}

}

}

FYI: Yes, I’m using my in-house debug tool.

im not sure if this is what you mean by ‘doesnt seem right’ but you’d be tracing from the ActorLocation (center) not ground?

Well, I’m trying to do the sweep trace from the ground to character’s height. But… GetActorLocation actually gets the location from the center of the capsule?

Plus, why would the trace, “slightly” pass through the ceiling, to the floor of the obstacle, as on the screen, giving… improper distance?

The free ALS (advanced locomotion system) plugin has a GitHub repo which does this. Can use it as example (uncrouch, probably on animation BP).

Can’t review the code of your system that you didn’t post, but difference formula is always “Abs (A - B)”. (Use FMath::Abs)

I don’t know, I thought it gets its 0 location regardless of what the actor’s components are offset to. Might be wrong though since I’ve seen similar screwy systems in other engines and it seems you have tested this?

Still, you can get the capsule relative location and calculate the extension if you require.

If extremely minimal (to the eye), it’s a float precision problem. If it’s a capsule trace problem, you could still try a line trace, since you’re only interested in the line distance.

  1. I want to rely on my own locomotion system.
  2. ALS uses simple Uncrouch.
  3. Made adjustments to debugging.
  4. I did posted some code, starting as “namespace CndELS_Func”

So why the sphere sweep hit is not hitting the actual ceiling, but over (hitting top of the mesh)? And no, no offset is added.

well id say check the collision profile

and debug a bit more, origin and HitResult. it kinda looks like your line is starting and finishing higher. it could be your line trace is correct but your debug line offset?

Did some thinkering and reworking this. Managed to get the ceiling to be hit propertly. I think it indeed had to do something with collision. Particularly a trace channel.

Instead of what I did, I focused on more better solution: Getting Distance between Desired Default Height and Current Location + Current Height.


namespace CndELS_Func
{


	FHitResult Get_Hit_Space_StandUp(ACndCharacter_Master* CharacterTarget, FVector OwnerLocation, float TargetHeight)
	{

		FHitResult HR_StandUp_Space;

		FVector CndOwnerLocation_Eye = OwnerLocation;
		FVector CndOwnerLocation_Ground = CndCharacter_Data_Basic::Get_Location_Ground(CharacterTarget);

		FVector TraceStart = CndOwnerLocation_Eye; // Dynamically reflects current character posture
		FVector TraceEnd = CndOwnerLocation_Ground + FVector(0.0f, 0.0f, TargetHeight); // Represents the full standing profile

		bool Hit = CharacterTarget->GetWorld()->SweepSingleByChannel(
			HR_StandUp_Space,
			TraceStart,
			TraceEnd,
			FQuat::Identity, // Orientation of the capsule
			CharacterTarget->CndSys_ODS.Params.CC.Trace_Floor, // Collision Trace Channel
			CharacterTarget->CndSys_ODS.Params.UpTrace.Shape,
			CharacterTarget->CndSys_ODS.Params.CollisionQueryParams);

		return HR_StandUp_Space;

	}

	float Get_StandUpSpace(ACndCharacter_Master* CharacterTarget, float TargetHeight)
	{

		// FVector CndOwnerLocation = CndCharacter_Data_Basic::Get_Location(CharacterTarget);
		FVector CndOwnerLocation_Eye = CndCharacter_Data_Basic::Get_Location_Eye(CharacterTarget);

		float Height_Desired = TargetHeight * 2;

		FVector ImpactPoint = CndELS_Func::Get_Hit_Space_StandUp(CharacterTarget, CndOwnerLocation_Eye, Height_Desired).ImpactPoint;

		// Get Distance Between Owner's Eye Location and Impact Point
		float Distance = CndFunc_Global::Get_DistanceBetween(CndOwnerLocation_Eye, ImpactPoint);

		return Distance;

	}

}

But there’s one more problem here:

void UCndComp_LocomotionSystem::BPF_UEF_CndCharacter_Desired_MVRecovery(ECndCharacter_Movement_Recovery Recovery)
{

	float Height_Default = CndCharacter_Data_Params::Get_Height_Map(CndCharacter_Target, H_Default); // 90,0
	float Height_Ducking = CndCharacter_Data_Params::Get_Height_Map(CndCharacter_Target, H_Ducking); // 70,0
	float Height_Crouching = CndCharacter_Data_Params::Get_Height_Map(CndCharacter_Target, H_Crouching); // 50,0
	float Height_Laying = CndCharacter_Data_Params::Get_Height_Map(CndCharacter_Target, H_Laying); // 20,0

	switch (Recovery)
	{


	// When Jump is Pressed (when laying) / Crouch is Released
	case MVRecovery_StandUp:
	{
		
		// Changes to MVRecovery_None, when reaching default height

		float Space_Stand = CndELS_Func::Get_StandUpSpace(CndCharacter_Target, Height_Default);
		
		// Check for Space to fully Stand Up
		if (Space_Stand >= Height_Default)
		{

			// Set Desired Stance to: Standing
			CndCharacter_Manage_Move::Set_Stance(CndCharacter_Target, MVStance_Standing, true);

			// Set Desired Gait to: Default
			CndCharacter_Manage_Move::Set_Gait(CndCharacter_Target, MVGait_Default, true);

		}

		// If there's space to Duck only
		else if (CndFunc_Global_Conditions::IsValue_RangedBetween(Space_Stand, Height_Ducking, Height_Default))
		{

			// Set Desired Stance to: Ducking
			CndCharacter_Manage_Move::Set_Stance(CndCharacter_Target, MVStance_Ducking, true);

			// Set Desired Gait to: Walking
			CndCharacter_Manage_Move::Set_Gait(CndCharacter_Target, MVGait_Walking, true);

		}

		// If there's space to Crouch only
		else if (CndFunc_Global_Conditions::IsValue_RangedBetween(Space_Stand, Height_Laying, Height_Crouching))
		{

			// Set Desired Stance to: Crouching
			CndCharacter_Manage_Move::Set_Stance(CndCharacter_Target, MVStance_Crouching, true);

			// Set Desired Gait to: Crouching
			CndCharacter_Manage_Move::Set_Gait(CndCharacter_Target, MVGait_Crouching, true);

		}

}

}

To be precise.
If: Available Space is over 70, and ducks up.
Then: When available Space decreases to matching crouching height - jumps between Crouching and Ducking.
Additional: This stops when available space is over 90, upon exiting the space with ceiling above.

Direct comparison as a fix to this is out of the question. Why?

// For many stances/postures, ranges are required.

if (Space_Stand >= Height_Default) // This is fine.
{
}
else if (Space_Stand >= Height_Ducking) // Not okay.
{

			// Set Desired Stance to: Ducking
			CndCharacter_Manage_Move::Set_Stance(CndCharacter_Target, MVStance_Ducking, true);

			// Set Desired Gait to: Walking
			CndCharacter_Manage_Move::Set_Gait(CndCharacter_Target, MVGait_Walking, true);

}
else if (Space_Stand >= Height_Crouching) // Not okay.
{

			// Set Desired Stance to: Crouching
			CndCharacter_Manage_Move::Set_Stance(CndCharacter_Target, MVStance_Crouching, true);

			// Set Desired Gait to: Crouching
			CndCharacter_Manage_Move::Set_Gait(CndCharacter_Target, MVGait_Crouching, true);


}
// Not okay. Goes all the way, triggering all Desired Stances at once. Values need to be are ranged.

Okay, I think I should’ve done this the other way, but I fixed this, by adding Current Stance Getter.

FYI: What’s going on here?

When you want to stand up, from sliding/laying, via Pressing Jump, you enter standing up Recovery State. It doesn’t change to MVRecovery_None until reaching default height.

This function sweeps sphere up from characters eye level (Current Location + FVector(0.0f, 0.0f, Current Capsule Half-Height)) to default height (Current Location - Current Capsule Half-Height + FVector(0.0f, 0.0f, DesiredHeight) ), returning Distance between head and ceiling.

float ClearanceFromEye = CndELS_Func::Get_StandUpSpace(CndCharacter_Target, Height_Default);

By adding Current Stance Getter to my function, whilst still checking for available space to stand up:

void UCndComp_LocomotionSystem::BPF_UEF_CndCharacter_Desired_MVRecovery(ECndCharacter_Movement_Recovery Recovery)
{

	float Height_Default = CndCharacter_Data_Params::Get_Height_Map(CndCharacter_Target, H_Default); // 90,0
	float Height_Ducking = CndCharacter_Data_Params::Get_Height_Map(CndCharacter_Target, H_Ducking); // 70,0
	float Height_Crouching = CndCharacter_Data_Params::Get_Height_Map(CndCharacter_Target, H_Crouching); // 50,0
	float Height_Laying = CndCharacter_Data_Params::Get_Height_Map(CndCharacter_Target, H_Laying); // 20,0

	switch (Recovery)
	{


	// When Jump is Pressed (when laying) / Crouch is Released
	case MVRecovery_StandUp:
	{
		
		// Changes to MVRecovery_None, when reaching default height

		// float CurrentHeight = CndCharacter_Data_Params::Get_Height_Current(CndCharacter_Target);
		float ClearanceFromEye = CndELS_Func::Get_StandUpSpace(CndCharacter_Target, Height_Default);

		// The closer to Default Height, the less available space.
		float Space_Stand = ClearanceFromEye;
		
		
		// Check for Space to fully Stand Up - 90,0
		if (Space_Stand >= Height_Default)
		{

			BPF_MVAction_Desired_Posture(MVPosture_Default);

		}

		// Get Current Stance
		ECndCharacter_Movement_StanceType StanceType = CndCharacter_Manage_Move::Get_Stance(CndCharacter_Target, false);

		if (StanceType != MVStance_Ducking)
		{

			// If there's space to Duck only - Between 70,0 and 90,0
			if (CndFunc_Global_Conditions::IsValue_RangedBetween(Space_Stand, Height_Crouching, Height_Default))
			{



				BPF_MVAction_Desired_Posture(MVPosture_Duck);

			}

		}
		else if (StanceType == MVStance_Laying)
		{
			// If there's space to Crouch only - Between 20,0 and 50,0
			if (CndFunc_Global_Conditions::IsValue_RangedBetween(Space_Stand, Height_Laying, Height_Crouching))
			{

				BPF_MVAction_Desired_Posture(MVPosture_Crouch);

			}

		}
break;
}
}

I’m preventing the character from bouncing between crouching and ducking, if there’s space to duck up.