# Haptics Example — Project Void Controller Feedback

**Version:** 1.0 | **Target UE:** 5.5–5.7 | **Framework System:** BPC_HapticsController (148) + DA_HapticProfile (121)

---

## Purpose

This document walks through setting up controller haptics/force feedback in the **Project Void** horror game prototype. It covers creating `DA_HapticProfile` Data Asset instances for every gameplay event, wiring `BPC_HapticsController` to all relevant systems, and platform-specific tuning for Xbox rumble and PS5 DualSense adaptive triggers.

**Rule:** All game haptic content lives in `Content/Game/DataAssets/Haptics/`. Never modify `Content/Framework/Settings/BPC_HapticsController`.

---

## Game Haptics Directory Structure

```
Content/Game/DataAssets/
├── Haptics/                              ← ALL game haptic profiles
│   ├── DA_Haptic_Damage_Light.uasset
│   ├── DA_Haptic_Damage_Heavy.uasset
│   ├── DA_Haptic_Heartbeat_Normal.uasset
│   ├── DA_Haptic_Heartbeat_Fast.uasset
│   ├── DA_Haptic_Weapon_Pistol.uasset
│   ├── DA_Haptic_Weapon_Shotgun.uasset
│   ├── DA_Haptic_Weapon_Crowbar.uasset
│   ├── DA_Haptic_Reload.uasset
│   ├── DA_Haptic_Footstep_Tile.uasset
│   ├── DA_Haptic_Footstep_Wood.uasset
│   ├── DA_Haptic_Footstep_Concrete.uasset
│   ├── DA_Haptic_Explosion.uasset
│   ├── DA_Haptic_Pickup_Item.uasset
│   ├── DA_Haptic_Pickup_Weapon.uasset
│   ├── DA_Haptic_Grab_Object.uasset
│   ├── DA_Haptic_Release_Throw.uasset
│   ├── DA_Haptic_Scare_JumpScare.uasset
│   ├── DA_Haptic_Scare_TensionRise.uasset
│   ├── DA_Haptic_Ambient_Void.uasset
│   ├── DA_Haptic_LowHealth.uasset
│   ├── DA_Haptic_StaminaExhaust.uasset
│   ├── DA_Haptic_Death.uasset
│   ├── DA_Haptic_UI_Confirm.uasset
│   └── DA_Haptic_UI_Navigate.uasset
```

---

## Step 1: Create DA_HapticProfile Data Asset Instances

For each event below, create a Data Asset instance in the Content Browser.

### How to Create a Haptic Profile

1. Navigate to `Content/Game/DataAssets/Haptics/`
2. Right-click → **Miscellaneous → Data Asset**
3. Select class: `DA_HapticProfile`
4. Name: `DA_Haptic_{Event}` (see table below)
5. Open the asset → fill in the fields:
   - **ProfileTag:** The GameplayTag for this effect (e.g., `Haptic.Damage.Light`)
   - **EventType:** Select from `EHapticEvent` enum
   - **ForceFeedbackEffect:** Assign the `UForceFeedbackEffect` waveform curve asset
   - **IntensityCurve:** Optional `UCurveFloat` for intensity over time
   - **Duration:** Total effect seconds
   - **MotorMask:** Left, Right, or Both
   - **Priority:** 0 (lowest, like footsteps) to 100 (highest, like death)
   - **bCanInterrupt:** Whether lower-priority effects can interrupt this
   - **PlatformMinIntensity:** Minimum intensity before effect is felt

### Complete Haptic Profile Data Table

