A Unity UI framework by the PurrNet team featuring procedural UI rendering, view management, and UI pooling.


- Procedural UI - SDF-based rendering with
RectangleGraphicandGlowGraphic(for glow/shadows) - Material Icons - Simple to use material icon support for textmeshpro
- View Management -
ViewStackandViewCollectionfor managing UI views with transition support - Color Palette - Theme your UI from a single asset;
ColoredGraphicapplies slots to any graphic with smooth transitions - Sounds2D - Lightweight 2D audio system with a fluent API for fire-and-forget sound effects
Latest release:
https://github.com/PurrNet/PurrUI.git?path=Assets/PurrUI#release
Latest development:
https://github.com/PurrNet/PurrUI.git?path=Assets/PurrUI#dev



Hello world
<icon=reddit><icon=gamepad_circle><icon=gamepad_variant>
<icon=gamepad_square><icon=keyboard_f1><icon=keyboard_return>
To use Material Icons you simply need to follow the example above <icon=your_icon_name> and you also need to add the MaterialIconProcessor component to your text.

This MaterialIconProcessor allows the icons to work but it also gives you a handy little search bar to search for icons so you never get lost.

The Copy button will give you something like this directly: <icon=account_alert>.
PurrUI provides a stack-based view navigation system for Unity. Views are managed through a ViewStack that handles pushing, popping, ordering, visibility, and animated transitions.
All classes live in the PurrNet.UI namespace.
| Class | Type | Purpose |
|---|---|---|
ViewStack | MonoBehaviour | Navigation controller that manages a stack of views |
MonoView | MonoBehaviour | Base class for all views |
ViewCollection | ScriptableObject | Asset that holds references to view prefabs |
ViewTransitions | Static class | Built-in transition animations (fade, slide, etc.) |
Right-click in your Project window and select Create > PurrNet > View Collection.
A ViewCollection is a ScriptableObject that stores references to your view prefabs. It has two modes:
- Auto Generate (default): Assign folders to watch and the collection automatically discovers all
MonoViewprefabs inside them. It refreshes whenever assets change. - Manual: Disable
autoGenerateand drag prefabs into theviewsarray yourself.

Each view is a prefab with a MonoView (or subclass) component on its root GameObject. MonoView automatically requires a Canvas and CanvasGroup, so those are added for you.

