I’m new to Unreal Engine and C++, and I’m currently learning how UCharacterMovementComponent works.
While trying to override CalcVelocity(), I noticed that UCharacterMovementComponent has an Acceleration member (FVector), but I’m having trouble understanding how this value is related to the player’s input.
I traced the code and found UPawnMovementComponent::GetPendingInputVector(), but I couldn’t figure out the complete flow from player input to CharacterMovementComponent::Acceleration.
Specifically, I’m trying to understand:
How does input from the PlayerController eventually become the Acceleration used by UCharacterMovementComponent?
Is the flow roughly:
PlayerController input → Character/Pawn → PendingInputVector → CharacterMovementComponent::Acceleration
If so, which function(s) are responsible for transferring the input between these stages?
I’m particularly interested in understanding the relevant C++ call path in CharacterMovementComponent / PawnMovementComponent, rather than just how to use the Blueprint input system.
Your flow is basically right. There is one link missing and it is the reason the trail went cold.
AddMovementInput on the Pawn is the entry point. It forwards to UPawnMovementComponent::AddInputVector which accumulates into ControlInputVector. That accumulated value is what GetPendingInputVector reads.
The consumption happens in UCharacterMovementComponent::TickComponent. It calls ConsumeInputVector which returns the accumulated vector and zeroes it in the same call. That single detail is probably what confused you. If you read GetPendingInputVector from inside CalcVelocity you get a zero vector every time because it was already consumed earlier in the same tick.
The consumed vector is handed to ControlledCharacterMove and the assignment you are hunting for is one line in there.
Acceleration = ScaleInputAcceleration(ConstrainInputAcceleration(InputVector));
Two transforms happen on that line and both matter.
ConstrainInputAcceleration zeroes the Z component while you are walking on the ground and applies your plane constraint if you set one. So a movement input with a vertical component does nothing while grounded.
ScaleInputAcceleration clamps the input to a maximum size of one and then multiplies by MaxAcceleration. So Acceleration is in centimetres per second squared and its magnitude never exceeds MaxAcceleration no matter how big the vector you fed in was.
The next line sets AnalogInputModifier from ComputeAnalogInputModifier. That is the value that lets a half deflected analog stick give you a lower top speed rather than just a slower ramp up. It feeds GetMaxSpeed rather than Acceleration so it is easy to miss when you are tracing only the acceleration path.
For what you are actually trying to do the ordering is the good news. Acceleration is assigned before PerformMovement runs so by the time your CalcVelocity override is called it already holds this tick’s input. Read Acceleration directly in there. You never need to reach back to the input vector.
One more thing worth knowing early. On a network client the path is not the same. ControlledCharacterMove is what runs on the authority and on a client the move goes through ReplicateMoveToServer first. If you put logic in CalcVelocity that depends on anything other than the movement component’s own state you will see it behave differently in a listen server test than in standalone.