The problem I needed to solve
Purchase dialogs, insufficient-coin messages and settings should not invent unrelated opening and closing lifecycles. I needed shared behavior separate from each popup’s text and action.
Implementation in this snapshot
PopupController.OpenPopup distinguishes a scene object from a prefab. OpenAnimated and CloseAnimated manage motion and CanvasGroups; FinishClose either destroys or disables the instance according to configuration.
The design decision and its cost
Outside clicks are checked against the panel rectangle, and BringToFront corrects stacking. PopupTextBinder receives content, so shared presentation does not require identical messages.
Current boundaries
Automatically recognizing close buttons from names or labels is a fragile convention. Specific dialogs need their wiring checked; a naming guess does not establish correct scene behavior.
A scene popup differs from an instantiated popup
A settings popup already in the scene may appropriately retain its instance after closing. Keeping every prefab-created message could accumulate unused objects. OpenPopup distinguishes the cases with popupRoot.scene.IsValid, instantiates prefabs and sets destroyOnClose accordingly. The input governs lifetime as well as appearance. Supplying a parent reparents a scene object only under the method’s particular conditions.
CloseAnimated guards repeated closure with isClosing and stops previous animation. FinishClose destroys the outside-click catcher, restores base alpha and scale, publishes Closed and then destroys or disables the object. Callback timing matters because Closed runs during the closure path before destruction. Removing the input-catching layer is part of UI behavior: shrinking a dialog while leaving it to intercept background input is insufficient.
OnPointerClick uses RectangleContainsScreenPoint to identify a touch outside panel, depending on the correct panel reference and event camera. WireButtons may close on every button through closeOnAnyButtonClick or discover close buttons by names and labels. A multi-action dialog needs a deliberate choice: purchasing and exiting are different actions. Shared animation and cleanup do not validate purchases, reward claims or settings actions; those belong to the dialog’s specific services and wiring.
What this stage taught me
I preferred defining the display lifecycle once. Each message could then retain its own purpose and action without rewriting animation and cleanup.




