151 lines
5.6 KiB
Markdown
151 lines
5.6 KiB
Markdown
# UI System
|
|
|
|
## Scene structure
|
|
|
|
```
|
|
RootUI (Control, fullscreen, always visible)
|
|
├── DebugMenu
|
|
├── PauseMenu
|
|
│ ├── PauseSettings
|
|
│ └── PauseMain
|
|
├── GameMenu
|
|
│ ├── GameMenuPartyTab
|
|
│ └── GameMenuItemsTab
|
|
└── VNTextbox
|
|
```
|
|
|
|
`RootUI` is a permanent child of `RootScene` and registers itself with the `UI` singleton on `_enter_tree`. Access its children via `UI.TEXTBOX`, `UI.DEBUG_MENU`, and `UI.GAME_MENU`.
|
|
|
|
## UI singleton
|
|
|
|
`UI` (autoload) is the global access point.
|
|
|
|
| Accessor | Returns | Notes |
|
|
|---|---|---|
|
|
| `UI.TEXTBOX` | `VNTextbox` | Bottom-screen dialogue box |
|
|
| `UI.DEBUG_MENU` | `DebugMenu` | Dev scene-jump overlay |
|
|
| `UI.GAME_MENU` | `GameMenu` | JRPG-style in-game menu |
|
|
| `UI.dialogueActive` | `bool` | `true` for the entire duration of a `DialogueAction`, including line transitions |
|
|
|
|
`dialogueActive` is set by `DialogueAction` — it is broader than just "textbox visible." Movement blocks on both flags: `UI.dialogueActive` prevents movement even while the textbox is briefly hidden between lines.
|
|
|
|
## VNTextbox
|
|
|
|
Bottom-anchored `PanelContainer` that reveals text character-by-character and paginates when content overflows 4 lines.
|
|
|
|
**Showing text from code:**
|
|
|
|
```gdscript
|
|
# Fire-and-forget
|
|
UI.TEXTBOX.setText("Hello world.")
|
|
|
|
# Await player dismiss — use this in cutscene callables
|
|
await UI.TEXTBOX.setTextAndWait("Hello world.")
|
|
```
|
|
|
|
**Flow:**
|
|
1. `setText` resets reveal state and sets new text; textbox becomes visible automatically
|
|
2. Player holds `interact` to speed up reveal; press again after reveal completes to advance page or close
|
|
3. `textboxClosing` signal fires when the last page is dismissed
|
|
4. `setTextAndWait` awaits that signal before returning
|
|
|
|
**Input guard:** `EntityMovement._canMove()` returns `false` while `!UI.TEXTBOX.isClosed`. Don't set text without also expecting movement to be blocked.
|
|
|
|
**Signal:** `textboxClosing` — emitted once per `setTextAndWait` call when player dismisses.
|
|
|
|
## AdvancedRichText
|
|
|
|
`RichTextLabel` subclass (`@tool`) used inside `VNTextbox`. Handles:
|
|
|
|
- Smart word-wrap (`TextServer.AUTOWRAP_WORD_SMART`)
|
|
- Pagination via `maxLines` / `startLine` exports
|
|
- Inline input icons: `[input action=interact]text[/input]` → replaced with the icon image from `res://ui/input/{action}.tres`
|
|
- Optional auto-translation via Godot's `tr()`
|
|
|
|
Supported icon actions: `interact`, `pause`, `debug`, `up`, `down`, `left`, `right`.
|
|
|
|
## ClosableMenu
|
|
|
|
Base class for any togglable panel. Extends `Control`; `isOpen` drives `visible`.
|
|
|
|
```gdscript
|
|
menu.open() # shows, emits opened
|
|
menu.close() # hides, emits closed
|
|
menu.toggle()
|
|
```
|
|
|
|
Signals: `opened`, `closed`.
|
|
|
|
All new menus that need standard show/hide behaviour should extend `ClosableMenu`.
|
|
|
|
## Game menu
|
|
|
|
JRPG-style in-game menu at `res://ui/gamemenu/`. Open with the `menu` input (**Tab** on keyboard, **Y** on controller). Blocks player movement while open via `EntityMovement._canMove()`.
|
|
|
|
**Structure:**
|
|
- Left sidebar: `ItemList` tab selector (Party, Items)
|
|
- Right panel: active tab content
|
|
|
|
| Tab | Content |
|
|
|---|---|
|
|
| Party | One card per `PartyMember` — name, status, HP/MP, ATK/DEF/SPD/MAG/LCK |
|
|
| Items | One row per `ItemStack` in `PARTY.BACKPACK` — item name + quantity |
|
|
|
|
Both tabs are populated dynamically on open; call `refresh()` on the active tab directly if data changes while the menu is already open.
|
|
|
|
**Key methods on `GameMenu`:**
|
|
|
|
| Method | Effect |
|
|
|---|---|
|
|
| `open()` | Shows menu, refreshes active tab, grabs sidebar focus |
|
|
| `close()` | Hides menu |
|
|
| `isOpen() -> bool` | Visibility state |
|
|
|
|
`ui_cancel` or `menu` closes the menu. The `menu` input opens it only when `UI.dialogueActive` is false and the textbox is closed.
|
|
|
|
To add a new tab: add a value to `GameMenu.Tab`, create a tab scene/script under `ui/gamemenu/`, instance it in `GameMenu.tscn` as a sibling of the other tabs, add an `@export` for it in `GameMenu.gd`, and add the `match` branch in `_selectTab()`.
|
|
|
|
## Pause menu
|
|
|
|
`PauseMenu` wraps `PauseMain` (item list) and `PauseSettings` (settings tabs).
|
|
|
|
| Method | Effect |
|
|
|---|---|
|
|
| `PauseMenu.open()` | Shows container, opens PauseMain |
|
|
| `PauseMenu.close()` | Hides everything |
|
|
| `PauseMenu.isOpen() -> bool` | Visibility state |
|
|
|
|
`ui_cancel` inside the pause menu: if PauseSettings is open, closes it and reopens PauseMain; otherwise closes the whole menu.
|
|
|
|
> **Stub:** `Pause.gd` (the singleton) has its logic commented out. Pause menu is not yet wired to actual game-pause state.
|
|
|
|
## Settings menu
|
|
|
|
`SettingsMenu` is a shared tabbed component instanced in both `MainMenuSettings` and `PauseSettings`. Three tabs: **Gameplay**, **Sound**, **Graphics** — currently contain placeholder labels only.
|
|
|
|
## Debug menu
|
|
|
|
`DebugMenu` (toggle via `debug` input — **F1**) provides four scene-jump buttons:
|
|
|
|
| Button | Action |
|
|
|---|---|
|
|
| Overworld | `SCENE.setScene(OVERWORLD)` |
|
|
| Battle | `SCENE.setScene(BATTLE)` |
|
|
| Cooking | `SCENE.setScene(COOKING)` |
|
|
| Initial | `SCENE.setScene(INITIAL)` |
|
|
|
|
Access via `UI.DEBUG_MENU`. Starts hidden; `isClosed` getter/setter controls visibility.
|
|
|
|
## Theme & assets
|
|
|
|
- Global theme: `res://ui/UI Theme.tres` — applied to all UI nodes
|
|
- Input icons: `res://ui/input/{action}.tres` — one file per action name
|
|
- Font: configured globally in project settings
|
|
|
|
## Adding a new menu
|
|
|
|
1. Create a scene whose root extends `ClosableMenu` (or `Control` if open/close isn't needed)
|
|
2. Add it as a child of `RootUI.tscn`
|
|
3. Export a typed reference on `RootUI.gd` and wire it in the Inspector
|
|
4. Expose via a getter on `UISingleton.gd` if other systems need access (follow the `TEXTBOX` / `DEBUG_MENU` pattern)
|