How to write a Custom Movement Mode?

I’d like to know where to find tutorials for how to implement your own custom movement mode. I’ve seen many tutorials going over high level concepts, replication and such, but none that actually explains what’s going on inside the Phys* functions (PhysWalking, PhysFalling, PhysFlying, etc). For example, what does RestorePreAdditiveRootMotionVelocity and ApplyRootMotionToVelocity do? How do I handle and react to collisions and modify Velocity and Position accordingly?

I’ve tried to create one as pasted below. It removes all friction with the ground, allowing the player to pick up speed when sliding down hills. It’s very flawed though and has a tendency to get stuck on (not so) uneven terrain, killing all momentum.

Preferably, I’d like some tutorial that breaks down the various Phys* functions that comes with the default CharacterMovementComponent, explaining what each function does and why. What I want to accomplish is the skiing mechanic from the Tribes Series, specifically Tribes: Ascend and have deemed the regular movement modes unfit for this mechanic.

Does anyone know any good material to learn from?

void UCustomCharacterMovementComponent::PhysCustom(float deltaTime, int32 Iterations)
{
    if (deltaTime < MIN_TICK_TIME)
    {
        return;
    }

    bJustTeleported = false;
    float remainingTime = deltaTime;
    const FVector Gravity(0.f, 0.f, GetGravityZ());

    // Perform the move
    while ((remainingTime >= MIN_TICK_TIME) && (Iterations < MaxSimulationIterations) && CharacterOwner && (CharacterOwner->Controller || bRunPhysicsWithNoController || HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocity() || (CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy)))
    {
        Iterations++;
        bJustTeleported = false;
        const float timeTick = GetSimulationTimeStep(remainingTime, Iterations);
        remainingTime -= timeTick;

        RestorePreAdditiveRootMotionVelocity();

        const FVector OldVelocity = Velocity;

        if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity())
        {
            Velocity.Z = 0.f;
            CalcVelocity(timeTick, 0, false, 0);
            Velocity.Z = OldVelocity.Z;
        }

        // Apply gravity
        Velocity = NewFallVelocity(Velocity, Gravity, timeTick);

        ApplyRootMotionToVelocity(timeTick);

        FVector OldLocation = UpdatedComponent->GetComponentLocation();
        const FVector Adjusted = Velocity * timeTick;
        FHitResult Hit(1.f);
        SafeMoveUpdatedComponent(Adjusted, UpdatedComponent->GetComponentQuat(), true, Hit);

        if (Hit.Time < 1.f)
        {
            const FVector GravDir = FVector(0.f, 0.f, -1.f);
            const FVector VelDir = Velocity.GetSafeNormal();
            const float UpDown = GravDir | VelDir;
            GEngine->AddOnScreenDebugMessage(-1, 0.f, FColor::Orange, FString::Printf(TEXT("UpDown: %f"), UpDown));

            bool bSteppedUp = false;
            if ((FMath::Abs(Hit.ImpactNormal.Z) < 0.2f) && (UpDown < 0.5f) && (UpDown > -0.2f) && CanStepUp(Hit))
            {
                float stepZ = UpdatedComponent->GetComponentLocation().Z;
                bSteppedUp = StepUp(GravDir, Adjusted * (1.f - Hit.Time), Hit);
                if (bSteppedUp)
                {
                    OldLocation.Z = UpdatedComponent->GetComponentLocation().Z + (OldLocation.Z - stepZ);
                }
            }

            if (!bSteppedUp)
            {
                //adjust and try again
                HandleImpact(Hit, timeTick, Adjusted);
                SlideAlongSurface(Adjusted, (1.f - Hit.Time), Hit.Normal, Hit, true);
            }
        }

        if (!bJustTeleported && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity())
        {
            Velocity = (UpdatedComponent->GetComponentLocation() - OldLocation) / timeTick;
        }
    }
}

What happens inside the character movement component is pretty advanced stuff, I don’t know of any tutorials that really go into how the code works. Not that there are no people that understand it (although it’s really messy and hard to read), it’s just that it’s so big that a full explanation would be very time consuming and the audience would be pretty small because beginners probably couldn’t follow anyway (so it wouldn’t be very lucrative for youtube and such). But to at least answer one of your questions regarding the root motion functions:

Root motion means we have animations that move the root component. When the capsule is moved from animation and not through a calculated location delta it becomes a little more tricky to manage the velocity of the character. For example, we usually still want to have gravity affect the character when in a root motion animation. Generally that’s what these checks are for and there are a lot of them, which is very unclean design. They probably just had regular movement before and slapped root motion support on top of it. I don’t think Tribes had any root motion so you can just throw these calls out in your code.

