1.Basic Gripping - Tutorial
This tutorial will go over the bare minimum to get going with a flexible grip setup with the plugin. It will NOT go as in depth as the full implementation in the example template where gameplay tags and special properties are taken into account.
You can reference the template for a complex full implementation. Keeping in mind that some things are purely for template reasons, like the gameplay tag input re-mapping, something you normally would not need in a game.
The gripping system is intended to be as open and flexible as possible, to follow this central design principal, I try NOT to automate things when possible as every fully automatic step is another step where the end user loses control in the logic chain. This means that the setup to grip is generally a series of utility nodes in a logic chain that the end user defines, giving them full control over when/why/how to grip objects, but also making the setup more complicated than “grip this”.
Due to the sparse nature of the grip logic pre-implementation, you are free to control grips in your character, player controller, the gripped object itself, or wherever you choose and can pull a GripMotionController reference in. This guide however will go over running grips from the Character itself.
The basic concept of the gripping system is to call a “GripObject” function from the motion controller attempting a grip, when done with the grip you can then call a “DropObject” function to release the held object.
There are different variations of the grip / drop nodes, but we will just be covering the primary two types, (Grip/Drop)Object and (Grip/Drop)ObjectByInterface.
GripObject allows you to define core grip settings on ANY primitive object in engine and then grip it. GripObjectByInterface uses the built in VRGripInterface to take advanced settings and features and automatically grip the object based on those settings.
The plugin comes with a large set of “Grippable” objects (Grippable mesh / skeletal mesh / shapes / components), with the grip interface and a bunch of bug fixes and utilities already implemented in them. The state of the VRGripSettings struct on those grippables defines how GripByInterface behaves. You can manually add the VRGripInterface to any object as well as long as you implement all of its functions.
Generally for most workflows you will use GripObjectByInterface 99% of the time, GripObject is saved for non interface objects or when you want to do something special like ignore an objects grip settings.
You can run both (like the template does) by pre-checking if the target object has the VRGripInterface or not and then filtering the node selection based on that.
1.2.Finding an object
The very basic form of checking for an object to grip is below, you can substitute tracing for overlapping or any other method that you choose (the template both traces AND overlaps to cover downsides with each method).
You can copy and paste the nodes directly into the editor with this link:
1.3.Trace For a Grip
To try and grip the traced object lets fill out and create a function for it.
Make a new function called “AttemptToGripFromTrace” and fill out the node structure like below. A screenshot of a basic grip of a component is below, the full node layout for choosing between the actor and the component and then gripping one is linked afterwards.
This function will automatically grip any interfaced object with its default settings. The world offset input is the world location relative to the motion controller that you want the gripped object to sit at. For instance, passing in the full transform of the object as was done above will lock the object in at that distance and rotation from the motion controller no matter how it moves.
If instead you passed in an already relative transform of 0,0,0 (Identity) then the object would snap to the motion controllers base point. This is abused to allow for socket snapping, offsetting, and full control over the required location relative to the gripping controller.
1.4.Adding In Slot Grips
Slotting is a term used through-out the plugin to define what is commonly more known as “SnapGrips”, a grip that “snaps” to location and doesn’t retain its original offset to the hand.
You can control these any way that you want, really all slotting involves is passing in a different transform to the grip request. However there is a default method of implementing one using mesh sockets with pre-defined prefix’s on their name.
For instance look at the mesh shown above, it has two mesh sockets added to it, VRGripP and VRGripS, the number after is just the identifier, you can have as many of them as you want. Unless changed these are the two default prefix’s for our utility gripping nodes. We will focus on VRGripP, the default prefix for a “Primary” grip (main hand). To utilize these sockets on the shown mesh we need to change around the gripping setup in our function a bit. Also to make it more clean with this method it has also been switched to “bIsAlreadyRelative” when gripping, generally you want to pass in relative values anyway.
This setup is now first checking for if there is a “GripSlotInRange” of the motion controller (range is setup in the VRGripInterface properties on the object). Then if there was one, it uses that location as the grip point instead of the objects world location, this will “snap” the object to the hand at that relative location (the sockets location on the object). If there was no slot in range then it instead grips the object in its world position (made relative as well). Setting bIsSlotGrip should be done, however it has no internal plugin use and exists for the end user to more easily differentiate grips.
If the default setup isn’t to your liking you can use your own transform getting method to pass in to the grip function yourself, or you can override the ClosestGripSlotInRange function on the object itself and pass out whatever you want.
For example, overriding the function like above and cutting out the Parent implementation. You could pass out scene component locations on the object instead to act like snap points. Or pass out true for HadSlotInRange always and a set transform, or check if the calling controller is left or right handed and adjust sockets or logic accordingly. Doing an override like this in a base class for your grippables would let ALL of them behave differently for you.
1.5.Dropping a Grip
Dropping a grip is simple, depending on if you set up your game to allow for more than one grip at a time per hand (the plugin supports 128 concurrent grips per hand) then you will need more or less advanced logic.
I will be covering the most simplistic case of just dropping all held objects from a hand.
This is the most simplistic case of dropping all held grips on a loop. The DropObject nodes can drop based off of the Object OR the GripID, it is preferrable to drop off of the GripID when possible as it is safer when doing things in multiplayer.
The OptionalVelocity pins are there for adjusting throw velocities or when passing client side values up to the server on release time, ignore them for now.
1.5.1.Drop And Socket A Grip
Dropping and socketing a grip is a newer setup for the plugin, before it was added it was expected that the end user implement their own attachment and “socketing” post drop so that they had full control over it.
In the end this was flawed, due to some serious attachment replication issues in engine and a concurrency issue with the physics thread I ended up creating a dedicated DropAndSocket node to help people wade through it.
This node will automatically drop a grip, attach it to the given parent at the given relative transform, and if they were both in a simulation state, will also delay a tick for the physic thread to catch up and then reset the relative position to solve some concurrency issues.
Its fairly self explanatory how it works, there is also the RequestsSocketing VRGripInterface function that can be overridden to allow anything to query the object and ask if it wants socketing prior to dropping it in the case where dropping is controlled outside of the object but you want the object to decide if socketing should happen on drop.
*Please note that as of 4.22 there is still a major engine issue with replicating attachment welding, in that the engine just straight up doesn’t do it. I have a bug report in with Epic concerning this oversight.*
The VRGripInterface comes with a lot of options on the back end, however considering that this is a “basic” gripping tutorial I will go over the public facing settings that are on each “Grippable” object type.
You can find the grip settings in the properties window of a grippable actor / component.
The descriptions of the basic settings are below
|Grip Logic Scripts||Grip Logic Scripts are modular grip logic scripts that allow you to just add additional logic on to a grippable object without custom scripting. This is an array of them that you want applied to your object.|
|Deny Gripping||If true then this object cannot be gripped, can be set with SetDenyGripping in blueprints/code.|
|Allow Multiple Grips||If true, the GripObject functions will not deny gripping on an object that is already held.|
|On teleport behavior||The behavior of the object when the controlling motion controller has PostTeleportGrips called on it.|
|Simulate On Drop||Whether the object should automatically enter simulation mode on being dropped (if false the end user has to simulate it if they want).|
|Slot Default Grip Type||The default grip type to use for Slotted (socket) grips. Details about the different grip types are in the Grip Types section.|
|Free Default Grip Type||The default grip type to use for Free (non socketed) grips. Details about the different grip types are in the Grip Types section.|
|Secondary Grip Type||The secondary grip type to use for the object. Details about the secondary grip types are in the secondary grip types section.|
|Movement Replication Type||The type of replication that this object wants to use, this is covered in more detail in the Replication Settings section.|
|Late Update Setting||This controls how the object wants to be late updated. Epics rendering thread is a full frame behind the game thread, so by the time it is presented to be rendered, the players hands can be greatly offset from where they were on the game thread.
To help this visual discontinuity they added a LateUpdate pathway to the engine that moves the render bodies to match the difference between the current hand location and the location it was at in the game thread.
|Constraint Stiffness||If this is a physics based grip, this is the stiffness (rigidity) of the physics constraint that moves the held object.|
|Constraint Damping||If this is a physics based grip, this is the damping (smoothing) of the physics constraint that moves the held object. It prevents rubber banding from trying too hold position too rigidly.|
|Constraint Break Distance||If this is a grip type that can be blocked by geometry (sweep or physics based) then this is how far away from the object (in the objects relative space) that the hand has to be before the grip is automatically dropped. If the value is 0.0 then the grip is never dropped.|
|Primary Slot Range||If using the default socketing logic, this is the distance that slots are considered “hittable” within. IE: If the hand attempting to grip the object is within this range of the socket then it will use that socket for the grip.|
|Secondary Slot Range||The same as the primary slot range, except for the secondary slots.|
|Advanced Grip Settings||Advanced settings for physics / secondary grips and things not needed as often. Advanced physics options are fairly important though as Force based constraints give more realistic behavior for things like melee weapons.|
There are many different Grip Types that are built in to the plugin, here is an overview of them.
|InteractiveCollisionWithPhysics||Uses physics constraints to move the gripped object. It is fully interactible with the environment.|
|InteractiveCollisionWithSweep||Uses the standard engine movement sweep to move the gripped object. It is not physics based, but will still stop on walls and obstacles.|
|InteractiveHybridCollisionWithPhysics||Uses physics constraints like its base version. However when not actually colliding with something the constraint strength is multiplied to make it more rigidly follow the hand.|
|InteractiveHybridCollisionWithSweep||Swaps between physics and position setting based on if it is colliding or not. This lets it follow the hand perfectly 1:1 and snap into place when not colliding, but still have interactability with the environment.|
|SweepWithPhysics||Only sweeps the grip, does not interact or stop on geometry but does throw OnHit events.|
|PhysicsOnly||Does not sweep or trigger OnHit events, just follows the hand.|
|ManipulationGrip||Is a physics based grip without any wrist rotation influence, only follows the position. Useful for things like physics based levers or drawers or interactible items.|
|ManipulationGripWithWristTwist||Same as above except includes the wrist roll|
|AttachmentGrip||Uses the engines native attachment to attach the object to the hand (like most VR gripping is done in engine).|
|CustomGrip||Throws the events for the grip and provides a TickGrip event in blueprints and c++ that an be used to “tick” movement when held. This is the mode that the interactibles use (lever, dial, slider, ect). It is for custom “hand scripted” movements and logic.|
|EventsOnly||This grip type does nothing except throw the OnGrip/OnGripRelease events.|
2.2.Secondary Grip Types
Secondary Grips are “Grip Modifiers” that are added to normal grips, they alter the grip behavior to add Rotation/Scaling based on the position of the Secondary scene component.
Used mostly for things like guns and two handed interactions.
|SG_None||Flags the object as not allowing secondary attachments|
|SG_Free||Allow secondary attachments anywhere|
|SG_SlotOnly||Allow secondary attachments only at Secondary Slot positions.|
|SG_Free_Retain||Same as SG_Free except when the attachment is released, the object will retain the positional offset.|
|SG_FreeWithScaling_Retain||Same as the normal Retain mode, except the object also scales with the hand distance.|
|SG_Custom||Does nothing except throw the OnSecondaryGrip and OnSecondaryGripReleased events. Lets the end user handle logic based on those.|
|SG_ScalingOnly||Does not track the attachment with rotation, only scales the object.|
2.3.Movement Replication Types
Movement replication types change how the plugin handles moving and authority with the grip. Depending on the setting the plugin will expect different user behavior to a point.
|KeepOriginalMovement||Will check the bReplicateMovement setting of the object and retains it as grip movement replication. Rarely (if ever) used.|
|ForceServerSideMovement||Server Authoritative: All grip and drop functions must be called server side.
Forces the objects movement logic to only be called on the servers side,
*Note that the engine currently does not have a native movement replication that is smooth when not using physics, so smooth motion would have to be handled by the object itself*
|ForceClientSideMovement||Server Authoritative: All grip and drop functions must be called server side.
Each client moves the object separately and do not rely on the servers movement replication (this is the default grip type).
|ClientSide_Authoritive||Client Authoritative: All grip and drop functions must be called on the gripping clients end.
Client auth grips are handled locally, they tell the server what they gripped and the server is expected to obey. The massive upside to this grip mode is that there is no intial grip / drop delay while waiting for the server to intialize the grip.
|ClientSide_Authoritive_NoRep||Client Authoritative: All grip and drop functions must be called on the gripping clients end.
This grip isn’t even sent to the serve to let it know what is happening. It is most useful if you want to have default non replicating objects on the body and manually grip / drop it on all clients separately, avoiding all replication requirements. Can be used for “proxy” stand in objects or menus or client side only objects.