English | 中文
ViewSystem is an element-based UI management system for Unity, developed by Macaca Games. It simplifies complex UI workflows with a visual node editor, element pooling, and runtime property/event overrides.
UI development in Unity often suffers from tight coupling between layout, logic, and data — and the workflow tends to bottleneck on engineers. Designers hand off mockups, engineers rebuild them in Unity, and the gap between "design intent" and "implementation result" leads to endless back-and-forth over pixel-level details.
ViewSystem addresses this by separating concerns across roles:
- ViewElement defines what a UI element looks like and how it animates — not where it goes.
- ViewPage defines where elements are placed — configurable in the Visual Editor without code.
- Runtime Override lets designers tweak properties (text, color, sprites, layout) per page directly in the editor — no Prefab Variants, no code changes.
- Model Injection lets engineers focus purely on data flow and logic, while the visual configuration stays in the editor.
The result: designers own the visual details, engineers own the behavior and data — each working in their own domain with minimal handoff friction.
- Element-based architecture — compose UI pages from reusable ViewElements
- ViewElement pooling — automatic pool management for optimal performance
- Runtime property & event overrides — create ViewElement variants per page without prefab variants
- Node-based visual editor — design and preview UI pages directly in the editor
- Fluent API — chain page transitions with a clean, readable syntax
- Lifecycle hooks & dependency injection —
IViewElementLifeCycle,ViewElementBehaviour, and[ViewElementInject] - Safe Area support — per-page or global safe area configuration
- Breakpoint system — responsive ViewElement transforms based on named breakpoints
Option 1: OpenUPM (Recommended)
openupm add com.macacagames.viewsystemOption 2: Unity Package Manager (Git URL)
Add to your Packages/manifest.json:
{
"dependencies": {
"com.macacagames.utility": "https://github.com/MacacaGames/MacacaUtility.git",
"com.macacagames.viewsystem": "https://github.com/MacacaGames/MacacaViewSystem.git"
}
}Option 3: Git Submodule
git submodule add https://github.com/MacacaGames/MacacaViewSystem.git Assets/MacacaViewSystem
git submodule add https://github.com/MacacaGames/MacacaUtility.git Assets/MacacaUtilityThe base unit of ViewSystem. Any UI item on a page (button, icon, panel, etc.) can be a ViewElement.
A ViewElement defines how it appears and disappears, but not where it will be placed. There are 5 transition types:
| Transition | Description |
|---|---|
| Animator | Triggers Animator states (Show, Loop, Leave) |
| Canvas Group Alpha | Tweens CanvasGroup alpha for fade in/out |
| Active Switch | Toggles GameObject.activeSelf |
| ViewElement Animation | Built-in tween for transform (position, rotation, scale) and alpha |
| Custom | Fires a UnityEvent for full control |
A ViewPage composes one or more ViewElements and defines where each should be placed. Two types:
- FullPage — Only one can be displayed at a time. Switching pages automatically leaves the current FullPage and shows the next.
- OverlayPage — Displays on top of the current screen. Multiple OverlayPages can coexist. Ideal for dialogs, loading screens, etc.
Defines shared ViewElement layouts across multiple ViewPages. Each ViewPage can reference at most one ViewState. ViewElements in a ViewState persist until the ViewState itself changes.
The core singleton that manages all page transitions and ViewElement lifecycle.
Create a GameObject in your scene and attach the ViewController component. Assign your ViewSystemData asset.
Open MacacaGames > ViewSystem > Visual Editor, click Global Setting, then Generate default UI Root Object.
Create a UI object (Image, Button, etc.), attach the ViewElement component, and save as a prefab.
In the Visual Editor, right-click > Add FullPage, then add your ViewElement to the page's ViewPageItems.
public class GameStart : MonoBehaviour
{
void Awake()
{
ViewController
.FullPageChanger()
.SetPage("TestPage")
.Show();
}
}ViewSystem uses a Fluent Interface via PageChanger:
// FullPage — auto-leaves previous page
ViewController
.FullPageChanger()
.SetPage("MyPage")
.SetPageModel(someData, "hello") // inject data into ViewElements
.SetIgnoreClickProtection(false) // enable click debounce (default 0.2s)
.SetWaitPreviousPageFinish(true) // queue transition instead of cancel
.SetIgnoreTimeScale(true) // ignore Time.timeScale
.OnStart(() => Debug.Log("Start"))
.OnComplete(() => Debug.Log("Done"))
.Show();
// OverlayPage — must manually Show/Leave
ViewController
.OverlayPageChanger()
.SetPage("DialogPage")
.Show();
// Leave an OverlayPage
ViewController
.OverlayPageChanger()
.SetPage("DialogPage")
.Leave();Override any property on a ViewElement per-page — no prefab variants needed:
Set property overrides directly in the ViewSystem Editor's Override Window.
public class MyUILogic : ViewElementBehaviour
{
[OverrideProperty("Frame", typeof(Image), nameof(Image.sprite))]
[SerializeField]
Sprite someSprite;
[OverrideButtonEvent("TopRect/Button")]
void OnButtonClick(Component component)
{
Debug.Log("Clicked!");
}
}public class MyScript : MonoBehaviour
{
[SerializeField]
ViewElementOverride myOverride;
void ApplyOverride()
{
GetComponent<ViewElement>().ApplyOverrides(myOverride);
}
}Declare methods with [ViewSystemEvent] to make them available in the Override Window:
[ViewSystemEvent("MyGroup")]
public void OnConfirmClick(Component selectable)
{
// Handle click
}Implement on any MonoBehaviour attached to a ViewElement:
public class MyUI : MonoBehaviour, IViewElementLifeCycle
{
public void OnBeforeShow() { }
public void OnBeforeLeave() { }
public void OnStartShow() { /* ViewElement is now visible */ }
public void OnStartLeave() { }
public void OnChangePage(bool show) { }
public void OnChangedPage() { }
public void RefreshView() { }
}A convenience base class implementing IViewElementLifeCycle with UnityEvent inspector support:
public class MyUI : ViewElementBehaviour
{
public override void OnStartShow()
{
RefreshView();
}
}Send data to ViewElements when changing pages:
public class MyUILogic : ViewElementBehaviour
{
[ViewElementInject]
int score;
[ViewElementInject]
string playerName { get; set; }
}
// Pass data via SetPageModel
ViewController.FullPageChanger()
.SetPage("ResultPage")
.SetPageModel(99999, "Player1")
.Show();Combine with override for one-liner data binding:
[ViewElementInject]
[OverrideProperty("Text", typeof(TextMeshProUGUI), nameof(TextMeshProUGUI.text))]
string displayText;For multiple values of the same type, use ViewInjectDictionary<T>:
var data = new ViewInjectDictionary<string>();
data.TryAdd("title", "Hello");
data.TryAdd("subtitle", "World");
ViewController.FullPageChanger()
.SetPage("MyPage")
.SetPageModel(data)
.Show();IViewElementSingleton instances automatically become Shared Models, available to all pages without SetPageModel():
public class CurrencyDisplay : ViewElementBehaviour, IViewElementSingleton { }
// Inject anywhere without SetPageModel
public class ShopUI : ViewElementBehaviour
{
[ViewElementInject]
CurrencyDisplay currency; // auto-injected
}Control search scope with InjectScope:
[ViewElementInject(InjectScope.SharedOnly)]
MyClass myData;Propagates OnShow/OnLeave to child ViewElements (similar to CanvasGroup). Enable Only Manual Mode to control show/leave from script:
[SerializeField] ViewElement child;
child.OnShow(true); // manual show
child.OnLeave(false, true); // manual leave without poolingOpen via MacacaGames > ViewSystem > Visual Editor.
| Tool | Description |
|---|---|
| Edit Mode | Preview ViewPages in edit time with a temporary scene |
| Save | Save all changes and exit Edit Mode |
| Global Setting | UI Root generation, Safe Area config, breakpoints, click protection interval |
| Overlay Order | Drag-and-drop sort order for overlay pages |
| Bake to Script | Generate C# constants for ViewPage/ViewState names |
| Verifiers | Check for missing GameObjects, overrides, events, and page references |
Generate type-safe page name constants:
ViewController
.OverlayPageChanger()
.SetPage(ViewSystemScriptable.ViewPages.ConfirmDialog)
.Show();| Issue | Solution |
|---|---|
GameObject.activeSelf not applied on ViewElement |
Avoid setting activeSelf in OnBeforeShow() — the ViewElement isn't ready yet and ViewSystem may override it |
| Untitled scene in Hierarchy | Caused by entering Play Mode during Edit Mode. Delete it manually |
| Can't drag prefab into editor | Ensure the prefab has a ViewElement component |
| Lifecycle events not triggered | Ensure ViewElement component exists. For child objects, add ViewElementGroup to the parent |
| Editor shows nothing after update | Click Normalized in the toolbar to migrate save data |




