Skip to main content
Autonomous AgentsMetaverse470 lines

Haptic Feedback Design for XR

Quick Summary18 lines
Haptic feedback transforms VR interactions from visual-only events into multi-sensory experiences. This skill covers designing, implementing, and testing haptic patterns for VR controllers, hand tracking devices, and advanced haptic hardware. Good haptics dramatically increase presence, usability, and satisfaction in XR applications.

## Key Points

1. Vary amplitude and frequency — constant vibration goes numb
2. Use short, sharp pulses for events (10-50ms)
3. Use texture patterns for continuous contact (varying frequency)
4. Stronger ≠ better — subtle haptics feel more natural
5. Pair haptics with audio for strongest perception
6. Maximum effective continuous duration: 2-3 seconds
1. Tap (confirmation/selection):
2. Double Tap (important event):
3. Buzz (error/warning):
4. Ramp Up (approaching/loading):
5. Ramp Down (release/fade):
6. Pulse Train (continuous event):
skilldb get metaverse-skills/haptic-feedback-designFull skill: 470 lines
Paste into your CLAUDE.md or agent config

Haptic Feedback Design for XR

Purpose

Haptic feedback transforms VR interactions from visual-only events into multi-sensory experiences. This skill covers designing, implementing, and testing haptic patterns for VR controllers, hand tracking devices, and advanced haptic hardware. Good haptics dramatically increase presence, usability, and satisfaction in XR applications.

Fundamentals of VR Haptics

Haptic Hardware Capabilities

Controller Haptic Capabilities:
┌─────────────────────┬──────────┬───────────┬──────────┐
│ Hardware            │ Channels │ Frequency │ Fidelity │
├─────────────────────┼──────────┼───────────┼──────────┤
│ Quest Touch (Pro)   │ 1/ctrl   │ ~160 Hz   │ Medium   │
│ Valve Index         │ 1/ctrl   │ ~320 Hz   │ High     │
│ PlayStation Sense   │ 2/ctrl   │ ~400 Hz   │ Very High│
│ HTC Vive            │ 1/ctrl   │ ~160 Hz   │ Low      │
│ Apple Vision Pro    │ None*    │ —         │ —        │
└─────────────────────┴──────────┴───────────┴──────────┘
*Vision Pro uses audio and visual feedback instead

Haptic Parameters (typical API):
├── Amplitude: 0.0 to 1.0 (vibration intensity)
├── Frequency: 0 to 1.0 (mapped to Hz range, platform-dependent)
├── Duration: Seconds (minimum ~5ms, maximum varies)
└── Channel: Left controller, right controller (or body location)

Perception Principles

Human Haptic Perception:
├── Temporal resolution: ~5ms (can feel gaps shorter than 5ms)
├── Frequency sensitivity: Peak at 200-300 Hz (Pacinian corpuscles)
├── Amplitude JND: ~15% (Just Noticeable Difference in intensity)
├── Adaptation: Constant vibration fades from perception in 2-5s
├── Masking: Strong haptic masks weaker one within ~100ms
└── Cross-modal: Haptics influence visual/audio perception

Design Implications:
1. Vary amplitude and frequency — constant vibration goes numb
2. Use short, sharp pulses for events (10-50ms)
3. Use texture patterns for continuous contact (varying frequency)
4. Stronger ≠ better — subtle haptics feel more natural
5. Pair haptics with audio for strongest perception
6. Maximum effective continuous duration: 2-3 seconds

Haptic Pattern Library

Basic Patterns

Fundamental Haptic Patterns:

1. Tap (confirmation/selection):
   ┌──┐
   │  │
───┘  └───────────────
   Duration: 10-20ms
   Amplitude: 0.3-0.6
   Use: Button press, menu selection

2. Double Tap (important event):
   ┌──┐  ┌──┐
   │  │  │  │
───┘  └──┘  └────────
   Duration: 10ms each, 80ms gap
   Amplitude: 0.4-0.6
   Use: Notification, pickup confirmation

3. Buzz (error/warning):
   ┌──────────────┐
   │              │
───┘              └──
   Duration: 100-200ms
   Amplitude: 0.7-1.0
   Frequency: High (0.8-1.0)
   Use: Invalid action, collision

