Add Figma main menu structure plugin
This commit is contained in:
parent
37b75756ff
commit
c11190d0d1
77
Tools/Figma/TH1MainMenuStructure/MAIN_MENU_LOGIC.md
Normal file
77
Tools/Figma/TH1MainMenuStructure/MAIN_MENU_LOGIC.md
Normal file
@ -0,0 +1,77 @@
|
||||
# TH1 Main Menu Logic Summary
|
||||
|
||||
## Entry
|
||||
|
||||
`UIManager.OnGameStart()` publishes `ShowUIOutsideMenu`.
|
||||
|
||||
`UIEventManagerBinder.HandleShowUIOutsideMenu()` receives it and calls:
|
||||
|
||||
`UIOutsideManager.StartNewTask(new UIOutsideTask(ViewControllerManager.UIOutsideMenuController, evt))`
|
||||
|
||||
`UIOutsideManager.StartNewTask()` closes the previous current Outside task, assigns the new task, and opens the controller.
|
||||
|
||||
`UIOutsideMenuController.OnOpen()` calls `WindowScript.SetContent(evt)` and hides the startup fallback image once.
|
||||
|
||||
## Main Menu Setup
|
||||
|
||||
`UIOutsideMenuView.SetContent()` performs the runtime setup:
|
||||
|
||||
- Randomizes `ButtonList` icons by selecting unique `GiantType` values from the implemented force/chess combinations.
|
||||
- Binds click listeners for every menu button.
|
||||
- Hides `ResumeButton` by default, then shows it only when `GameArchiveManager.Instance.HasQuickResumeArchive(NetMode.Single)` is true.
|
||||
- Sets `QQHintVersion` from `ConfigManager.Instance.VersionCfg.CurVersionInfo.FullVersion`.
|
||||
- Configures the Traditional Chinese Discord hint.
|
||||
- Plays the `Main` BGM through `AudioManager.Instance.PlayMusic("Main", 1f, 2f, true)`.
|
||||
|
||||
## Button Flow
|
||||
|
||||
| Button | Runtime action |
|
||||
| --- | --- |
|
||||
| `StartButton` | Publishes `ShowUIOutsideSelect`. |
|
||||
| `ResumeButton` | Loads the quick single-player archive, opens loading UI, calls `Main.Instance.ResumeMatch(record, preread)`, then hides menu/all outside UI. |
|
||||
| `MultiplayButton` | Publishes `ShowUIOutsideMultiplay`. |
|
||||
| `LibraryButton` | Publishes `ShowUIOutsideLibrary`. |
|
||||
| `HistoryButton` | Publishes `ShowUIOutsideHistory`. |
|
||||
| `TutorButton` | Publishes `ShowUIOutsideTutor`. |
|
||||
| `StoryButton` | Publishes `ShowUIOutsideStory`; binder opens the independent Story controller through `UIOutsideManager.OpenStory()`. |
|
||||
| `TransReportButton` | Publishes `ShowUIGlobalBugReport`. |
|
||||
| `QuestionnaireButton` | Found or runtime-created, then publishes `ShowUIOutsideQuestionnaire`. |
|
||||
| `AnnouncementButton` | Opens `AnnouncementPanel` locally. |
|
||||
| `AboutButton` | Opens `AboutPanel` locally. |
|
||||
| `SettingButton` | Opens `SettingPanel` locally. |
|
||||
| `ShutdownButton` | Opens `ShutdownPanel` locally. |
|
||||
|
||||
## Local Child Panels
|
||||
|
||||
`AnnouncementPanel`:
|
||||
|
||||
- Close by close button or blocking area.
|
||||
- Builds version tabs from `ConfigManager.Instance.VersionCfg.Versions`.
|
||||
- Shows selected version content through multilingual text.
|
||||
|
||||
`AboutPanel`:
|
||||
|
||||
- Displays current version.
|
||||
- Binds external links: Bilibili, website, X, YouTube, Discord.
|
||||
- Builds staff rows from `Table.Instance.StaffDataAssets.StaffList`.
|
||||
|
||||
`SettingPanel`:
|
||||
|
||||
- Initializes `LanguageOptionGroup` to the current language.
|
||||
- Treats ZH, TDZH, JP, EN, KR as primary language options.
|
||||
- Uses option index `5` for `MoreLanguageModule`.
|
||||
- `ManageButton` hides the local setting panel and publishes `ShowUIOutsideMod`.
|
||||
|
||||
`ShutdownPanel`:
|
||||
|
||||
- Cancel and block close the panel.
|
||||
- Confirm calls `Application.Quit()` in build or stops Play Mode in editor.
|
||||
|
||||
## Important UI Layer Behavior
|
||||
|
||||
Most Outside screens use `UIOutsideManager.StartNewTask()`, so opening another normal Outside screen closes the previous one.
|
||||
|
||||
Loading is separate from the current Outside task.
|
||||
|
||||
Story, Invited, and Wiki are independent controllers in `UIOutsideManager` and do not follow the ordinary single current-task replacement path.
|
||||
|
||||
32
Tools/Figma/TH1MainMenuStructure/README.md
Normal file
32
Tools/Figma/TH1MainMenuStructure/README.md
Normal file
@ -0,0 +1,32 @@
|
||||
# TH1 Main Menu Figma Structure Plugin
|
||||
|
||||
This is a local Figma development plugin that recreates the current TH1 main menu structure as editable Figma frames.
|
||||
|
||||
## How to run
|
||||
|
||||
1. Open Figma Desktop.
|
||||
2. Create or open a blank design file.
|
||||
3. Go to `Plugins > Development > Import plugin from manifest...`.
|
||||
4. Select:
|
||||
|
||||
`C:\TH1\TH1\Tools\Figma\TH1MainMenuStructure\manifest.json`
|
||||
|
||||
5. Run `Plugins > Development > TH1 Main Menu Structure`.
|
||||
|
||||
The plugin creates two frames:
|
||||
|
||||
- `TH1 Main Menu - Current Unity Structure`
|
||||
- `TH1 Main Menu - Logic Flow`
|
||||
|
||||
## Scope
|
||||
|
||||
The generated Figma file is a structural restoration, not a visual redesign. It uses placeholder shapes and labels instead of importing Unity texture assets.
|
||||
|
||||
The structure is based on:
|
||||
|
||||
- `Unity/Assets/Scripts/TH1_UI/View/Outside/UIOutsideMenuView.cs`
|
||||
- `Unity/Assets/Scripts/TH1_UI/Controller/Outside/UIOutsideMenuController.cs`
|
||||
- `Unity/Assets/Scripts/TH1_Core/Events/UIEventManagerBinder.cs`
|
||||
- `Unity/Assets/Scripts/TH1_UI/Core/UIOutsideManager.cs`
|
||||
- `Unity/Assets/BundleResources/Prefab/UI/Outside/UIOutsideMenu.prefab`
|
||||
|
||||
265
Tools/Figma/TH1MainMenuStructure/code.js
Normal file
265
Tools/Figma/TH1MainMenuStructure/code.js
Normal file
@ -0,0 +1,265 @@
|
||||
async function main() {
|
||||
await figma.loadFontAsync({ family: "Inter", style: "Regular" });
|
||||
await figma.loadFontAsync({ family: "Inter", style: "Medium" });
|
||||
await figma.loadFontAsync({ family: "Inter", style: "Bold" });
|
||||
|
||||
const page = figma.currentPage;
|
||||
page.name = "TH1 Main Menu Reverse Map";
|
||||
|
||||
const frames = page.children.filter((node) =>
|
||||
node.name === "TH1 Main Menu - Current Unity Structure" ||
|
||||
node.name === "TH1 Main Menu - Logic Flow"
|
||||
);
|
||||
for (const frame of frames) frame.remove();
|
||||
|
||||
const layout = createFrame(page, "TH1 Main Menu - Current Unity Structure", 0, 0, 1920, 1080, "#101319");
|
||||
const flow = createFrame(page, "TH1 Main Menu - Logic Flow", 2040, 0, 1920, 1080, "#0f1419");
|
||||
|
||||
drawMenuLayout(layout);
|
||||
drawLogicFlow(flow);
|
||||
|
||||
figma.viewport.scrollAndZoomIntoView([layout, flow]);
|
||||
figma.closePlugin("Created TH1 main menu structure and logic flow frames.");
|
||||
}
|
||||
|
||||
function drawMenuLayout(parent) {
|
||||
addRect(parent, "Background image: Background / MenuPreview", 0, 0, 1920, 1080, "#171d24", 0);
|
||||
addRect(parent, "dim overlay", 0, 0, 1920, 1080, "#000000", 0, 0.18);
|
||||
|
||||
addText(parent, "title", "TH1 Main Menu - current Unity structure", 32, 28, 820, 36, 26, "#f5f0d7", "Bold");
|
||||
addText(parent, "source note",
|
||||
"Sources: UIOutsideMenuView.cs, UIOutsideMenuController.cs, UIEventManagerBinder.cs, UIOutsideManager.cs, UIOutsideMenu.prefab",
|
||||
32, 66, 1220, 28, 14, "#aab3bd", "Regular");
|
||||
|
||||
addRect(parent, "GameLogo placeholder", 883, -21, 100, 100, "#d9c07a", 10, 0.95);
|
||||
addText(parent, "GameLogo text", "GameLogo", 871, 84, 140, 24, 13, "#d9c07a", "Medium", "CENTER");
|
||||
|
||||
addRect(parent, "Logo image placeholder", 195, 467, 1651, 390, "#2b3542", 12, 0.68, "#d9c07a");
|
||||
addText(parent, "Logo label", "Logo image node\nanchored center: x=60, y=-122, size=1651x390", 275, 586, 600, 76, 30, "#f4e8be", "Bold");
|
||||
|
||||
addRect(parent, "ButtonRow container", 620, 250, 761, 341, "#1b2430", 8, 0.82, "#6e8bb8");
|
||||
addText(parent, "ButtonRow label", "ButtonRow - six MenuButton prefab instances", 648, 264, 520, 22, 16, "#b9c8df", "Medium");
|
||||
|
||||
const buttons = [
|
||||
["StartButton", "Start / 开始", "ShowUIOutsideSelect", 650, 310],
|
||||
["MultiplayButton", "Multiplay / 多人", "ShowUIOutsideMultiplay", 1030, 310],
|
||||
["ResumeButton", "Resume / 继续", "visible only when quick resume exists", 650, 420],
|
||||
["LibraryButton", "Library / 图鉴", "ShowUIOutsideLibrary", 1030, 420],
|
||||
["HistoryButton", "History / 历史", "ShowUIOutsideHistory", 650, 530],
|
||||
["TutorButton", "Tutor / 教程", "ShowUIOutsideTutor", 1030, 530]
|
||||
];
|
||||
for (const [name, label, event, x, y] of buttons) {
|
||||
addButton(parent, name, label, event, x, y, 300, 82);
|
||||
}
|
||||
|
||||
addButton(parent, "StoryButton", "Story / 序章", "ShowUIOutsideStory", 26, 747, 322, 105, "#4d3844");
|
||||
addButton(parent, "TransReport", "Translation / bug feedback", "ShowUIGlobalBugReport", 786, 564, 338, 54, "#314658");
|
||||
addButton(parent, "Questionnaire", "Questionnaire / 问卷", "Created or found at runtime, then ShowUIOutsideQuestionnaire", 786, 628, 338, 54, "#314658");
|
||||
|
||||
addIconButton(parent, "AnnouncementButton / BoardingButton", "updates", 372, 862);
|
||||
addIconButton(parent, "AboutButton", "about", 1825, 789);
|
||||
addIconButton(parent, "SettingButton", "settings", 1825, 887);
|
||||
addIconButton(parent, "ShutdownButton", "quit", 1825, 985);
|
||||
|
||||
addPanel(parent, "SteamHint", "Early access / DEMO copy\nPrefab text: 当前是抢先体验版本", 26, 566, 391, 171);
|
||||
addPanel(parent, "QQHint / DiscordHint", "Community copy\nZH: QQ group\nTDZH: Discord URL becomes clickable", 26, 886, 392, 179);
|
||||
|
||||
const runtime = createGroupLabel(parent, "Runtime behavior", 1368, 144, 500, 384);
|
||||
const notes = [
|
||||
"SetContent() binds all button listeners each open.",
|
||||
"ButtonList icons are randomized from GiantType sprites.",
|
||||
"ResumeButton is hidden unless GameArchiveManager.HasQuickResumeArchive(NetMode.Single).",
|
||||
"AudioManager.PlayMusic(\"Main\", 1f, 2f, true) starts menu BGM.",
|
||||
"Traditional Chinese Discord hint opens external URL on click.",
|
||||
"Startup fallback image is hidden once the menu opens."
|
||||
];
|
||||
let noteY = 194;
|
||||
for (const note of notes) {
|
||||
addText(parent, "runtime note", "- " + note, 1392, noteY, 450, 34, 14, "#d8dfeb", "Regular");
|
||||
noteY += 48;
|
||||
}
|
||||
|
||||
addModal(parent, "AnnouncementPanel", "VersionButtonList + VersionContent\nOpens locally from AnnouncementButton", 1260, 650, "#293647");
|
||||
addModal(parent, "AboutPanel", "VersionLabel + external links + StaffArea\nBilibili / Website / X / YouTube / Discord", 1480, 650, "#293647");
|
||||
addModal(parent, "SettingPanel", "LanguageOptionGroup\nMoreLanguageModule + ManageButton -> UIOutsideMod", 1260, 840, "#293647");
|
||||
addModal(parent, "ShutdownPanel", "CancelButton / CheckButton / BlockButton\nConfirm calls Application.Quit()", 1480, 840, "#293647");
|
||||
|
||||
addText(parent, "implementation note",
|
||||
"This frame is a structural restoration. It intentionally uses placeholders instead of copying Unity texture assets into Figma.",
|
||||
32, 1030, 1180, 24, 14, "#9aa6b2", "Regular");
|
||||
}
|
||||
|
||||
function drawLogicFlow(parent) {
|
||||
addText(parent, "title", "TH1 Main Menu - logic flow", 32, 28, 760, 36, 26, "#f5f0d7", "Bold");
|
||||
addText(parent, "source note",
|
||||
"Main menu is an OutsideUI task. One current Outside task is replaced by StartNewTask(), while loading and some overlays are independent.",
|
||||
32, 66, 1220, 28, 14, "#aab3bd", "Regular");
|
||||
|
||||
const init = [
|
||||
["UIManager.OnGameStart()", "publishes ShowUIOutsideMenu"],
|
||||
["UIEventManagerBinder", "HandleShowUIOutsideMenu"],
|
||||
["UIOutsideManager", "StartNewTask(UIOutsideMenuController)"],
|
||||
["UIOutsideMenuController.OnOpen()", "WindowScript.SetContent(evt)"],
|
||||
["UIOutsideMenuView.SetContent()", "bind buttons, version text, Discord hint, BGM"]
|
||||
];
|
||||
|
||||
let y = 132;
|
||||
for (let i = 0; i < init.length; i++) {
|
||||
addFlowCard(parent, init[i][0], init[i][1], 72 + i * 350, y, 290, 100, "#223044");
|
||||
if (i < init.length - 1) addArrowText(parent, "->", 372 + i * 350, y + 36);
|
||||
}
|
||||
|
||||
addSectionLabel(parent, "Button event map", 72, 300);
|
||||
const events = [
|
||||
["StartButton", "ShowUIOutsideSelect", "Outside task replaced by select screen."],
|
||||
["ResumeButton", "ShowUIOutsideLoading -> Main.ResumeMatch -> HideUIOutsideAll", "Only visible when quick single-player archive exists."],
|
||||
["MultiplayButton", "ShowUIOutsideMultiplay", "Opens Steam lobby / room flow."],
|
||||
["LibraryButton", "ShowUIOutsideLibrary", "Library screen can later open Select."],
|
||||
["HistoryButton", "ShowUIOutsideHistory", "History screen can later open Select."],
|
||||
["TutorButton", "ShowUIOutsideTutor", "Tutorial selection screen."],
|
||||
["StoryButton", "ShowUIOutsideStory", "Uses UIOutsideManager.OpenStory(), independent of current task."],
|
||||
["TransReport", "ShowUIGlobalBugReport", "GlobalTop overlay, does not replace menu task."],
|
||||
["Questionnaire", "ShowUIOutsideQuestionnaire", "Runtime-created if prefab child is absent."],
|
||||
["AnnouncementButton", "AnnouncementPanel.Open()", "Local child panel, version list from VersionCfg."],
|
||||
["AboutButton", "AboutPanel.Open()", "Local child panel, external links and staff list."],
|
||||
["SettingButton", "SettingPanel.Open()", "Language options; ManageButton opens UIOutsideMod."],
|
||||
["ShutdownButton", "ShutdownPanel.Open()", "Confirm exits game or stops editor play mode."]
|
||||
];
|
||||
|
||||
let col = 0;
|
||||
let row = 0;
|
||||
for (const [button, event, note] of events) {
|
||||
const x = 72 + col * 455;
|
||||
const yy = 340 + row * 112;
|
||||
addEventCard(parent, button, event, note, x, yy);
|
||||
row++;
|
||||
if (row === 6) {
|
||||
row = 0;
|
||||
col++;
|
||||
}
|
||||
}
|
||||
|
||||
addSectionLabel(parent, "OutsideUI replacement rules", 72, 1030);
|
||||
addText(parent, "outside rules",
|
||||
"StartNewTask closes the previous Outside task before opening the new one. Loading uses a separate task. Story, Invited, and Wiki are independent controllers in UIOutsideManager.",
|
||||
350, 1028, 1300, 32, 15, "#d8dfeb", "Regular");
|
||||
}
|
||||
|
||||
function addButton(parent, name, label, event, x, y, w, h, fill) {
|
||||
const color = fill || "#34415a";
|
||||
addRect(parent, name, x, y, w, h, color, 8, 0.94, "#e2c576");
|
||||
addRect(parent, name + " icon mask", x + 14, y + 10, 64, h - 20, "#202733", 6, 1, "#6f7e92");
|
||||
addText(parent, name + " label", label, x + 92, y + 13, w - 110, 27, 18, "#fff2c3", "Bold");
|
||||
addText(parent, name + " event", event, x + 92, y + 43, w - 110, h - 47, 12, "#c2ccd9", "Regular");
|
||||
}
|
||||
|
||||
function addIconButton(parent, name, label, x, y) {
|
||||
addRect(parent, name, x, y, 64, 64, "#222c37", 32, 1, "#d2bd72");
|
||||
addText(parent, name + " glyph", label, x - 12, y + 22, 88, 18, 11, "#f5e6b6", "Medium", "CENTER");
|
||||
}
|
||||
|
||||
function addPanel(parent, name, text, x, y, w, h) {
|
||||
addRect(parent, name, x, y, w, h, "#1c2630", 8, 0.92, "#52687d");
|
||||
addText(parent, name + " title", name, x + 18, y + 16, w - 36, 24, 17, "#f4e8be", "Bold");
|
||||
addText(parent, name + " text", text, x + 18, y + 52, w - 36, h - 64, 14, "#c7d0dc", "Regular");
|
||||
}
|
||||
|
||||
function addModal(parent, name, text, x, y, fill) {
|
||||
addRect(parent, name, x, y, 180, 140, fill, 8, 0.96, "#e2c576");
|
||||
addText(parent, name + " title", name, x + 14, y + 14, 152, 22, 15, "#fff2c3", "Bold");
|
||||
addText(parent, name + " body", text, x + 14, y + 42, 152, 86, 11, "#d8dfeb", "Regular");
|
||||
}
|
||||
|
||||
function addFlowCard(parent, title, body, x, y, w, h, fill) {
|
||||
addRect(parent, title, x, y, w, h, fill, 8, 0.97, "#637a94");
|
||||
addText(parent, title + " title", title, x + 16, y + 16, w - 32, 24, 16, "#fff2c3", "Bold");
|
||||
addText(parent, title + " body", body, x + 16, y + 48, w - 32, 38, 13, "#d8dfeb", "Regular");
|
||||
}
|
||||
|
||||
function addEventCard(parent, button, event, note, x, y) {
|
||||
addRect(parent, button, x, y, 410, 92, "#1d2835", 8, 0.96, "#40576d");
|
||||
addText(parent, button + " title", button, x + 14, y + 12, 150, 22, 14, "#f5e6b6", "Bold");
|
||||
addText(parent, button + " event", event, x + 168, y + 12, 228, 26, 12, "#9fd1ff", "Medium");
|
||||
addText(parent, button + " note", note, x + 14, y + 44, 382, 34, 11, "#c7d0dc", "Regular");
|
||||
}
|
||||
|
||||
function addSectionLabel(parent, text, x, y) {
|
||||
addText(parent, text + " label", text, x, y, 260, 26, 18, "#f5e6b6", "Bold");
|
||||
addRect(parent, text + " underline", x, y + 28, 220, 2, "#d2bd72", 0, 0.9);
|
||||
}
|
||||
|
||||
function addArrowText(parent, text, x, y) {
|
||||
addText(parent, "arrow", text, x, y, 48, 24, 20, "#d2bd72", "Bold", "CENTER");
|
||||
}
|
||||
|
||||
function createGroupLabel(parent, title, x, y, w, h) {
|
||||
addRect(parent, title, x, y, w, h, "#111821", 8, 0.78, "#40576d");
|
||||
addText(parent, title + " title", title, x + 24, y + 22, w - 48, 28, 20, "#f5e6b6", "Bold");
|
||||
}
|
||||
|
||||
function createFrame(parent, name, x, y, w, h, fill) {
|
||||
const frame = figma.createFrame();
|
||||
frame.name = name;
|
||||
frame.x = x;
|
||||
frame.y = y;
|
||||
frame.resize(w, h);
|
||||
frame.fills = [paint(fill)];
|
||||
parent.appendChild(frame);
|
||||
return frame;
|
||||
}
|
||||
|
||||
function addRect(parent, name, x, y, w, h, fill, radius, opacity, stroke) {
|
||||
const rect = figma.createRectangle();
|
||||
rect.name = name;
|
||||
rect.x = x;
|
||||
rect.y = y;
|
||||
rect.resize(w, h);
|
||||
rect.cornerRadius = radius || 0;
|
||||
rect.fills = [paint(fill, opacity == null ? 1 : opacity)];
|
||||
if (stroke) {
|
||||
rect.strokes = [paint(stroke)];
|
||||
rect.strokeWeight = 1;
|
||||
}
|
||||
parent.appendChild(rect);
|
||||
return rect;
|
||||
}
|
||||
|
||||
function addText(parent, name, value, x, y, w, h, size, fill, style, align) {
|
||||
const text = figma.createText();
|
||||
text.name = name;
|
||||
text.x = x;
|
||||
text.y = y;
|
||||
text.resize(w, h);
|
||||
text.fontName = { family: "Inter", style: style || "Regular" };
|
||||
text.fontSize = size;
|
||||
text.lineHeight = { unit: "AUTO" };
|
||||
text.textAlignHorizontal = align || "LEFT";
|
||||
text.fills = [paint(fill)];
|
||||
text.characters = value;
|
||||
parent.appendChild(text);
|
||||
return text;
|
||||
}
|
||||
|
||||
function paint(hex, opacity) {
|
||||
const rgb = hexToRgb(hex);
|
||||
return {
|
||||
type: "SOLID",
|
||||
color: {
|
||||
r: rgb.r / 255,
|
||||
g: rgb.g / 255,
|
||||
b: rgb.b / 255
|
||||
},
|
||||
opacity: opacity == null ? 1 : opacity
|
||||
};
|
||||
}
|
||||
|
||||
function hexToRgb(hex) {
|
||||
const cleaned = hex.replace("#", "");
|
||||
return {
|
||||
r: parseInt(cleaned.slice(0, 2), 16),
|
||||
g: parseInt(cleaned.slice(2, 4), 16),
|
||||
b: parseInt(cleaned.slice(4, 6), 16)
|
||||
};
|
||||
}
|
||||
|
||||
main();
|
||||
7
Tools/Figma/TH1MainMenuStructure/manifest.json
Normal file
7
Tools/Figma/TH1MainMenuStructure/manifest.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "TH1 Main Menu Structure",
|
||||
"id": "th1-main-menu-structure",
|
||||
"api": "1.0.0",
|
||||
"main": "code.js",
|
||||
"editorType": ["figma"]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user