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 stateGetVelocity() returns
Root primitive is simulating physicsthat primitive's GetComponentVelocity()
Otherwise, a UPawnMovementComponent exists (UCharacterMovementComponent, UFloatingPawnMovement, …)that component's Velocity
OtherwiseFVector::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)

  1. Open your pawn Blueprint — the one carrying the UK Interactor component.
  2. Select the interactor component, or create a Blueprint child class of UK Interactor.
  3. Add the event Get Interactor Velocity (it appears under UK Interaction | Advanced).
  4. 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() against FUKHoldParams::MaxHoldSpeedCmPerSec (default 10) 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.
  • 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_VERSION anywhere.