Need Help: Improving my Custom Locomotion System

I need some help improving my locomotion system.

This is my setup so far:

namespace CndSys_Locomotion

FHitResult Get_Hit_Obstacle_Face(ACndCharacter_Master* CharacterTarget, FGD_CndSys_ODS_Input_Face InputSweepData)
{

	FHitResult HR_Face;

	// Get Data

	FVector CndOwner_Location = InputSweepData.Owner_Location;
	float CndOwner_Height = InputSweepData.Owner_Height;		
	float SweepDistance = InputSweepData.SweepDistance;
	ECndComp_LocomotionSys_SweepType SweepType = InputSweepData.SweepType;

	// Modifies Shape of Face Trace Height
	CharacterTarget->CndSys_ODS.Params.Trace.Face.Height = CndOwner_Height;

	FVector TraceFace_Start = CndOwner_Location;

	FVector TraceFace_End;

	if (SweepType == Velocity)
	{

		FVector CndOwnerVelocity = CndCharacter_Data_Basic::Get_Velocity(CharacterTarget);
		FVector TraceFace_End_Velocity = TraceFace_Start + CndOwnerVelocity.GetSafeNormal() * SweepDistance;

		TraceFace_End = TraceFace_End_Velocity;

	}
	else if (SweepType == Facing)
	{

		FVector CndOwnerForwardVector = CndCharacter_Data_Basic::Get_ForwardVector(CharacterTarget);
		FVector TraceFace_End_Face = TraceFace_Start + CndOwnerForwardVector.GetSafeNormal() * SweepDistance;

		TraceFace_End = TraceFace_End_Face;

	}

	bool Hit = CharacterTarget->GetWorld()->SweepSingleByObjectType(
		HR_Face,
		TraceFace_Start,
		TraceFace_End,
		FQuat::Identity,
		CharacterTarget->CndSys_ODS.Params.CollisionObjectQueryParams,
		CharacterTarget->CndSys_ODS.Params.Trace.Face.Shape,
		CharacterTarget->CndSys_ODS.Params.CollisionQueryParams
	);

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

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

		DrawDebugLine(CharacterTarget->GetWorld(),
			TraceFace_Start,
			TraceFace_End,
			TraceColor,
			false,
			0.0f
		);

		if (HR_Face.bBlockingHit)
		{

		DrawDebugSphere(
			CharacterTarget->GetWorld(),
			HR_Face.ImpactPoint,
			10.0f, // Radius
			CharacterTarget->CndSys_ODS.Params.Trace.Ground.Segments,
			TraceColor,
			false,
			0.0f
		);

		}
	}

	return HR_Face;
}
FHitResult Get_Hit_Obstacle_Top(ACndCharacter_Master* CharacterTarget, FGD_CndSys_ODS_Input_Height InputSweepData)
{

	FHitResult HR_HeightCheck;

	FVector Wall_Location = InputSweepData.Location;
	FVector Wall_Normal = InputSweepData.Normal;
	float Owner_Height = InputSweepData.Owner_Height;

	FRotator Normal_RotFromX = FRotationMatrix::MakeFromX(Wall_Normal).Rotator();
	FVector Normal_ForwardVector = Normal_RotFromX.Vector(); // Same as GetForwardVector()

	float Height_Owner = Owner_Height;

	FVector LocAndNormal = Wall_Location + Normal_ForwardVector;

	FVector TraceDown_Start = LocAndNormal + FVector(0.0, 0.0, Height_Owner);
	FVector TraceDown_End = LocAndNormal - FVector(0.0, 0.0, Height_Owner);


	bool Hit = CharacterTarget->GetWorld()->SweepSingleByObjectType(
		HR_HeightCheck,
		TraceDown_Start,
		TraceDown_End,
		FQuat::Identity,
		CharacterTarget->CndSys_ODS.Params.CollisionObjectQueryParams,
		CharacterTarget->CndSys_ODS.Params.Trace.Down.Shape,
		CharacterTarget->CndSys_ODS.Params.CollisionQueryParams
	);

	if (CharacterTarget->CO_ELS->ELS_DebugEnabled)
	{

		FColor TraceColor = HR_HeightCheck.bBlockingHit ? FColor::Yellow : FColor::Magenta;

		DrawDebugSphere(
			CharacterTarget->GetWorld(),
			TraceDown_Start,
			CharacterTarget->CndSys_ODS.Params.Trace.Down.Radius,
			CharacterTarget->CndSys_ODS.Params.Trace.Down.Segments,
			TraceColor,
			false,
			0.0f, // Lifetime
			0, // Depth Priority (Front Visibility)
			0.50f // Thickness 
		);

		DrawDebugLine(CharacterTarget->GetWorld(),
			TraceDown_Start,
			TraceDown_End,
			TraceColor,
			false,
			0.0f, // Lifetime
			0, // Depth Priority (Front Visibility)
			1.0f // Thickness
		);

		DrawDebugSphere(
			CharacterTarget->GetWorld(),
			TraceDown_End,
			CharacterTarget->CndSys_ODS.Params.Trace.Down.Radius,
			CharacterTarget->CndSys_ODS.Params.Trace.Down.Segments,
			TraceColor,
			false,
			0.0f, // Lifetime
			0, // Depth Priority
			0.50f // Thickness
		);

		DrawDebugSphere(
			CharacterTarget->GetWorld(),
			HR_HeightCheck.ImpactPoint,
			5.0f, // Radius
			CharacterTarget->CndSys_ODS.Params.Trace.Down.Segments,
			FColor::Magenta,
			false,
			0.0f, // Lifetime
			0, // Depth Priority
			1.0f // Thickness
		);

	}

	return HR_HeightCheck;

}

