Skip to content

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:

  1. Refreshes the layout-to-screen scale cache from the frame size passed to BeginFrame and the configuration's aspect-ratio map. For the cache and the scale model, refer to Coordinate spaces.
  2. Updates input via the active IInput provider, then updates the per-feature Bindings.
  3. Resets per-frame mouse and hover state (the hovered panel, hovered widget ID, and hovered scroll ID).
  4. Sorts blocking (modal) panels to the end of the panel list, then clears every panel's DrawCommands list.
  5. 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 BlocksInput is true, or if AcceptsInput is 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:

  1. With TICK_DEBUGGER defined, runs DoDebugger before flushing.
  2. Flushes the draw commands of every visible user-scene panel (PanelScene.User) into the drawer.
  3. With TICK_DEBUGGER defined, flushes the visible debugger-scene panels (PanelScene.Debugger) inside a BeginDebuggerSection/EndDebuggerSection pair, and runs DoDebuggerOverlay.
  4. Pops the seed rect that BeginFrame pushed.
  5. Pops the seed clip rect, which restores the previous clip — and because the stack then empties, disables the GPU scissor.
  6. Resets the consumed-input trackers (m_lastConsumedInput takes this frame's value; m_currentConsumeInput clears).
  7. Calls ValidateStacks(), which asserts the rect, clip, panel, ID, timeline, and measure stacks are all empty.
  8. 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 ValidateStacks enforces.
  • 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.