AFS_Core_MotionController
The motion controller actors the VR pawn spawns for each hand: controller and hand variants, a headset-independent controller model, hand animation and the gesture definitions.
What it gives you
- One motion controller actor per hand, the home of every hand ability
- Controller variants that show the model the XR runtime supplies, with a fallback mesh
- Hand variants with an animated hand mesh driven by controller input or hand tracking
- Ready-made gesture definitions: pinch, point, fist, grasp, open palm, drag
Requires
AFL_Libraries AFM_Abilities AFM_AutoDestroy AFM_Settings AFS_Core_Pawn
Engine: OpenXR OpenXRHandTracking
Required by
Description
The VR pawn does not carry its hands as components. It spawns one BP_MotionController actor per
side, from the classes set on the pawn, and attaches it to its camera handle. Every hand ability -
laser, grab, teleport, turn, movement - is a component on that actor, so a hand can be swapped at
runtime without touching the pawn.
There are two families. BP_MotionController_Controller shows a controller. With a headset, the
engine's XR Device Visualization draws whatever model the XR runtime supplies for the controller
in use, so no headset-specific meshes are needed. Its Controller Mesh is only the fallback: it
shows in the Blueprint viewport, in desktop debug mode and in VR until the runtime has supplied a
model. BP_MotionController_Hands shows an animated hand instead, driven by grip, trigger and
touch input or, with hand tracking, by the tracked bones through ABP_Hand and the control rig
CR_Hand_HandTracking.
The gesture definitions in Data/Gestures are read by the hand-tracking system in
AFS_Core_Pawn, which turns a recognised gesture into a synthetic input key.
Setup
- On the VR pawn, set Left Controller and Right Controller to the
_Leftand_Rightvariant you want, controller or hands, or to your own subclasses of them. - Add the hand abilities you need to those subclasses, not to the pawn.
- For hands, enable OpenXR Hand Tracking if you want tracked hands rather than animated ones.
Usage
Hiding the controller while something is held
Call MC_SetControllerVisibility on the controller. It hides the fallback mesh and the XR device
model together and brings their collision back half a second after showing them again.
Giving one ability the whole hand
Call MC_SetFocusControllerAbility. Every other ability on that hand is blocked under the
Unfocussed reason until the focus is released.
Key properties
| Property | On | When to change |
|---|---|---|
Controller Mesh | BP_MotionController_Controller_* | To change the fallback model shown without a runtime model |
Side | BP_MotionController | Never by hand; the pawn sets it when it spawns the controller |
Troubleshooting
Two controller models appear. A subclass hides the XR device model or shows the fallback mesh on
its own; leave both to SetupMotionController, which hides the fallback once the runtime model has
arrived.
No controller model in VR. The XR runtime has not supplied one yet; the fallback mesh stays visible until it does.
API reference
Every blueprint asset in this module. Expand an asset to see its members.
AnimBP
ABP_Hand2 members
Animation blueprint driving the framework's hand mesh. It implements the hand animation contract, easing every value handed to it (pointing, thumb up, trigger and grip axes, hand tracking and a custom pose) into a blend weight over time rather than snapping, and mirrors the right-handed pose set for the left hand after asking the owning motion controller which side it is. Assign it as the animation class on a hand mesh, or subclass it to add poses of your own.
| Kind | Name | Signature | Description |
|---|---|---|---|
| event | BlueprintInitializeAnimation | () | Asks the owning motion controller which hand it is and sets the mirror flag from the answer. The source animations are all right-handed, so the left hand is mirrored through the hand mirror table; a controller that does not report a side leaves the hand unmirrored. |
| event | BlueprintUpdateAnimation | (DeltaTimeX: float) | Deliberately empty. Every pose value is pushed in from the motion controller through the hand animation interface rather than pulled each frame, so there is nothing to do per tick. |
CR_Hand_HandTracking2 members
Control rig posing the hand mesh from tracked hand data. It takes the bone transforms the motion controller reports, maps them onto the named left and right bone chains, and mirrors the right-hand set when it drives the left. Driven by the hand animation blueprint; there is nothing to set unless your hand skeleton names its bones differently.
| Kind | Name | Signature | Description |
|---|---|---|---|
| var | Transforms | Transform[] | Tracked joint transforms the rig poses the hand from, one per XR hand joint, in the hand mesh's own space. Filled in by the animation blueprint through Hand Hand Tracking Transforms rather than set here, and empty whenever hand tracking is not reporting. |
| var | Mirror | bool | Whether the incoming joint transforms are mirrored before the rig applies them. Set for the left hand, because the hand mesh and its animations are right-handed and both hands share this one rig. |
Blueprints
BP_MotionController4 members
Base motion controller actor, one per hand: the actor that hand abilities are added to and initiated by. The VR pawn spawns it from its per-side controller class and attaches it to the camera handle, handing over the pawn and the hand side, both of which replicate. It answers the motion controller contract for side and attached actors, and grants one ability exclusive focus by holding a blocker on every other ability on the hand. Subclass it rather than placing it.
| Kind | Name | Signature | Description |
|---|---|---|---|
| var | PawnInternal | Pawn | Owning VR pawn, handed over on spawn. Used to tell whether this hand belongs to the locally controlled player, so replicated hands do not run local-only logic. |
| var | SideInternal | E_Side | Which hand this is, Left or Right, fixed at spawn by the pawn. Abilities read it through Get Motion Controller Side to choose their input mappings, grip sockets and mirrored hand poses. |
| event | SetMeshVisibility | (Set: bool) | Broadcasts Mesh Visibility Changed with the requested state. Nothing is hidden here; the derived controller listens for the broadcast and hides its own mesh, so an ability can ask for the hand to disappear without knowing what it looks like. |
| event | SetupMotionController | () | Empty hook left for a derived controller to do its own setup after being spawned and given its pawn, side and headset type. Neither shipped controller uses it. |
Blueprints/Controller
BP_MotionController_Controller2 members
Motion controller that shows a controller model rather than a hand. With a headset, the engine's XR Device Visualization draws whatever model the XR runtime supplies for the controller in use, so no headset-specific meshes are needed. The Controller Mesh is the fallback: it shows in the Blueprint viewport, in desktop debug mode without a headset, and in VR for as long as the runtime has not supplied a model (checked once a second until one arrives). The fallback mesh also carries the collision, next to a small sphere at the tip. Use the Left and Right variants, which fill in the per-hand fallback mesh and offset.
| Kind | Name | Signature | Description |
|---|---|---|---|
| event | SetupMotionController | () | Runs the parent set-up, then picks the visual: with a headset it switches XR Device Visualization on and hides the fallback Controller Mesh in game; then, once a second, it shows the fallback while the runtime has not supplied a model and stops checking (fallback hidden) as soon as one has arrived, which can take a few seconds after start. Without a headset nothing changes and the fallback mesh stays visible. |
| event | MC_SetControllerVisibilityMotion Controller | (Set: bool) | Shows or hides the controller's visuals (the fallback mesh and the XR device model), for example while an object is held, and switches the collision with them; collision comes back half a second after showing so a released object is not pushed away. |
BP_MotionController_Controller_Left0 members
Left-hand instance of the controller-model motion controller, carrying the left fallback mesh (shown in the editor, in desktop debug mode and when the XR runtime supplies no model) and the offset that seats it on the tracked pose. Set it as the VR pawn's Left Controller when the pawn should show controllers rather than hands.
No editor-visible members; this asset configures defaults only.
BP_MotionController_Controller_Right0 members
Right-hand instance of the controller-model motion controller: the right fallback mesh (shown in the editor, in desktop debug mode and when the XR runtime supplies no model), the offset that seats it on the tracked pose, and the motion source switched to the right hand. Set it as the VR pawn's Right Controller when the pawn should show controllers rather than hands.
No editor-visible members; this asset configures defaults only.
Blueprints/Hand
BP_MotionController_Hands10 members
Motion controller that shows an animated hand instead of a controller model. It carries the hand mesh and its animation blueprint under a hand offset, folds grip, trigger, index-touch and thumb-touch input for its own side into replicated hand state and pushes that to the animation, and feeds OpenXR hand-tracking bones through when the headset reports them. Answers the hands contract: snapping to a socket, hand collision and whether the hand is open. Use the Left and Right variants.
| Kind | Name | Signature | Description |
|---|---|---|---|
| fn | UpdateHandTrackingMeshHand Tracking | () | Pushes a tracked hand pose into the animation blueprint. Reads the hand tracking state for this controller's side, converts every joint into the hand mesh's own space with the hand tracking correction applied, and hands the array over through Hand Hand Tracking Transforms, which is where the control rig reads it. Called while hand tracking is live, and it has nothing to apply when tracking is not valid. |
| fn | DrawHandtrackingDebugHand Tracking | () | Draws a short set of red, green and blue axes at every tracked hand joint, each line lasting about a frame. Purely a debugging aid: hook it to a key or the tick while you are working out why a tracked hand looks wrong, and leave it out otherwise. |
| event | SetupMotionController | () | Override of the set-up hook the pawn calls once the controller knows which hand it is. It adds nothing of its own and passes straight to the parent, so it sits here as somewhere to hang your own set-up. |
| event | Server_SnapToSocket | (SnapSocket: ST_Socket) | Server event storing the socket this hand has snapped to, so other clients see the hand holding the object the same way the holder does. |
| event | Server_SetPose | (GrabPose: AnimSequenceBase) | Server event storing the custom hand pose with notify, so a grab's pose plays on every client rather than only in the holder's own view. |
| event | Server_GribAxis | (GribAxis: float) | Server event publishing this hand's grip axis with notify, so remote clients see the fingers close by the same amount. The name carries a typo for grip and is left as it is so existing graphs keep compiling. |
| event | ServerSetThumbDown | (ThumbDown: bool) | Server event publishing whether the thumb is resting on the controller, with notify, which is what gives remote clients the thumbs-up pose. |
| event | Server_SetIndexDown | (IndexDown: bool) | Server event publishing whether the index finger is resting on the trigger, with notify, which is what makes a remote hand point. |
| event | CheckHandTracking | () | Decides whether this hand is being tracked rather than held. Reads the hand tracking state for its side and, only when the answer has changed, stores it, moves the hand offset to either the hand tracking or the default offset, and tells the animation blueprint to blend between the driven finger poses and the control rig. It is called repeatedly, so that gate is what keeps it cheap. |
| event | Server_TriggerAxis | (TriggerAxis: float) | Server event publishing this hand's trigger axis with notify, so the index finger curls by the same amount on every client. |
BP_MotionController_Hands_Left1 members
Left-hand instance of the hand-mesh motion controller: the mannequin XR left mesh, the offset that seats it on the tracked pose, and a grab transform read from that mesh's left grab-search socket. Set it as the VR pawn's Left Controller for hand-style VR, or subclass it to use a hand mesh of your own.
| Kind | Name | Signature | Description |
|---|---|---|---|
| fn | MCHands_getGrabWorldTransformMotion Controller Hands | (out Transform: Transform) | Returns the world transform of the hand's grab point, the position objects are picked up relative to. |
BP_MotionController_Hands_Right1 members
Right-hand instance of the hand-mesh motion controller: the mannequin XR right mesh, its offset, the motion source switched to the right hand, and a grab transform read from that mesh's right grab-search socket. Set it as the VR pawn's Right Controller for hand-style VR, or subclass it to use a hand mesh of your own.
| Kind | Name | Signature | Description |
|---|---|---|---|
| fn | MCHands_getGrabWorldTransformMotion Controller Hands | (out Transform: Transform) | Returns the world transform of the hand's grab point, the position objects are picked up relative to. |
Interfaces
BPI_AnimBP_Hand7 members
Contract between a hand motion controller and the animation blueprint on its mesh: index pointing, thumb up, trigger and grip axes, hand tracking on or off, a custom pose and the hand-tracking bone transforms. The controller calls it on whatever animation instance the mesh happens to have, so an animation blueprint of your own needs only to implement this to stand in for the shipped one.
| Kind | Name | Signature | Description |
|---|---|---|---|
| fn | Hand_HandTrackingTransformsHand Anim BP | (Transforms: Transform[]) | Implemented by the hand's animation blueprint to receive the tracked joint transforms for this hand, relative to the hand mesh. Called by the hand motion controller once per frame while tracking is valid. |
| fn | Hand_SetHandPoseHand Anim BP | (HandPose: AnimSequenceBase) | Implemented by the hand's animation blueprint to play a custom pose over the whole hand. Called by the hand motion controller, typically when something gripped supplies its own pose; an empty sequence means return to the driven poses. |
| fn | Hand_SetHandTrackingHand Anim BP | (Set: bool) | Implemented by the hand's animation blueprint to blend between the driven finger poses and the pose built from tracked joints. Called by the hand motion controller when hand tracking starts or stops being available. |
| fn | Hand_SetHandValue_GripAxisHand Anim BP | (Axis: float) | Implemented by the hand's animation blueprint to close the hand by the analogue grip amount, 0 for open and 1 for a full grasp. Called by the hand motion controller as the grip moves. |
| fn | Hand_SetHandValue_TriggerAxisHand Anim BP | (Axis: float) | Implemented by the hand's animation blueprint to curl the index finger by the analogue trigger amount, 0 to 1. Called by the hand motion controller as the trigger moves. |
| fn | Hand_SetHandValue_ThumbHand Anim BP | (IsThumbUp: bool) | Implemented by the hand's animation blueprint to raise or lower the thumb. Called by the hand motion controller when the thumbs-up state changes or replicates in. |
| fn | Hand_SetHandValue_IndexHandAnimBP | (IsPointing: bool) | Implemented by the hand's animation blueprint to point or unpoint the index finger. Called by the hand motion controller when the pointing state changes or replicates in. |
Libraries
ML_MotionController0 members
Macro library for motion controllers: a side guard that only continues on the hand this controller stands for, and a button handler folding the left and right start and completed input events into one boolean with that guard applied. Parented to the motion controller base, so the macros appear only inside controllers inheriting from it; the hand controller uses them for its per-hand input.
No editor-visible members; this asset configures defaults only.