FGD_CndSys_ObstacleDetection_Results_Obstacle Get_ObstacleData(ACndCharacter_Master* CharacterTarget, ECndComp_LocomotionSys_SweepType SweepType)
{

	FVector Owner_Location = CndCharacter_Data_Basic::Get_Location(CharacterTarget);
	float Target_Height = CndCharacter_Data_Params::Get_Height_Current(CharacterTarget);	

	// Step 01: Input Data for Face Sweep
	FGD_CndSys_ODS_Input_Face FaceSweep_Data;

	FaceSweep_Data.Owner_Location = Owner_Location;
	FaceSweep_Data.Owner_Height = Target_Height;
	FaceSweep_Data.SweepDistance = CharacterTarget->CndSys_ODS.Settings.Range.Springboard;
	FaceSweep_Data.SweepType = SweepType;

	// Then Sweep Capsule Forward, using Facing / Velocity sweep type.

	// Step 02: Get Results from Facing/Velocity sweep.
	FHitResult HR_Face = Get_Hit_Obstacle_Face(CharacterTarget, FaceSweep_Data);

	// Step 03: Input Data for Height Sweep
	FGD_CndSys_ODS_Input_Height HeightSweep_Data;

	HeightSweep_Data.Location = HR_Face.Location;
	HeightSweep_Data.Normal = HR_Face.Normal;
	HeightSweep_Data.Owner_Height = Target_Height;

	// Step 04: Trace Line Down from Owner's Height
	FHitResult HR_Height = Get_Hit_Obstacle_Top(CharacterTarget, HeightSweep_Data);

	// Step 05: Check if there's room.

	// Step 06: Get Obstacle Data
	FGD_CndSys_ObstacleDetection_Results_Obstacle Obstacle;

	Obstacle.bHit = HR_Face.bBlockingHit;
	Obstacle.Top = HR_Height.Location;

	return Obstacle;
}

  1. I want to build and rely on my own Locomotion System.

  2. Tutorials, like LocoDev’s Vaulting Tutorial only covers the line-trace, which is useless for my types of vaultable obstacles (varying with height and spaces between/below them). Hence where Capsule Sweep is needed here.

  3. I’m trying to make it coop with climbing up the vents.

Basically, what I want is to get better apex of the vaultable obstacle, obstacle thickness, the room on the other side of the obstacle, so I can trace down, if there’s ground. And also to coop with climbing up the vents.

But first, I wanna focus on vaultable obstacles.

And that’s how it all works so far:

UPDATE:

I’ve decided to give a try with GAS (Game Animation Sample)'s obstacle detection system, but got disappointed, as it came out with few major flaws, like ignoring spaces, like vents, and skipping ledges with the same distance away from the wall (going all the way to the toppest ledge instead of nearest), hence I’m making my own, to trace down from eye level, making it more accurate.

And give it more info, like approach angle, and obstacle angle.

Is there a way I can get the back ledge of the obstacles, like railings (obstacles with spaces below)? Like trace along them, to get the back ledge? Or sweep in the facing direction, until finding available space, to determine the depth of an obstacle?

I’m sitting on this for way too long. And this is nowhere covered.

FYI: I ended up asking ChatGPT and Copilot, but these know just the same as I do, how to fix it - nothing.