| Asset Name | GameplayTag | EventType | Duration | Motor | Priority | Trigger Condition |
|------------|------------|-----------|----------|-------|----------|-------------------|
| `DA_Haptic_Damage_Light` | `Haptic.Damage.Light` | Damage | 0.15s | Both | 60 | Player takes ≤10 damage |
| `DA_Haptic_Damage_Heavy` | `Haptic.Damage.Heavy` | HeavyDamage | 0.4s | Both | 80 | Player takes >30 damage |
| `DA_Haptic_Damage_Critical` | `Haptic.Damage.Critical` | HeavyDamage | 0.6s | Both | 90 | Player HP <10% and hit |
| `DA_Haptic_Heartbeat_Normal` | `Haptic.Heartbeat.Normal` | Heartbeat | 0.1s | Left | 30 | BPM 60–90 (calm/tense) |
| `DA_Haptic_Heartbeat_Fast` | `Haptic.Heartbeat.Fast` | Heartbeat | 0.08s | Left | 40 | BPM 90–140 (scared) |
| `DA_Haptic_Heartbeat_Panic` | `Haptic.Heartbeat.Panic` | Heartbeat | 0.05s | Both | 50 | BPM 140–180 (panic) |
| `DA_Haptic_Weapon_Pistol` | `Haptic.WeaponFire.Pistol` | WeaponFire | 0.08s | Right | 50 | Pistol fired |
| `DA_Haptic_Weapon_Shotgun` | `Haptic.WeaponFire.Shotgun` | WeaponFire | 0.25s | Both | 70 | Shotgun fired |
| `DA_Haptic_Weapon_Crowbar` | `Haptic.MeleeImpact.Crowbar` | MeleeImpact | 0.15s | Both | 55 | Crowbar hits enemy |
| `DA_Haptic_Reload` | `Haptic.WeaponReload` | WeaponReload | 0.1s | Right | 40 | Reload magazine click |
| `DA_Haptic_Footstep_Tile` | `Haptic.Footstep.Tile` | Footstep | 0.02s | Left | 10 | Walking on tile floor |
| `DA_Haptic_Footstep_Wood` | `Haptic.Footstep.Wood` | Footstep | 0.03s | Left | 10 | Walking on wood floor |
| `DA_Haptic_Footstep_Concrete` | `Haptic.Footstep.Concrete` | Footstep | 0.04s | Both | 10 | Walking on concrete |
| `DA_Haptic_Explosion` | `Haptic.Explosion` | Explosion | 0.6s | Both | 85 | Nearby explosion |
| `DA_Haptic_Pickup_Item` | `Haptic.Pickup.Item` | PickupItem | 0.05s | Right | 20 | Item picked up |
| `DA_Haptic_Pickup_Weapon` | `Haptic.Pickup.Weapon` | PickupItem | 0.1s | Both | 25 | Weapon equipped |
| `DA_Haptic_Grab_Object` | `Haptic.Grab` | GrabObject | 0.08s | Both | 35 | Physics object grabbed |
| `DA_Haptic_Release_Throw` | `Haptic.Release.Throw` | ReleaseObject | 0.12s | Both | 35 | Physics object thrown |
| `DA_Haptic_Scare_JumpScare` | `Haptic.Scare.JumpScare` | ScareEvent | 0.5s | Both | 95 | Jump scare triggers |
| `DA_Haptic_Scare_TensionRise` | `Haptic.Scare.TensionRise` | ScareEvent | 2.0s | Left | 65 | Ambient tension building |
| `DA_Haptic_Ambient_Void` | `Haptic.Ambient.Void` | AmbientPulse | 3.0s | Left | 15 | Void Space ambient rumble |
| `DA_Haptic_LowHealth` | `Haptic.LowHealth` | LowHealth | 0.2s | Both | 75 | HP drops below 25% |
| `DA_Haptic_StaminaExhaust` | `Haptic.StaminaExhausted` | StaminaExhausted | 0.3s | Both | 45 | Stamina reaches zero |
| `DA_Haptic_Death` | `Haptic.Death` | Death | 0.8s | Both | 100 | Player dies |
| `DA_Haptic_UI_Confirm` | `Haptic.UI.Confirm` | UI_Confirm | 0.03s | Right | 5 | Menu option selected |
| `DA_Haptic_UI_Navigate` | `Haptic.UI.Navigate` | UI_Navigate | 0.02s | Right | 5 | Menu cursor moved |

### Creating ForceFeedbackEffect Assets (Waveform Curves)

For each haptic profile that uses a `UForceFeedbackEffect`:

1. Navigate to `Content/Game/DataAssets/Haptics/Curves/`
2. Right-click → **Miscellaneous → Force Feedback Effect**
3. Name: `FFE_{Event}` (e.g., `FFE_Damage_Light`)
4. Open the asset:
   - Add a channel: **Left Large** (low frequency rumble motor)
   - Add a channel: **Right Small** (high frequency precision motor)
   - For the Damage Light curve: short spike at 0.5 intensity, 0.15s duration
   - For the Shotgun curve: heavy dual-motor spike, 0.25s duration
   - For the Heartbeat curve: single low-frequency pulse at 0.1s
5. Assign the FFE asset to the corresponding `DA_HapticProfile`

