forked from Shardstone/trail-into-darkness
added documentation, fixed some bugs
This commit is contained in:
@@ -27,21 +27,26 @@ See the [Prefab Setup](#prefab-setup) section below for step-by-step instruction
|
||||
using Jovian.PopupSystem;
|
||||
using Jovian.PopupSystem.UI;
|
||||
|
||||
// Create the system. viewPrefab is a reference to your PopupReference prefab.
|
||||
var popup = new PopupSystem(settings, viewPrefab);
|
||||
// Create the system. Pass the scene Canvas as canvasRoot to auto-scan all PopupTrigger components.
|
||||
var popup = new PopupSystem(settings, viewPrefab, canvasRoot);
|
||||
|
||||
// Register categories you intend to use.
|
||||
popup.RegisterCategory(PopupCategory.Item);
|
||||
popup.RegisterCategory(PopupCategory.Character);
|
||||
|
||||
// Show a popup anchored to a UI element.
|
||||
popup.Show(PopupCategory.Item, builder => {
|
||||
// Option A: Set content on auto-scanned triggers by name.
|
||||
var trigger = popup.GetTrigger("ItemSlot");
|
||||
trigger?.SetContent(builder => {
|
||||
builder
|
||||
.AddHeader("Health Potion")
|
||||
.AddSeparator()
|
||||
.AddText("Restores a moderate amount of health.")
|
||||
.AddStat("Heal Amount", 50)
|
||||
.AddStat("Uses", "3 / 3");
|
||||
.AddStat("Heal Amount", 50);
|
||||
});
|
||||
|
||||
// Option B: Show a popup directly from code.
|
||||
popup.Show(PopupCategory.Item, builder => {
|
||||
builder.AddHeader("Iron Sword").AddStat("Damage", 12);
|
||||
}, anchorRect, AnchorSide.Right);
|
||||
|
||||
// Tick each frame (typically in Update or a game-state loop).
|
||||
@@ -139,35 +144,69 @@ All elements are drawn from a grow-only pool inside `PopupReference`. No allocat
|
||||
| `anchorSide` | AnchorSide | Which side of this element to anchor the popup. |
|
||||
| `positionMode` | PopupPositionMode | `AnchorToElement` or `FollowMouse`. |
|
||||
|
||||
### Initialization
|
||||
### Approach 1: Auto-scanned triggers (recommended for static UI)
|
||||
|
||||
`PopupTrigger` must be initialized from code before it will respond to pointer events:
|
||||
When you pass a `canvasRoot` to the `PopupSystem` constructor, it auto-scans all `PopupTrigger` components and binds them. You only need to set content:
|
||||
|
||||
```csharp
|
||||
var popup = new PopupSystem(settings, viewPrefab, canvasRoot);
|
||||
popup.RegisterCategory(PopupCategory.Character);
|
||||
|
||||
// Find a trigger by its GameObject name and set content
|
||||
var trigger = popup.GetTrigger("HeroPortrait");
|
||||
trigger?.SetContent(builder => {
|
||||
builder.AddHeader("Kael").AddStat("Health", 55);
|
||||
});
|
||||
|
||||
// Or set content on all triggers of a category
|
||||
foreach(var t in popup.GetTriggers(PopupCategory.Item)) {
|
||||
t.SetContent(builder => builder.AddHeader("Item"));
|
||||
}
|
||||
```
|
||||
|
||||
### Approach 2: Dynamic triggers (for runtime-created UI)
|
||||
|
||||
For UI elements instantiated at runtime (inventory slots, party portraits), use `InitializeTriggersInChildren` after instantiation:
|
||||
|
||||
```csharp
|
||||
// After instantiating slot prefabs under a container:
|
||||
popup.InitializeTriggersInChildren(slotsContainer, trigger => {
|
||||
var slotData = trigger.GetComponentInParent<SlotComponent>();
|
||||
trigger.SetContent(builder => {
|
||||
builder.AddHeader(slotData.itemName);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
This scans, binds, and registers all triggers under the parent, then calls the callback on each.
|
||||
|
||||
### Approach 3: Full manual initialization
|
||||
|
||||
For complete control, bypass auto-scan and initialize triggers directly:
|
||||
|
||||
```csharp
|
||||
var trigger = button.GetComponent<PopupTrigger>();
|
||||
trigger.Initialize(popupSystem, builder => {
|
||||
builder
|
||||
.AddHeader("Attack")
|
||||
.AddStat("Damage", 25);
|
||||
builder.AddHeader("Attack").AddStat("Damage", 25);
|
||||
});
|
||||
```
|
||||
|
||||
You can also override the category at initialization time:
|
||||
|
||||
```csharp
|
||||
// Or with a category override:
|
||||
trigger.Initialize(popupSystem, PopupCategory.Skill, builder => {
|
||||
builder.AddHeader("Fireball");
|
||||
});
|
||||
```
|
||||
|
||||
To change content without re-initializing:
|
||||
### Updating content
|
||||
|
||||
To change content on an already-initialized trigger without re-binding:
|
||||
|
||||
```csharp
|
||||
trigger.UpdateContent(builder => {
|
||||
builder
|
||||
.AddHeader("Attack")
|
||||
.AddStat("Damage", newDamageValue);
|
||||
trigger.SetContent(builder => {
|
||||
builder.AddHeader("Attack").AddStat("Damage", newDamageValue);
|
||||
});
|
||||
|
||||
// UpdateContent is an alias for SetContent
|
||||
trigger.UpdateContent(builder => { ... });
|
||||
```
|
||||
|
||||
## Code-Only Triggers
|
||||
@@ -226,12 +265,14 @@ Settings-based priorities (from `PopupSettings.categoryPriorities`) take precede
|
||||
The popup system is designed to be created per game state, not as a global singleton. Each game state owns its own `IPopupSystem` instance:
|
||||
|
||||
```csharp
|
||||
// In your game state constructor or initialization:
|
||||
var popupSystem = new PopupSystem(popupSettings, popupViewPrefab);
|
||||
// In your game state initialization:
|
||||
var guiCanvas = guiReferences.GetComponentInParent<Canvas>().transform;
|
||||
var popupSystem = new PopupSystem(popupSettings, popupViewPrefab, guiCanvas);
|
||||
popupSystem.RegisterCategory(PopupCategory.Character, priority: 10);
|
||||
popupSystem.RegisterCategory(PopupCategory.Item, priority: 5);
|
||||
|
||||
// Pass to views/play modes via constructor DI
|
||||
// All PopupTrigger components under guiCanvas are auto-scanned and bound.
|
||||
// Pass popupSystem to views/play modes via constructor DI.
|
||||
|
||||
// In your game state's Tick/Update:
|
||||
popupSystem.Tick(Time.deltaTime);
|
||||
@@ -318,54 +359,68 @@ var popup = new PopupSystem(settings, viewPrefab, () => new ScalePopupAnimator()
|
||||
|
||||
## Prefab Setup
|
||||
|
||||
Build the `PopupReference` prefab with the following hierarchy:
|
||||
Build the `PopupReference` prefab with the following hierarchy. Reference prefabs are included in the `Samples~/Prefabs` folder.
|
||||
|
||||
```
|
||||
PopupReferencePrefab Canvas, CanvasGroup, CanvasScaler, PopupReference
|
||||
Background Image (popup background), ContentSizeFitter (V=Preferred)
|
||||
Content VerticalLayoutGroup (Control Child Size Width, Child Force Expand Width)
|
||||
```
|
||||
|
||||
### Step 1: Root GameObject
|
||||
|
||||
1. Create a new GameObject named `PopupReference`.
|
||||
2. Add a `Canvas` component. Set **Render Mode** to **Screen Space - Overlay**. Set **Sort Order** to match `PopupSettings.sortingOrder` (default 100).
|
||||
3. Add a `CanvasGroup` component.
|
||||
4. Add a `PopupReference` component (from `Jovian.PopupSystem.UI`).
|
||||
1. Create a new GameObject named `PopupReferencePrefab`
|
||||
2. Add a `Canvas` component. Set **Render Mode** to **Screen Space - Overlay**
|
||||
3. Add a `CanvasScaler` with **Scale With Screen Size**, reference resolution matching your project (e.g. 1920x1080). This is needed for prefab editing; at runtime the nested Canvas inherits the parent's scaler
|
||||
4. Add a `CanvasGroup` component
|
||||
5. Add a `PopupReference` component (from `Jovian.PopupSystem.UI`)
|
||||
6. Set anchors to a single point (e.g. Min 0,1 Max 0,1), Pivot (0,1) for top-left origin
|
||||
7. Do **not** add a `ContentSizeFitter` to the root
|
||||
|
||||
### Step 2: Background
|
||||
|
||||
1. Create a child GameObject named `Background`.
|
||||
2. Add a `RectTransform` and an `Image` component. Set the image color/sprite to your desired popup background.
|
||||
3. Add a `ContentSizeFitter` with **Vertical Fit** set to **Preferred Size**.
|
||||
1. Create a child GameObject named `Background`
|
||||
2. Add an `Image` component. Set the image sprite/color to your desired popup background
|
||||
3. Add a `ContentSizeFitter` with **Horizontal Fit = Unconstrained** and **Vertical Fit = Preferred Size**
|
||||
4. Stretch the RectTransform to fill the root (anchors 0,0 to 1,1, offsets 0) or use point anchors with the ContentSizeFitter driving the size
|
||||
|
||||
### Step 3: Content container
|
||||
|
||||
1. Create a child of `Background` named `Content`.
|
||||
2. Add a `RectTransform`. Stretch it to fill the background with appropriate padding.
|
||||
3. Add a `VerticalLayoutGroup`. Configure padding and spacing to taste.
|
||||
4. Add a `ContentSizeFitter` with **Vertical Fit** set to **Preferred Size**.
|
||||
1. Create a child of `Background` named `Content`
|
||||
2. Stretch the RectTransform to fill the Background (anchors 0,0 to 1,1, offsets for padding)
|
||||
3. Add a `VerticalLayoutGroup`:
|
||||
- **Control Child Size**: Width checked, Height unchecked
|
||||
- **Child Force Expand**: Width checked, Height unchecked
|
||||
- **Spacing**: 2 (or to taste)
|
||||
- **Padding**: as needed
|
||||
4. Do **not** add a `ContentSizeFitter` to Content. The VerticalLayoutGroup reports its preferred size to the parent Background's ContentSizeFitter
|
||||
|
||||
### Step 4: Element prefabs
|
||||
|
||||
Create these as child prefabs (or separate prefabs). Each must be a prefab reference, not an in-hierarchy instance:
|
||||
Create these as separate prefabs. Do not add `ContentSizeFitter` to any element prefab:
|
||||
|
||||
| Prefab | Component | Notes |
|
||||
|---|---|---|
|
||||
| Header | `TMP_Text` | Bold, larger font size |
|
||||
| Text | `TMP_Text` | Regular body text |
|
||||
| Stat | `RectTransform` with `HorizontalLayoutGroup` | Must have exactly two `TMP_Text` children: label and value |
|
||||
| Image | `Image` | For icons or artwork |
|
||||
| Separator | `Image` | Thin horizontal line |
|
||||
| PopupHeader | `TMP_Text` | Bold, larger font size. Add `LayoutElement` if you need minimum height |
|
||||
| PopupText | `TMP_Text` | Regular body text, rich text enabled |
|
||||
| PopupStat | `RectTransform` with `HorizontalLayoutGroup` | Must have exactly two `TMP_Text` children: label (left) and value (right) |
|
||||
| PopupIcon | `Image` | For icons or artwork. Add `LayoutElement` with preferred height |
|
||||
| PopupSeparator | `Image` | Thin horizontal line. Add `LayoutElement` with preferred height = 1 or 2 |
|
||||
|
||||
### Step 5: Wire references
|
||||
|
||||
On the `PopupReference` component, assign:
|
||||
|
||||
- **Content** - the Content RectTransform
|
||||
- **Canvas Group** - the root CanvasGroup
|
||||
- **Background** - the Background RectTransform
|
||||
- **Header Prefab** - your header TMP_Text prefab
|
||||
- **Text Prefab** - your body text TMP_Text prefab
|
||||
- **Stat Prefab** - your stat row RectTransform prefab
|
||||
- **Image Prefab** - your image prefab
|
||||
- **Separator Prefab** - your separator Image prefab
|
||||
- **Content**: the Content RectTransform
|
||||
- **Canvas Group**: the root CanvasGroup
|
||||
- **Background**: the Background RectTransform
|
||||
- **Header Prefab**: your PopupHeader prefab
|
||||
- **Text Prefab**: your PopupText prefab
|
||||
- **Stat Prefab**: your PopupStat prefab
|
||||
- **Image Prefab**: your PopupIcon prefab
|
||||
- **Separator Prefab**: your PopupSeparator prefab
|
||||
|
||||
Save as a prefab. The `maxPopupWidth` from `PopupSettings` is applied at runtime to constrain the popup's horizontal size.
|
||||
Save as a prefab. The `maxPopupWidth` from `PopupSettings` is applied at runtime to constrain the popup's horizontal size. At runtime, when parented under a scene Canvas, the popup's own Canvas becomes a nested override Canvas inheriting the parent's scaling.
|
||||
|
||||
## Optimization Notes
|
||||
|
||||
@@ -379,6 +434,19 @@ The popup system is designed for minimal runtime allocation:
|
||||
- Layout rebuilds only occur when content changes (dirty flag), not on every position update
|
||||
- Each registered category gets its own `IPopupAnimator` instance, preventing state corruption during concurrent show/hide animations
|
||||
|
||||
## Samples
|
||||
|
||||
The `Samples~` folder contains ready-to-use reference material:
|
||||
|
||||
- **Prefabs**: PopupReferencePrefab and all element prefabs (header, text, stat, icon, separator) with correct component setup
|
||||
- **Settings**: Pre-configured PopupSettings asset with sensible defaults
|
||||
- **Scripts**: Three example scripts demonstrating different integration patterns:
|
||||
- `PopupSystemExample` -- auto-scanned triggers with `GetTrigger` and `GetTriggers`
|
||||
- `DynamicTriggersExample` -- runtime-instantiated UI with `InitializeTriggersInChildren`
|
||||
- `CodeOnlyPopupExample` -- showing popups from code (anchored, fixed position, follow mouse)
|
||||
|
||||
Import via Unity Package Manager: select the package, expand Samples, click Import.
|
||||
|
||||
## API Reference
|
||||
|
||||
### Core Types
|
||||
@@ -386,7 +454,7 @@ The popup system is designed for minimal runtime allocation:
|
||||
| Type | Namespace | Description |
|
||||
|---|---|---|
|
||||
| `IPopupSystem` | `Jovian.PopupSystem` | Main interface for showing/hiding popups. |
|
||||
| `PopupSystem` | `Jovian.PopupSystem` | Concrete implementation. Constructor: `(PopupSettings, PopupReference, Func<IPopupAnimator>)`. |
|
||||
| `PopupSystem` | `Jovian.PopupSystem` | Concrete implementation. Constructor: `(PopupSettings, PopupReference, Transform canvasParent, Func<IPopupAnimator>)`. Auto-scans triggers under canvasParent. Methods: `ScanTriggers`, `GetTrigger`, `GetTriggers`, `InitializeTriggersInChildren`. |
|
||||
| `PopupSettings` | `Jovian.PopupSystem` | ScriptableObject with all configuration fields. |
|
||||
| `PopupCategory` | `Jovian.PopupSystem` | Readonly struct identifying a popup channel. |
|
||||
| `PopupContentBuilder` | `Jovian.PopupSystem` | Fluent struct for building popup content in callbacks. |
|
||||
|
||||
Reference in New Issue
Block a user