|
1 | | -# Breathing Dots |
| 1 | +# 🫁 Breathing Dot Indicators |
2 | 2 |
|
3 | | -The "Breathing Dots" are a key visual feedback mechanism within the Shock System, providing immediate and intuitive information about physiological states or interactive points on the anatomical model. (All the elements used for it can be found inside Unreal Engine a folder called ButtonsForShocks) |
| 3 | +## Overview |
4 | 4 |
|
5 | | -## Concept and Purpose |
| 5 | +Breathing Dots are dynamic UI widgets that visually mark **diagnosis-specific anatomical points** (e.g., a faint wrist pulse during Cardiogenic Shock). They: |
| 6 | +- Animate with a subtle "breathing" pulse. |
| 7 | +- Follow a 3D socket on the anatomical model. |
| 8 | +- Display an **info panel** on hover or click with title, description, and optional image. |
| 9 | +- Are **spawned dynamically** based on diagnosis configuration, and removed on diagnosis change. |
6 | 10 |
|
7 | | -These dots are dynamic UI elements that visually change (e.g., color, size, opacity, animation) to signify: |
8 | | -* **Vital Signs:** Representing a pulse, respiration rate, or other continuous physiological data in a visually engaging manner. |
9 | | -* **Interactive Hotspots:** Guiding the user to specific areas that can be interacted with to trigger events or apply medical interventions. |
10 | | -* **Status Indicators:** Showing the current "health" or state of a particular organ or system within a specific diagnosis. |
| 11 | +They are implemented via: |
| 12 | +- A C++ data class: `UShockIndicator` |
| 13 | +- A Blueprint widget: `UBreathingDotWidget` (WBP_BreathingDot) |
| 14 | +- Spawned/managed by: `UCPP_SimulationManager` |
11 | 15 |
|
12 | | -## Implementation Details |
| 16 | +--- |
| 17 | + |
| 18 | +## 🧬 System Architecture |
| 19 | + |
| 20 | +### Data Source: `UShockIndicator` |
| 21 | + |
| 22 | +Each indicator is described by: |
| 23 | + |
| 24 | +| Field | Description | |
| 25 | +|--------------|----------------------------------------------------------| |
| 26 | +| `SocketName` | Bone/socket name the indicator should follow (e.g. `"Wrist"`) | |
| 27 | +| `Title` | Title text for the info panel | |
| 28 | +| `Description`| Tooltip-style detailed description | |
| 29 | +| `Image` | Optional image/texture shown in the info panel | |
| 30 | + |
| 31 | +Each `UCPP_Diagnosis` has an array: |
| 32 | +```cpp |
| 33 | +TArray<UShockIndicator*> Indicators; |
| 34 | +``` |
| 35 | + |
| 36 | +These are registered during diagnosis building using: |
| 37 | + |
| 38 | +```cpp |
| 39 | +.AddIndicator("Wrist", "Pulse", "Faint pulse detected", IconTexture) |
| 40 | +``` |
| 41 | + |
| 42 | +--- |
| 43 | + |
| 44 | +### Runtime Spawning Logic |
| 45 | + |
| 46 | +Inside `UCPP_SimulationManager::ChangeDiagnosis()`: |
| 47 | + |
| 48 | +1. All previously spawned breathing dots are destroyed. |
| 49 | +2. The `Indicators` array from the selected diagnosis is retrieved. |
| 50 | +3. For each `UShockIndicator`, a `UBreathingDotWidget` is: |
| 51 | + - Created dynamically |
| 52 | + - Anchored to the corresponding bone/socket |
| 53 | + - Initialized via `SetDescription()` or `InitializeFromIndicator()` using `ProcessEvent` |
| 54 | + - Tracked in a local `TArray<UUserWidget*> SpawnedWidgets` for cleanup |
| 55 | + |
| 56 | +--- |
| 57 | + |
| 58 | +### Widget: `UBreathingDotWidget` |
| 59 | + |
| 60 | +This UMG Blueprint handles: |
| 61 | +- **3D following**: Converts world position of the socket to screen position. |
| 62 | +- **Visual pulsing**: Looping animation to draw user attention. |
| 63 | +- **Info Panel logic**: |
| 64 | + - Tooltip-style display on hover or click |
| 65 | + - Receives title, description, and image from the indicator |
| 66 | + |
| 67 | +Functions called from C++: |
| 68 | +- `InitializeFromIndicator()` or `SetDescription(FString Title, FString Desc, UTexture2D* Image)` |
| 69 | +- Toggle visibility or appearance based on current diagnosis |
| 70 | + |
| 71 | +--- |
| 72 | + |
| 73 | +## 🔁 Example Builder Usage |
13 | 74 |
|
14 | | -Breathing Dots are managed by the `UCPP_SimulationManager` and defined within each `UCPP_Diagnosis` profile. |
| 75 | +Here’s how a diagnosis defines its breathing dots: |
15 | 76 |
|
16 | | -### Core Classes: `UShockIndicator` and `UBreathingDotWidget` |
| 77 | +```cpp |
| 78 | +DiagnosisBuilder(this) |
| 79 | + .SetName("Cardiogenic Shock") |
| 80 | + .SetDescription("Heart failure causing reduced blood circulation") |
| 81 | + .AddIndicator("Wrist", "Weak Pulse", "Pulse is faint and irregular", WristIcon) |
| 82 | + .AddIndicator("Chest", "Chest Discoloration", "Bluish skin near heart", ChestIcon) |
| 83 | + .Build(); |
| 84 | +``` |
17 | 85 |
|
18 | | -1. **`UShockIndicator` (Base Class)**: |
19 | | - * This is the base C++ class (`Simulation/Diagnosis/Indicators/ShockIndicator.h`) that conceptually represents a "shock indicator." It holds common data like the `BoneName` it should attach to, and potentially the `Title`, `Description`, and `Image` used for a tooltip or pop-up associated with the dot. |
20 | | - * It is a `UObject` and managed by the `UCPP_Diagnosis` to define the *properties* of an indicator. |
21 | | - * It includes a `GetScreenPosition` method to project the 3D bone location to a 2D screen position. |
22 | | - * It also contains a private `DotWidgetRef` which is set when the widget is spawned by the `SimulationManager`. |
| 86 | +--- |
23 | 87 |
|
24 | | -2. **`UBreathingDotWidget` (UMG Widget)**: |
25 | | - * Defined in `BreathingDotWidget.h`. This is a `UUserWidget` subclass, meaning it's a Blueprint-implementable UI widget. |
26 | | - * It contains a `UPROPERTY(meta = (BindWidgetAnim), Transient) UWidgetAnimation* ImageBreathing;` which is a crucial part. This property is designed to be linked to an animation created in the Unreal Engine UMG editor (e.g., in `WBP_BreathingDotWidget`). This animation is what gives the "breathing" pulse effect. |
27 | | - * The `bIsRuntimeSpawned` flag helps distinguish widgets spawned by the simulation from those placed manually in the editor. |
28 | | - * Its `NativeConstruct()` method is where you would typically start the `ImageBreathing` animation or hide editor-only debugging UI. |
| 88 | +## 🎯 Integration Map |
29 | 89 |
|
30 | | -### Integration with `UCPP_Diagnosis` and `UCPP_SimulationManager` |
| 90 | +| Class | Responsibility | |
| 91 | +|---------------------------|------------------------------------------------| |
| 92 | +| `UShockIndicator` | Stores metadata for one indicator | |
| 93 | +| `UCPP_Diagnosis` | Holds multiple indicators | |
| 94 | +| `UCPP_SimulationManager` | Spawns/removes widgets based on diagnosis | |
| 95 | +| `UBreathingDotWidget` | UMG widget shown on-screen for each dot | |
| 96 | +
|
| 97 | +--- |
31 | 98 |
|
32 | | -* **Defining in `UCPP_Diagnosis`**: Each `UCPP_Diagnosis` object holds an array of `UShockIndicator*` (`UPROPERTY() TArray<UShockIndicator*> Indicators;`). This array is populated using the `AddIndicator()` method of the `DiagnosisBuilder`. |
33 | | - ```cpp |
34 | | - // Example from DiagnosisBuilder (conceptual) |
35 | | - // AddIndicator(FName BoneName, const FText& Title, const FText& Description, UTexture2D* Image); |
36 | | - DiagnosisBuilder(this) |
37 | | - .SetName("Cardiogenic Shock") |
38 | | - // ... other settings |
39 | | - .AddIndicator(FName("HeartSocket"), FText::FromString("Cardiac Pulse"), FText::FromString("Monitors heart rate and rhythm."), HeartIconTexture) |
40 | | - .Build(); |
41 | | - ``` |
42 | | -* **Spawning by `UCPP_SimulationManager`**: When a diagnosis is changed or started, the `UCPP_SimulationManager` calls `SpawnIndicators(const UCPP_Diagnosis& Diagnosis)`. This function is responsible for: |
43 | | - 1. Iterating through the `Indicators` array of the active `UCPP_Diagnosis`. |
44 | | - 2. For each `UShockIndicator`, it creates an instance of `UBreathingDotWidgetClass` (a `TSubclassOf<UBreathingDotWidget>` exposed in `UCPP_SimulationManager`'s properties). |
45 | | - 3. It then attaches these `UBreathingDotWidget` instances to the `TargetSkeletalMesh` (e.g., `BP_Bones`) at the specified `BoneName` (socket) from the `UShockIndicator`, likely converting the 3D socket location to a 2D screen position for the UMG widget. |
46 | | - 4. The spawned widgets are added to the `ActiveIndicators` array in `UCPP_SimulationManager` for later management (e.g., `ClearActiveIndicators()`). |
| 99 | +## 🧪 Debugging Breathing Dots |
47 | 100 |
|
48 | | -## Customization and Best Practices |
| 101 | +### If dots don’t appear: |
| 102 | +- ✅ Check that `SocketName` exists on the skeletal mesh: |
| 103 | +```cpp |
| 104 | +SkeletalMesh->DoesSocketExist(FName("Wrist")) |
| 105 | +``` |
| 106 | +- ✅ Log dot creation in C++: |
| 107 | +```cpp |
| 108 | +UE_LOG(LogTemp, Warning, TEXT("Spawning indicator for socket %s"), *SocketName.ToString()); |
| 109 | +``` |
| 110 | +- ✅ Use `DrawDebugSphere()` to confirm socket world location is valid. |
49 | 111 |
|
50 | | -* **Blueprint Customization**: `UBreathingDotWidget` is designed to be extended in Blueprint. You can customize its visual appearance, add more complex animations, and implement interactive logic directly within the Blueprint subclass (`WBP_BreathingDotWidget`). |
51 | | -* **Material Parameters**: The "breathing" effect itself is often driven by a Material Instance. You can expose parameters in your material (e.g., `EmissiveColor`, `PulseSpeed`) that can be dynamically updated by C++ or Blueprint logic to change the dot's visual behavior based on real-time simulation data. |
52 | | -* **Performance**: Be mindful of the number of active breathing dots, especially if they involve complex animations or many materials, to maintain good performance. |
| 112 | +### If info panel doesn’t show: |
| 113 | +- ✅ Confirm that `SetDescription()` or `InitializeFromIndicator()` is called. |
| 114 | +- ✅ Use breakpoints inside the Blueprint widget. |
| 115 | +- ✅ Ensure image is valid or pass `nullptr`. |
53 | 116 |
|
54 | 117 | --- |
55 | 118 |
|
56 | | -For how these indicators fit into the broader system, see [[How Everything Connects at Runtime]]. |
| 119 | +## 🔥 Advanced Features & Customization |
| 120 | +
|
| 121 | +### Add Conditional Visibility |
| 122 | +Inside `UBreathingDotWidget`, use Blueprint logic: |
| 123 | +- Only show info panel when hovered. |
| 124 | +- Auto-hide based on diagnosis-specific rules. |
| 125 | +- Support alternate states (e.g., blinking, fading). |
| 126 | +
|
| 127 | +### Override Appearance Per Dot |
| 128 | +Each indicator could eventually have: |
| 129 | +- A custom **color theme** |
| 130 | +- A **different pulse rate** or animation |
| 131 | +- Even **contextual behavior** depending on vital stats (BPM, blood volume) |
| 132 | +
|
| 133 | +Just extend `UShockIndicator` with new properties like: |
| 134 | +
|
| 135 | +```cpp |
| 136 | +FLinearColor DotColor; |
| 137 | +float PulseRateOverride; |
| 138 | +``` |
| 139 | + |
| 140 | +--- |
| 141 | + |
| 142 | +## ✅ Best Practices |
| 143 | + |
| 144 | +- Keep socket names consistent with the skeletal mesh you're targeting. |
| 145 | +- Group related indicators close together in the builder for easy debugging. |
| 146 | +- Don’t hardcode dot spawn logic — always use `Indicators` array. |
| 147 | +- Use `nullptr` in `AddIndicator()` for optional image to avoid crashing. |
| 148 | +- Remove or destroy all dot widgets when diagnosis changes. |
| 149 | + |
| 150 | +--- |
| 151 | + |
| 152 | +## 🧱 Example Use Case: Cardiogenic Shock |
| 153 | + |
| 154 | +```cpp |
| 155 | +// In DiagnosisRegistery.cpp |
| 156 | +DiagnosisBuilder(this) |
| 157 | + .SetName("Cardiogenic Shock") |
| 158 | + .AddIndicator("Wrist", "Pulse", "Faint wrist pulse detected", WristIcon) |
| 159 | + .AddIndicator("Face", "Pale Skin", "Skin tone loss near eyes", nullptr) |
| 160 | + .SetBehavior(NewObject<UCardiogenicShockBehavior>(this)) |
| 161 | + .Build(); |
| 162 | +``` |
| 163 | +
|
| 164 | +--- |
0 commit comments