### Platform-Specific Force Feedback Assets

For PS5 DualSense-only effects, create separate FFE assets in `Curves/PS5/`:
- `FFE_Damage_Heavy_PS5` — higher fidelity trigger and haptic pattern
- `FFE_Shotgun_PS5` — trigger kick + body rumble simultaneously
- `FFE_Heartbeat_PS5` — pulse on both adaptive triggers

The `BPC_HapticsController` selects the right asset at runtime based on `CurrentPlatform`.

---

## Step 2: Wiring BPC_HapticsController to Gameplay Systems

### 2.1 Player Damage → Haptic

In `BP_HorrorPlayerCharacter` (or wherever `BPC_HealthSystem` is attached):

```
[In BPC_HealthSystem: OnTakeDamage Event]
  → Get BPC_HapticsController from Owner (PlayerController)
  → Branch on damage amount:
    ≤10:    PlayHapticByTag(Haptic.Damage.Light, 1.0)
    10–30:  PlayHapticByTag(Haptic.Damage.Heavy, 1.0)
    >30:    PlayHapticByTag(Haptic.Damage.Critical, 1.0)
  → Branch on current health / max health:
    <0.25:  PlayHapticByTag(Haptic.LowHealth, 1.0)
```

### 2.2 Weapon Fire → Haptic

In `BP_Pistol_Held` and `BP_Shotgun_Held` (or the `BPC_FirearmSystem`):

```
[In BPC_FirearmSystem: OnFire Event]
  → Get Owner PlayerController → Get BPC_HapticsController
  → Switch on equipped weapon:
    Pistol:  PlayHapticByTag(Haptic.WeaponFire.Pistol, 1.0)
    Shotgun: PlayHapticByTag(Haptic.WeaponFire.Shotgun, 1.0)
```

For DualSense adaptive triggers (PS5 only — no-op on other platforms):

```
[AimDownSights pressed]
  → if IsDualSenseConnected:
    → SetDualSenseTriggerEffect("Right", "WeaponFire", 2, 5)  ← R2 stiffens at position 2

[AimDownSights released]
  → if IsDualSenseConnected:
    → SetDualSenseTriggerEffect("Right", "Resistance", 0, 0)  ← Remove trigger resistance
```

### 2.3 Reload → Haptic

```
[In BPC_ReloadSystem: ReloadComplete Event]
  → Get BPC_HapticsController
  → PlayHapticByTag(Haptic.WeaponReload, 1.0)
```

### 2.4 Melee → Haptic

```
[In BPC_MeleeSystem: OnMeleeHit Event]
  → Get BPC_HapticsController
  → PlayHapticByTag(Haptic.MeleeImpact.Crowbar, 1.0)
```

### 2.5 Heartbeat → Continuous Haptic

In `PC_HorrorController` (child of PlayerController with `BPC_HapticsController` attached):

```
[Event BeginPlay]
  → Get Pawn → Get BPC_StateManager
  → Bind Event OnHeartRateChanged → [Custom Event]
  
[Custom Event: OnHeartRateChanged(BPM)]
  → Get BPC_HapticsController (self)
  → PlayHeartbeatHaptic(BPM)    ← This starts/stops the looping heartbeat pulse
```

The heartbeat haptic automatically switches profiles based on BPM range:

```
In PlayHapticByTag (heartbeat):
  → Clamp BPM to 40–180
  → Select profile:
    BPM 40–90:  Haptic.Heartbeat.Normal  (slow pulse on left motor)
    BPM 90–140: Haptic.Heartbeat.Fast    (faster pulse, stronger)
    BPM 140–180: Haptic.Heartbeat.Panic  (both motors, double-pulse)
```

### 2.6 Footsteps → Surface-Dependent Haptic

In GASP animation notifies or `BPC_MovementStateSystem`:

```
[Animation Notify: Footstep]
  → Physical Surface Trace → Get Surface Type (Tile, Wood, Concrete, Carpet, Metal)
  → Get BPC_HapticsController
  → Switch on Surface:
    Tile:     PlayHapticByTag(Haptic.Footstep.Tile, surfaceDependentIntensity)
    Wood:     PlayHapticByTag(Haptic.Footstep.Wood, surfaceDependentIntensity)
    Concrete: PlayHapticByTag(Haptic.Footstep.Concrete, surfaceDependentIntensity)
    Metal:    PlayHapticByTag(Haptic.Footstep.Metal, surfaceDependentIntensity)
    Carpet:   Skip (no haptic on soft surfaces)
  → Scale intensity by movement speed:
    Sneak (0.3x), Walk (1.0x), Sprint (1.5x)
```

