Movement and the interactor's velocity hook
How the interactor reads your pawn's velocity, and how to plug in your own movement.
Assumes you have finished quick-start.
This page explains how the interactor reads your pawn's velocity and how to plug in a movement system the engine does not report on.
The kit reads your pawn's speed in exactly one place: UUKInteractorComponent::GetInteractorVelocity().
The kit's hold behaviours use it to decide whether movement cancels a hold; nothing else in the kit
reads velocity, and the kit never reads a movement component of any kind.
/** BlueprintNativeEvent. Default implementation: return GetOwner()->GetVelocity(); */
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "UK Interaction|Advanced")
FVector GetInteractorVelocity() const;
Why it is a BlueprintNativeEvent and not a plain getter. AActor::GetVelocity() and
APawn::GetVelocity() are the engine's only movement-agnostic velocity questions, and on a pawn the
answer can legitimately be zero for a pawn that is visibly moving. Overriding this one function is
the whole integration surface for any movement system the engine does not answer for.
What APawn::GetVelocity() actually does
Read from engine source on 5.6, 5.7 and 5.8 (the body is the same on all three).
Runtime/Engine/Private/Pawn.cpp (5.6, lines 240-249), verbatim:
FVector APawn::GetVelocity() const
{
if(GetRootComponent() && GetRootComponent()->IsSimulatingPhysics())
{
return GetRootComponent()->GetComponentVelocity();
}
const UPawnMovementComponent* MovementComponent = GetMovementComponent();
return MovementComponent ? MovementComponent->Velocity : FVector::ZeroVector;
}
Read it as a two-branch decision, and note what is absent:
| Pawn state | GetVelocity() returns |
|---|---|
| Root primitive is simulating physics | that primitive's GetComponentVelocity() |
Otherwise, a UPawnMovementComponent exists (UCharacterMovementComponent, UFloatingPawnMovement, …) | that component's Velocity |
| Otherwise | FVector::ZeroVector |
There is no Super::GetVelocity() call. A pawn whose root does not simulate and that has no
UPawnMovementComponent reports zero — it never falls through to AActor::GetVelocity()'s
root-forwarding body. The kit's automation suite pins this with
UK.Interaction.Core.Interactor.test_a_pawn_with_no_movement_component_reports_zero.
A non-pawn owner is the only case that reaches AActor::GetVelocity()
(RootComponent ? RootComponent->GetComponentVelocity() : FVector::ZeroVector), and it is covered
by test_a_non_pawn_owner_reports_the_root_component_velocity.
Mover (Experimental through 5.8)
UMoverComponent is not a UPawnMovementComponent — the two share no class hierarchy
(verified against engine source). It follows directly from the
source above that a Mover-driven pawn whose root does not simulate physics reports zero from
APawn::GetVelocity(), and therefore zero from the kit's default GetInteractorVelocity().
That is not a defect in the kit and it is not something the kit can fix generically: Mover is
Experimental in every engine version this plugin supports (5.6 / 5.7 / 5.8), its component lives in
an optional engine plugin, and the kit's core module names no movement type at all — no
UCharacterMovementComponent, no UMoverComponent, no include from either (GetCharacterMovement
and UMoverComponent do not appear anywhere under Source/UKInteraction/).
So: if your pawn's velocity comes from anything other than a simulating root primitive or a
UPawnMovementComponent, override GetInteractorVelocity. The recipe below names no Mover type
precisely so that it keeps working when Mover's API changes.
The override recipe
Blueprint (no C++ required)
- Open your pawn Blueprint — the one carrying the UK Interactor component.
- Select the interactor component, or create a Blueprint child class of
UK Interactor. - Add the event Get Interactor Velocity (it appears under
UK Interaction | Advanced). - Return the velocity your movement system publishes. For a Mover pawn that is the Mover component's own velocity output; for a custom controller it is whatever vector you integrate.
Event Get Interactor Velocity
└── Get <your movement component> ──► Get Velocity ──► Return Value
C++
UCLASS()
class UMyInteractorComponent : public UUKInteractorComponent
{
GENERATED_BODY()
protected:
virtual FVector GetInteractorVelocity_Implementation() const override
{
// Ask YOUR movement system. The kit deliberately does not know its type.
if (const AActor* Owner = GetOwner())
{
if (const UMyMovementComponent* Movement = Owner->FindComponentByClass<UMyMovementComponent>())
{
return Movement->GetCurrentVelocity();
}
}
return Super::GetInteractorVelocity_Implementation();
}
};
Both forms are exercised: test_a_component_override_wins_over_the_default proves an
override takes precedence over the default implementation.
Units and what reads the number
- Units: cm/s, the engine's. The hold behaviours compare
Velocity.Size()againstFUKHoldParams::MaxHoldSpeedCmPerSec(default10) with a strict>; the comparison is instantaneous and stateless. - The kit calls
GetInteractorVelocity()only while a hold is running — never on the detection tick, never per candidate. An expensive override costs nothing when nobody is holding anything. - A non-finite velocity (NaN / infinity) is treated as "not moving" and warns once
(
UK-VAL-062) rather than cancelling every hold.
Related
Docs/detection.md— the detection profile, gather sources and the LOS rules.Docs/input.md— the Enhanced Input binding and its edge cases.Docs/version-gates.md— why the core module has no#if ENGINE_MINOR_VERSIONanywhere.