added documentation, fixed some bugs

This commit is contained in:
Sebastian Bularca
2026-04-06 17:30:09 +02:00
parent 333539eb0e
commit f42885830a
67 changed files with 1963 additions and 151 deletions

View File

@@ -1,8 +1,6 @@
using Jovian.Logger; using Jovian.Logger;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Jovian.PopupSystem; using Jovian.PopupSystem;
using Jovian.PopupSystem.UI;
using Nox.UI; using Nox.UI;
using UnityEngine; using UnityEngine;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
@@ -72,40 +70,32 @@ namespace Nox.Game.UI {
slot.portrait.sprite = portraitsHolder.portraits[idx]; slot.portrait.sprite = portraitsHolder.portraits[idx];
} }
// Name
if(slot.nameText != null) {
slot.nameText.text = member.Name;
}
UpdateSlotStats(slot, member); UpdateSlotStats(slot, member);
activeSlots.Add(slot); activeSlots.Add(slot);
} }
// Initialize all popup triggers under the container // Initialize all popup triggers under the container
if(popupSystem != null) { popupSystem?.InitializeTriggersInChildren(portraitsContainer, (trigger, view) => {
popupSystem.InitializeTriggersInChildren(portraitsContainer, trigger => { var slot = trigger.GetComponentInParent<PartyMemberSlot>();
var slot = trigger.GetComponentInParent<PartyMemberSlot>(); if(slot == null) {
if(slot == null) { return;
return; }
} var slotIndex = activeSlots.IndexOf(slot);
var slotIndex = activeSlots.IndexOf(slot); if(slotIndex < 0 || slotIndex >= trackedParty.members.Count) {
if(slotIndex < 0 || slotIndex >= trackedParty.members.Count) { return;
return; }
} var member = trackedParty.members[slotIndex];
var member = trackedParty.members[slotIndex]; view.SetContent(builder => BuildCharacterPopup(builder, member));
trigger.Initialize(popupSystem, builder => BuildCharacterPopup(builder, member)); });
});
}
} }
private void BuildCharacterPopup(PopupContentBuilder builder, CharacterDefinition member) { private void BuildCharacterPopup(PopupContentBuilder builder, CharacterDefinition member) {
GlobalLogger.LogInfo($"Building character popup for {member.Name}", LogCategory.UI);
// Header // Header
builder.AddHeader(member.Name); builder
builder.AddText($"{member.Race} {member.Class}", "#CCCCCC"); .AddHeader(member.Name)
builder.AddText($"Role: {member.Role}"); .AddText($"{member.Race} {member.Class}", "#CCCCCC")
builder.AddSeparator(); .AddText($"Role: {member.Role}")
.AddSeparator();
// Stats // Stats
if(member.Stats?.stats != null) { if(member.Stats?.stats != null) {
@@ -155,16 +145,10 @@ namespace Nox.Game.UI {
if(slot.healthBar != null) { if(slot.healthBar != null) {
slot.healthBar.fillAmount = Mathf.Clamp01(health / 100f); slot.healthBar.fillAmount = Mathf.Clamp01(health / 100f);
} }
if(slot.healthText != null) {
slot.healthText.text = health.ToString();
}
if(slot.manaBar != null) { if(slot.manaBar != null) {
slot.manaBar.fillAmount = Mathf.Clamp01(mana / 100f); slot.manaBar.fillAmount = Mathf.Clamp01(mana / 100f);
} }
if(slot.manaText != null) {
slot.manaText.text = mana.ToString();
}
} }
public void Dispose() { public void Dispose() {

View File

@@ -1,4 +1,3 @@
using TMPro;
using UnityEngine; using UnityEngine;
using UnityEngine.UI; using UnityEngine.UI;
@@ -7,8 +6,5 @@ namespace Nox.Game.UI {
public Image portrait; public Image portrait;
public Image healthBar; public Image healthBar;
public Image manaBar; public Image manaBar;
public TextMeshProUGUI nameText;
public TextMeshProUGUI healthText;
public TextMeshProUGUI manaText;
} }
} }

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 75a4a222ad797a54aac8ff58761899cf
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b57e33c3f937ba146a2a13d68b0ce173
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -92,7 +92,7 @@ GameObject:
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
m_NavMeshLayer: 0 m_NavMeshLayer: 0
m_StaticEditorFlags: 0 m_StaticEditorFlags: 0
m_IsActive: 1 m_IsActive: 0
--- !u!224 &7663002820715980796 --- !u!224 &7663002820715980796
RectTransform: RectTransform:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -229,7 +229,7 @@ GameObject:
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
m_NavMeshLayer: 0 m_NavMeshLayer: 0
m_StaticEditorFlags: 0 m_StaticEditorFlags: 0
m_IsActive: 1 m_IsActive: 0
--- !u!224 &8741529769002405325 --- !u!224 &8741529769002405325
RectTransform: RectTransform:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -408,7 +408,7 @@ MonoBehaviour:
m_Name: m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0} m_Material: {fileID: 0}
m_Color: {r: 0.6156863, g: 0.6509804, b: 0.63529414, a: 1} m_Color: {r: 0.9818833, g: 1, b: 0, a: 1}
m_RaycastTarget: 0 m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1 m_Maskable: 1
@@ -442,7 +442,7 @@ GameObject:
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
m_NavMeshLayer: 0 m_NavMeshLayer: 0
m_StaticEditorFlags: 0 m_StaticEditorFlags: 0
m_IsActive: 1 m_IsActive: 0
--- !u!224 &32220060764936852 --- !u!224 &32220060764936852
RectTransform: RectTransform:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -599,8 +599,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0.041302264} m_AnchorMin: {x: 0, y: 0.041302264}
m_AnchorMax: {x: 1, y: 1} m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: -0.19400787, y: -0.000015258789} m_AnchoredPosition: {x: -0.0000076293945, y: -0.000015258789}
m_SizeDelta: {x: -0.38810003, y: 0} m_SizeDelta: {x: 0.000022888, y: 0}
m_Pivot: {x: 0.5, y: 0.5} m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &713960887884573537 --- !u!222 &713960887884573537
CanvasRenderer: CanvasRenderer:
@@ -720,9 +720,6 @@ MonoBehaviour:
portrait: {fileID: 1445432521138085750} portrait: {fileID: 1445432521138085750}
healthBar: {fileID: 4550488616099254946} healthBar: {fileID: 4550488616099254946}
manaBar: {fileID: 4614622083837966787} manaBar: {fileID: 4614622083837966787}
nameText: {fileID: 7895725010274726425}
healthText: {fileID: 2204466678496895551}
manaText: {fileID: 1991527881912334546}
--- !u!114 &3828666794624103248 --- !u!114 &3828666794624103248
MonoBehaviour: MonoBehaviour:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -778,8 +775,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0.041302264} m_AnchorMin: {x: 0, y: 0.041302264}
m_AnchorMax: {x: 1, y: 0.041302264} m_AnchorMax: {x: 1, y: 0.041302264}
m_AnchoredPosition: {x: -0.19399261, y: -1.4669495} m_AnchoredPosition: {x: -0.19453812, y: -2.3094025}
m_SizeDelta: {x: -0.3880005, y: 2.6903992} m_SizeDelta: {x: -0.38894, y: 4.3752}
m_Pivot: {x: 0.5, y: 0.5} m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &2223287144791830491 --- !u!222 &2223287144791830491
CanvasRenderer: CanvasRenderer:
@@ -802,7 +799,7 @@ MonoBehaviour:
m_Name: m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0} m_Material: {fileID: 0}
m_Color: {r: 0.48627454, g: 0.06666667, b: 0.08627451, a: 1} m_Color: {r: 0.19131038, g: 0.06666666, b: 0.4862745, a: 1}
m_RaycastTarget: 0 m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1 m_Maskable: 1

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 80b7de964780c3a4d91efbe9df16d961
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b7ddc322bd65e7e46abca4c4c6540bb6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,297 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &1599460330468667833
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 623181081371217224}
- component: {fileID: 1835601435911948781}
- component: {fileID: 695849844002832445}
- component: {fileID: 3081303906751693297}
- component: {fileID: -4214546602618600400}
- component: {fileID: 7236468329886607693}
m_Layer: 5
m_Name: PopupReferencePrefab
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &623181081371217224
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1599460330468667833}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 8899521584296352500}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 300, y: 0}
m_Pivot: {x: 0, y: 1}
--- !u!225 &1835601435911948781
CanvasGroup:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1599460330468667833}
m_Enabled: 1
m_Alpha: 1
m_Interactable: 1
m_BlocksRaycasts: 1
m_IgnoreParentGroups: 0
--- !u!114 &695849844002832445
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1599460330468667833}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bc25da4712d7cc4419eb6f364e032431, type: 3}
m_Name:
m_EditorClassIdentifier: Jovian.PopupSystem::Jovian.PopupSystem.UI.PopupReference
content: {fileID: 176628901263125209}
canvasGroup: {fileID: 1835601435911948781}
background: {fileID: 8899521584296352500}
headerPrefab: {fileID: 6612787789151041457, guid: dfc1bc0bd5b4905409615c3e770a5b77, type: 3}
textPrefab: {fileID: 2506259255305457008, guid: bfa97c92d1878cc448ddc7dc456f4b17, type: 3}
statPrefab: {fileID: 1843470073663794312, guid: 5882db210c62d8647858933649f64c29, type: 3}
imagePrefab: {fileID: 7093821785826926595, guid: 5e715f4b614d02b4fa0b4d3fcfe3c053, type: 3}
separatorPrefab: {fileID: 4190588985333916705, guid: 7ccdfa1a2079db044be4b1684303ec7f, type: 3}
--- !u!223 &3081303906751693297
Canvas:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1599460330468667833}
m_Enabled: 1
serializedVersion: 3
m_RenderMode: 2
m_Camera: {fileID: 0}
m_PlaneDistance: 100
m_PixelPerfect: 0
m_ReceivesEvents: 1
m_OverrideSorting: 0
m_OverridePixelPerfect: 0
m_SortingBucketNormalizedSize: 0
m_VertexColorAlwaysGammaSpace: 0
m_AdditionalShaderChannelsFlag: 25
m_UpdateRectTransformForStandalone: 0
m_SortingLayerID: 0
m_SortingOrder: 100
m_TargetDisplay: 0
--- !u!114 &-4214546602618600400
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1599460330468667833}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler
m_UiScaleMode: 1
m_ReferencePixelsPerUnit: 100
m_ScaleFactor: 1
m_ReferenceResolution: {x: 1920, y: 1080}
m_ScreenMatchMode: 0
m_MatchWidthOrHeight: 0.5
m_PhysicalUnit: 3
m_FallbackScreenDPI: 96
m_DefaultSpriteDPI: 96
m_DynamicPixelsPerUnit: 1
m_PresetInfoIsWorld: 0
--- !u!114 &7236468329886607693
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1599460330468667833}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.ContentSizeFitter
m_HorizontalFit: 0
m_VerticalFit: 2
--- !u!1 &3774543863822571266
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 176628901263125209}
- component: {fileID: 2618782962856630769}
- component: {fileID: 4695064839375127024}
- component: {fileID: 8584485639437491102}
- component: {fileID: 7537071497970891545}
m_Layer: 5
m_Name: Content
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &176628901263125209
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3774543863822571266}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 8899521584296352500}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0, y: 1}
--- !u!114 &2618782962856630769
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3774543863822571266}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.VerticalLayoutGroup
m_Padding:
m_Left: 8
m_Right: 8
m_Top: 8
m_Bottom: 8
m_ChildAlignment: 0
m_Spacing: 3.82
m_ChildForceExpandWidth: 1
m_ChildForceExpandHeight: 1
m_ChildControlWidth: 1
m_ChildControlHeight: 0
m_ChildScaleWidth: 0
m_ChildScaleHeight: 0
m_ReverseArrangement: 0
--- !u!222 &4695064839375127024
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3774543863822571266}
m_CullTransparentMesh: 1
--- !u!114 &8584485639437491102
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3774543863822571266}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 0.1509434, g: 0.116711326, b: 0.088999644, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 52125a3c3df558448a5af5a04dbf8d2d, type: 3}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &7537071497970891545
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3774543863822571266}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.ContentSizeFitter
m_HorizontalFit: 0
m_VerticalFit: 2
--- !u!1 &7921791084240859601
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8899521584296352500}
- component: {fileID: 4414385109869753182}
m_Layer: 5
m_Name: Background
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &8899521584296352500
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7921791084240859601}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 176628901263125209}
m_Father: {fileID: 623181081371217224}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: -0.000015258789, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0, y: 1}
--- !u!222 &4414385109869753182
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7921791084240859601}
m_CullTransparentMesh: 1