4. Ramp Up (approaching/loading):
      ╱──
     ╱
    ╱
───╱
   Duration: 200-500ms
   Amplitude: 0.1 → 0.7
   Use: Object approaching hand, charging

5. Ramp Down (release/fade):
──╲
   ╲
    ╲
     ╲___
   Duration: 100-300ms
   Amplitude: 0.7 → 0.0
   Use: Release object, deactivation

6. Pulse Train (continuous event):
┌┐ ┌┐ ┌┐ ┌┐ ┌┐ ┌┐
││ ││ ││ ││ ││ ││
┘└─┘└─┘└─┘└─┘└─┘└
   Duration: 5ms on, 20ms off (repeat)
   Amplitude: 0.2-0.5
   Use: Hover over interactive, proximity

7. Texture (surface contact):
┌┐┌┐ ┌┐┌┐ ┌┐┌┐ ┌┐┌┐
││││ ││││ ││││ ││││
┘└┘└─┘└┘└─┘└┘└─┘└┘└
   Duration: Continuous while touching
   Pattern varies by "texture type"
   Use: Sliding hand along surface

Interaction-Specific Patterns

Grab Object:
├── Approach: Gentle pulse train (increasing frequency as hand nears)
├── Contact: Sharp tap (15ms, amplitude 0.5)
├── Hold: Very subtle continuous (amplitude 0.05-0.1)
└── Release: Ramp down (100ms, 0.3 → 0.0)

Throw Object:
├── Release point: Short buzz (20ms, amplitude 0.4)
└── Velocity proportional: Stronger haptic for harder throws

Button Press (virtual):
├── Hover: Single light pulse (5ms, amplitude 0.1)
├── Press: Tap (10ms, amplitude 0.5)
├── Hold: Nothing (or very faint if toggle)
└── Release: Lighter tap (10ms, amplitude 0.2)

Slider Drag:
├── Movement: Tick pattern at interval positions
│   └── Each tick: 5ms pulse, amplitude 0.15
├── Endpoints: Strong tap (15ms, amplitude 0.6)
└── Snap points: Medium tap (10ms, amplitude 0.3)

Teleport:
├── Aim (valid target): Soft pulse (every 200ms, amplitude 0.1)
├── Aim (invalid): No haptic (absence = invalid)
├── Execute: Ramp up+down (150ms total, amplitude 0.4)
└── Land: Tap (20ms, amplitude 0.3)

Collision/Impact:
├── Light impact: Tap (15ms, amplitude proportional to velocity)
├── Heavy impact: Buzz (50ms, amplitude 0.8-1.0)
├── Continuous contact: Texture pattern (amplitude proportional to pressure)
└── Scale: Velocity → amplitude mapping (logarithmic feels natural)

Implementation

Cross-Platform Haptic API

// Unity — XR Interaction Toolkit haptics
public class HapticManager : MonoBehaviour
{
    // Simple pulse
    public void SendHapticPulse(XRBaseController controller,
                                 float amplitude, float duration)
    {
        controller.SendHapticImpulse(amplitude, duration);
    }

    // Pattern player
    public IEnumerator PlayPattern(XRBaseController controller,
                                    HapticPattern pattern)
    {
        foreach (var step in pattern.Steps)
        {
            if (step.Type == HapticStepType.Vibrate)
            {
                controller.SendHapticImpulse(step.Amplitude, step.Duration);
                yield return new WaitForSeconds(step.Duration);
            }
            else if (step.Type == HapticStepType.Pause)
            {
                yield return new WaitForSeconds(step.Duration);
            }
        }
    }

    // Continuous haptic with modulation
    public IEnumerator PlayContinuous(XRBaseController controller,
                                       float baseAmplitude,
                                       float modulationFrequency,
                                       float totalDuration)
    {
        float elapsed = 0;
        while (elapsed < totalDuration)
        {
            float modulation = Mathf.Sin(elapsed * modulationFrequency *
                              Mathf.PI * 2) * 0.5f + 0.5f;
            float amplitude = baseAmplitude * modulation;
            controller.SendHapticImpulse(amplitude, 0.02f);
            yield return new WaitForSeconds(0.02f);
            elapsed += 0.02f;
        }
    }
}

