Files
UE5-Modular-Game-Framework/docs/game/haptics-example.md
Lefteris Notas 14441c000c Add haptics example documentation for Project Void controller feedback
- Introduced comprehensive guide for setting up controller haptics and force feedback.
- Detailed directory structure for haptic profiles and creation steps for DA_HapticProfile instances.
- Included platform-specific configurations for Xbox and PS5 DualSense adaptive triggers.
- Outlined wiring of BPC_HapticsController to various gameplay systems and events.
- Provided accessibility integration options and testing checklist for haptic functionality.
2026-05-22 17:16:34 +03:00

439 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Haptics Example — Project Void Controller Feedback
**Version:** 1.0 | **Target UE:** 5.55.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 6090 (calm/tense) |
| `DA_Haptic_Heartbeat_Fast` | `Haptic.Heartbeat.Fast` | Heartbeat | 0.08s | Left | 40 | BPM 90140 (scared) |
| `DA_Haptic_Heartbeat_Panic` | `Haptic.Heartbeat.Panic` | Heartbeat | 0.05s | Both | 50 | BPM 140180 (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)
1030: 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 40180
→ Select profile:
BPM 4090: Haptic.Heartbeat.Normal (slow pulse on left motor)
BPM 90140: Haptic.Heartbeat.Fast (faster pulse, stronger)
BPM 140180: 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).*