How to write a Custom Movement Mode?

Posting this as a new answer for readability and because it’s a better answer to your question.

As I mentioned previously I am using my own movement system so some of the functions will not be available to you and you’ll have to find substitutes or implement them yourself, so treat this more as pseudocode. I think the function names are fairly descriptive though. I can go over some things very quickly:

  • The curcial part for the sliding mechanic is that the gravity is scaled depending on the normal of the ground the pawn is standing on. The steeper the slope, the more gravity accelerates the pawn.
  • CalculateAirResistance and CalculateRollingResistance just apply the formulas, which you can google.
  • When using real-world physics you have to be careful with the units. 1 unreal unit is 1 cm but scientific formulas are usually based on meters. The functions I use account for this internally so I did not need to do this here, but if you mess up the conversions you will get very strange behaviour.
  • AdjustVelocityFromHit just projects the velocity onto the hit normal (FVector::VectorPlaneProject) which prevents the pawn getting stuck when colliding. It also allows us to just continually apply gravity without having to worry about collisions with the ground.
  • This being physically based, you really have to find the right parameters otherwise you will also get very weird movement. Especially the rolling and air resistance in relation to the input force and gravity have to be quite finely tuned to each other.

This works really well for the sliding movement and can work for regular movement if you change the parameters at runtime, but the character phys walking is still much better suited for “normal” humanoid movement. You should have this as a custom movement mode that gets triggered once you reach a certain velocity or press a button or whatever.

  FVector Gravity{0.f, 0.f, GetGravityZ()};
  FFloorParameters Floor;
  UpdateFloor(Floor, 1.f, 0.f);
  const bool bGrounded = Floor.HasValidShapeData() ? Floor.GetShapeDistanceToFloor() < 2.f : false;

  AddForce(InputForce * (bGrounded ? 1.f : AirControl) * GetInputVector());

  float GravityScaleDynamic{1.f};
  if (bGrounded)
  {
    // Scale gravity with the floor slope.
    GravityScaleDynamic = 2.f - FMath::Abs(Floor.ShapeHit().Normal | Gravity.GetSafeNormal());
  }
  AddAcceleration(Gravity * GravityScaleDynamic * GravityScaleConst);

  const FVector AirResistance = CalculateAirResistance(GetVelocity(), DragCoefficient);
  AddForce(AirResistance);

  if (bGrounded)
  {
    const FVector RollingResistance = CalculateRollingResistance(GetVelocity(), Mass, GetGravityZ(), RollingCoefficient);
    AddForce(RollingResistance);
  }

  if (GetVelocity().SizeSquared() < FMath::Square(BRAKE_TO_STOP_VELOCITY))
  {
    UpdateVelocity(FVector::ZeroVector);
    return;
  }

  const FVector LocationDelta = GetVelocity() * DeltaTime;

  // Move updated component.
  FHitResult MoveHitResult;
  float RemainingDeltaTime = DeltaTime;
  FVector RemainingLocationDelta = LocationDelta;
  SafeMoveUpdatedComponent(RemainingLocationDelta, UpdatedComponent->GetComponentQuat(), true, MoveHitResult);
  RemainingDeltaTime -= RemainingDeltaTime * MoveHitResult.Time;
  if (MoveHitResult.IsValidBlockingHit())
  {
    AdjustVelocityFromHit(MoveHitResult);
    RemainingLocationDelta = Velocity * RemainingDeltaTime;
    SafeMoveUpdatedComponent(RemainingLocationDelta, UpdatedComponent->GetComponentQuat(), true, MoveHitResult);
    RemainingDeltaTime -= RemainingDeltaTime * MoveHitResult.Time;
    if (MoveHitResult.IsValidBlockingHit())
    {
      AdjustVelocityFromHit(MoveHitResult);
      RemainingLocationDelta = Velocity * RemainingDeltaTime;
      const float SlideDeltaApplied = SlideAlongSurface(RemainingLocationDelta, 1.f, MoveHitResult.Normal, MoveHitResult);
      RemainingDeltaTime -= RemainingDeltaTime * SlideDeltaApplied;
      if (MoveHitResult.IsValidBlockingHit())
      {
        AdjustVelocityFromHit(MoveHitResult);
      }
    }
  }
  if (MoveHitResult.bStartPenetrating)
  {
    // Handle pawn being stuck in geometry.
  }
1 Like