The main menu is the first thing every player sees, and a clumsy one makes a game feel unfinished before it even starts. This guide shows how to make a main menu in Unity 6 with uGUI and TextMeshPro: a Play button that loads your game, a settings panel with volume, resolution, fullscreen and quality, proper keyboard and gamepad navigation, and a Quit button that actually works in a build. Everything is plain C# you can drop into any project.
Setting Up the Menu Scene and Canvas
Create a new scene called MainMenu and add it to File > Build Profiles (the Scene List) as index 0, with your game scene after it. Then:
- GameObject > UI > Canvas. Unity also creates an EventSystem. Keep it, because without it no button will click.
- On the Canvas Scaler, set UI Scale Mode to Scale With Screen Size, reference resolution 1920 x 1080, and Match 0.5. This keeps the layout consistent across monitors and phones.
- If your project uses the Input System, select the EventSystem and click Replace with InputSystemUIInputModule when prompted. The old Standalone Input Module doesn’t read the new Input System.
- Create two child panels:
MainPanelandSettingsPanel. Each is an empty RectTransform stretched to fill the canvas.
Inside MainPanel, add a title (TextMeshPro text) and an empty object with a Vertical Layout Group holding three buttons: Play, Settings, Quit. The layout group spaces them automatically, so you never nudge buttons by hand. uGUI is still the simplest path for a menu like this.
Play and Quit Buttons
One script on the Canvas handles the whole menu:
using UnityEngine;
using UnityEngine.SceneManagement;
public class MainMenu : MonoBehaviour
{
[SerializeField] string gameScene = "Game";
[SerializeField] GameObject mainPanel;
[SerializeField] GameObject settingsPanel;
public void Play()
{
SceneManager.LoadScene(gameScene);
}
public void Quit()
{
#if UNITY_EDITOR
UnityEditor.EditorApplication.isPlaying = false;
#else
Application.Quit();
#endif
}
}
Select each button, click + under On Click (), drag the Canvas in, and choose MainMenu.Play or MainMenu.Quit. Application.Quit does nothing in the Editor, which confuses a lot of people, hence the editor branch. On WebGL it also does nothing, and on iOS Apple’s guidelines discourage quitting programmatically, so hide the Quit button on those platforms:
[SerializeField] GameObject quitButton;
void Start()
{
#if UNITY_WEBGL || UNITY_IOS
quitButton.SetActive(false);
#endif
}
For a big game scene, a plain LoadScene freezes the menu while loading. Swap it for an async load with a progress bar, as shown in our async loading screen tutorial, or fade out first with a scene transition fade.
Switching Between Panels
Showing and hiding panels is just SetActive. Add these to MainMenu:
public void OpenSettings()
{
mainPanel.SetActive(false);
settingsPanel.SetActive(true);
}
public void CloseSettings()
{
settingsPanel.SetActive(false);
mainPanel.SetActive(true);
}
Hook Settings to OpenSettings and a Back button inside the settings panel to CloseSettings. If you want panels to fade rather than pop, put a CanvasGroup on each one and animate its alpha, setting interactable and blocksRaycasts to false while hidden. Don’t fade with alpha alone: an invisible panel with blocksRaycasts on still eats every click.
The Settings Panel: Volume, Resolution, Quality
Volume
The right way to do volume is an Audio Mixer with exposed parameters for Master, Music and SFX, converting the slider’s 0 to 1 value to decibels. Our Audio Mixer volume slider guide covers this in detail; the short version is:
using UnityEngine.Audio;
[SerializeField] AudioMixer mixer;
public void SetMusicVolume(float value) // slider 0.0001 to 1
{
mixer.SetFloat("MusicVolume", Mathf.Log10(value) * 20f);
PlayerPrefs.SetFloat("MusicVolume", value);
}
Resolution and fullscreen
Screen.resolutions lists every supported mode, often with duplicates at different refresh rates. Filter those out and fill a TMP_Dropdown:
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class DisplaySettings : MonoBehaviour
{
[SerializeField] TMP_Dropdown resolutionDropdown;
[SerializeField] TMP_Dropdown qualityDropdown;
[SerializeField] Toggle fullscreenToggle;
readonly List<Resolution> options = new();
void Start()
{
resolutionDropdown.ClearOptions();
var labels = new List<string>();
int current = 0;
foreach (var r in Screen.resolutions)
{
if (options.Exists(o => o.width == r.width && o.height == r.height)) continue;
options.Add(r);
labels.Add($"{r.width} x {r.height}");
if (r.width == Screen.width && r.height == Screen.height)
current = options.Count - 1;
}
resolutionDropdown.AddOptions(labels);
resolutionDropdown.value = current;
resolutionDropdown.RefreshShownValue();
resolutionDropdown.onValueChanged.AddListener(SetResolution);
qualityDropdown.ClearOptions();
qualityDropdown.AddOptions(new List<string>(QualitySettings.names));
qualityDropdown.value = QualitySettings.GetQualityLevel();
qualityDropdown.RefreshShownValue();
qualityDropdown.onValueChanged.AddListener(i => QualitySettings.SetQualityLevel(i, true));
fullscreenToggle.isOn = Screen.fullScreenMode != FullScreenMode.Windowed;
fullscreenToggle.onValueChanged.AddListener(SetFullscreen);
}
void SetResolution(int index)
{
var r = options[index];
Screen.SetResolution(r.width, r.height, Screen.fullScreenMode);
}
void SetFullscreen(bool on)
{
Screen.fullScreenMode = on ? FullScreenMode.FullScreenWindow : FullScreenMode.Windowed;
}
}
FullScreenWindow is borderless fullscreen, which players generally prefer because alt-tab is instant. Resolution changes don’t apply in the Editor Game view, so test this in a build. Hide the resolution row on mobile and WebGL, where it has no meaning.
Remembering settings
Unity remembers resolution and fullscreen in standalone builds on its own, but not your quality choice or mixer volumes. Save each value to PlayerPrefs when it changes and apply them in Start of the first scene. Apply mixer values in Start, not Awake: AudioMixer.SetFloat called in Awake is a known case where the value is ignored.
Keyboard and Gamepad Navigation
A menu that only works with the mouse is broken on Steam Deck and consoles. uGUI already supports navigation; you just need to give it a starting point.
- On the EventSystem, set First Selected to the Play button. Arrow keys and the left stick now move between buttons, and Enter or the south button presses them.
- When a panel opens, select its first control in code, because First Selected only applies once:
using UnityEngine.EventSystems;
[SerializeField] GameObject settingsFirst; // e.g. the music slider
[SerializeField] GameObject settingsButton;
public void OpenSettings()
{
mainPanel.SetActive(false);
settingsPanel.SetActive(true);
EventSystem.current.SetSelectedGameObject(settingsFirst);
}
public void CloseSettings()
{
settingsPanel.SetActive(false);
mainPanel.SetActive(true);
EventSystem.current.SetSelectedGameObject(settingsButton); // return focus
}
Check navigation with the Visualize button on any Selectable’s Navigation field. Automatic navigation guesses based on position and sometimes jumps somewhere odd; set Explicit on the problem elements and choose neighbours by hand. Make sure the Selected colour on your buttons is clearly visible, since that’s the only cue a gamepad player has. For a Back action on B or Escape, bind the UI map’s Cancel action and call CloseSettings from it. Our controller support guide goes further into UI navigation with pads.
One common annoyance: clicking empty space with the mouse deselects everything, leaving a gamepad player with no focus. Fix it with a tiny script that reselects the last selected object when EventSystem.current.currentSelectedGameObject becomes null.
Polish: Music, Transitions and First Impressions
- Background: a slowly moving camera over your game world, or a looping parallax, looks far better than a static image and costs almost nothing.
- Button feedback: a hover scale of 1.05 and a click sound make buttons feel physical. Use the Button’s Animation transition or a small script with
IPointerEnterHandlerandISelectHandlerso mouse and gamepad both get the effect. - Music: start the music on the first frame. A silent menu feels broken.
- Continue button: if a save exists, make “Continue” the first and default-selected button.
- Version number: put
Application.versionin a corner. It saves you endless confusion in bug reports.
📚 Want menus that feel professional?
UI and UX design for games is its own craft. A couple of the books on our list cover menu flow, feedback and onboarding better than any tutorial can.
Troubleshooting
Buttons don’t respond to clicks
Check that an EventSystem exists in the scene, that it uses the right input module for your Active Input Handling, and that no invisible Image or panel with Raycast Target is covering the buttons. A CanvasGroup with blocksRaycasts off on a parent also disables them.
Quit doesn’t close the game
Application.Quit is ignored in the Editor and in WebGL. Test it in a desktop build.
Play loads the wrong scene or throws “couldn’t be loaded”
The scene isn’t in the Build Profiles scene list, or the name string has a typo. Scene names are case-sensitive.
Resolution dropdown does nothing
Resolution changes don’t affect the Editor Game view. Build and run. On macOS and some laptops, the list may contain only a few modes; that’s the OS reporting.
Gamepad can’t reach the menu at all
Nothing is selected. Set First Selected on the EventSystem, and call SetSelectedGameObject when you open panels or return from gameplay.
FAQ
Should the main menu be its own scene?
Yes for most games. It keeps the menu lightweight and gives you a clean place to load settings before gameplay starts.
uGUI or UI Toolkit for a main menu?
Both work. uGUI is easier for world-space backgrounds, animations and gamepad navigation, while UI Toolkit suits text-heavy settings screens styled with USS.
How do I open the settings menu during gameplay?
Make the settings panel a prefab and reuse it in a pause menu. Pause with Time.timeScale = 0 and make sure UI animations use unscaled time.
Why does my menu look different on other screens?
The Canvas Scaler is set to Constant Pixel Size. Use Scale With Screen Size with a reference resolution and anchor elements properly.
Do I need TextMeshPro?
It’s strongly recommended. TMP text stays sharp at any size and is the default text in Unity 6 UI menus.
Reader questions
Asked by readers of this article and answered by AI. Behavior can differ between Unity versions, so test in yours.




Leave a Reply