View File

@@ -27,21 +27,26 @@ See the [Prefab Setup](#prefab-setup) section below for step-by-step instruction
using Jovian.PopupSystem; using Jovian.PopupSystem;
using Jovian.PopupSystem.UI; using Jovian.PopupSystem.UI;
// Create the system. viewPrefab is a reference to your PopupReference prefab. // Create the system. Pass the scene Canvas as canvasRoot to auto-scan all PopupTrigger components.
var popup = new PopupSystem(settings, viewPrefab); var popup = new PopupSystem(settings, viewPrefab, canvasRoot);
// Register categories you intend to use. // Register categories you intend to use.
popup.RegisterCategory(PopupCategory.Item); popup.RegisterCategory(PopupCategory.Item);
popup.RegisterCategory(PopupCategory.Character); popup.RegisterCategory(PopupCategory.Character);
// Show a popup anchored to a UI element. // Option A: Set content on auto-scanned triggers by name.
popup.Show(PopupCategory.Item, builder => { var trigger = popup.GetTrigger("ItemSlot");
trigger?.SetContent(builder => {
builder builder
.AddHeader("Health Potion") .AddHeader("Health Potion")
.AddSeparator() .AddSeparator()
.AddText("Restores a moderate amount of health.") .AddText("Restores a moderate amount of health.")
.AddStat("Heal Amount", 50) .AddStat("Heal Amount", 50);
.AddStat("Uses", "3 / 3"); });
// Option B: Show a popup directly from code.
popup.Show(PopupCategory.Item, builder => {
builder.AddHeader("Iron Sword").AddStat("Damage", 12);
}, anchorRect, AnchorSide.Right); }, anchorRect, AnchorSide.Right);
// Tick each frame (typically in Update or a game-state loop). // 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. | | `anchorSide` | AnchorSide | Which side of this element to anchor the popup. |
| `positionMode` | PopupPositionMode | `AnchorToElement` or `FollowMouse`. | | `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 ```csharp
var trigger = button.GetComponent<PopupTrigger>(); var trigger = button.GetComponent<PopupTrigger>();
trigger.Initialize(popupSystem, builder => { trigger.Initialize(popupSystem, builder => {
builder builder.AddHeader("Attack").AddStat("Damage", 25);
.AddHeader("Attack")
.AddStat("Damage", 25);
}); });
```
You can also override the category at initialization time: // Or with a category override:
```csharp
trigger.Initialize(popupSystem, PopupCategory.Skill, builder => { trigger.Initialize(popupSystem, PopupCategory.Skill, builder => {
builder.AddHeader("Fireball"); builder.AddHeader("Fireball");
}); });
``` ```
To change content without re-initializing: ### Updating content
To change content on an already-initialized trigger without re-binding:
```csharp ```csharp
trigger.UpdateContent(builder => { trigger.SetContent(builder => {
builder builder.AddHeader("Attack").AddStat("Damage", newDamageValue);
.AddHeader("Attack")
.AddStat("Damage", newDamageValue);
}); });
// UpdateContent is an alias for SetContent
trigger.UpdateContent(builder => { ... });
``` ```
## Code-Only Triggers ## 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: 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 ```csharp
// In your game state constructor or initialization: // In your game state initialization:
var popupSystem = new PopupSystem(popupSettings, popupViewPrefab); var guiCanvas = guiReferences.GetComponentInParent<Canvas>().transform;
var popupSystem = new PopupSystem(popupSettings, popupViewPrefab, guiCanvas);
popupSystem.RegisterCategory(PopupCategory.Character, priority: 10); popupSystem.RegisterCategory(PopupCategory.Character, priority: 10);
popupSystem.RegisterCategory(PopupCategory.Item, priority: 5); 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: // In your game state's Tick/Update:
popupSystem.Tick(Time.deltaTime); popupSystem.Tick(Time.deltaTime);
@@ -318,54 +359,68 @@ var popup = new PopupSystem(settings, viewPrefab, () => new ScalePopupAnimator()
## Prefab Setup ## 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 ### Step 1: Root GameObject
1. Create a new GameObject named `PopupReference`. 1. Create a new GameObject named `PopupReferencePrefab`
2. Add a `Canvas` component. Set **Render Mode** to **Screen Space - Overlay**. Set **Sort Order** to match `PopupSettings.sortingOrder` (default 100). 2. Add a `Canvas` component. Set **Render Mode** to **Screen Space - Overlay**
3. Add a `CanvasGroup` component. 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 `PopupReference` component (from `Jovian.PopupSystem.UI`). 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 ### Step 2: Background
1. Create a child GameObject named `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. 2. Add an `Image` component. Set the image sprite/color to your desired popup background
3. Add a `ContentSizeFitter` with **Vertical Fit** set to **Preferred Size**. 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 ### Step 3: Content container
1. Create a child of `Background` named `Content`. 1. Create a child of `Background` named `Content`
2. Add a `RectTransform`. Stretch it to fill the background with appropriate padding. 2. Stretch the RectTransform to fill the Background (anchors 0,0 to 1,1, offsets for padding)
3. Add a `VerticalLayoutGroup`. Configure padding and spacing to taste. 3. Add a `VerticalLayoutGroup`:
4. Add a `ContentSizeFitter` with **Vertical Fit** set to **Preferred Size**. - **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 ### 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 | | Prefab | Component | Notes |
|---|---|---| |---|---|---|
| Header | `TMP_Text` | Bold, larger font size | | PopupHeader | `TMP_Text` | Bold, larger font size. Add `LayoutElement` if you need minimum height |
| Text | `TMP_Text` | Regular body text | | PopupText | `TMP_Text` | Regular body text, rich text enabled |
| Stat | `RectTransform` with `HorizontalLayoutGroup` | Must have exactly two `TMP_Text` children: label and value | | PopupStat | `RectTransform` with `HorizontalLayoutGroup` | Must have exactly two `TMP_Text` children: label (left) and value (right) |
| Image | `Image` | For icons or artwork | | PopupIcon | `Image` | For icons or artwork. Add `LayoutElement` with preferred height |
| Separator | `Image` | Thin horizontal line | | PopupSeparator | `Image` | Thin horizontal line. Add `LayoutElement` with preferred height = 1 or 2 |
### Step 5: Wire references ### Step 5: Wire references
On the `PopupReference` component, assign: On the `PopupReference` component, assign:
- **Content** - the Content RectTransform - **Content**: the Content RectTransform
- **Canvas Group** - the root CanvasGroup - **Canvas Group**: the root CanvasGroup
- **Background** - the Background RectTransform - **Background**: the Background RectTransform
- **Header Prefab** - your header TMP_Text prefab - **Header Prefab**: your PopupHeader prefab
- **Text Prefab** - your body text TMP_Text prefab - **Text Prefab**: your PopupText prefab
- **Stat Prefab** - your stat row RectTransform prefab - **Stat Prefab**: your PopupStat prefab
- **Image Prefab** - your image prefab - **Image Prefab**: your PopupIcon prefab
- **Separator Prefab** - your separator Image 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 ## 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 - 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 - 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 ## API Reference
### Core Types ### Core Types
@@ -386,7 +454,7 @@ The popup system is designed for minimal runtime allocation:
| Type | Namespace | Description | | Type | Namespace | Description |
|---|---|---| |---|---|---|
| `IPopupSystem` | `Jovian.PopupSystem` | Main interface for showing/hiding popups. | | `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. | | `PopupSettings` | `Jovian.PopupSystem` | ScriptableObject with all configuration fields. |
| `PopupCategory` | `Jovian.PopupSystem` | Readonly struct identifying a popup channel. | | `PopupCategory` | `Jovian.PopupSystem` | Readonly struct identifying a popup channel. |
| `PopupContentBuilder` | `Jovian.PopupSystem` | Fluent struct for building popup content in callbacks. | | `PopupContentBuilder` | `Jovian.PopupSystem` | Fluent struct for building popup content in callbacks. |

View File

@@ -2,33 +2,37 @@ using System;
using UnityEngine; using UnityEngine;
namespace Jovian.PopupSystem { namespace Jovian.PopupSystem {
/// <summary>
/// Default popup animator that fades CanvasGroup alpha. Each category gets its own instance
/// so concurrent show/hide animations don't corrupt each other.
/// </summary>
public sealed class FadePopupAnimator : IPopupAnimator { public sealed class FadePopupAnimator : IPopupAnimator {
private CanvasGroup target; private CanvasGroup target;
private float duration; private float timer;
private float elapsed; private float elapsed;
private float startAlpha; private float startAlpha;
private float endAlpha; private float endAlpha;
private Action onComplete; private Action onFinish;
public bool IsAnimating => target != null; public bool IsAnimating => target != null;
public void Show(CanvasGroup canvasGroup, float duration, Action onComplete) { public void Show(CanvasGroup canvasGroup, float duration, Action onComplete) {
target = canvasGroup; target = canvasGroup;
this.duration = Mathf.Max(duration, 0.001f); timer = Mathf.Max(duration, 0.001f);
elapsed = 0f; elapsed = 0f;
startAlpha = 0f; startAlpha = 0f;
endAlpha = 1f; endAlpha = 1f;
this.onComplete = onComplete; onFinish = onComplete;
canvasGroup.alpha = 0f; canvasGroup.alpha = 0f;
} }
public void Hide(CanvasGroup canvasGroup, float duration, Action onComplete) { public void Hide(CanvasGroup canvasGroup, float duration, Action onComplete) {
target = canvasGroup; target = canvasGroup;
this.duration = Mathf.Max(duration, 0.001f); timer = Mathf.Max(duration, 0.001f);
elapsed = 0f; elapsed = 0f;
startAlpha = canvasGroup.alpha; startAlpha = canvasGroup.alpha;
endAlpha = 0f; endAlpha = 0f;
this.onComplete = onComplete; onFinish = onComplete;
} }
public void Tick(float deltaTime) { public void Tick(float deltaTime) {
@@ -37,15 +41,16 @@ namespace Jovian.PopupSystem {
} }
elapsed += deltaTime; elapsed += deltaTime;
var t = Mathf.Clamp01(elapsed / duration); var t = Mathf.Clamp01(elapsed / timer);
target.alpha = Mathf.Lerp(startAlpha, endAlpha, t); target.alpha = Mathf.Lerp(startAlpha, endAlpha, t);
if(t >= 1f) { if(!(t >= 1f)) {
var callback = onComplete; return;
target = null;
onComplete = null;
callback?.Invoke();
} }
var callback = onFinish;
target = null;
onFinish = null;
callback?.Invoke();
} }
} }
} }

View File

@@ -2,10 +2,36 @@ using System;
using UnityEngine; using UnityEngine;
namespace Jovian.PopupSystem { namespace Jovian.PopupSystem {
/// <summary>
/// Interface for popup show/hide animations. Each registered category gets its own instance
/// to prevent state corruption during concurrent animations. Driven by float timers in
/// <see cref="Tick"/>, not coroutines.
/// </summary>
public interface IPopupAnimator { public interface IPopupAnimator {
/// <summary>
/// Begins a show animation on the given CanvasGroup. Typically fades alpha from 0 to 1.
/// </summary>
/// <param name="canvasGroup">The CanvasGroup to animate.</param>
/// <param name="duration">Animation duration in seconds.</param>
/// <param name="onComplete">Callback invoked when the animation finishes. May be null.</param>
void Show(CanvasGroup canvasGroup, float duration, Action onComplete); void Show(CanvasGroup canvasGroup, float duration, Action onComplete);
/// <summary>
/// Begins a hide animation on the given CanvasGroup. Typically fades alpha to 0.
/// </summary>
/// <param name="canvasGroup">The CanvasGroup to animate.</param>
/// <param name="duration">Animation duration in seconds.</param>
/// <param name="onComplete">Callback invoked when the animation finishes. May be null.</param>
void Hide(CanvasGroup canvasGroup, float duration, Action onComplete); void Hide(CanvasGroup canvasGroup, float duration, Action onComplete);
/// <summary>
/// Advances the animation by deltaTime. Call every frame from <see cref="IPopupSystem.Tick"/>.
/// </summary>
void Tick(float deltaTime); void Tick(float deltaTime);
/// <summary>
/// True if an animation is currently in progress.
/// </summary>
bool IsAnimating { get; } bool IsAnimating { get; }
} }
} }

View File

@@ -1,18 +1,79 @@
using System; using System;
using System.Collections.Generic;
using Jovian.PopupSystem.UI; using Jovian.PopupSystem.UI;
using UnityEngine; using UnityEngine;
namespace Jovian.PopupSystem { namespace Jovian.PopupSystem {
/// <summary>
/// Core interface for the popup system. Created per game state, not as a singleton.
/// Manages category registration, trigger discovery, popup display, and lifecycle.
/// </summary>
public interface IPopupSystem { public interface IPopupSystem {
void InitializeTriggersInChildren(Transform parent, Action<PopupTrigger> configureTrigger); /// <summary>
/// Scans all <see cref="PopupTrigger"/> components under the given parent and binds them
/// to this system. Each trigger receives a <see cref="PopupTriggerView"/> for behavior.
/// </summary>
void ScanTriggers(Transform parent);
/// <summary>
/// Scans all <see cref="PopupTrigger"/> components under the given parent, binds them,
/// and invokes the configure callback with both the trigger (for hierarchy queries) and
/// its view (for setting content).
/// </summary>
void InitializeTriggersInChildren(Transform parent, Action<PopupTrigger, PopupTriggerView> configureTrigger);
/// <summary>
/// Returns the <see cref="PopupTriggerView"/> for the first registered trigger whose
/// GameObject name matches. Returns null if not found.
/// </summary>
PopupTriggerView GetTriggerHandler(string gameObjectName);
/// <summary>
/// Returns all <see cref="PopupTriggerView"/> instances registered under the given category.
/// </summary>
IReadOnlyList<PopupTriggerView> GetTriggerHandlers(PopupCategory category);
/// <summary>
/// Registers a popup category. Each category gets its own <see cref="PopupReference"/> instance
/// (lazily created on first show) and its own <see cref="IPopupAnimator"/>.
/// </summary>
/// <param name="category">The category to register.</param>
/// <param name="priority">Fallback priority if not defined in <see cref="PopupSettings"/>. Higher dismisses lower.</param>
void RegisterCategory(PopupCategory category, int priority = 0); void RegisterCategory(PopupCategory category, int priority = 0);
/// <summary>
/// Shows a popup for the given category after the configured delay. The build callback
/// populates content via <see cref="PopupContentBuilder"/>. Optionally anchors to a
/// RectTransform or follows the mouse if no anchor is provided.
/// </summary>
void Show(PopupCategory category, Action<PopupContentBuilder> buildContent, void Show(PopupCategory category, Action<PopupContentBuilder> buildContent,
RectTransform anchor = null, AnchorSide? anchorSide = null); RectTransform anchor = null, AnchorSide? anchorSide = null);
/// <summary>
/// Shows a popup for the given category at a fixed screen position.
/// </summary>
void ShowAtPosition(PopupCategory category, Action<PopupContentBuilder> buildContent, void ShowAtPosition(PopupCategory category, Action<PopupContentBuilder> buildContent,
Vector2 screenPosition); Vector2 screenPosition);
/// <summary>
/// Hides the popup for the given category with a fade-out animation.
/// </summary>
void Hide(PopupCategory category); void Hide(PopupCategory category);
/// <summary>
/// Hides all visible popups across all categories.
/// </summary>
void HideAll(); void HideAll();
/// <summary>
/// Drives delay timers, animations, and follow-mouse positioning. Call every frame.
/// </summary>
void Tick(float deltaTime); void Tick(float deltaTime);
/// <summary>
/// Destroys all popup view GameObjects and clears registered categories.
/// Call when the owning game state exits.
/// </summary>
void Dispose(); void Dispose();
} }
} }

View File

@@ -2,6 +2,10 @@ using System;
using UnityEngine; using UnityEngine;
namespace Jovian.PopupSystem { namespace Jovian.PopupSystem {
/// <summary>
/// Value type identifying a popup channel. Each category gets its own popup view instance.
/// Compared by string ID using ordinal comparison. Define custom categories as static fields.
/// </summary>
[Serializable] [Serializable]
public struct PopupCategory : IEquatable<PopupCategory> { public struct PopupCategory : IEquatable<PopupCategory> {
[SerializeField] string id; [SerializeField] string id;

View File

@@ -2,25 +2,42 @@ using Jovian.PopupSystem.UI;
using UnityEngine; using UnityEngine;
namespace Jovian.PopupSystem { namespace Jovian.PopupSystem {
/// <summary>
/// Fluent API struct for building popup content. Operates directly on cached elements
/// inside a <see cref="PopupReference"/> — no intermediate allocations. Received in
/// the build callback passed to <see cref="IPopupSystem.Show"/>.
/// </summary>
public struct PopupContentBuilder { public struct PopupContentBuilder {
readonly PopupReference view; readonly PopupReference view;
/// <summary>
/// Creates a builder targeting the given popup reference.
/// </summary>
public PopupContentBuilder(PopupReference view) { public PopupContentBuilder(PopupReference view) {
this.view = view; this.view = view;
} }
/// <summary>
/// Adds a header text element (bold, larger font).
/// </summary>
public PopupContentBuilder AddHeader(string text) { public PopupContentBuilder AddHeader(string text) {
var header = view.GetHeader(); var header = view.GetHeader();
header.text = text; header.text = text;
return this; return this;
} }
/// <summary>
/// Adds a body text element.
/// </summary>
public PopupContentBuilder AddText(string text) { public PopupContentBuilder AddText(string text) {
var label = view.GetText(); var label = view.GetText();
label.text = text; label.text = text;
return this; return this;
} }
/// <summary>
/// Adds a colored body text element. Hex color can be with or without the # prefix.
/// </summary>
public PopupContentBuilder AddText(string text, string hexColor) { public PopupContentBuilder AddText(string text, string hexColor) {
var label = view.GetText(); var label = view.GetText();
var prefix = hexColor.Length > 0 && hexColor[0] == '#' ? "" : "#"; var prefix = hexColor.Length > 0 && hexColor[0] == '#' ? "" : "#";
@@ -28,6 +45,9 @@ namespace Jovian.PopupSystem {
return this; return this;
} }
/// <summary>
/// Adds a stat row with a label and integer value.
/// </summary>
public PopupContentBuilder AddStat(string label, int value) { public PopupContentBuilder AddStat(string label, int value) {
var (labelText, valueText) = view.GetStat(); var (labelText, valueText) = view.GetStat();
labelText.text = label; labelText.text = label;
@@ -35,6 +55,9 @@ namespace Jovian.PopupSystem {
return this; return this;
} }
/// <summary>
/// Adds a stat row with a label and string value.
/// </summary>
public PopupContentBuilder AddStat(string label, string value) { public PopupContentBuilder AddStat(string label, string value) {
var (labelText, valueText) = view.GetStat(); var (labelText, valueText) = view.GetStat();
labelText.text = label; labelText.text = label;
@@ -42,6 +65,9 @@ namespace Jovian.PopupSystem {
return this; return this;
} }
/// <summary>
/// Adds an image element with the given sprite and optional height.
/// </summary>
public PopupContentBuilder AddImage(Sprite sprite, float height = 64f) { public PopupContentBuilder AddImage(Sprite sprite, float height = 64f) {
var image = view.GetImage(); var image = view.GetImage();
image.sprite = sprite; image.sprite = sprite;
@@ -50,6 +76,9 @@ namespace Jovian.PopupSystem {
return this; return this;
} }
/// <summary>
/// Adds a horizontal separator line.
/// </summary>
public PopupContentBuilder AddSeparator() { public PopupContentBuilder AddSeparator() {
view.GetSeparator(); view.GetSeparator();
return this; return this;

View File

@@ -3,6 +3,10 @@ using System.Collections.Generic;
using UnityEngine; using UnityEngine;
namespace Jovian.PopupSystem { namespace Jovian.PopupSystem {
/// <summary>
/// ScriptableObject holding all popup system configuration. Create via
/// Assets > Create > Jovian > Popup System > Popup Settings.
/// </summary>
[CreateAssetMenu(fileName = "PopupSettings", menuName = "Jovian/Popup System/Popup Settings")] [CreateAssetMenu(fileName = "PopupSettings", menuName = "Jovian/Popup System/Popup Settings")]
public class PopupSettings : ScriptableObject { public class PopupSettings : ScriptableObject {
[Header("General")] [Header("General")]
@@ -26,6 +30,9 @@ namespace Jovian.PopupSystem {
[Header("Per-Category Overrides")] [Header("Per-Category Overrides")]
public List<CategoryDelay> categoryDelayOverrides = new(); public List<CategoryDelay> categoryDelayOverrides = new();
/// <summary>
/// Returns the configured priority for a category, or 0 if not configured.
/// </summary>
public int GetPriority(PopupCategory category) { public int GetPriority(PopupCategory category) {
foreach(var cp in categoryPriorities) { foreach(var cp in categoryPriorities) {
if(cp.category == category) { if(cp.category == category) {
@@ -35,6 +42,9 @@ namespace Jovian.PopupSystem {
return 0; return 0;
} }
/// <summary>
/// Returns the configured delay override for a category, or the default popupDelay.
/// </summary>
public float GetDelay(PopupCategory category) { public float GetDelay(PopupCategory category) {
foreach(var cd in categoryDelayOverrides) { foreach(var cd in categoryDelayOverrides) {
if(cd.category == category) { if(cd.category == category) {

View File

@@ -2,23 +2,44 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Jovian.PopupSystem.UI; using Jovian.PopupSystem.UI;
using UnityEngine; using UnityEngine;
using UnityEngine.UI;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
namespace Jovian.PopupSystem { namespace Jovian.PopupSystem {
/// <summary>
/// Core implementation of <see cref="IPopupSystem"/>. Created per game state, not as a singleton.
/// Manages category registration, trigger discovery, popup lifecycle, priority dismissal, and
/// tick-driven delay timers and animations. Pass a canvasParent to auto-scan triggers on construction.
/// </summary>
public sealed class PopupSystem : IPopupSystem { public sealed class PopupSystem : IPopupSystem {
readonly PopupSettings settings; readonly PopupSettings settings;
readonly PopupReference viewPrefab; readonly PopupReference viewPrefab;
readonly Func<IPopupAnimator> animatorFactory; readonly Func<IPopupAnimator> animatorFactory;
readonly Transform canvasParent; readonly Transform canvasParent;
readonly Dictionary<PopupCategory, ViewState> categories = new(); readonly Dictionary<PopupCategory, ViewState> categories = new();
readonly List<PopupTrigger> registeredTriggers = new();
/// <summary>
/// Creates a new popup system instance.
/// </summary>
/// <param name="settings">Configuration ScriptableObject with delays, priorities, and display settings.</param>
/// <param name="viewPrefab">The PopupReference prefab to instantiate per category.</param>
/// <param name="canvasParent">Optional parent Canvas transform. When provided, popup views are parented
/// here (inheriting CanvasScaler) and all PopupTrigger components are auto-scanned.</param>
/// <param name="animatorFactory">Optional factory for custom animators. Defaults to FadePopupAnimator.</param>
public PopupSystem(PopupSettings settings, PopupReference viewPrefab, Transform canvasParent = null, Func<IPopupAnimator> animatorFactory = null) { public PopupSystem(PopupSettings settings, PopupReference viewPrefab, Transform canvasParent = null, Func<IPopupAnimator> animatorFactory = null) {
this.settings = settings; this.settings = settings;
this.viewPrefab = viewPrefab; this.viewPrefab = viewPrefab;
this.canvasParent = canvasParent; this.canvasParent = canvasParent;
this.animatorFactory = animatorFactory ?? (() => new FadePopupAnimator()); this.animatorFactory = animatorFactory ?? (() => new FadePopupAnimator());
// Auto-scan if a parent was provided
if(canvasParent != null) {
ScanTriggers(canvasParent);
}
} }
/// <inheritdoc />
public void RegisterCategory(PopupCategory category, int priority = 0) { public void RegisterCategory(PopupCategory category, int priority = 0) {
if(categories.ContainsKey(category)) { if(categories.ContainsKey(category)) {
return; return;
@@ -34,6 +55,7 @@ namespace Jovian.PopupSystem {
}; };
} }
/// <inheritdoc />
public void Show(PopupCategory category, Action<PopupContentBuilder> buildContent, public void Show(PopupCategory category, Action<PopupContentBuilder> buildContent,
RectTransform anchor = null, AnchorSide? anchorSide = null) { RectTransform anchor = null, AnchorSide? anchorSide = null) {
if(!categories.TryGetValue(category, out var state)) { if(!categories.TryGetValue(category, out var state)) {
@@ -50,6 +72,7 @@ namespace Jovian.PopupSystem {
state.isPending = true; state.isPending = true;
} }
/// <inheritdoc />
public void ShowAtPosition(PopupCategory category, Action<PopupContentBuilder> buildContent, public void ShowAtPosition(PopupCategory category, Action<PopupContentBuilder> buildContent,
Vector2 screenPosition) { Vector2 screenPosition) {
if(!categories.TryGetValue(category, out var state)) { if(!categories.TryGetValue(category, out var state)) {
@@ -65,6 +88,7 @@ namespace Jovian.PopupSystem {
state.isPending = true; state.isPending = true;
} }
/// <inheritdoc />
public void Hide(PopupCategory category) { public void Hide(PopupCategory category) {
if(!categories.TryGetValue(category, out var state)) { if(!categories.TryGetValue(category, out var state)) {
return; return;
@@ -78,12 +102,14 @@ namespace Jovian.PopupSystem {
} }
} }
/// <inheritdoc />
public void HideAll() { public void HideAll() {
foreach(var kvp in categories) { foreach(var kvp in categories) {
Hide(kvp.Key); Hide(kvp.Key);
} }
} }
/// <inheritdoc />
public void Tick(float deltaTime) { public void Tick(float deltaTime) {
foreach(var kvp in categories) { foreach(var kvp in categories) {
kvp.Value.animator.Tick(deltaTime); kvp.Value.animator.Tick(deltaTime);
@@ -112,13 +138,55 @@ namespace Jovian.PopupSystem {
} }
} }
public void InitializeTriggersInChildren(Transform parent, Action<PopupTrigger> configureTrigger) { /// <inheritdoc />
public void ScanTriggers(Transform parent) {
var triggers = parent.GetComponentsInChildren<PopupTrigger>(true); var triggers = parent.GetComponentsInChildren<PopupTrigger>(true);
foreach(var trigger in triggers) { foreach(var trigger in triggers) {
configureTrigger(trigger); if(!registeredTriggers.Contains(trigger)) {
BindTrigger(trigger);
}
} }
} }
/// <inheritdoc />
public PopupTriggerView GetTriggerHandler(string gameObjectName) {
foreach(var trigger in registeredTriggers) {
if(trigger != null && trigger.gameObject.name == gameObjectName) {
return trigger.Handler;
}
}
return null;
}
/// <inheritdoc />
public IReadOnlyList<PopupTriggerView> GetTriggerHandlers(PopupCategory category) {
var result = new List<PopupTriggerView>();
foreach(var trigger in registeredTriggers) {
if(trigger != null && trigger.Category == category && trigger.Handler != null) {
result.Add(trigger.Handler);
}
}
return result;
}
/// <inheritdoc />
public void InitializeTriggersInChildren(Transform parent, Action<PopupTrigger, PopupTriggerView> configureTrigger) {
var triggers = parent.GetComponentsInChildren<PopupTrigger>(true);
foreach(var trigger in triggers) {
if(!registeredTriggers.Contains(trigger)) {
BindTrigger(trigger);
}
configureTrigger(trigger, trigger.Handler);
}
}
private void BindTrigger(PopupTrigger trigger) {
var handler = new PopupTriggerView(this);
trigger.Bind(handler);
registeredTriggers.Add(trigger);
}
/// <inheritdoc />
public void Dispose() { public void Dispose() {
foreach(var kvp in categories) { foreach(var kvp in categories) {
if(kvp.Value.view != null) { if(kvp.Value.view != null) {
@@ -135,6 +203,15 @@ namespace Jovian.PopupSystem {
var builder = new PopupContentBuilder(state.view); var builder = new PopupContentBuilder(state.view);
state.pendingBuild?.Invoke(builder); state.pendingBuild?.Invoke(builder);
// Activate before layout rebuild so Unity has an active hierarchy to calculate
state.view.CanvasGroup.alpha = 0f;
state.view.SetVisible(true);
// Force full layout rebuild so positioning has correct size on first show
Canvas.ForceUpdateCanvases();
LayoutRebuilder.ForceRebuildLayoutImmediate(state.view.Content);
LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform)state.view.transform);
if(state.pendingScreenPos.HasValue) { if(state.pendingScreenPos.HasValue) {
state.view.SetFixedPosition(state.pendingScreenPos.Value, settings.screenEdgePadding); state.view.SetFixedPosition(state.pendingScreenPos.Value, settings.screenEdgePadding);
state.isFollowMouse = false; state.isFollowMouse = false;
@@ -148,7 +225,6 @@ namespace Jovian.PopupSystem {
state.isFollowMouse = true; state.isFollowMouse = true;
} }
state.view.SetVisible(true);
state.view.UpdatePosition(); state.view.UpdatePosition();
state.animator.Show(state.view.CanvasGroup, settings.fadeDuration, null); state.animator.Show(state.view.CanvasGroup, settings.fadeDuration, null);
} }

View File

@@ -0,0 +1,54 @@
using System;
using Jovian.PopupSystem.UI;
using UnityEngine;
namespace Jovian.PopupSystem {
/// <summary>
/// Handles popup behavior for a <see cref="PopupTrigger"/>. Holds the content callback and
/// forwards pointer events to the <see cref="IPopupSystem"/>. This is the behavior layer;
/// the MonoBehaviour trigger is the reference holder that forwards events here.
/// </summary>
public sealed class PopupTriggerView {
readonly IPopupSystem popupSystem;
Action<PopupContentBuilder> contentCallback;
/// <summary>
/// Creates a new trigger view bound to the given popup system.
/// </summary>
public PopupTriggerView(IPopupSystem popupSystem) {
this.popupSystem = popupSystem;
}
/// <summary>
/// Sets the content builder callback that will be invoked when the popup is shown.
/// </summary>
public void SetContent(Action<PopupContentBuilder> callback) {
contentCallback = callback;
}
/// <summary>
/// Called by <see cref="PopupTrigger"/> when the pointer enters. Reads the trigger's
/// category, anchor side, and position mode to show the popup.
/// </summary>
public void OnPointerEnter(PopupTrigger trigger) {
if(popupSystem == null || contentCallback == null) {
return;
}
if(trigger.PositionMode == PopupPositionMode.AnchorToElement) {
popupSystem.Show(trigger.Category, contentCallback, (RectTransform)trigger.transform, trigger.AnchorSide);
}
else {
popupSystem.Show(trigger.Category, contentCallback);
}
}
/// <summary>
/// Called by <see cref="PopupTrigger"/> when the pointer exits. Hides the popup
/// for the trigger's category.
/// </summary>
public void OnPointerExit(PopupTrigger trigger) {
popupSystem?.Hide(trigger.Category);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1e2351b6a4b8bf046923790d4d09141c

View File

@@ -5,6 +5,10 @@ using UnityEngine.InputSystem;
using UnityEngine.UI; using UnityEngine.UI;
namespace Jovian.PopupSystem.UI { namespace Jovian.PopupSystem.UI {
/// <summary>
/// MonoBehaviour reference holder for a popup view. Manages the grow-only element cache,
/// screen positioning, and visibility. One instance per registered <see cref="PopupCategory"/>.
/// </summary>
public class PopupReference : MonoBehaviour { public class PopupReference : MonoBehaviour {
[SerializeField] RectTransform content; [SerializeField] RectTransform content;
[SerializeField] CanvasGroup canvasGroup; [SerializeField] CanvasGroup canvasGroup;
@@ -52,6 +56,7 @@ namespace Jovian.PopupSystem.UI {
public RectTransform Content => content; public RectTransform Content => content;
public bool IsVisible => isVisible; public bool IsVisible => isVisible;
/// <summary>Constrains the popup's horizontal size to the given maximum width in pixels.</summary>
public void SetMaxWidth(float maxPopupWidth) { public void SetMaxWidth(float maxPopupWidth) {
maxWidth = maxPopupWidth; maxWidth = maxPopupWidth;
if(maxWidth > 0f) { if(maxWidth > 0f) {
@@ -62,6 +67,7 @@ namespace Jovian.PopupSystem.UI {
} }
} }
/// <summary>Shows or hides the popup GameObject. Resets alpha to 0 when hiding.</summary>
public void SetVisible(bool visible) { public void SetVisible(bool visible) {
isVisible = visible; isVisible = visible;
gameObject.SetActive(visible); gameObject.SetActive(visible);
@@ -70,6 +76,7 @@ namespace Jovian.PopupSystem.UI {
} }
} }
/// <summary>Deactivates all cached content elements and marks layout as dirty.</summary>
public void ClearContent() { public void ClearContent() {
DeactivateRange(headerCache, ref headerIndex); DeactivateRange(headerCache, ref headerIndex);
DeactivateRange(textCache, ref textIndex); DeactivateRange(textCache, ref textIndex);
@@ -91,14 +98,17 @@ namespace Jovian.PopupSystem.UI {
// --- Element access (grow-only) --- // --- Element access (grow-only) ---
/// <summary>Returns the next available header element from the cache, or creates one.</summary>
public TMP_Text GetHeader() { public TMP_Text GetHeader() {
return GetOrCreate(headerCache, headerPrefab, ref headerIndex); return GetOrCreate(headerCache, headerPrefab, ref headerIndex);
} }
/// <summary>Returns the next available text element from the cache, or creates one.</summary>
public TMP_Text GetText() { public TMP_Text GetText() {
return GetOrCreate(textCache, textPrefab, ref textIndex); return GetOrCreate(textCache, textPrefab, ref textIndex);
} }
/// <summary>Returns the next available stat row (label + value pair) from the cache, or creates one.</summary>
public (TMP_Text label, TMP_Text value) GetStat() { public (TMP_Text label, TMP_Text value) GetStat() {
if(statIndex < statCache.Count) { if(statIndex < statCache.Count) {
var existing = statCache[statIndex]; var existing = statCache[statIndex];
@@ -118,10 +128,12 @@ namespace Jovian.PopupSystem.UI {
return (entry.label, entry.value); return (entry.label, entry.value);
} }
/// <summary>Returns the next available image element from the cache, or creates one.</summary>
public Image GetImage() { public Image GetImage() {
return GetOrCreate(imageCache, imagePrefab, ref imageIndex); return GetOrCreate(imageCache, imagePrefab, ref imageIndex);
} }
/// <summary>Returns the next available separator element from the cache, or creates one.</summary>
public Image GetSeparator() { public Image GetSeparator() {
return GetOrCreate(separatorCache, separatorPrefab, ref separatorIndex); return GetOrCreate(separatorCache, separatorPrefab, ref separatorIndex);
} }
@@ -145,6 +157,7 @@ namespace Jovian.PopupSystem.UI {
// --- Positioning --- // --- Positioning ---
/// <summary>Configures the popup to anchor to a target element on the specified side.</summary>
public void SetAnchorMode(RectTransform target, AnchorSide side, float edgePadding) { public void SetAnchorMode(RectTransform target, AnchorSide side, float edgePadding) {
positionMode = PopupPositionMode.AnchorToElement; positionMode = PopupPositionMode.AnchorToElement;
anchorTarget = target; anchorTarget = target;
@@ -152,12 +165,14 @@ namespace Jovian.PopupSystem.UI {
screenEdgePadding = edgePadding; screenEdgePadding = edgePadding;
} }
/// <summary>Configures the popup to follow the mouse cursor with the given offset.</summary>
public void SetFollowMouseMode(Vector2 offset, float edgePadding) { public void SetFollowMouseMode(Vector2 offset, float edgePadding) {
positionMode = PopupPositionMode.FollowMouse; positionMode = PopupPositionMode.FollowMouse;
followOffset = offset; followOffset = offset;
screenEdgePadding = edgePadding; screenEdgePadding = edgePadding;
} }
/// <summary>Positions the popup at a fixed screen coordinate with edge clamping.</summary>
public void SetFixedPosition(Vector2 screenPos, float edgePadding) { public void SetFixedPosition(Vector2 screenPos, float edgePadding) {
positionMode = PopupPositionMode.AnchorToElement; positionMode = PopupPositionMode.AnchorToElement;
anchorTarget = null; anchorTarget = null;
@@ -165,6 +180,7 @@ namespace Jovian.PopupSystem.UI {
PositionAtScreenPoint(screenPos); PositionAtScreenPoint(screenPos);
} }
/// <summary>Updates the popup position based on the current mode (follow mouse or anchored).</summary>
public void UpdatePosition() { public void UpdatePosition() {
if(positionMode == PopupPositionMode.FollowMouse) { if(positionMode == PopupPositionMode.FollowMouse) {
PositionAtScreenPoint(Mouse.current.position.ReadValue() + followOffset); PositionAtScreenPoint(Mouse.current.position.ReadValue() + followOffset);

View File

@@ -1,54 +1,48 @@
using System;
using UnityEngine; using UnityEngine;
using UnityEngine.EventSystems; using UnityEngine.EventSystems;
namespace Jovian.PopupSystem.UI { namespace Jovian.PopupSystem.UI {
/// <summary>
/// Reference holder MonoBehaviour for popup triggers. Attach to any UI element with a
/// Graphic component (Image, TMP_Text, etc.) that has Raycast Target enabled.
/// Configure category, anchor side, and position mode in the Inspector.
/// Forwards pointer events to the bound <see cref="PopupTriggerView"/>.
/// </summary>
public class PopupTrigger : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler { public class PopupTrigger : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler {
[SerializeField] PopupCategory category; [SerializeField] PopupCategory category;
[SerializeField] AnchorSide anchorSide = AnchorSide.Below; [SerializeField] AnchorSide anchorSide = AnchorSide.Below;
[SerializeField] PopupPositionMode positionMode = PopupPositionMode.AnchorToElement; [SerializeField] PopupPositionMode positionMode = PopupPositionMode.AnchorToElement;
IPopupSystem popupSystem; PopupTriggerView handler;
Action<PopupContentBuilder> contentCallback;
bool initialized;
/// <summary>The popup category this trigger belongs to.</summary>
public PopupCategory Category => category; public PopupCategory Category => category;
public void Initialize(IPopupSystem popupSystem, Action<PopupContentBuilder> contentCallback) { /// <summary>Which side of this element the popup anchors to.</summary>
this.popupSystem = popupSystem; public AnchorSide AnchorSide => anchorSide;
this.contentCallback = contentCallback;
initialized = true; /// <summary>Whether the popup anchors to this element or follows the mouse.</summary>
} public PopupPositionMode PositionMode => positionMode;
public void Initialize(IPopupSystem popupSystem, PopupCategory category, Action<PopupContentBuilder> contentCallback) { /// <summary>The bound behavior handler. Null until <see cref="Bind"/> is called.</summary>
this.popupSystem = popupSystem; public PopupTriggerView Handler => handler;
this.category = category;
this.contentCallback = contentCallback; /// <summary>
initialized = true; /// Binds a <see cref="PopupTriggerView"/> to this trigger. Called automatically
} /// by <see cref="IPopupSystem.ScanTriggers"/> or <see cref="IPopupSystem.InitializeTriggersInChildren"/>.
/// </summary>
public void UpdateContent(Action<PopupContentBuilder> contentCallback) { public void Bind(PopupTriggerView view) {
this.contentCallback = contentCallback; handler = view;
} }
/// <summary>Forwards pointer enter to the bound handler.</summary>
public void OnPointerEnter(PointerEventData eventData) { public void OnPointerEnter(PointerEventData eventData) {
if(!initialized || popupSystem == null || contentCallback == null) { handler?.OnPointerEnter(this);
return;
}
if(positionMode == PopupPositionMode.AnchorToElement) {
popupSystem.Show(category, contentCallback, (RectTransform)transform, anchorSide);
}
else {
popupSystem.Show(category, contentCallback);
}
} }
/// <summary>Forwards pointer exit to the bound handler.</summary>
public void OnPointerExit(PointerEventData eventData) { public void OnPointerExit(PointerEventData eventData) {
if(!initialized || popupSystem == null) { handler?.OnPointerExit(this);
return;
}
popupSystem.Hide(category);
} }
} }
} }

View File

@@ -0,0 +1,160 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &7034836061828108288
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1241885294582312999}
- component: {fileID: 1942692710355281447}
- component: {fileID: 6612787789151041457}
- component: {fileID: 743893275569378137}
m_Layer: 5
m_Name: PopupHeader
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1241885294582312999
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7034836061828108288}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 741.369, y: -210.32233}
m_SizeDelta: {x: 214.8432, y: 16}
m_Pivot: {x: 0, y: 1}
--- !u!222 &1942692710355281447
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7034836061828108288}
m_CullTransparentMesh: 1
--- !u!114 &6612787789151041457
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7034836061828108288}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: New Text
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: a0ee74bf6f853704a8a568d5ef638ee9, type: 2}
m_sharedMaterial: {fileID: 9074173216178389243, guid: a0ee74bf6f853704a8a568d5ef638ee9, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: -1183493901
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 16
m_fontSizeBase: 36
m_fontWeight: 400
m_enableAutoSizing: 1
m_fontSizeMin: 5
m_fontSizeMax: 16
m_fontStyle: 17
m_HorizontalAlignment: 2
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_characterHorizontalScale: 1
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 0
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!114 &743893275569378137
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7034836061828108288}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: 1
m_FlexibleHeight: 1
m_LayoutPriority: 1

View File

@@ -0,0 +1,98 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &5887814251614319338
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3721371113883694790}
- component: {fileID: 5474191059961008429}
- component: {fileID: 7093821785826926595}
- component: {fileID: 6531872381202374245}
m_Layer: 5
m_Name: PopupIcon
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &3721371113883694790
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5887814251614319338}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 989.9083, y: -555.6331}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &5474191059961008429
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5887814251614319338}
m_CullTransparentMesh: 1
--- !u!114 &7093821785826926595
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5887814251614319338}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: ea02ea44fa86ee445be0f7ca82098b75, type: 3}
m_Type: 0
m_PreserveAspect: 1
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &6531872381202374245
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5887814251614319338}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1

View File

@@ -0,0 +1,98 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &6770634903822758885
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6952702389074691349}
- component: {fileID: 8646545094697898389}
- component: {fileID: 4190588985333916705}
- component: {fileID: 4150277454754236166}
m_Layer: 5
m_Name: PopupSeparator
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &6952702389074691349
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6770634903822758885}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 5.3594}
m_Pivot: {x: 0, y: 1}
--- !u!222 &8646545094697898389
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6770634903822758885}
m_CullTransparentMesh: 1
--- !u!114 &4190588985333916705
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6770634903822758885}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 1, g: 0.61809045, b: 0.15566039, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &4150277454754236166
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6770634903822758885}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: 1
m_FlexibleHeight: -1
m_LayoutPriority: 1

View File

@@ -0,0 +1,376 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &3992841297615171806
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 292870618973774166}
- component: {fileID: 4060824052845831606}
- component: {fileID: 572642145995679888}
m_Layer: 5
m_Name: label
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &292870618973774166
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3992841297615171806}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1843470073663794312}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 177.619, y: 25.6566}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &4060824052845831606
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3992841297615171806}
m_CullTransparentMesh: 1
--- !u!114 &572642145995679888
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3992841297615171806}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: New Text
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: a0ee74bf6f853704a8a568d5ef638ee9, type: 2}
m_sharedMaterial: {fileID: 9074173216178389243, guid: a0ee74bf6f853704a8a568d5ef638ee9, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: -1183493901
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 16
m_fontSizeBase: 36
m_fontWeight: 400
m_enableAutoSizing: 1
m_fontSizeMin: 5
m_fontSizeMax: 16
m_fontStyle: 17
m_HorizontalAlignment: 1
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_characterHorizontalScale: 1
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 0
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!1 &6246834368258800846
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1843470073663794312}
- component: {fileID: 7829576473989485776}
- component: {fileID: 5426390226938478828}
- component: {fileID: 2356989198378852018}
m_Layer: 0
m_Name: PopupStat
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1843470073663794312
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6246834368258800846}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 292870618973774166}
- {fileID: 8849778187297648043}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 835.73267, y: -543.18}
m_SizeDelta: {x: 308.5346, y: 25.640076}
m_Pivot: {x: 0, y: 1}
--- !u!114 &7829576473989485776
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6246834368258800846}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 30649d3a9faa99c48a7b1166b86bf2a0, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.HorizontalLayoutGroup
m_Padding:
m_Left: 18
m_Right: 10
m_Top: 0
m_Bottom: 0
m_ChildAlignment: 0
m_Spacing: 5.4
m_ChildForceExpandWidth: 1
m_ChildForceExpandHeight: 1
m_ChildControlWidth: 0
m_ChildControlHeight: 0
m_ChildScaleWidth: 0
m_ChildScaleHeight: 0
m_ReverseArrangement: 0
--- !u!114 &5426390226938478828
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6246834368258800846}
m_Enabled: 0
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.ContentSizeFitter
m_HorizontalFit: 0
m_VerticalFit: 0
--- !u!114 &2356989198378852018
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6246834368258800846}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: 1
m_FlexibleHeight: 1
m_LayoutPriority: 1
--- !u!1 &9078345592336978365
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8849778187297648043}
- component: {fileID: 7828883841352225497}
- component: {fileID: 6763883082192230688}
m_Layer: 5
m_Name: value
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &8849778187297648043
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 9078345592336978365}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1843470073663794312}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 178.0124, y: 25.64}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &7828883841352225497
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 9078345592336978365}
m_CullTransparentMesh: 1
--- !u!114 &6763883082192230688
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 9078345592336978365}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: New Text
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: a0ee74bf6f853704a8a568d5ef638ee9, type: 2}
m_sharedMaterial: {fileID: 9074173216178389243, guid: a0ee74bf6f853704a8a568d5ef638ee9, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: -1183493901
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 16
m_fontSizeBase: 36
m_fontWeight: 400
m_enableAutoSizing: 1
m_fontSizeMin: 1.5
m_fontSizeMax: 16
m_fontStyle: 0
m_HorizontalAlignment: 1
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_characterHorizontalScale: 1
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 0
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}

View File

@@ -0,0 +1,184 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &3157287847714375358
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3436634297340404643}
- component: {fileID: 386873398398603487}
- component: {fileID: 2506259255305457008}
- component: {fileID: 8858913019524689709}
- component: {fileID: 6923921070612219825}
m_Layer: 5
m_Name: PopupText
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &3436634297340404643
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3157287847714375358}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 705.51843, y: -303.7433}
m_SizeDelta: {x: 133.05402, y: 0}
m_Pivot: {x: 0, y: 1}
--- !u!222 &386873398398603487
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3157287847714375358}
m_CullTransparentMesh: 1
--- !u!114 &2506259255305457008
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3157287847714375358}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: New Text New TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew
TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew
TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew
TextNew TextNew Text New TextNew TextNew TextNew TextNew TextNew TextNew TextNew
TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew
TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew
TextNew TextNew TextNew Text New TextNew TextNew TextNew TextNew TextNew TextNew
TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew
TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew
TextNew TextNew TextNew Text
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: a0ee74bf6f853704a8a568d5ef638ee9, type: 2}
m_sharedMaterial: {fileID: 9074173216178389243, guid: a0ee74bf6f853704a8a568d5ef638ee9, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: -1183493901
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 12
m_fontSizeBase: 36
m_fontWeight: 400
m_enableAutoSizing: 1
m_fontSizeMin: 1.5
m_fontSizeMax: 12
m_fontStyle: 0
m_HorizontalAlignment: 2
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_characterHorizontalScale: 1
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 0
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!114 &8858913019524689709
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3157287847714375358}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: 1
m_FlexibleHeight: 1
m_LayoutPriority: 1
--- !u!114 &6923921070612219825
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3157287847714375358}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.ContentSizeFitter
m_HorizontalFit: 0
m_VerticalFit: 2

View File

@@ -1,3 +1,37 @@
# Samples # Popup System Samples
This folder is reserved for sample scenes and scripts demonstrating the Popup System. ## Contents
### Prefabs
Reference prefabs for the popup system. Copy these into your project as a starting point.
| Prefab | Description |
|---|---|
| `PopupReferencePrefab` | Main popup container with Canvas, CanvasGroup, Background, and Content |
| `PopupHeader` | Header text element (TMP_Text, bold, larger font) |
| `PopupText` | Body text element (TMP_Text, regular) |
| `PopupStat` | Stat row element (HorizontalLayoutGroup with label + value TMP_Text) |
| `PopupIcon` | Image element for icons or artwork |
| `PopupSeparator` | Horizontal divider line (Image, thin) |
### Settings
| Asset | Description |
|---|---|
| `PopupSettings` | Pre-configured PopupSettings ScriptableObject with sensible defaults |
### Scripts
| Script | Description |
|---|---|
| `PopupSystemExample` | Basic setup with auto-scanned triggers, GetTrigger by name, and GetTriggers by category |
| `DynamicTriggersExample` | Setting up triggers on dynamically instantiated UI using InitializeTriggersInChildren |
| `CodeOnlyPopupExample` | Showing popups from code without PopupTrigger (anchored, fixed position, follow mouse) |
## How to use
1. Import the samples via the Unity Package Manager (select the package, expand Samples, click Import)
2. Copy the prefabs into your project's Prefabs folder
3. Create a PopupSettings asset or use the provided one
4. Reference the example scripts for integration patterns

View File

@@ -0,0 +1,63 @@
using Jovian.PopupSystem;
using Jovian.PopupSystem.UI;
using UnityEngine;
/// <summary>
/// Example: Showing popups from code without PopupTrigger components.
///
/// Use this approach for confirmation dialogs, tutorial tips, or any popup
/// that is triggered by game logic rather than hover events.
/// </summary>
public class CodeOnlyPopupExample : MonoBehaviour {
[SerializeField] PopupSettings popupSettings;
[SerializeField] PopupReference popupReferencePrefab;
[SerializeField] Transform canvasRoot;
[SerializeField] RectTransform targetElement;
IPopupSystem popupSystem;
void Start() {
popupSystem = new PopupSystem(popupSettings, popupReferencePrefab, canvasRoot);
popupSystem.RegisterCategory(PopupCategory.General, priority: 1);
}
void Update() {
popupSystem?.Tick(Time.deltaTime);
// Show anchored to an element on key press
if(Input.GetKeyDown(KeyCode.Alpha1)) {
popupSystem.Show(PopupCategory.General, builder => {
builder
.AddHeader("Anchored Popup")
.AddText("This popup is anchored to a UI element.");
}, targetElement, AnchorSide.Right);
}
// Show at a fixed screen position
if(Input.GetKeyDown(KeyCode.Alpha2)) {
popupSystem.ShowAtPosition(PopupCategory.General, builder => {
builder
.AddHeader("Fixed Position")
.AddText("This popup appears at the center of the screen.");
}, new Vector2(Screen.width * 0.5f, Screen.height * 0.5f));
}
// Show following the mouse
if(Input.GetKeyDown(KeyCode.Alpha3)) {
popupSystem.Show(PopupCategory.General, builder => {
builder
.AddHeader("Follow Mouse")
.AddText("This popup follows the cursor.");
});
}
// Hide on key press
if(Input.GetKeyDown(KeyCode.Escape)) {
popupSystem.HideAll();
}
}
void OnDestroy() {
popupSystem?.Dispose();
}
}

View File

@@ -0,0 +1,50 @@
using Jovian.PopupSystem;
using Jovian.PopupSystem.UI;
using UnityEngine;
/// <summary>
/// Example: Using PopupSystem with dynamically instantiated UI elements.
///
/// When UI elements are created at runtime (e.g. inventory slots, party member portraits),
/// use InitializeTriggersInChildren to scan and bind triggers after instantiation.
/// </summary>
public class DynamicTriggersExample : MonoBehaviour {
[SerializeField] PopupSettings popupSettings;
[SerializeField] PopupReference popupReferencePrefab;
[SerializeField] Transform canvasRoot;
[SerializeField] Transform slotsContainer;
[SerializeField] GameObject slotPrefab;
IPopupSystem popupSystem;
void Start() {
popupSystem = new PopupSystem(popupSettings, popupReferencePrefab, canvasRoot);
popupSystem.RegisterCategory(PopupCategory.Item, priority: 5);
// Simulate creating dynamic UI slots
for(int i = 0; i < 5; i++) {
Instantiate(slotPrefab, slotsContainer);
}
// Scan the container for any PopupTrigger components on the new slots.
// Each trigger is automatically bound to the popup system.
// The configure callback lets you set content per trigger.
popupSystem.InitializeTriggersInChildren(slotsContainer, trigger => {
var slotName = trigger.gameObject.name;
trigger.SetContent(builder => {
builder
.AddHeader(slotName)
.AddSeparator()
.AddText("This is a dynamically created slot.");
});
});
}
void Update() {
popupSystem?.Tick(Time.deltaTime);
}
void OnDestroy() {
popupSystem?.Dispose();
}
}

View File

@@ -0,0 +1,70 @@
using Jovian.PopupSystem;
using Jovian.PopupSystem.UI;
using UnityEngine;
/// <summary>
/// Example: Setting up PopupSystem with auto-scanned triggers.
///
/// 1. Attach this to a GameObject in your scene.
/// 2. Assign the PopupSettings asset and PopupReference prefab.
/// 3. Set canvasRoot to the root Canvas that contains your UI and PopupTrigger components.
/// 4. Place PopupTrigger components on any UI elements that should show popups on hover.
/// 5. The system auto-scans triggers on creation. Use SetContent() to provide data.
/// </summary>
public class PopupSystemExample : MonoBehaviour {
[SerializeField] PopupSettings popupSettings;
[SerializeField] PopupReference popupReferencePrefab;
[SerializeField] Transform canvasRoot;
IPopupSystem popupSystem;
void Start() {
// Create the system. Passing canvasRoot auto-scans all PopupTrigger components.
popupSystem = new PopupSystem(popupSettings, popupReferencePrefab, canvasRoot);
// Register categories before showing popups.
popupSystem.RegisterCategory(PopupCategory.Character, priority: 10);
popupSystem.RegisterCategory(PopupCategory.Item, priority: 5);
popupSystem.RegisterCategory(PopupCategory.General, priority: 1);
// Option A: Set content on an auto-scanned trigger by GameObject name.
var characterTrigger = popupSystem.GetTrigger("CharacterPortrait");
if(characterTrigger != null) {
characterTrigger.SetContent(builder => {
builder
.AddHeader("Kael")
.AddText("Human Warrior", "CCCCCC")
.AddSeparator()
.AddStat("Health", 55)
.AddStat("Mana", 42)
.AddStat("Level", 1)
.AddSeparator()
.AddStat("Might", 8)
.AddStat("Reflex", 2)
.AddStat("Knowledge", 5)
.AddStat("Perception", 1);
});
}
// Option B: Set content on all triggers of a category.
foreach(var trigger in popupSystem.GetTriggers(PopupCategory.Item)) {
trigger.SetContent(builder => {
builder
.AddHeader("Health Potion")
.AddSeparator()
.AddText("Restores a moderate amount of health.")
.AddStat("Heal Amount", 50);
});
}
}
void Update() {
// Tick drives delay timers and animations.
popupSystem?.Tick(Time.deltaTime);
}
void OnDestroy() {
// Clean up all popup views.
popupSystem?.Dispose();
}
}

View File

@@ -0,0 +1,28 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e38e313f3665d464b82b22699b2a4634, type: 3}
m_Name: PopupSettings
m_EditorClassIdentifier: Jovian.PopupSystem::Jovian.PopupSystem.PopupSettings
popupDelay: 0.4
fadeDuration: 0.2
defaultAnchorSide: 3
screenEdgePadding: 10
maxPopupWidth: 400
sortingOrder: 100
followMouseOffset: {x: 15, y: -15}
touchHoldDuration: 0.6
gamepadFocusTrigger: 1
categoryPriorities:
- category:
id: Character
priority: 0
categoryDelayOverrides: []