// Haptic pattern data
[System.Serializable]
public class HapticPattern
{
    public string Name;
    public List<HapticStep> Steps;
}

[System.Serializable]
public class HapticStep
{
    public HapticStepType Type;
    public float Amplitude;  // 0.0 - 1.0
    public float Frequency;  // 0.0 - 1.0
    public float Duration;   // seconds
}

public enum HapticStepType { Vibrate, Pause }

Velocity-Based Haptics

// Haptic intensity based on interaction physics
public class PhysicsHapticFeedback : MonoBehaviour
{
    [SerializeField] private AnimationCurve velocityToAmplitude;
    // Curve: 0 m/s → 0.0 amplitude, 2 m/s → 0.5, 5+ m/s → 1.0

    private void OnCollisionEnter(Collision collision)
    {
        float velocity = collision.relativeVelocity.magnitude;
        float amplitude = velocityToAmplitude.Evaluate(velocity);
        float duration = Mathf.Lerp(0.01f, 0.1f, amplitude);

        // Find which controller is holding this object
        var controller = GetHoldingController();
        if (controller != null)
        {
            controller.SendHapticImpulse(amplitude, duration);
        }
    }

    // Continuous contact friction
    private void OnCollisionStay(Collision collision)
    {
        float velocity = collision.relativeVelocity.magnitude;
        if (velocity > 0.1f) // Moving contact
        {
            float amplitude = Mathf.Clamp01(velocity * 0.1f) * 0.2f;
            var controller = GetHoldingController();
            controller?.SendHapticImpulse(amplitude, Time.fixedDeltaTime);
        }
    }
}

Spatial Haptic Patterns

// Haptics that convey spatial information
public class SpatialHapticGuide : MonoBehaviour
{
    // Guide user toward a target using haptic intensity
    public void UpdateProximityHaptic(XRBaseController leftController,
                                      XRBaseController rightController,
                                      Vector3 targetPosition)
    {
        float leftDist = Vector3.Distance(
            leftController.transform.position, targetPosition);
        float rightDist = Vector3.Distance(
            rightController.transform.position, targetPosition);

        // Closer = stronger haptic (inverse distance, capped)
        float maxRange = 2.0f; // meters
        float leftIntensity = Mathf.Clamp01(1.0f - (leftDist / maxRange));
        float rightIntensity = Mathf.Clamp01(1.0f - (rightDist / maxRange));

        // Apply with minimum threshold
        if (leftIntensity > 0.05f)
            leftController.SendHapticImpulse(leftIntensity * 0.3f, 0.05f);
        if (rightIntensity > 0.05f)
            rightController.SendHapticImpulse(rightIntensity * 0.3f, 0.05f);
    }

    // Directional guidance: left/right haptic indicates direction
    public void DirectionalGuide(XRBaseController leftController,
                                  XRBaseController rightController,
                                  Vector3 headForward,
                                  Vector3 targetDirection)
    {
        float dot = Vector3.Dot(
            Vector3.Cross(headForward, Vector3.up).normalized,
            targetDirection.normalized);

        // dot > 0 = target is to the right
        // dot < 0 = target is to the left
        if (dot > 0.1f)
            rightController.SendHapticImpulse(Mathf.Abs(dot) * 0.3f, 0.05f);
        else if (dot < -0.1f)
            leftController.SendHapticImpulse(Mathf.Abs(dot) * 0.3f, 0.05f);
    }
}

Design Process

Haptic Design Workflow

1. Identify Haptic Moments:
   └── List every interaction that would benefit from touch feedback
       ├── Must-have: Grab, release, button press, collision
       ├── Should-have: Hover, proximity, UI navigation
       └── Nice-to-have: Texture, environmental (rain, wind)

