UI & Theming
Every screen the kit ships — inventory, settings, results, the pause menu — is built from one small widget set reading one theme asset. Duplicate that asset, change the colours, and the whole game restyles. Nothing is a bespoke prefab you have to re-skin by hand.
One asset, every screen
A ThemeProvider sits on each canvas root and holds a UiThemeAsset. Every kit widget below it walks up to the nearest provider and paints itself from that asset's tokens — colours, fonts, corner sprite, spacing, bar and button heights. Nothing hardcodes a colour, so there is exactly one place to change.
Duplicate the installed theme
Install createsDefaultUiThemeunder aResourcesfolder. Duplicate it — leave the original alone so Install keeps finding one.Change the tokens
Primary, danger, panel and slot colours, the text tones, the font, the padding unit. The defaults in the asset are the shipped look, so you are always editing something complete.Assign it to a canvas root
Drop the copy into that canvas'sThemeProvider. Every widget under it restyles — in the editor, immediately, with no play mode and no prefab edits.
A widget with no provider above it, or a provider with an empty field, falls back to the installed default. A build that somehow has neither still runs: the theme's own field defaults carry the shipped palette, so the fallback is a real theme rather than a null.
A chosen theme and volume survive a quit
GameSettingsStore.Currentis the one place a player's live settings actually live — a plain C# class, not aScriptableObject, because writing choices back into an asset works in the editor and silently fails in a built player. Reading it auto-creates and restores it, so the first thing that asks already sees the saved values rather than defaults.
| Field | Type | Default | What it does |
|---|---|---|---|
MasterVolume | float | 1 | 0 (silent) to 1 (full). SetMasterVolume(v) clamps, applies to AudioListener.volume immediately, and saves. |
ThemeId | string | "" | The player's picked UiThemeAsset.ThemeId. Empty means "never chosen" — consumers fall back to their own default theme rather than guessing. |
Both setters save immediately rather than waiting for a save point — a player who drags the volume slider and quits without ceremony keeps the change. See Saving & Loading for how this fits the rest of the persistence seam: settings are Shared scope and Player owned, so they follow the machine rather than the save slot.
The widget set
All of these live under Party Core Kit › UI Kit in the Add Component menu. They style; they do not decide what your screen says. Text content, option lists and selection state stay with the screen composing them.
| Field | Type | What it does |
|---|---|---|
Panel | Surface | Any themed surface — a card, a slot, a sunken well, the dim behind a popup, or an accent highlight. |
Label | Role | Themed text: body, muted, title, caption or danger. Size follows the theme's font scale. |
Button | Role | A uGUI button whose hover, pressed and disabled looks derive from one role colour — including the highlight a gamepad needs. |
Toggle | — | A uGUI toggle with the same derived states as Button. |
Slider | — | Sunken track, accent fill, a handle that reads as a small button. |
Input | — | A TextMeshPro input field: surface, text, placeholder, caret and selection colour. |
Stepper | — | A left/right cycler through a fixed option set. Owns the index and wraps; you supply the count and the text. |
Tab Bar | — | A row of tabs where exactly one is active. Raises SelectedChanged; the active tab wears the Primary role. |
List Row | — | A row surface with a selected look, for level pickers, results tables and settings lists. |
Card Grid | — | A sunken grid whose padding and spacing come from the theme, so grids keep one rhythm across a reskin. |
Stat Bar | Kind | A value bar for health or stamina; the colour shifts from the empty tone to the full one as it fills. |
Badge | — | The small count pill on a button or icon. Hides at zero, shows 99+ above ninety-nine. |
Currency Chip | — | An icon-plus-amount chip for coins or gems. |
Avatar Frame | — | A framed portrait: accent border, sunken well, avatar sprite. |
Top Bar | — | A full-width header with left, centre and right regions to parent widgets into. |
Popup Frame | — | The shape every popup shares — full-screen dim, centred card, title. Put your content under Content. |
Panel Stack | — | A stack of menu pages with open, back and close, and the gamepad focus handling that goes with them. |
Collapsible Panel | — | A sidebar or drawer that shrinks to a thin toggle strip and back, so extra information doesn't crowd the view when the player doesn't want it. |
Gradient | Direction | Shades any image toward the theme's gradient tint. One theme value restyles every gradient; white turns them off. |
Theme Provider | — | Holds the theme for everything under it. One per canvas root. |
Focusable | — | The Selectable to use inside a player's own hierarchy. See the warning below. |
Objective Indicator | — | An edge-of-screen arrow pointing at the player's current objective while it is out of view. |
Windows, the cursor and the pad
An in-game window has three jobs beyond looking right: it needs the mouse, it needs the pad to land somewhere useful, and the player expects to be able to move it.
UI Focus Panelon the panel root handles the first two. While the panel is active, the owning player's camera releases the cursor and stops looking around — movement keys stay live — and keyboard or gamepad focus lands on the panel's first control so pad navigation works without touching the mouse. Both happen on enable and unwind on disable, so a window closed by any route leaves the cursor as it found it.
Draggable Window goes on the window's header, not the whole panel: the header is the grab handle, so dragging it moves the window while the body's own drag and drop — inventory slots, container slots — keeps working untouched. Positions are remembered for the session, so a window dragged into a corner reopens there even if it was rebuilt in between.
Escape unwinds in the order a player expects, and that is a single call: UiFocusPanel.TryCloseTopExcept(...)closes the most recently opened panel and reports whether it closed one. A false return is the pause menu's cue that nothing else was up.
Telling the player something
The notification centre on each player's HUD takes two kinds of message, and gameplay code never builds UI to send one:
// Small, stacks in the corner: "Poison applied", "Picked up 3 Stone".
notifications.ShowToast("Picked up 3 Stone");
// Big, centred, one at a time: "You Died", "Level Up!".
notifications.ShowBanner("You Win!", NotificationStyle.Positive);A banner arriving while another is on screen queues behind it instead of drawing over it. Stat events reach this through a relay, and the scripting library exposes it as an instruction — so “when this happens, tell the player” is composition, not code.
The Objective Indicatoranswers “where am I supposed to be going?”. It pins an arrow to the screen edge whenever the objective is out of view and hides itself the moment it comes into view. It binds one small interface rather than any game mode, and the kit ships two adapters for it: the goal-race one points at the start zone while players gather and the end zone once the round is running; the lap-circuit one points at each player's own next gate.
Two more, under Party Core Kit › UI
Everything above lives under the UI Kit menu folder because it reads the theme. A couple of other pieces live one folder over, under Party Core Kit › UI, because they don't:
| Field | Type | What it does |
|---|---|---|
Billboard To Camera | — | Turns a world-space element -- an overhead stat bar, an interaction prompt -- to face the active camera every frame, so it stays readable from any angle. |
Round Series Scoreboard | — | Cumulative points per participant, round progress and final standings for a Round Series. The stock results panel already shows this between rounds; add it yourself only for a custom results screen. |
Extending it
A widget of your own that follows the theme is one class. Derive from ThemedWidget and paint in Apply; it is called when the widget is enabled and on every theme change, never per frame.
using Curitor.PartyCoreKit.UI;
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(Image))]
[AddComponentMenu("My Game/UI/Ribbon")]
public class Ribbon : ThemedWidget
{
private Image _image;
protected override void Apply(UiThemeAsset theme)
{
if (_image == null) _image = GetComponent<Image>();
_image.color = theme.primary;
}
}Pointing the objective arrow at something the kit has never heard of is one class too — implement IObjectiveSource, register in OnEnable, unregister in OnDisable, and return false whenever you have nothing to point at.
public class EscapePodObjective : MonoBehaviour, IObjectiveSource
{
[SerializeField] private Transform pod;
private void OnEnable() => ObjectiveSources.Register(this);
private void OnDisable() => ObjectiveSources.Unregister(this);
public bool TryGetObjective(GameObject playerRoot, out Vector3 worldPosition)
{
worldPosition = pod != null ? pod.position : default;
return pod != null;
}
}