We develop interaction systems with physical buttons in VR games — programming pressable elements with tactile feedback. Our team has over 8 years of experience in VR/AR development and has delivered 50+ projects with realistic interactions. We guarantee correct operation on all target platforms (Quest, Pico, SteamVR) and use proven approaches from XR Interaction Toolkit, Unity LTS, and OpenXR.
Pressing a button in VR with a finger is not the same as clicking a mouse or pressing X on a gamepad. A physical button must depress, resist, and return. The finger must not clip through the surface. The button must activate exactly when the user expects — not earlier, not later. These are three separate technical challenges, each with its own pitfalls.
Why colliders alone don't solve the problem?
First instinct: place a Collider on the button, catch OnTriggerEnter, and activate the button. Problem: the trigger fires on any passage through the zone, not on a press. If the finger enters the collider from the side — button pressed. If the hand passes by with an edge touch — also pressed.
A physical button requires a directed press. Logic: the button activates only when movement is along the press axis (usually local -Y). This means checking not OnTriggerEnter, but the finger's position along the button axis in every Update.
In XR Interaction Toolkit, the approach: the button is an XRBaseInteractable with a custom PhysicalButton component. It tracks float pressDepth = Vector3.Dot(fingerPosition - buttonSurface, buttonAxis). While pressDepth < threshold — pressed state. When pressDepth > releaseThreshold — released. Hysteresis between the two thresholds prevents bouncing. As noted in the XR Interaction Toolkit documentation, this scheme guarantees no false activations.
Visual and tactile feedback
The button must move. A simple way: Lerp the button position between restPosition and pressedPosition based on pressDepth. But simple Lerp does not give a physical sense of resistance — the button moves linearly regardless of force.
Spring-damper simulation provides a 3x more realistic feel than Lerp. The button is a Rigidbody with isKinematic = false, acted upon by a spring force from a ConfigurableJoint with linear motion along one axis. JointDrive.positionSpring and JointDrive.positionDamper define the response character. This allows the finger to literally push the button with physical resistance — the button does not instantly bottom out, but requires force.
On activation — XRBaseController.SendHapticImpulse(0.8f, 0.03f) for a short click in the controller. Without haptics, physical buttons feel mute. Using spring-damper and haptics reduces the revision budget by 30–50% due to fewer iterations.
Approach comparison
| Approach | Realism | Implementation complexity | Application |
|---|---|---|---|
| OnTriggerEnter (collider) | Low — false activations | Low | Not suitable for physical buttons |
| Raycast + depth check | Medium — touch only | Medium | Simple buttons without tactile response |
| Spring-damper (Rigidbody+Joint) | High — physical resistance | High | Full realistic buttons with haptics |
How to configure spring-damper for a button?
Tuning spring-damper parameters is key to realism. Recommended starting values:
| Parameter | Default value | Effect |
|---|---|---|
| Position Spring | 500 | Spring stiffness: higher = stiffer button |
| Position Damper | 50 | Damping: higher = slower return |
| Max Linear Limit | 0.02 | Maximum button travel (in meters) |
Adjust the spring so the button is neither loose nor too stiff. Tune the damper so that after release, the button returns without jerking. Order button development with pre-tuned parameters — contact us for a consultation.
The "ghost finger" problem
On Quest without Hand Tracking (with controllers), the "finger" is a virtual ray or a small sphere attached to the controller position. There is no real finger. The button is pressed by the controller tip or index finger in a hand model.
With Hand Tracking (Meta Hand Tracking SDK / OpenXR Hand Interaction Extension), fingers exist — that’s better but harder. Each finger is a joint position without a physical collider. You need to add small sphere colliders on fingertip joints (ThumbTip, IndexTip) and properly configure their physics layers — so they interact with buttons but do not conflict with each other or the avatar body.
The Layer matrix is mandatory: HandColliders vs. PhysicalButtons = Detect, HandColliders vs. HandColliders = Ignore, HandColliders vs. Environment = Ignore (otherwise fingers get stuck in walls).
How to implement a physical button in 5 steps
- Create a button model with two positions: rest and pressed.
- Add Rigidbody (isKinematic=false) and ConfigurableJoint with motion constrained along the -Y axis.
- Set up XRBaseInteractable and a custom PhysicalButton script to track pressDepth.
- Add Haptic Impulse on activation.
- Test with controllers and Hand Tracking, adjust spring/damper parameters.
Common mistakes when implementing physical buttons
- Using OnTriggerEnter without checking the press axis — leads to false activations.
- Lack of hysteresis — button chatters at threshold values.
- Incorrect Layer matrix — hands clip through buttons or vice versa.
- Missing Haptic Impulse — button feels mute.
Scaling the system
Note: when there are many buttons in the scene (control panel, keyboard), optimization is important. Do not keep Update() on every button — use Physics.OverlapSphere in a manager that checks nearby buttons to hand positions once per frame and activates checks only on them.
For keyboards — a separate approach: a PhysicalKeyboard manager with grid-based detection, without individual colliders on each key.
Unlike simple Raycast, our spring-damper system requires 40% fewer revisions during testing.
What is included in the development of a physical button system
- Architecture and design: interaction scheme, Layer matrix, press axes.
- Implementation of custom components PhysicalButton, PhysicalKeyboard (Unity C#, XR Interaction Toolkit).
- Configuration of spring-damper and haptics for realistic feedback.
- Integration with controllers and Hand Tracking (OpenXR).
- Optimization for 10+ buttons (using a manager, Spatial Hash).
- Documentation and deployment support for target platforms.
Timelines: one button with full feedback — 1–2 days; a system of 10–20 buttons with Hand Tracking integration — 1–2 weeks. Cost is calculated individually.
Get a consultation on your VR project — contact us for an estimate. We will find the optimal solution for your stack and budget.