To be honest, I don’t think you need a custom movement mode. It would probably suffice for you to override a few specific functions to achieve Tribes style movement. First and foremost you should look at CalcVelocity, which predominantly determines your velocity and handles the friction calculation when grounded, which is probably the most crucial part for the style of movement you want to achieve. The rest of the Phys functions is mainly stuff that assures that you don’t get stuck (which is why you do get stuck because you don’t run that code anymore). There is nothing else in there that is diametrically opposed to Tribe’s movement style.

The problem with the walking physics is that, while in that state, the Z velocity is set to 0. The player’s Z position is instead handled by sticking to whatever floor that’s underneath the player. This messes up the transition between the walking and falling states since the Z component velocity gets “eaten”, causing a bad velocity to be inherited by the new state. You can avoid the stickiness by setting MaxStepHeight=0 and I managed modify the Velocity.Z to better reflect the true velocity, but the biggest problem is that the walking state doesn’t consider slopes in any way – other than checking whether it’s walkable or not – and I thought that there had to be a better way.

The code I provided it mostly copied from PhysFlying with added gravity, though I didn’t expect the collision handling to be so bad. I’ll try improving my old solution again, focusing on the CalcVelocity function, and tell you how it went (though it’ll probably take a while). Thank you.

I haven’t tried to implement it so I can’t say anything for sure but you probably don’t want to change the velocity Z = 0 thing. It will only make things more complicated and as you said, the player will stick to the floor anyway so you only need to be concerned about the velocity 2D direction and size. By default the slope doesn’t affect the speed correct, but that’s precisely the kind of stuff you need to change. You usually have the information you need within the hit result of CurrentFloor, so you can kinda scale the acceleration according to the floor normal Z in CalcVelocity. If you really need the Z velocity afterall, you can override MaintainHorizontalGroundVelocity(). That’s the good thing about the character movement component. You can override almost every function so you can hook yourself in pretty much wherever you like.

The reason you need the Z velocity is when transitioning to the falling state. It needs a proper Z value to work with, else it be impossible to gain vertical height when jumping of a ramp/bump (Velocity.Z is always 0 at the start of the falling state). The same goes for landing as the Z velocity simply gets “eaten” on impact, continuing up or down whatever slope at the same X and Y values before landing, not considering how much speed that would realistically get absorbed by the normal force of the ground.

But as you say, this is probably better handled by storing the Z component in a separate variable and handle collisions when landing on / leaving the ground, rather that having the Z component be updated continuously. After that, it’s only a matter of how I handle that variable in the CalcVelocity function, of which I’ve yet to attempt.

Actually, I have to backpaddle on some of my previous suggestions. I would actually recommend a custom movement mode after all. I had a look at some videos on youtube and the movement is actually more physically based than I remembered. In fact, it looked so fun that I implemented a basic version myself:

I actually only needed about 50 lines of code to achieve this. I am using my own movement framework which already has a lot of utility so it’s probably a bit more work to do it in the character movement, but it’s really not difficult. The crux of the matter is that the character movement component goes to great lengths to simulate the way living things move while actually being a rigid body (a capsule). But the Tribes movement (at least the high velocity sliding part) behaves much more like an actual rigid body, meaning you move more like a fast ball or a car than a human. This means that you can use basic physics equations to simulate the movement and just slide along surfaces willy-nilly without regard for where the hit normals are pointing. If you are still interested I can share the code.

Yes, it would be appreciated if you could do that. I feel like I’ve been struggling with it for long enough.

That video looks really nice!

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

Thank you very much! As I thought, there was a simpler solution. I’ll be trying it after work.

Could you elaborate on the “FFloorParameters” type and “UpdateFloor” function? Google doesn’t recognize them, so I assume it’s a custom type?

Edit: Nvm, switched them for FFindFloorResult and FindFloor instead.

Grim, Could you share the code with me as well as I been trying to work this out and make a surf like game for fun with friends and been having the same issues!

AerialTights,

Could you give some guidance on where the best place to put this would be? I am a kind of new to C++ coding and mainly did blueprint coding and I am trying to learn as there are some restrictions such as the sliding like your trying to do that I would like to code.

I see Grim gave really good code that makes sense to me just where is the best place to place it and can I call it from a blueprint after I have added the code into the engine?

Had the exact same issue for several days before figuring out the solution.

I know it’s very, VERY late but for the sake of your issue of fixing this in blueprint and for anyone in the future searching for a solution to this problem in blueprint, here you fine folks are:
https://dev.epicgames.com/community/snippets/1ee/unreal-engine-sliding-movement-based-on-surface-geometry

The link is 404

4 Likes