Main Menu: Difference between revisions
No edit summary |
No edit summary |
||
| Line 1: | Line 1: | ||
= Main menu = | |||
The '''main menu''' is the first interactive screen in '' | The '''main menu''' is the first interactive screen in '''PC Simulator''' after the '''Menu''' scene loads. It is driven by the <code>MainMenu</code> behaviour, which initializes localization, restores settings, coordinates child pages through <code>MenuManager</code>, and starts asynchronous scene loads behind a '''Loading''' screen. | ||
{| style="float:right; width:300px; margin:0 0 1em 1em; border-collapse:collapse; border:1px solid #a7a7a7; background:#f8f9fa; font-size:92%;" | |||
|+ style="background:#eaecf0; border:1px solid #a7a7a7; padding:6px; font-weight:bold;" | Quick facts | |||
|- | |||
| style="border:1px solid #a7a7a7; padding:6px; vertical-align:top; width:38%;" | '''Unity scene''' | |||
| style="border:1px solid #a7a7a7; padding:6px;" | <code>Menu</code> (project: <code>Assets/Scenes/Menu.unity</code>) | |||
|- | |||
| style="border:1px solid #a7a7a7; padding:6px; vertical-align:top;" | '''Core scripts''' | |||
| style="border:1px solid #a7a7a7; padding:6px;" | <code>MainMenu.cs</code>, <code>MenuManager.cs</code>, <code>FileMenu.cs</code> | |||
|- | |||
| style="border:1px solid #a7a7a7; padding:6px; vertical-align:top;" | '''Singleton''' | |||
| style="border:1px solid #a7a7a7; padding:6px;" | <code>MainMenu.Instance</code> | |||
|- | |||
| style="border:1px solid #a7a7a7; padding:6px; vertical-align:top;" | '''Save loads''' | |||
| style="border:1px solid #a7a7a7; padding:6px;" | <code>SaveManager.Loader</code> + scene <code>startRoomSceneIndex + GameData.room</code> | |||
|- | |||
| style="border:1px solid #a7a7a7; padding:6px; vertical-align:top;" | '''Examples''' | |||
| style="border:1px solid #a7a7a7; padding:6px;" | <code>StreamingAssets/Examples/*.pc</code> | |||
|} | |||
== Overview == | == Overview == | ||
The main menu | The main menu is the hub for: | ||
* | * Opening and starting games from user '''save files''' (<code>.pc</code>) in the save folder | ||
* Launching the ''' | * Launching the '''Tutorial''' scene and recording that the current tutorial revision was shown | ||
* Loading '''example''' presets from | * Loading '''example''' presets from streaming assets (e.g. <code>Classic.pc</code>, versioned examples, <code>Hidden Room.pc</code> when triggered from in-game) | ||
* | * Navigating sub-pages (options, language, about, file tools, etc.) via a stacked UI managed by <code>MenuManager</code> | ||
* | * Quitting the application where supported | ||
The | The title line alternates every 0.5 seconds between <code>PC Simulator</code> and <code>PC Simulator_</code>, with <code>PC</code> in orange using Unity UI Rich Text. | ||
== Localization == | == Localization == | ||
On | On <code>Awake</code>, <code>MainMenu</code>: | ||
# Calls | # Calls <code>Localization.CreateContent()</code> | ||
# Sets | # Sets language from <code>PlayerPrefs</code> key <code>"Language"</code>, or from <code>Application.systemLanguage</code> mapped to codes such as <code>EN</code>, <code>DE</code>, <code>ZH-CN</code>, <code>JA</code>, etc. | ||
# Subscribes to | # Subscribes to <code>Localization.LanguageChanged</code> to write the active language back to <code>PlayerPrefs</code> | ||
== Settings restored at menu start == | == Settings restored at menu start == | ||
<code>Start</code> restores: | |||
* '''FPS''' | * '''FPS''' — <code>FpsSetting.RestoreSetting()</code> | ||
* '''Resolution''' | * '''Resolution''' — <code>ResolutionSetting.RestoreSetting()</code> | ||
* ''' | * '''Volume''' — <code>AudioListener.volume</code> from <code>PlayerPrefs "Volume"</code> (default <code>1f</code>) | ||
== Tutorial prompt == | == Tutorial prompt == | ||
If | If <code>PlayerPrefs.GetInt("TutorialVersion", -1)</code> is less than the inspector field <code>tutorialVersion</code>, the object <code>guideToTutorial</code> is enabled; otherwise it is hidden. Calling <code>Tutorial()</code> sets <code>TutorialVersion</code> to the current value and loads the scene named <code>"Tutorial"</code>. | ||
== Navigation stack == | == Navigation stack (<code>MenuManager</code>) == | ||
<code>MenuManager</code> keeps a stack of active menu <code>GameObject</code> pages: | |||
* | * <code>ShowMenu(string pageName)</code> — finds a child whose '''hierarchy name''' equals <code>pageName</code> (e.g. <code>Loading</code>, <code>FileInformation</code>), hides the previous top if different, pushes and shows the new page | ||
* | * <code>Back()</code> — pops one level if the stack has more than one entry, reactivates the previous page | ||
* | * <code>HideMenu(bool hide)</code> — toggles visibility of the top page without changing the stack | ||
Optional UI click sounds can play on <code>ShowMenu</code> and <code>Back</code> when enabled in the inspector. | |||
Scene loads always call <code>ShowMenu("Loading")</code> first so the loading panel is visible during the async operation. | |||
== Loading screen behaviour == | == Loading screen behaviour == | ||
Loads use <code>SceneManager.LoadSceneAsync</code> with <code>allowSceneActivation == false</code> until progress reaches <code>0.9f</code>. | |||
: <code>Mathf.Clamp01(operation.progress / 0.9f) * 100f</code> | * Displayed percent: <code>Mathf.Clamp01(operation.progress / 0.9f) * 100f</code> | ||
* When <code>progress >= 0.9f</code>, the text shows 100%, waits '''0.5''' seconds, then sets <code>allowSceneActivation = true</code> | |||
When progress | * Label text: localized <code>"Loading..."</code> plus a new line and <code>[percentage%]</code> | ||
== Save files and examples == | == Save files and examples == | ||
=== User saves === | === User saves (<code>FileMenu</code>) === | ||
* Scans the save directory for files matching the save extension | |||
* Sorts by '''last write time''', newest first | |||
* Each slot shows <code>GameData.roomName</code> and a '''Hardcore''' marker when <code>GameData.hardcore</code> is true | |||
* Clicking the name runs <code>MainMenu.Instance.LoadFile(loader)</code>, which sets <code>SaveManager.Loader</code> and loads build index <code>startRoomSceneIndex + GameData.room</code> | |||
=== Example presets === | === Example presets === | ||
<code>LoadExample(string name)</code> reads <code>StreamingAssets/Examples/</code> + name + <code>.pc</code>. On Android and WebGL the file is read via <code>UnityWebRequest</code>; elsewhere via <code>File.ReadAllText</code>. The string is parsed with <code>DataLoader.LoadFromString</code>, then the same <code>LoadFile</code> path as a normal save. | |||
=== Scene index convention === | |||
<code>LoadScene(int sceneBuildIndex)</code> loads <code>startRoomSceneIndex + sceneBuildIndex</code>. Saves store a small integer <code>room</code>; the inspector base index aligns that id with the correct Unity scene in build order. | |||
=== Exit === | |||
<code>Exit()</code> calls <code>Application.Quit()</code> (no visible effect in the Unity Editor). | |||
== Implementation reference == | |||
{| style="border-collapse:collapse; width:100%; border:1px solid #a7a7a7;" | |||
|+ style="caption-side:top; text-align:left; font-weight:bold; padding:4px 0;" | Components | |||
|- style="background:#eaecf0;" | |||
! style="border:1px solid #a7a7a7; padding:8px; text-align:left; width:22%;" | Script | |||
! style="border:1px solid #a7a7a7; padding:8px; text-align:left;" | Role | |||
|- | |||
| style="border:1px solid #a7a7a7; padding:8px; vertical-align:top;" | <code>MainMenu</code> | |||
| style="border:1px solid #a7a7a7; padding:8px;" | Singleton; localization; tutorial gating; title blink (<code>InvokeRepeating</code> every 0.5 s); example and scene loading; quit | |||
|- | |||
| style="border:1px solid #a7a7a7; padding:8px; vertical-align:top;" | <code>MenuManager</code> | |||
| style="border:1px solid #a7a7a7; padding:8px;" | Stack of menu pages; show, back, hide; optional click sound | |||
|- | |||
| style="border:1px solid #a7a7a7; padding:8px; vertical-align:top;" | <code>FileMenu</code> | |||
| style="border:1px solid #a7a7a7; padding:8px;" | Lists saves, rename/metadata UI, delete, import external <code>.pc</code> | |||
|} | |||
== In-depth: source code == | |||
=== <code>MainMenu.cs</code> — lifecycle === | |||
==== <code>Awake</code> ==== | |||
* Sets <code>MainMenu.Instance</code> so other scripts (e.g. <code>FileMenu</code>, <code>HiddenRoom</code>) can invoke <code>LoadFile</code> / <code>LoadExample</code> without serialized references. | |||
* Runs localization setup and applies stored or system-default language. | |||
* Persists language changes through <code>Localization.LanguageChanged</code>. | |||
==== <code>Start</code> ==== | |||
* Restores FPS and resolution from saved settings. | |||
* Shows or hides the tutorial guide based on <code>TutorialVersion</code> vs <code>tutorialVersion</code>. | |||
* Applies master volume to <code>AudioListener</code>. | |||
* Starts the repeating title animation. | |||
==== <code>Blinking</code> ==== | |||
Toggles between two Rich Text strings on the title <code>Text</code> component for a blinking underscore effect. | |||
==== <code>LoadExample(string name)</code> ==== | |||
Resolves <code>StreamingAssets/Examples/{name}.pc</code>, reads file bytes as text (platform-specific), then <code>DataLoader.LoadFromString</code> and <code>LoadFile</code>. On Android/WebGL the implementation uses <code>UnityWebRequest</code> and waits synchronously for completion. | |||
==== <code>LoadFile(DataLoader loader)</code> ==== | |||
= | Assigns <code>SaveManager.Loader = loader</code> and loads <code>startRoomSceneIndex + loader.GameData.room</code>. | ||
==== <code>Tutorial()</code> ==== | |||
Writes <code>PlayerPrefs "TutorialVersion"</code> and loads the <code>"Tutorial"</code> scene by name via <code>LoadScene(string)</code>. | |||
{| | ==== <code>LoadAsync</code> coroutine ==== | ||
! | |||
# Shows the <code>Loading</code> menu page by name. | |||
# Holds <code>allowSceneActivation</code> false while updating <code>loadingText</code> from async <code>progress</code>. | |||
# After <code>0.9f</code> progress, shows 100%, waits 0.5 s, then allows activation. | |||
=== <code>MenuManager.cs</code> === | |||
==== <code>Start</code> ==== | |||
Initializes the stack: whichever entry in the <code>menus</code> array is already active is pushed as the initial page. | |||
==== <code>ShowMenu(string pageName)</code> ==== | |||
Linear search for <code>menu.name == pageName</code>. If the stack top is a different object, deactivate it, push the new page, activate it. Same reference as current top results in no duplicate push. | |||
==== <code>Back</code> ==== | |||
If <code>menuStack.Count <= 1</code>, return. Otherwise pop, deactivate popped page, activate new top. | |||
==== <code>HideMenu(bool hide)</code> ==== | |||
Sets <code>SetActive(!hide)</code> on the stack top without popping. | |||
=== <code>FileMenu.cs</code> === | |||
==== <code>Start</code> ==== | |||
Enumerates save files, maps path → last write time, sorts descending, calls <code>AddSlot</code> for each. Shows <code>empty</code> when the list is empty. | |||
==== <code>AddSlot(string path)</code> ==== | |||
Wrapped in try/catch; failures are skipped. Instantiates a row, binds <code>roomName</code> and hardcore flag, wires '''Edit''' to file information and '''Name''' to <code>MainMenu.Instance.LoadFile</code>. | |||
==== <code>RefreshLoadButton</code> / <code>DeleteLoadButton</code> ==== | |||
Refresh updates labels after metadata changes. Delete removes the file, destroys the row, updates empty state. | |||
==== <code>Import</code> ==== | |||
Uses native file picker filtered to <code>.pc</code>. Verifies read permission, validates via <code>DataLoader.LoadFromPath</code>, copies into the save folder with <code>SaveUtility.GetNewPath</code>, then clears and rebuilds the slot list from disk. | |||
=== Data flow summary === | |||
{| style="border-collapse:collapse; width:100%; border:1px solid #a7a7a7;" | |||
|- style="background:#eaecf0;" | |||
! style="border:1px solid #a7a7a7; padding:8px; text-align:left; width:28%;" | Action | |||
! style="border:1px solid #a7a7a7; padding:8px; text-align:left;" | Code path | |||
|- | |||
| style="border:1px solid #a7a7a7; padding:8px; vertical-align:top;" | Player starts a save from the list | |||
| style="border:1px solid #a7a7a7; padding:8px;" | <code>MainMenu.LoadFile</code> → <code>SaveManager.Loader</code> → async load <code>startRoomSceneIndex + room</code> | |||
|- | |- | ||
| | | style="border:1px solid #a7a7a7; padding:8px; vertical-align:top;" | Example or secret loads a preset | ||
| style="border:1px solid #a7a7a7; padding:8px;" | <code>LoadExample</code> reads <code>StreamingAssets/Examples/<name>.pc</code> → <code>LoadFile</code> | |||
|- | |- | ||
| | | style="border:1px solid #a7a7a7; padding:8px; vertical-align:top;" | Any scene load from menu | ||
| style="border:1px solid #a7a7a7; padding:8px;" | <code>MenuManager.ShowMenu("Loading")</code> then <code>LoadAsync</code> | |||
|- | |- | ||
| | | style="border:1px solid #a7a7a7; padding:8px; vertical-align:top;" | Player confirms tutorial | ||
| style="border:1px solid #a7a7a7; padding:8px;" | <code>Tutorial()</code> updates <code>TutorialVersion</code> → load <code>"Tutorial"</code> | |||
|} | |} | ||
== See also == | == See also == | ||
* | * Tutorial scene and <code>Tutorial.cs</code> in-game steps | ||
* Pause menu and in-game <code>MenuManager</code> usage | |||
* | * Save format: <code>DataLoader</code>, <code>GameData</code>, <code>SaveUtility</code> | ||
* | |||
<!-- Optional: add wiki links when your wiki has pages, e.g. [[Tutorial]] --> | |||
[[ | |||
Revision as of 18:11, 22 March 2026
The main menu is the first interactive screen in PC Simulator after the Menu scene loads. It is driven by the MainMenu behaviour, which initializes localization, restores settings, coordinates child pages through MenuManager, and starts asynchronous scene loads behind a Loading screen.
| Unity scene | Menu (project: Assets/Scenes/Menu.unity)
|
| Core scripts | MainMenu.cs, MenuManager.cs, FileMenu.cs
|
| Singleton | MainMenu.Instance
|
| Save loads | SaveManager.Loader + scene startRoomSceneIndex + GameData.room
|
| Examples | StreamingAssets/Examples/*.pc
|
Overview
The main menu is the hub for:
- Opening and starting games from user save files (
.pc) in the save folder - Launching the Tutorial scene and recording that the current tutorial revision was shown
- Loading example presets from streaming assets (e.g.
Classic.pc, versioned examples,Hidden Room.pcwhen triggered from in-game) - Navigating sub-pages (options, language, about, file tools, etc.) via a stacked UI managed by
MenuManager - Quitting the application where supported
The title line alternates every 0.5 seconds between PC Simulator and PC Simulator_, with PC in orange using Unity UI Rich Text.
Localization
On Awake, MainMenu:
- Calls
Localization.CreateContent() - Sets language from
PlayerPrefskey"Language", or fromApplication.systemLanguagemapped to codes such asEN,DE,ZH-CN,JA, etc. - Subscribes to
Localization.LanguageChangedto write the active language back toPlayerPrefs
Start restores:
- FPS —
FpsSetting.RestoreSetting() - Resolution —
ResolutionSetting.RestoreSetting() - Volume —
AudioListener.volumefromPlayerPrefs "Volume"(default1f)
Tutorial prompt
If PlayerPrefs.GetInt("TutorialVersion", -1) is less than the inspector field tutorialVersion, the object guideToTutorial is enabled; otherwise it is hidden. Calling Tutorial() sets TutorialVersion to the current value and loads the scene named "Tutorial".
MenuManager keeps a stack of active menu GameObject pages:
ShowMenu(string pageName)— finds a child whose hierarchy name equalspageName(e.g.Loading,FileInformation), hides the previous top if different, pushes and shows the new pageBack()— pops one level if the stack has more than one entry, reactivates the previous pageHideMenu(bool hide)— toggles visibility of the top page without changing the stack
Optional UI click sounds can play on ShowMenu and Back when enabled in the inspector.
Scene loads always call ShowMenu("Loading") first so the loading panel is visible during the async operation.
Loading screen behaviour
Loads use SceneManager.LoadSceneAsync with allowSceneActivation == false until progress reaches 0.9f.
- Displayed percent:
Mathf.Clamp01(operation.progress / 0.9f) * 100f - When
progress >= 0.9f, the text shows 100%, waits 0.5 seconds, then setsallowSceneActivation = true - Label text: localized
"Loading..."plus a new line and[percentage%]
Save files and examples
User saves (FileMenu)
- Scans the save directory for files matching the save extension
- Sorts by last write time, newest first
- Each slot shows
GameData.roomNameand a Hardcore marker whenGameData.hardcoreis true - Clicking the name runs
MainMenu.Instance.LoadFile(loader), which setsSaveManager.Loaderand loads build indexstartRoomSceneIndex + GameData.room
Example presets
LoadExample(string name) reads StreamingAssets/Examples/ + name + .pc. On Android and WebGL the file is read via UnityWebRequest; elsewhere via File.ReadAllText. The string is parsed with DataLoader.LoadFromString, then the same LoadFile path as a normal save.
Scene index convention
LoadScene(int sceneBuildIndex) loads startRoomSceneIndex + sceneBuildIndex. Saves store a small integer room; the inspector base index aligns that id with the correct Unity scene in build order.
Exit
Exit() calls Application.Quit() (no visible effect in the Unity Editor).
Implementation reference
| Script | Role |
|---|---|
MainMenu
|
Singleton; localization; tutorial gating; title blink (InvokeRepeating every 0.5 s); example and scene loading; quit
|
MenuManager
|
Stack of menu pages; show, back, hide; optional click sound |
FileMenu
|
Lists saves, rename/metadata UI, delete, import external .pc
|
In-depth: source code
MainMenu.cs — lifecycle
Awake
- Sets
MainMenu.Instanceso other scripts (e.g.FileMenu,HiddenRoom) can invokeLoadFile/LoadExamplewithout serialized references. - Runs localization setup and applies stored or system-default language.
- Persists language changes through
Localization.LanguageChanged.
Start
- Restores FPS and resolution from saved settings.
- Shows or hides the tutorial guide based on
TutorialVersionvstutorialVersion. - Applies master volume to
AudioListener. - Starts the repeating title animation.
Blinking
Toggles between two Rich Text strings on the title Text component for a blinking underscore effect.
LoadExample(string name)
Resolves StreamingAssets/Examples/{name}.pc, reads file bytes as text (platform-specific), then DataLoader.LoadFromString and LoadFile. On Android/WebGL the implementation uses UnityWebRequest and waits synchronously for completion.
LoadFile(DataLoader loader)
Assigns SaveManager.Loader = loader and loads startRoomSceneIndex + loader.GameData.room.
Tutorial()
Writes PlayerPrefs "TutorialVersion" and loads the "Tutorial" scene by name via LoadScene(string).
LoadAsync coroutine
- Shows the
Loadingmenu page by name. - Holds
allowSceneActivationfalse while updatingloadingTextfrom asyncprogress. - After
0.9fprogress, shows 100%, waits 0.5 s, then allows activation.
MenuManager.cs
Start
Initializes the stack: whichever entry in the menus array is already active is pushed as the initial page.
ShowMenu(string pageName)
Linear search for menu.name == pageName. If the stack top is a different object, deactivate it, push the new page, activate it. Same reference as current top results in no duplicate push.
Back
If menuStack.Count <= 1, return. Otherwise pop, deactivate popped page, activate new top.
HideMenu(bool hide)
Sets SetActive(!hide) on the stack top without popping.
FileMenu.cs
Start
Enumerates save files, maps path → last write time, sorts descending, calls AddSlot for each. Shows empty when the list is empty.
AddSlot(string path)
Wrapped in try/catch; failures are skipped. Instantiates a row, binds roomName and hardcore flag, wires Edit to file information and Name to MainMenu.Instance.LoadFile.
RefreshLoadButton / DeleteLoadButton
Refresh updates labels after metadata changes. Delete removes the file, destroys the row, updates empty state.
Import
Uses native file picker filtered to .pc. Verifies read permission, validates via DataLoader.LoadFromPath, copies into the save folder with SaveUtility.GetNewPath, then clears and rebuilds the slot list from disk.
Data flow summary
| Action | Code path |
|---|---|
| Player starts a save from the list | MainMenu.LoadFile → SaveManager.Loader → async load startRoomSceneIndex + room
|
| Example or secret loads a preset | LoadExample reads StreamingAssets/Examples/<name>.pc → LoadFile
|
| Any scene load from menu | MenuManager.ShowMenu("Loading") then LoadAsync
|
| Player confirms tutorial | Tutorial() updates TutorialVersion → load "Tutorial"
|
See also
- Tutorial scene and
Tutorial.csin-game steps - Pause menu and in-game
MenuManagerusage - Save format:
DataLoader,GameData,SaveUtility