Frame lifecycle¶
Tick UI runs the whole UI once per frame. This page is the
authoritative reference for what happens, in what order, between
the BeginFrame call that opens a frame and the EndFrame call
that closes it. Read this when you need to know whether a piece
of behaviour belongs in your DoUI or in the framework, or when
input or layout looks wrong and you want to find where it's set.
Where the lifecycle is driven from¶
BaseGameUI is a MonoBehaviour. Unity's Start creates the UI
instance via CreateUI(). Unity's LateUpdate calls DoUI(m_ui),
which is your override.
BaseGameUI does not call BeginFrame or EndFrame for you. Your
DoUI body must call them itself. The minimum body has the
following shape:
protected override void DoUI(UI ui)
{
ui.BeginFrame(new Vector2Int(Screen.width, Screen.height));
// your draw code
ui.EndFrame();
}
For the rest of the setup pipeline, refer to Set up Tick UI.
What BeginFrame does¶
BeginFrame(size) runs in this order:
- Refreshes the layout-to-screen scale cache from the frame size
passed to
BeginFrameand the configuration's aspect-ratio map. For the cache and the scale model, refer to Coordinate spaces. - Updates input via the active
IInputprovider, then updates the per-featureBindings. - Resets per-frame mouse and hover state (the hovered panel, hovered widget ID, and hovered scroll ID).
- Sorts blocking (modal) panels to the end of the panel list,
then clears every panel's
DrawCommandslist. - Resolves the hovered panel with a single priority walk.
Blocking panels were sorted to the end of the list in the
previous step, so they sit "topmost". The frame then walks the
panel list once and remembers the last panel that matches.
A panel matches if
BlocksInputis true, or ifAcceptsInputis true and its rect contains the mouse. The last match wins.
For the full focus model, refer to
Input and focus.
6. If the user pressed a mouse button, walks the visible panels
with AutoCloseOnOutsideClick set and hides any whose rect
doesn't contain the mouse.
7. Clears the rect stack and seeds it with a single full-screen
LayoutRect.
8. Pushes a full-screen clip onto the clip stack via
PushClipRect.
After BeginFrame returns, the rect stack has depth 1, the clip
stack has depth 1, and the per-frame trackers are clean.
Your draw code¶
Everything between BeginFrame and EndFrame is your DoUI
body. Calls into Button, Textbox, Fill, and friends record
draw commands into the active panel's DrawCommands list and
push or pop entries on the rect, clip, ID, animation, panel, and
timeline stacks.
For the per-stack contract, refer to Stacks. For panel mechanics during the body, refer to Panels and modals.
What EndFrame does¶
EndFrame() runs in this order:
- With
TICK_DEBUGGERdefined, runsDoDebuggerbefore flushing. - Flushes the draw commands of every visible user-scene
panel (
PanelScene.User) into the drawer. - With
TICK_DEBUGGERdefined, flushes the visible debugger-scene panels (PanelScene.Debugger) inside aBeginDebuggerSection/EndDebuggerSectionpair, and runsDoDebuggerOverlay. - Pops the seed rect that
BeginFramepushed. - Pops the seed clip rect, which restores the previous clip — and because the stack then empties, disables the GPU scissor.
- Resets the consumed-input trackers (
m_lastConsumedInputtakes this frame's value;m_currentConsumeInputclears). - Calls
ValidateStacks(), which asserts the rect, clip, panel, ID, timeline, and measure stacks are all empty. - Calls
m_drawer.Frame(), which prunes cached text meshes that have been idle for more than 10 frames.
For TICK_DEBUGGER and the rest of the opt-in defines, refer to
Compile flags.
When the screen draws¶
DoUI records draw commands into per-panel lists. The actual
rasterisation happens later, when
TickRenderPass (URP) or TickBIRPInjector (Built-in) replays
the recorded commands into a real CommandBuffer bound to the
camera target.
This separation is why BeginFrame and EndFrame are a balanced
pair, and why the stacks must be balanced too. The drawer relies
on the recorded commands being sequentially consistent, not on
DoUI having access to the GPU.
For the integration paths, refer to Render pipelines.
Pitfalls¶
Forgetting to call BeginFrame or EndFrame.
: BaseGameUI does not wrap these for you. If DoUI doesn't
call them, the next frame's stacks are not reset. Pair them
at the top and bottom of your override.
Calling BeginFrame twice in one frame.
: The second call clears the rect stack and seeds a new one. The
clip stack accumulates a second seed, which ValidateStacks
asserts on at end of frame. Avoid this.
Drawing after EndFrame.
: The active draw target reverts. Fill or Button calls
afterwards write into a panel that has already been flushed,
and they appear next frame at the wrong time.
Mismatched push and pop.
: Any imbalance on the rect, clip, panel, ID, timeline, or
measure stack fires a Debug.Assert from ValidateStacks.
The assert names
the stack that leaked. Use ui.Auto() for layout to remove
the most common source of imbalance.
Additional resources¶
- Stacks: the per-stack push and pop contract that
ValidateStacksenforces. - Panels and modals: how the panel state machine and modal stack participate in each frame.
- State: what gets cleared per frame versus what persists between frames.
- Architecture: where the lifecycle code lives in the source tree.