By George Jones — PlayMobile.online
Start with one concrete fix you can apply in two minutes: add a larger, invisible Image behind a visible button and enable Raycast Target on that image only. This gives you “hit slop” or a minimum touch target without changing visuals.
In practice, your menu may work with a mouse but feel unreliable with thumbs. Touch is imprecise and screens vary, so you must increase hit areas and respect reach zones. In Unity the transparent queue affects overdraw, so more invisible images can raise fill-rate and GPU cost.
Tiny runtime snippet (C#) to clamp a Button RectTransform to 96×96 px at reference resolution:
var rt = button.GetComponent();
rt.sizeDelta = new Vector2(Mathf.Max(rt.sizeDelta.x,96), Mathf.Max(rt.sizeDelta.y,96));
Validate physical sizing with your Canvas Scaler after applying this.
This guide maps cleanly: hit targets → navigation patterns → scaling/layout → safe area → input routing → feedback/animation → profiling/optimization → text/scroll/settings. You’ll balance thumb reach, draw calls, canvas rebuilds, and battery impact. I reference official Unity docs and the UI optimization guide and avoid beginner mistakes like tiny icons without labels, a single canvas that rebuilds constantly, or placing controls under notches.
Implement touch targets that actually feel tappable in Unity uGUI
Increase actual hit zones so on-screen buttons respond where users expect. Treat hit slop as a standard industry practice: a 32×32 visual often needs a larger tappable footprint to avoid misses on small screens.
Step-by-step: add a dedicated hit area child object behind your visible button
- Create a child GameObject under the Button. Add an Image component and set its alpha to 0.
- In the Inspector set Raycast Target = true on the child Image and Raycast Target = false on the visible art Image.
- Place the child behind the art in hierarchy order so it receives input without blocking neighboring elements.
Working code: expand a control’s tappable area with a custom raycast filter
Use a lightweight Graphic implementing ICanvasRaycastFilter to expand raycast by padding. Serialized padding keeps values editable per build.
using UnityEngine;
using UnityEngine.UI;
public class ExpandRaycast : Graphic, ICanvasRaycastFilter {
public Vector2 padding = new Vector2(16,16);
public override void SetMaterialDirty(){}
public override void SetVerticesDirty(){}
public bool IsRaycastLocationValid(Vector2 sp, Camera cam){
Rect r = rectTransform.rect;
r.xMin -= padding.x; r.xMax += padding.x;
r.yMin -= padding.y; r.yMax += padding.y;
return r.Contains(rectTransform.InverseTransformPoint(cam.ScreenToWorldPoint(sp)));
}
}
Minimum size enforcement and when not to use padding
Alternative: a small component that enforces a minimum RectTransform size so artists cannot shrink controls below a safe size.
using UnityEngine;
[RequireComponent(typeof(RectTransform))]
public class MinSizeEnforcer : MonoBehaviour{
public Vector2 minSize = new Vector2(96,96);
void Awake(){
var rt = (RectTransform)transform;
var s = rt.sizeDelta;
rt.sizeDelta = new Vector2(Mathf.Max(s.x,minSize.x), Mathf.Max(s.y,minSize.y));
}
}
Prefer larger RectTransforms in tight grids; padding can overlap neighbors and cause wrong-button taps. Keep hit areas non-drawing to avoid extra vertices and overdraw.
| Method | Setup Complexity | Overlap Risk | Performance Notes |
|---|---|---|---|
| Child hit area (alpha Image) | Low | Low | No extra verts if invisible Image is used |
| ExpandRaycast Graphic | Medium | Medium | Lightweight; avoid if many elements |
| MinSizeEnforcer | Low | Low | Deterministic, easiest to debug |
Pick a navigation pattern that matches your game’s pace and thumb reach
Pick a navigation pattern that keeps key options a tap or two away. Fast sessions need shallow flows; long meta-heavy sessions tolerate deeper stacks when Back is clear.
Hub-and-spoke vs. stacked screens
Hub-and-spoke sends players from a main hub into subpages and back to the hub. Stacked screens push and pop state like a stack. For Android, stacked navigation maps cleanly to the Back button. For iOS, support swipe-to-go-back on pushed screens.
Bottom vs. top placement and thumb reach
Bottom controls favor one-handed play. Top tabs look neat but cause missed taps near corners on large phones. Aim to surface high-frequency options within thumb reach.
| Pattern | Back behavior | Ergonomics | Performance |
|---|---|---|---|
| Hub-and-spoke | Explicit Return to hub | Good for deep exploration | Unload inactive screens to save CPU |
| Stacked (push/pop) | Back pops state; Android Back maps naturally | Fast exits for quick play | Keep stack shallow to avoid memory cost |
| Bottom nav / Top tabs | Direct switches, no deep stack | Bottom = easy reach; top = risk of missed taps | Cache only active panels to limit rebuilds |
Implementation note: centralize transitions in a single navigation service so Back behavior is predictable and testable. Avoid mixing patterns without defined Back semantics; that causes users to land on the wrong screen.
Set up your Canvas for consistent physical sizing across devices
Set your Canvas so buttons preserve a real-world size across phones and tablets. The goal is a consistent physical touch size, not matching pixel counts. That keeps primary controls tappable on both small and large screens.
In the Inspector use Canvas Scaler → UI Scale Mode: “Scale With Screen Size”. Pick a reference resolution that matches your most common device family. Set Match to favor width for wide layouts or height for tall layouts. This controls how the scaler blends between axes.
Quick checklist for the Canvas Scaler
- Scale Mode: Scale With Screen Size
- Reference Resolution: choose a baseline (e.g., 1080×1920)
- Match: slide toward width or height based on aspect targets
- Constant Pixel Size is a common beginner mistake—avoid it for varied devices
| Setting | Typical Value | Why it matters | Inspector Location |
|---|---|---|---|
| UI Scale Mode | Scale With Screen Size | Keeps physical element size consistent | Canvas → Canvas Scaler |
| Reference Resolution | 1080 × 1920 | Baseline for on-device checks | Canvas Scaler |
| Match | 0.5 (adjust) | Balances wide vs tall screens | Canvas Scaler |
Do a sanity check in Game view with common aspect ratios and test on-device to confirm your primary button stays finger-sized. Note: changing scale every frame in code causes Canvas rebuild churn and hurts performance. For full technical guidance see the Unity Manual pages on Canvas, Canvas Scaler, and UI Auto Layout as the primary reference.
Build layouts that survive every screen size without manual per-device tweaks
Layouts that adapt to any screen start with anchor choices, not pixel nudges. Set anchors to edges or relative corners so elements keep their intended position across aspect ratios.
Anchors first: eliminate layout drift
Check anchors and pivots when an object moves on a different screen. Use anchor presets for groups and avoid fixed pixel offsets that assume one aspect ratio.
- Set anchors to match the intended anchor edges.
- Test extreme tall and wide screens in the Game view.
- Keep pivots consistent for rotating or scaling elements.
Layout Groups and Content Size Fitter: when they help and when they hurt
Layout Groups are great for lists and settings rows. They automate spacing and order. But nested groups and Content Size Fitter can trigger frequent layout rebuilds.
Unity sorts rebuilds by hierarchy depth. Deep nesting increases CPU work. Avoid combining Content Size Fitter with a Layout Group on the same object to stop rebuild loops.
Margins and safe spacing
Reserve horizontal padding so localized text and icons don’t collide at smaller sizes. Prefer truncation rules or ellipses over shrinking fonts during runtime.
| Use case | When to use | Performance note |
|---|---|---|
| Anchors & pivots | Keep elements stable across screens | Zero runtime cost |
| Layout Group | Lists, rows, uniform spacing | Good if shallow; avoid deep nesting |
| Content Size Fitter | Dynamic content sizing | Can cause rebuild spikes if combined with Layout Group |
Respect notches and rounded corners with Safe Area handling
A single misplaced button under a notch can ruin a session; prevent that with a reliable safe-area strategy.
Safe areas protect your layout from physical cutouts, rounded corners, and OS gesture zones that steal edge taps. You must treat the root container as the gatekeeper for all high-frequency controls.
Implementation detail: Safe Area fitter behavior
Use a small component that reads Screen.safeArea each frame or on orientation events. Convert the safeRect into anchorMin and anchorMax and apply them to a root RectTransform.
On orientation change the component recalculates anchors and repositions the child elements so no important control overlaps a cutout. Keep the component lightweight and avoid per-frame layout churn unless the safe area actually changed.
Testing checklist and version notes
- Include at least one notched iPhone and one Android with a central or side cutout.
- Test a large tablet to confirm rounded corners and status regions are respected.
- Rotate portrait and landscape; verify no key button sits under a cutout or OS gesture area.
- Record device OS version differences—safe area values can vary by device and OS version.
| What it protects | How the fitter acts | Why test multiple |
|---|---|---|
| Notches & cutouts | Read Screen.safeArea, convert to anchors | Cutout placement differs by device model |
| Rounded corners & status bars | Inset root RectTransform to avoid edge taps | OS updates can change gesture areas |
| Gesture regions | Reapply on orientation/size change events | Behavior varies across device versions |
Beginner mistake callout: shipping a layout that looked fine in the Editor but hid controls under a notch is common. Add the fitter to your release checklist and include specific devices in your test pool so your users never tap dead space.
Touch input routing: make taps reliable and prevent “ghost clicks”
Make your input pipeline predictable: one active event system, sane raycasters, and guarded pointers. Keep the routing simple so taps consistently hit the intended objects.
EventSystem basics and common failures
Run a single EventSystem per scene. Multiple systems cause duplicated events, erratic selection, and double activation of a button. Missing an EventSystem leaves nothing to route pointer events.
Debugging workflow for broken input
- Search the scene for EventSystem and confirm one active component.
- Check Input Module entries (Standalone or Input System) for duplicates.
- Scan canvases for extra Graphic Raycasters on overlay objects.
Graphic Raycaster gotchas and invisible blockers
A full-screen Image with Raycast Target checked will block an entire area silently. Disable Raycast Target on non-interactive art or move the image to a lower raycast layer.
Multi-pointer rules during transitions
Ignore extra pointers while panels animate. Gate input for a short time window rather than trusting timing.
| Method | How to apply | Cost |
|---|---|---|
| Disable GraphicRaycaster | Turn off on animating canvas | Zero code, instant |
| UI input lock | Global flag checked by handlers | Minimal logic |
| Blocking overlay | Temp transparent Image with Raycast Target | Watch for invisible blockers |
Beginner mistake callout: leaving an invisible loading overlay active with raycast enabled is a frequent bug. If you follow the gating approaches above, developers avoid confused reports and reduce incident time when troubleshooting.
Button feedback that reads instantly on a small screen
Immediate feedback turns a doubtful tap into a confident action. Your goal is a clear, instant cue so users never ask “did it register?”
Pressed states: color tint, sprite swap, or animation
Color tint is cheap and consistent. It uses existing art and shader work with near-zero layout cost.
Sprite swap gives an artist-led look, but it can complicate atlases and memory if overused.
Animation is the most expressive option. Keep animations off layout properties. Animate opacity or a simple scale on a transform to avoid costly rebuilds.
- Trigger visual feedback on press-down for perceived responsiveness.
- Prefer scale or alpha changes over changing anchors or sizes.
- Throttle repeat feedback for sliders and toggles to avoid spam.
Haptics and sound timing: snappy without spam
Fire haptics and a short click sound on press-down to feel immediate. Avoid firing again on release unless the action is confirmed.
Keep sound effects short and subtle. Throttle repeated cues so rapid taps don’t produce a noisy background that annoys users.
| Type | Latency | Performance cost |
|---|---|---|
| Color Tint | Very low | Minimal |
| Sprite Swap | Low | Atlas/memory impact |
| Animation (scale/alpha) | Low–Medium | Watch rebuilds; use transform |
Animation and transitions that don’t tank mobile performance
A crisp transition can sell an action; a choppy one will annoy players and cost CPU. You need motion that reads fast and runs light. This section explains why transitions hitch and how to keep them smooth in unity projects.
Animate the right properties
A common cause of stutter is animating layout properties. Changing Layout Group settings or toggling anchors forces a layout rebuild. That marks the canvas dirty and triggers costly graphic and layout passes.
Animate opacity and position instead. Use a CanvasGroup alpha for fades and anchoredPosition for slides. These avoid recalculating layout and limit expensive work to a single component.
Isolate changes with sub-canvases and keep order stable
Put frequently animated panels on a nested Canvas so only that sub-canvas rebuilds when it changes. Unity’s sub-canvas approach isolates dirty work and reduces batch rebuilding across the background.
Avoid moving objects around the hierarchy during transitions. Reparenting or changing sibling order breaks batching and can spike draw calls. Keep sorting stable and animate transforms rather than hierarchy.
- Why menus hitch: dirty canvases force layout/graphic rebuilds and batch breaks.
- Practical budget: keep transitions under 300 ms and avoid multiple full-screen translucent layers at once.
- Tip: throttle simultaneous fades to reduce fill-rate and overdraw on the background.
| Issue | Cheap fix | Why it helps |
|---|---|---|
| Layout animation | Animate CanvasGroup alpha / anchoredPosition | Avoids layout recalculation |
| Many animated panels | Use nested Canvas per panel | Limits dirty region and rebuilds |
| Hierarchy reorders | Keep order static; animate transforms | Preserves batching and draw call order |
Follow this process and you’ll cut hiccups in your scene. Well-scoped transitions make your game feel faster with minimal CPU cost.
Mobile performance constraints that hit menus hard
Menus often fail not because of code, but because they ask too much of a phone’s GPU and battery.
Four real bottlenecks typically cause slowdowns:
- Fill-rate and overdraw from translucent layers.
- Canvas batch rebuild CPU cost when a canvas is marked “dirty”.
- Over-dirtying: frequent changes that force repeated work.
- Vertex generation, often driven by dynamic text.
Fill-rate and overdraw
Unity draws interface elements in the transparent queue, so stacked translucent backgrounds multiply pixel work. Big full-screen panels, soft shadows, and layered effects cause the GPU to redraw the same pixels many times.
On small screens and constrained GPUs, that extra work shows up as low frame rates and heat.
What “dirty” means and why it spikes CPU
When a canvas changes geometry or material the engine marks it dirty. That triggers a rebatch and can force layout and graphic passes across many elements.
Animating layout properties or reparenting during transitions makes phones do heavy CPU work in short bursts.
Text mesh hotspots
Dynamic counters, timers, or localized strings often regenerate meshes each update. Features like Best Fit can become profiling hotspots and dominate a frame.
Battery and idle-time guidance
Stop per-frame updates when screens are idle. Replace constant timers, animated gradients, and layout churn with event-driven updates or paused coroutines.
Also plan atlases and shared materials so you keep draw calls and memory pressure low. Fewer textures and consistent materials reduce batching breaks and runtime cost.
| Constraint | Cause | Quick fix |
|---|---|---|
| Fill-rate | Large translucent backgrounds, many overlays | Use opaque regions, reduce overlapping layers |
| Canvas rebuilds | Geometry/material changes on a root canvas | Isolate animating panels on sub-canvases |
| Text mesh cost | Dynamic updates, Best Fit, localization swaps | Cache meshes, avoid per-frame string changes |
| Draw call pressure | Many unique textures/materials | Use atlases and shared materials |
Profile your UI like you profile gameplay
Approach interface hitches with a repeatable profiling loop that exposes where CPU or GPU time is spent.
Start on device: reproduce the hitch, then capture a profiler trace from an IL2CPP build when possible. Next, open the Editor Profiler and check for spikes in Canvas.BuildBatch and Canvas.SendWillRenderCanvases.
Signals to watch and what they mean
- Canvas.BuildBatch spike — the renderer rebuilt draw batches; look for changing materials or masks.
- Canvas.SendWillRenderCanvases spike — a C# event or layout change is forcing work before render.
Workflows that find the guilty element
- Use the UI Profiler batch viewer to find which canvas produces many batches.
- Read the “Batch Breaking Reason” to spot texture or material switches.
- Open the Frame Debugger, step draw calls, and watch UI/Default passes to confirm overlaps causing extra draws.
Profile in order: reproduce on device, capture Editor traces, then validate with the Frame Debugger. This process saves you time and stops blind edits.
| Signal | Likely cause | Action |
|---|---|---|
| Canvas.BuildBatch | Texture/material switches or masks | Use atlases, align material order |
| Canvas.SendWillRenderCanvases | Code-driven layout or events firing | Throttle events, avoid per-frame layout |
| Many draw calls | Overdraw or ordering issues | Check overlaps, reorder hierarchy |
Reference the official “A guide to optimizing Unity UI” when you need deeper details. A common beginner mistake is deleting animations before you identify the expensive canvas or text element; profile first, change later.
Reduce draw calls in menu screens without flattening your art
You can keep rich visuals while cutting draw calls by aligning assets, order, and masking strategy. Understand how batching works in practical terms: renderers group sprites that share the same texture and material into a single draw call.
If one icon uses a different material, it breaks that batch and forces the GPU to submit another call. On constrained GPUs this adds up quickly and shows as lower frame rates or heat.
Sprite atlases to prevent batch breaks from texture switches
Pack related assets—icons, button frames, and common background items—into atlases so the renderer sees one texture. Atlases cut texture switches and keep draw calls low without flattening visuals.
Hierarchy order matters: stop one odd material from splitting everything
Group elements that share a material together in the hierarchy. If an object uses a unique component or shader, place it at the end of the group so it does not sit between batchable elements.
Masking is expensive: use RectMask2D carefully, avoid nested masks
Masks and clipping trigger additional culling and can break batching. Prefer RectMask2D over Mask when you only need rectangular clipping.
- Avoid nested masks in scroll lists and inventories; they often force extra work.
- Keep masked regions small to limit overdraw from translucent backgrounds and effects.
- Use shared materials and atlases for repeated items to further reduce draw calls.
| Issue | Cause | Quick fix |
|---|---|---|
| Texture switches | Different sprites use separate textures | Use sprite atlases |
| Hierarchy splits | Odd material sits between similar elements | Group shared-material elements together |
| Mask cost | Nested masks or large masked areas | Prefer RectMask2D, reduce nesting |
Beginner mistake: stacking semi-transparent background panels plus multiple masks. That combination multiplies overdraw and triggers extra rebuild/cull work, harming performance and battery life. Fix ordering and atlas strategy first so your screens stay rich but efficient.
Text, localization, and readability on mobile
Short, scannable labels and consistent font rules help users act fast on handheld screens.
Font size rules and the “Best Fit” trap
Define minimum font size tiers for phones and tablets so primary copy stays legible. Readability beats stylistic micro text; users scan, they do not decode.
Avoid Best Fit: it looks tempting but often triggers repeated Text_OnPopulateMesh work. That shows up in a profiler as expensive mesh regeneration and slows the app.
Short labels plus helper text
Keep labels short and use a second line of helper content when needed. This keeps rows scannable and reduces long sentences that hide options.
Plan for localization: allow flexible containers and extra horizontal padding so translated strings do not break layouts.
| Approach | Perf impact | When to use |
|---|---|---|
| Fixed font tiers | Low | Default for most screens |
| Best Fit | High (Text_OnPopulateMesh) | Only for rare, non-dynamic labels |
| Truncate + helper line | Low | Lists, settings, inventories |
Scroll views, lists, and inventories: make them touch-first
Make long lists behave predictably under a thumb: spacing, thresholds, and pooling matter more than polish. Treat every list as interactive content that must scroll smoothly and still allow reliable taps on items.
Thumb-friendly spacing and row height
Increase row height and vertical spacing so fast flicks don’t cause accidental presses. Aim for a comfortable size per row and pause animation deceleration so users can fling and stop naturally.
ScrollRect hit testing and tap vs drag
Tune the ScrollRect drag threshold and your item hit logic so input doesn’t fight scrolling. Give item buttons extra hit area or a short tap-vs-drag timeout so a quick finger move becomes a scroll, not a mis-tap.
Nested scrolling and large-list performance
Avoid nested ScrollRect components unless you implement explicit gesture routing. Nested regions often feel broken because parent and child both claim the drag.
For big inventories, use pooling or virtualization to spawn visible items only. Large content lists plus Layout Groups can cause layout rebuild spikes and slow the screen if every object regenerates meshes or sprites.
| Issue | Why it hurts | Quick fix |
|---|---|---|
| Dense rows | Accidental taps | Increase row size / spacing |
| Nested scrolls | Conflicting drag claims | Route gestures to one component |
| Full-list rebuild | Layout & mesh spikes | Pool items, update on demand |
Beginner mistake: adding dynamic text, shadows, and unique sprites to every list item, then wondering why mid-range devices drop frames. Keep per-item visuals light and centralize heavy work in a single process.
Settings menus that don’t annoy players
A calm, predictable settings flow saves your users time and reduces support requests.
Keep the screen focused. Surface common options first so players find what they need fast.
Order and priority
Put high-frequency toggles at the top: audio, graphics, controls, notifications. That ordering rule cuts search time and lowers friction.
Immediate preview vs. Apply
Use immediate preview for volume, brightness, and simple control remaps. Players get instant feedback and can undo quickly.
Use an Apply button for changes that risk breaking scale, require a restart, or alter resolution. Confirming heavy changes prevents unreadable text or forced reloads on constrained systems.
- Write settings on change for safe items like volume.
- For Apply flows, persist only when the player confirms to avoid storage churn.
- Throttle saves; do not write every frame.
| Action | When to use | Why it helps |
|---|---|---|
| Immediate preview | Audio/brightness | Fast feedback, low risk |
| Apply | Resolution/scale | Prevents bad layouts or reloads |
| Delayed persist | Bulk changes | Reduces write cycles and saves time |
Beginner mistake: hiding “restore defaults” and accessibility options deep in subpages. Keep them visible so users recover quickly and avoid complaints or refunds.
Common beginner mistakes in Unity mobile menus (and how you avoid them)
Small oversights in your scene setup cause the largest runtime headaches on devices. Below are blunt, fix-forward entries that tie each mistake to performance and user experience.
One giant Canvas that hitches during animations
Problem: animating any child marks the whole canvas dirty and spikes Canvas.BuildBatch.
Fix: split frequently animated panels onto nested canvases and keep static chrome on a separate root. This isolates rebuilds and cuts visible stutter for players and developers alike.
Overusing Shadow/Outline effects on dynamic text
Problem: Shadow and Outline run as mesh modifiers. They re-generate geometry on each text update and in lists this multiplies cost.
Fix: remove per-item effects, bake a subtle shadow into art where possible, and profile text mesh generation before shipping.
Tiny icons with no labels
Problem: small icons break discoverability and accessibility. Taps miss and players get frustrated.
Fix: add short labels, grow hit size, and ensure buttons meet a comfortable physical size on the screen.
Ignoring safe area and not testing real devices
Problem: controls end up under notches or gesture zones after release.
Fix: apply a safe-area component that converts Screen.safeArea to anchors and test on several devices and orientations.
| Mistake | Symptom | How it hurts | Quick fix |
|---|---|---|---|
| Single Canvas | Frame spikes during small animations | High CPU from rebuilds | Use sub-canvases |
| Mesh modifiers | Slow scrolling, high text cost | Repeated mesh regen | Prefer baked art, limit effects |
| Tiny icons | Poor tap accuracy | Bad UX & accessibility | Labels + larger hit area |
| Missing safe area | Hidden controls on some phones | User-blocking bugs | Apply safe-area fitter, test devices |
Conclusion
Finish by turning profiling data into a repeatable optimization loop. Start with hit targets and reliable input routing, then lock in scaling, anchors, and a safe area container before tuning visuals.
Prioritize four bottlenecks: overdraw, draw-call/batch breaks, Canvas rebuild spikes, and text-mesh churn. Watch Canvas.BuildBatch, Canvas.SendWillRenderCanvases, and the UI Profiler “Batch Breaking Reason” to find the costly element quickly.
If you do only three things: enforce finger-sized controls via Canvas Scaler + anchors, add a safe-area root, and isolate animated panels on sub-canvases. Profile on device, iterate, and verify progress as menus grow.
See the Unity Manual (Canvas / Canvas Scaler / Auto Layout) and “A guide to optimizing Unity UI” for exact settings. Test early on real devices so users see reliable performance.