2. Design Patterns on Paper:
   └── Sketch amplitude-over-time for each interaction
       ├── Define timing, intensity, frequency
       ├── Note relationships (grab onset → hold → release)
       └── Consider adaptation (don't numb the user)

3. Prototype and Iterate:
   └── Implement basic version
       ├── Test on hardware (not in simulator)
       ├── Adjust timing and amplitude
       ├── Compare with audio sync
       └── A/B test with users

4. Polish:
   └── Fine-tune for each platform
       ├── Different motors feel different
       ├── Amplitude scales differently per device
       └── Create platform-specific profiles

5. Accessibility Review:
   └── Ensure haptics enhance but aren't required
       ├── All haptic feedback paired with visual/audio
       ├── Haptic intensity slider in settings
       └── Option to disable haptics entirely

Common Mistakes

Haptic Anti-Patterns:
✗ Constant vibration — User adapts, becomes irritating
✗ Maximum amplitude for everything — No dynamic range
✗ No haptics at all — Interactions feel disconnected
✗ Haptics without audio — Feels incomplete and unnatural
✗ Long continuous buzzes — Uncomfortable, drains battery
✗ Haptics on every frame — CPU waste, muddy feeling
✗ Same pattern for all events — No information conveyed
✗ Ignoring latency — Haptic arriving 100ms late feels wrong
✗ Not testing on hardware — Simulator ≠ real controller feel
✗ Forgetting accessibility — Some users have reduced sensitivity

Haptic-Audio Synchronization

Synchronization Principles:
├── Haptic should arrive at same time or slightly before audio
│   └── Human tolerance: haptic can lead audio by up to 50ms
│       but audio should not lead haptic by more than 20ms
├── Frequency correlation:
│   └── Low-frequency audio → low-frequency haptic
│   └── High-frequency audio → high-frequency haptic
├── Amplitude correlation:
│   └── Loud sound → strong haptic
│   └── Soft sound → gentle haptic
└── Duration correlation:
    └── Short sound → short haptic
    └── Sustained sound → pulsing haptic (avoid constant)

Audio-Haptic Design Pairs:
├── Click sound + tap haptic: UI selection
├── Thud sound + buzz haptic: Object collision
├── Whoosh sound + ramp haptic: Object throw
├── Ding sound + double tap haptic: Achievement
└── Ambient rumble + subtle pulse: Environmental

Advanced Haptic Devices

Beyond Controller Vibration

Advanced Haptic Hardware:
├── Haptic Gloves (Meta Haptic Gloves, HaptX):
│   ├── Per-finger haptic actuators
│   ├── Force feedback (resist finger closure)
│   ├── Thermal feedback (hot/cold)
│   └── Texture rendering (surface micropatterns)
├── Haptic Vests (bHaptics TactSuit):
│   ├── 40+ vibration motors on torso
│   ├── Spatial haptic patterns (directional hits)
│   ├── Environmental effects (rain, heartbeat)
│   └── SDK integration for Unity/Unreal
├── Floor Haptics:
│   ├── Vibration platforms
│   ├── Terrain simulation
│   └── Footstep feedback
└── Ultrasonic Haptics (Ultraleap):
    ├── Mid-air touch sensation
    ├── No wearable required
    ├── Limited range and intensity
    └── Best for kiosk/exhibition use

Design for advanced haptics:
- Design core experience for standard controllers
- Layer advanced haptics as enhancement
- Use spatial haptic channels when available
- Map game events to body regions logically

Testing Haptic Designs

Haptic Testing Protocol:
1. Technical Validation:
   □ Haptic fires at correct moment (no latency)
   □ Amplitude within expected range
   □ No unwanted continuous vibration
   □ Haptic stops when interaction ends
   □ No interference between left/right controller

2. Perception Testing (5+ testers):
   □ Can users feel the difference between events?
   □ Do haptics feel natural/appropriate?
   □ Is any haptic annoying or uncomfortable?
   □ Do haptics help or distract from the task?
   □ Rate overall haptic satisfaction (1-5)

3. Duration Testing:
   □ Play for 30+ minutes
   □ Check for haptic fatigue/numbness
   □ Verify battery impact is acceptable
   □ No accumulation of phantom sensation

4. Accessibility Testing:
   □ Test with haptics disabled — is experience still usable?
   □ Test intensity slider across full range
   □ Verify visual/audio alternatives exist for all haptic cues

When to Apply This Skill

Use this skill when:

  • Designing interaction feedback for a VR application
  • Implementing haptic patterns for controller-based VR
  • Creating a haptic design language for a VR product
  • Integrating advanced haptic hardware (vests, gloves)
  • Testing and refining haptic feedback quality
  • Ensuring accessible haptic design

Install this skill directly: skilldb add metaverse-skills

Get CLI access →