### 2.7 Scare Events → Haptic

```
[In BPC_ScareEventSystem: OnScareTriggered(ScareType)]
  → Get BPC_HapticsController
  → Switch on ScareType:
    JumpScare:  PlayHapticByTag(Haptic.Scare.JumpScare, 1.0)
    TensionRise: PlayHapticByTag(Haptic.Scare.TensionRise, 1.0)
                  → This plays a 2s ramp-up rumble on the left motor
```

### 2.8 Void Space Ambient → Continuous Tension Rumble

```
[In BP_AtmosphereController_WardA: EnterVoidSpace Event]
  → Get BPC_HapticsController
  → PlayHapticByTag(Haptic.Ambient.Void, 0.6)    ← Low-intensity ambient rumble

[ExitVoidSpace Event]
  → Get BPC_HapticsController
  → StopHaptic    ← Stop all ambient
```

### 2.9 Death → Final Rumble

```
[In BPC_DeathHandlingSystem: OnDeath Event]
  → Get BPC_HapticsController
  → PlayHapticByTag(Haptic.Death, 1.0)
```

### 2.10 Stamina Exhausted → Warning Pulse

```
[In BPC_StaminaSystem: Stamina = 0 Event]
  → Get BPC_HapticsController
  → PlayHapticByTag(Haptic.StaminaExhausted, 1.0)
```

### 2.11 UI Navigation → Subtle Clicks

```
[In WBP_MainMenu or WBP_PauseMenu: OnButtonHovered / OnSelectionChanged]
  → Get BPC_HapticsController (from owning PlayerController)
  → PlayHapticByTag(Haptic.UI.Navigate, 1.0)

[OnButtonPressed / Confirm]
  → PlayHapticByTag(Haptic.UI.Confirm, 1.0)
```

---

## Step 3: Platform-Specific Configuration

### 3.1 Xbox Controller Settings

Xbox controllers use standard dual-motor rumble (Left = low frequency, Right = high frequency). No special setup required beyond creating `UForceFeedbackEffect` assets.

For Xbox-specific tuning:
- Left motor (low frequency): Use for heavy impacts, explosions, death
- Right motor (high frequency): Use for weapon fire, reload clicks, UI feedback
- Both motors: Use for damage, jump scares, melee impacts

### 3.2 PS5 DualSense Adaptive Triggers

The DualSense supports two trigger effect types via the PS5 Controller Plugin:

| Effect Type | Description | Use Case |
|------------|-------------|----------|
| `Resistance` | Trigger stiffens at a certain press point | Aiming down sights (R2) |
| `Vibration` | Trigger motor vibrates | Weapon fire kick on R2 |
| `WeaponFire` | Simulated trigger break | Pulling trigger on pistol/shotgun |
| `BowDraw` | Increasing resistance as trigger pulls | (future: bow weapon) |
| `AutomaticRifle` | Rapid trigger vibration | (future: automatic rifles) |

**Trigger Effect Parameters:**
- `StartPosition`: Where on the trigger pull the effect begins (0 = fully released, 9 = fully pressed)
- `Strength`: Effect intensity (0 = off, 8 = maximum)
- `EndPosition` (Resistance only): Where the resistance wall ends

**Example: Pistol trigger effect:**
```
WeaponFire effect:
  StartPosition = 4    ← Effect starts midway through pull
  Strength = 6         ← Moderate break-point feel
```

**Graceful fallback:** On Xbox/PC, `SetDualSenseTriggerEffect` is a no-op. No crash, no error.

### 3.3 Controller Speaker Audio (PS5)

The DualSense has a small speaker. Use it sparingly for immersion:

| Game Event | Speaker Audio |
|------------|--------------|
| Radio crackle | When near a working radio or walkie-talkie |
| Heartbeat | Quiet heartbeat thump when stress is high |
| Key jingle | When picking up keys |
| Flashlight click | Mechanical click when toggling flashlight |

**Implementation:**
1. Create `USoundWave` or `UMetaSoundSource` assets
2. Route through `SS_AudioManager` with `Bus = Dialogue` (or a dedicated `Speaker` sub-bus)
3. `SS_AudioManager` detects platform and routes to controller speaker on PS5, falls back to main output on other platforms