Add a ViewStack component to a GameObject. In the inspector, configure:
- Parent: The transform where instantiated views will be parented (defaults to the ViewStack's own transform).
- Prefabs: Your
ViewCollectionasset. - Push On Start (optional): A view to automatically push when the scene starts.
- Order Offset: An offset applied to canvas sorting order values.

Subclass MonoView to create your own views:
usingPurrNet.UI;usingUnityEngine;publicclassProfileView:MonoView{[SerializeField]privateTMPro.TMP_Text_username;[SerializeField]privateTMPro.TMP_Text_bio;publicvoidSetup(stringusername,stringbio){_username.text=username;_bio.text=bio;}}We follow a convention of adding a Setup method to views that need initialization data. This is not enforced by the base class, it's simply a pattern. The idea is that Setup is called immediately after pushing:
_stack.Push<ProfileView>().Setup("Jane","Hello world!");All built-in views provided by PurrUI follow this pattern. You are encouraged to do the same for consistency, but it is not required.
There are two ways to push a view onto the stack:
// Generic push: looks up the prefab by type from the ViewCollectionvarview=_stack.Push<DialogView>();// Direct prefab pushvarview=_stack.Push(someViewPrefab);When a view is pushed:
- The current top view (if any) is moved to the background (raycasts disabled).
- The prefab is instantiated under the configured parent transform.
- The view is initialized and assigned a sorting order.
- If the view defines an enter transition, it plays (raycasts are blocked until it completes).
// Pop the top view_stack.Pop();// Pop a specific view instance (regardless of position in the stack)_stack.Pop(viewInstance);// From inside a view, call CloseMe() (can be wired to a button in the inspector)CloseMe();When a view is popped:
- If the view defines an exit transition, it plays before the view is destroyed.
- The new top view is moved to the foreground (raycasts re-enabled,
OnBecomeForegroundcalled).
// Remove all views from the stack (exit transitions play for each)_stack.Clear();// Move an existing view to the top of the stack_stack.MoveToTop(viewInstance);_stack.MoveToTop<DialogView>();Override OnEnterTransition and OnExitTransition in your MonoView subclass to define animated transitions. They return IEnumerator coroutines.
publicclassMyView:MonoView{[SerializeField]privateRectTransform_content;protectedoverrideIEnumeratorOnEnterTransition(){returnViewTransitions.FadeIn(this);}protectedoverrideIEnumeratorOnExitTransition(){returnViewTransitions.FadeOut(this);}}ViewTransitions provides ready-to-use animations. All use smoothstep easing and unscaled time (unaffected by Time.timeScale). Default duration is 0.2s.
Fade:
ViewTransitions.FadeIn(view,duration)
ViewTransitions.FadeOut(view,duration)ViewTransitions.Fade(canvasGroup,fromAlpha,toAlpha,duration)Slide:
ViewTransitions.SlideFromLeft(content,duration)
ViewTransitions.SlideToLeft(content,duration)
ViewTransitions.SlideFromRight(content,duration)
ViewTransitions.SlideToRight(content,duration)
ViewTransitions.SlideFromTop(content,duration)
ViewTransitions.SlideToTop(content,duration)
ViewTransitions.SlideFromBottom(content,duration)
ViewTransitions.SlideToBottom(content,duration)Slide methods operate on a RectTransform (typically a child content container, not the root view), using anchoredPosition and the rect's own width/height to calculate offsets.
Use ViewTransitions.Parallel to run multiple transitions at the same time:
protectedoverrideIEnumeratorOnEnterTransition(){varfade=ViewTransitions.FadeIn(this);varslide=ViewTransitions.SlideFromLeft(_content);returnViewTransitions.Parallel(fade,slide);}protectedoverrideIEnumeratorOnExitTransition(){varfade=ViewTransitions.FadeOut(this);varslide=ViewTransitions.SlideToRight(_content);returnViewTransitions.Parallel(fade,slide);}- Sorting Order: Each view in the stack is assigned an incrementing sorting order (plus
_orderOffset), so views higher in the stack render on top. - Raycasts: Only the top view receives raycasts. Views below the top have raycasts disabled.
- Cull Windows Behind: If a view has
cullWindowsBehindenabled, all views below it in the stack have theirCanvasdisabled entirely. Useful for fullscreen views where nothing behind them is visible. - OnBecomeForeground: Override this virtual method in your
MonoViewsubclass to react when the view returns to the top of the stack (e.g., after the view above it is popped).
A theming system for UI. Define a ColorPalette once, drive any Graphic from it with ColoredGraphic, and cascade the palette down the hierarchy through IPaletteProvider. Changes propagate live, with optional smooth transitions between colors.
Right-click in your Project window and select Create > PurrNet > PurrUI > Color Palette. The asset exposes nine slots grouped into Base (Black, White, Muted), Backgrounds (Background, Surface), Primary (Accent), and Status (Success, Warning, Danger). Each slot (except Base) also has a dedicated contrast color - the foreground used for text or content rendered on top of it.
Color Palette Scriptable Object:

The inspector includes a live mock-UI preview built from rectangles so you can see how the slots land together as you tweak them - useful for catching low-contrast pairings at a glance.
Add a ColoredGraphic next to any UI.Graphic (Image, RawImage, TMP_Text, etc.) to drive its color from the active palette.
Pick a slot via the dropdown. Toggle Contrast to use that slot's paired foreground instead (e.g. Accent with contrast on = the accent foreground color).
For multi-colored targets - components implementing IColored - the inspector lists one row per named key with its index ([0] Background, [1] Text, etc.). Each row has an enable checkbox so you opt in only to the slots you want to override; untouched slots keep whatever color the target already had.
ColoredGraphic has a Transition Duration field. Any palette change - either editing the asset or calling the runtime API - smoothly lerps from the current color to the new target. Set to 0 to snap. Edit-mode changes always snap (for a stable editor preview without needing continuous repaints).
// Single-graphic targetcoloredGraphic.SetColor(newColorInfo{enabled=true,color=ColorType.Accent});// Specific slot on an IColored targetcoloredGraphic.SetColor(1,newColorInfo{enabled=true,color=ColorType.Background,contrast=true});// Tweak the blend duration at runtimecoloredGraphic.transitionDuration=0.25f;Your own state machine (hover/pressed/etc.) can push new ColorInfos and the transition system handles the visual blend.
ColoredGraphic walks up the hierarchy via GetComponentInParent<IPaletteProvider>() to find the active palette:
ViewStackalready implementsIPaletteProvider- the palette you configure on it cascades into every view pushed onto the stack.- Drop a
PaletteProvidercomponent on any child GameObject to override the palette for that subtree (e.g., a "dark card" inside an otherwise-light scene).
The closest provider wins, so nested providers work naturally for scoped theme overrides.
A lightweight, fire-and-forget 2D audio system. It manages a small pool of AudioSource components (up to 5) on a DontDestroyOnLoad GameObject, so you never have to wire up audio sources yourself.
usingPurrNet.UI;usingUnityEngine;publicclassButtonSFX:MonoBehaviour{[SerializeField]privateAudioClip_clickSound;publicvoidOnClick(){Sounds2D.Play(newAudioSession(_clickSound));}}AudioSession is a struct that describes what to play and how. It uses a fluent builder API:
// Simple: play a clip at default volume and pitchSounds2D.Play(newAudioSession(clip));// With volume and pitchSounds2D.Play(newAudioSession(clip).WithVolume(0.8f).WithPitch(1.2f));// Random variation: adds ± randomRange to the base value each timeSounds2D.Play(newAudioSession(clip).WithVolume(0.7f,0.1f).WithPitch(1f,0.15f));// Random clip from an array (great for footsteps, impacts, etc.)AudioClip[]hitSounds={hit1,hit2,hit3};Sounds2D.Play(newAudioSession(hitSounds).WithVolume(0.9f));// Set a global master volume (0-1) that scales all soundsSounds2D.masterVolume=0.5f;