yeah usually its just a loop of line traces. (ie check every x distance until the line trace misses meaning the last hit was the end)

its tricky for complex geometry, for simple geometry i’d use traces which make it modular but for complex it may be worth just determining it in blueprint and use an interface to GetMantlePoints or something

I’ve been thinking of something more like this (best approach I could come up with):


LEGEND:

Cyan Circle and Arrow = Player Location and Facing

White Circle, in the middle = Obstacle Origin (World Position)
Dark Green Circle = Front Sweep Capsule.
It returns Impact Location and Normal of the hit obstacle. Normal of the hit obstacle is represented by Red Arrows.

Trace Down from Player’s Eye level to the impact from Front Sweep Capsule.

Green X = Front Ledge Location

Get Distance from World Origin to Front Impact Point, then reverse it to detect the Back Ledge.

Yellow X = Back Ledge Location
Dark Blue X = Exit (Desired) Impact Point based of Player Facing.

		FVector HitComp_Origin;
		FVector HitComp_BoxExtent;
		float HitComp_SphereRadius;

		// Get the component's origin and bounds
		UKismetSystemLibrary::GetComponentBounds(HitComp, HitComp_Origin, HitComp_BoxExtent, HitComp_SphereRadius);

UPDATE:
Did some changes, it kinda works but… messes up at certain obstacles. Like when they’re at certain angle.



FYI:
Black Capsule = Forward Sweep Trace
Green Sphere = Traces down to find front ledge.

HELPER FUNCTION

		FGD_LineTraceByChannel_Primitive_Out LineTraceComponent_Primitive(FGD_LineTraceByChannel_Primitive_In Input)
		{

			FGD_LineTraceByChannel_Primitive_Out Result;

			FHitResult HR_Trace;

			FCollisionQueryParams Params;
			Params.bTraceComplex = Input.TraceComplex;

			bool ReturnValue = Input.Target->LineTraceComponent
			(
				HR_Trace,
				Input.TraceStart,
				Input.TraceEnd,
				Params
			);

			Result.OutHit = HR_Trace;
			Result.ReturnValue = ReturnValue;

			return Result;

		}

// Step 02: Try to find back ledge
if (HR_FrontLedgeTrace.ReturnValue)
{
	// 1) Grab the wall normal from the Forward Sweep Impact
	FVector WallNormal = Initial_HR.ImpactNormal.GetSafeNormal();
	FVector FrontPoint = HR_FrontLedgeTrace.OutHit.ImpactPoint;

	// 2) Always trace Into the wall via -WallNormal:
	float   StepOut = Actor_CapsuleRadius + 2.0f;           // start just outside
	FVector TraceStart = FrontPoint + WallNormal * StepOut;

	// 3) Dynamically get half-depth of the mesh along that same normal
	FVector BoxOrigin, BoxExtent;
	float SphereRadius;
	UKismetSystemLibrary::GetComponentBounds(
		HitComp, BoxOrigin, BoxExtent, SphereRadius);

	// Project the box-extents onto the wall normal to find half-depth
	float HalfDepth =
		FMath::Abs(FVector::DotProduct(WallNormal, BoxExtent));

	// Sweep THROUGH: full depth + a tiny margin
	float SweepDist = (HalfDepth * 2.0f) + 2.0f;
	FVector TraceEnd = FrontPoint - WallNormal * SweepDist;

	// 4) Fire a component-only line trace and capture the result
	auto BackResult = CndFunc::Helper::LineTraceComponent_Primitive({
		HitComp, TraceStart, TraceEnd, TraceComplex
		});

	// 5) Debug-draw the exact line so you see you’re piercing the mesh
	if (Debug.Enabled)
	{

		DrawDebugSphere(
			CharacterTarget->GetWorld(),
			TraceStart,
			10.0,
			12,
			FColor::Red,
			false,
			1.0,
			1,
			1.0);

		DrawDebugSphere(
			CharacterTarget->GetWorld(),
			TraceEnd,
			10.0,
			12,
			FColor::Red,
			false,
			1.0,
			1,
			1.0);

		DrawDebugLine(
			CharacterTarget->GetWorld(),
			TraceStart,
			TraceEnd,
			FColor::Red,
			false,
			1.0f,
			1,
			2.0f
		);


			DrawDebugSphere(
				CharacterTarget->GetWorld(),
				HitComp_Origin,
				10.0,
				12,
				FColor::White,
				false,
				1.0,
				1,
				1.0);

	}

Bumping this until I get an solution. I got lots of other things to do, than mess with this for months.