---

## Step 4: Accessibility Integration

### Settings Menu Integration

In `WBP_SettingsMenu` (or `WBP_AccessibilityUI`), add a **Haptics** section:

```
Haptics Settings:
├── [Toggle] Controller Vibration: ON/OFF
│   └─ Calls BPC_AccessibilitySettings.SetHapticsEnabled(bool)
│        └─ Dispatcher → BPC_HapticsController.SetHapticsEnabled()
├── [Slider] Vibration Intensity: 0% – 100%
│   └─ Sets BPC_HapticsController.HapticIntensityScale
├── [Toggle] Adaptive Triggers (PS5 Only): ON/OFF
│   └─ Sets BPC_HapticsController.bEnableDualSenseTriggers
└── [Toggle] Controller Speaker (PS5 Only): ON/OFF
    └─ Sets BPC_HapticsController.bEnableSpeakerAudio
```

### Accessibility Presets

Provide preset profiles:
- **Full Haptics** (default): All effects on, full intensity
- **Reduced Haptics:** Half intensity, no ambient rumble, no trigger effects
- **No Haptics:** All vibration off (accessibility minimum)

---

## Step 5: Testing Checklist

### Basic Functionality
- [ ] Pick up an item → controller vibrates briefly
- [ ] Fire pistol → right motor kicks
- [ ] Fire shotgun → both motors heavy kick
- [ ] Take damage → vibration intensity scales with damage amount
- [ ] Heartbeat BPM increases when enemy nearby → pulse speeds up
- [ ] Walk on tile floor → light footstep ticks on left motor
- [ ] Sprint on concrete → heavier footstep pulses on both motors
- [ ] Jump scare triggers → intense 0.5s dual-motor rumble
- [ ] Enter Void Space → low continuous ambient rumble starts
- [ ] Leave Void Space → ambient rumble stops
- [ ] Death → final heavy 0.8s rumble plays

### Platform
- [ ] Xbox controller: all effects work (standard rumble)
- [ ] PS5 DualSense: adaptive triggers stiffen when aiming
- [ ] PS5 DualSense: trigger kick on weapon fire
- [ ] PS4 DualShock: falls back to standard rumble
- [ ] PC keyboard/mouse: no vibration (expected — no controller connected)
- [ ] Controller hot-swap: new platform detected, correct profiles load

### Accessibility
- [ ] Toggle vibration OFF in settings → no haptic effects play
- [ ] Toggle vibration ON again → haptics resume
- [ ] Reduce intensity to 50% → all effects half strength
- [ ] Disable adaptive triggers → trigger effects stop on PS5

### Edge Cases
- [ ] Rapid weapon fire → respects MinTimeBetweenEffects, no rumble spam
- [ ] Damage + weapon fire simultaneously → higher priority damage wins
- [ ] Heartbeat continues during combat — doesn't overwhelm other effects
- [ ] Controller disconnected mid-gameplay → no crash, haptics gracefully stop
- [ ] Load save file → haptics settings restored from SS_SettingsSystem

---

## Haptic GameplayTag Reference

All tags are in the `Haptic.` namespace, registered in `DT_Tags_Player.csv`:

```
Haptic.Damage.Light
Haptic.Damage.Heavy
Haptic.Damage.Critical
Haptic.Heartbeat.Normal
Haptic.Heartbeat.Fast
Haptic.Heartbeat.Panic
Haptic.WeaponFire.Pistol
Haptic.WeaponFire.Shotgun
Haptic.WeaponReload
Haptic.MeleeImpact.Crowbar
Haptic.MeleeImpact.Default
Haptic.Footstep.Tile
Haptic.Footstep.Wood
Haptic.Footstep.Concrete
Haptic.Footstep.Metal
Haptic.Footstep.Gravel
Haptic.Explosion
Haptic.Pickup.Item
Haptic.Pickup.Weapon
Haptic.Grab
Haptic.Release.Throw
Haptic.Scare.JumpScare
Haptic.Scare.TensionRise
Haptic.Ambient.Void
Haptic.Ambient.Default
Haptic.LowHealth
Haptic.StaminaExhausted
Haptic.Death
Haptic.UI.Confirm
Haptic.UI.Navigate
```

---

*Haptics Example v1.0 — Part of the Project Void horror game prototype. Framework systems: BPC_HapticsController (148), DA_HapticProfile (121).*
