Lots of little tweaks and fixes
This commit is contained in:
@@ -17,7 +17,7 @@ Never instantiate these — access only via the global handle.
|
|||||||
| `QUEST` | Quest management (stub) |
|
| `QUEST` | Quest management (stub) |
|
||||||
| `CUTSCENE` | Cutscene global (stub) |
|
| `CUTSCENE` | Cutscene global (stub) |
|
||||||
| `DialogueManager` | godot_dialogue_manager v3.10.4 — parses and steps through `.dialogue` files |
|
| `DialogueManager` | godot_dialogue_manager v3.10.4 — parses and steps through `.dialogue` files |
|
||||||
| `UI` | Root UI accessor — `UI.TEXTBOX`, `UI.DEBUG_MENU`, `UI.GAME_MENU` |
|
| `UI` | Root UI accessor — `UI.PAUSE_MENU`, `UI.QUIT_DIALOG`, `UI.MAIN_MENU_DIALOG`, `UI.BACKDROP`, `UI.DEBUG_MENU`, `UI.GAME_MENU` |
|
||||||
|
|
||||||
## Scene Graph
|
## Scene Graph
|
||||||
|
|
||||||
@@ -25,7 +25,13 @@ Never instantiate these — access only via the global handle.
|
|||||||
RootScene (Node3D)
|
RootScene (Node3D)
|
||||||
└─ overworld / battle / cooking / initial ← one shown at a time
|
└─ overworld / battle / cooking / initial ← one shown at a time
|
||||||
RootUI (Control, always visible)
|
RootUI (Control, always visible)
|
||||||
└─ VNTextbox, DebugMenu, PauseMenu, GameMenu
|
├─ DebugMenu
|
||||||
|
├─ GameMenu
|
||||||
|
├─ ChatBoxContainer (world-space dialogue textboxes)
|
||||||
|
├─ ModalBackdrop (repositions dynamically — see ui.md)
|
||||||
|
├─ PauseMenu
|
||||||
|
├─ QuitConfirmDialog
|
||||||
|
└─ MainMenuConfirmDialog
|
||||||
```
|
```
|
||||||
|
|
||||||
`RootScene` listens to `SCENE.sceneChanged` and shows/hides the correct sub-tree.
|
`RootScene` listens to `SCENE.sceneChanged` and shows/hides the correct sub-tree.
|
||||||
|
|||||||
+10
-10
@@ -118,14 +118,14 @@ Whether a line of dialogue is triggered by:
|
|||||||
|
|
||||||
### Dialogue and movement control
|
### Dialogue and movement control
|
||||||
|
|
||||||
Dialogue does **not** automatically block movement. Each dialogue sequence declares whether it pauses movement:
|
Dialogue does **not** automatically block movement or camera. Each dialogue sequence declares whether it pauses them:
|
||||||
|
|
||||||
- **Blocking** (most NPC conversations triggered by player): sets `UI.dialogueActive = true` for the duration; `EntityMovement._canMove()` returns false.
|
- **Blocking** (most NPC conversations triggered by player): sets `UI.activeConversation = true` for the duration; `EntityMovement._canMove()` returns false and `OverworldCamera._canOrbit()` returns false.
|
||||||
- **Non-blocking** (ambient chatter, background NPC conversations, timed popups): dialogue runs without setting `dialogueActive`; the player can move freely.
|
- **Non-blocking** (ambient chatter, background NPC conversations, timed popups): dialogue runs without setting `activeConversation`; the player can move and orbit the camera freely.
|
||||||
|
|
||||||
This is configured per `DialogueAction` call, not per line.
|
This is configured per `DialogueAction` call, not per line.
|
||||||
|
|
||||||
> **Implemented:** `DialogueMode.CONVERSATION` sets `UI.activeConversation = true` (blocks movement). `NARRATION` and `AMBIENT` are non-blocking. `UI.dialogueActive` is driven by `DialogueManager.dialogue_started/ended` signals and is true for any running dialogue regardless of mode.
|
> **Implemented:** `DialogueMode.CONVERSATION` sets `UI.activeConversation = true` (blocks movement and camera orbit). `NARRATION` and `AMBIENT` are non-blocking. `UI.dialogueActive` is driven by `DialogueManager.dialogue_started/ended` signals (emitted by `DialogueAction`) and is true for any running dialogue regardless of mode.
|
||||||
|
|
||||||
### Text reveal (scrolling)
|
### Text reveal (scrolling)
|
||||||
|
|
||||||
@@ -185,13 +185,13 @@ The choice textbox follows the same world-space anchor and screen-edge clamping
|
|||||||
|
|
||||||
Every dialogue sequence has a `DialogueMode` that controls movement blocking and advancement behaviour. Set it per `DialogueAction` call — not per line.
|
Every dialogue sequence has a `DialogueMode` that controls movement blocking and advancement behaviour. Set it per `DialogueAction` call — not per line.
|
||||||
|
|
||||||
| Mode | Movement | Advancement | Typical use |
|
| Mode | Movement | Camera orbit | Advancement | Typical use |
|
||||||
|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| `CONVERSATION` | Blocked | Player (Interact) | NPC interactions, cutscene dialogue |
|
| `CONVERSATION` | Blocked | Locked | Player (Interact) | NPC interactions, cutscene dialogue |
|
||||||
| `NARRATION` | Non-blocking | Player (Interact) | Item pickups, announcements the player can dismiss when ready |
|
| `NARRATION` | Non-blocking | Free | Player (Interact) | Item pickups, announcements the player can dismiss when ready |
|
||||||
| `AMBIENT` | Non-blocking | Timed (auto) | Background NPC-to-NPC chatter, timed popups |
|
| `AMBIENT` | Non-blocking | Free | Timed (auto) | Background NPC-to-NPC chatter, timed popups |
|
||||||
|
|
||||||
`UI.dialogueActive` is driven by `DialogueManager.dialogue_started` / `dialogue_ended` signals and is true whenever any dialogue is running, regardless of mode. Movement blocking is checked separately: `EntityMovement._canMove()` is false only when an active `CONVERSATION` sequence is in progress.
|
`UI.dialogueActive` is driven by `DialogueManager.dialogue_started` / `dialogue_ended` signals (emitted by `DialogueAction`) and is true whenever any dialogue is running, regardless of mode. Movement and camera blocking are checked separately via `UI.activeConversation`: `EntityMovement._canMove()` and `OverworldCamera._canOrbit()` both return false only when an active `CONVERSATION` sequence is in progress.
|
||||||
|
|
||||||
More modes can be added to this enum as new use cases arise.
|
More modes can be added to this enum as new use cases arise.
|
||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,8 @@ Camera-relative direction is derived from the active `Camera3D`'s basis — the
|
|||||||
| `centeredPitch:float` | `30.0` | Target pitch in CENTERED mode |
|
| `centeredPitch:float` | `30.0` | Target pitch in CENTERED mode |
|
||||||
| `collisionMask:int` | `1` | Physics layers the camera avoids; entities are on layer 2 and excluded by default |
|
| `collisionMask:int` | `1` | Physics layers the camera avoids; entities are on layer 2 and excluded by default |
|
||||||
|
|
||||||
|
**Input lock:** `_canOrbit()` returns false when `UI.activeConversation` is true. All manual orbit input (controller stick, right-click drag, `center_camera`) is suppressed. If right-click was held when a conversation starts, the mouse is released automatically. The camera stays at its current position and the positioning math still runs, so it remains correctly placed relative to the (non-moving) player.
|
||||||
|
|
||||||
**Mode transitions:**
|
**Mode transitions:**
|
||||||
- FREE → CENTERED: after `centeredDelay` seconds of player movement with no camera input, or immediately via `center_camera` (G / LB)
|
- FREE → CENTERED: after `centeredDelay` seconds of player movement with no camera input, or immediately via `center_camera` (G / LB)
|
||||||
- CENTERED → FREE: any manual camera input (controller stick or right-click drag)
|
- CENTERED → FREE: any manual camera input (controller stick or right-click drag)
|
||||||
|
|||||||
@@ -12,4 +12,4 @@ These exist in the codebase but have no real implementation yet. Don't assume th
|
|||||||
| `BattleItem.perform()` | Stub |
|
| `BattleItem.perform()` | Stub |
|
||||||
| `CookingScene.tscn` | Placeholder UI only |
|
| `CookingScene.tscn` | Placeholder UI only |
|
||||||
| Response branching in `DialogueAction` | Auto-selects first allowed response; no response UI exists yet |
|
| Response branching in `DialogueAction` | Auto-selects first allowed response; no response UI exists yet |
|
||||||
| `Pause.gd` | Logic commented out |
|
| `Pause.gd` | Wired — opens/closes `UI.PAUSE_MENU`; blocked on `INITIAL` scene |
|
||||||
|
|||||||
+113
-53
@@ -5,68 +5,44 @@
|
|||||||
```
|
```
|
||||||
RootUI (Control, fullscreen, always visible)
|
RootUI (Control, fullscreen, always visible)
|
||||||
├── DebugMenu
|
├── DebugMenu
|
||||||
├── PauseMenu
|
|
||||||
│ ├── PauseSettings
|
|
||||||
│ └── PauseMain
|
|
||||||
├── GameMenu
|
├── GameMenu
|
||||||
│ ├── GameMenuPartyTab
|
│ ├── GameMenuPartyTab
|
||||||
│ └── GameMenuItemsTab
|
│ └── GameMenuItemsTab
|
||||||
└── VNTextbox
|
├── ChatBoxContainer
|
||||||
|
│ └── InteractIndicator
|
||||||
|
├── ModalBackdrop ← shared backdrop; repositions dynamically
|
||||||
|
├── PauseMenu
|
||||||
|
│ ├── PauseMain
|
||||||
|
│ └── PauseSettings
|
||||||
|
├── QuitConfirmDialog
|
||||||
|
└── MainMenuConfirmDialog
|
||||||
```
|
```
|
||||||
|
|
||||||
`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`.
|
Child order matters — later siblings render on top. `ModalBackdrop` shifts its own position in the tree at runtime to sit just below whichever modal overlay is currently topmost (see [ModalBackdrop](#modalbackdrop)).
|
||||||
|
|
||||||
|
`RootUI` is a permanent child of `RootScene` and registers itself with the `UI` singleton on `_enter_tree`. Access its children through the `UI` singleton.
|
||||||
|
|
||||||
## UI singleton
|
## UI singleton
|
||||||
|
|
||||||
`UI` (autoload) is the global access point.
|
`UI` (autoload) is the global access point.
|
||||||
|
|
||||||
| Accessor | Returns | Notes |
|
| Accessor | Type | Notes |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `UI.TEXTBOX` | `VNTextbox` | Bottom-screen dialogue box |
|
|
||||||
| `UI.DEBUG_MENU` | `DebugMenu` | Dev scene-jump overlay |
|
| `UI.DEBUG_MENU` | `DebugMenu` | Dev scene-jump overlay |
|
||||||
| `UI.GAME_MENU` | `GameMenu` | JRPG-style in-game menu |
|
| `UI.GAME_MENU` | `GameMenu` | JRPG-style in-game menu |
|
||||||
| `UI.dialogueActive` | `bool` | `true` for the entire duration of a `DialogueAction`, including line transitions |
|
| `UI.PAUSE_MENU` | `PauseMenu` | Pause overlay; also pauses the scene tree |
|
||||||
|
| `UI.QUIT_DIALOG` | `QuitConfirmDialog` | "Quit to desktop?" confirm; call `.open()` to show |
|
||||||
|
| `UI.MAIN_MENU_DIALOG` | `ConfirmDialog` | "Return to main menu?" confirm; call `.open()` to show |
|
||||||
|
| `UI.BACKDROP` | `ModalBackdrop` | Shared semi-transparent backdrop; managed automatically |
|
||||||
|
| `UI.dialogueActive` | `bool` | `true` for the entire duration of a `DialogueAction` |
|
||||||
|
| `UI.activeConversation` | `bool` | `true` only during a `CONVERSATION`-mode dialogue |
|
||||||
|
| `UI.chatBoxContainer` | `Control` | Parent node for world-space dialogue textboxes |
|
||||||
|
|
||||||
`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.
|
`dialogueActive` is set by `DialogueManager` signals — it is broader than any single textbox being visible. Movement blocks on `dialogueActive` and on `UI.GAME_MENU.isOpen()`.
|
||||||
|
|
||||||
## 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
|
## ClosableMenu
|
||||||
|
|
||||||
Base class for any togglable panel. Extends `Control`; `isOpen` drives `visible`.
|
Base class for any togglable panel. Extends `Control`; the `isOpen:bool` export drives `visible`.
|
||||||
|
|
||||||
```gdscript
|
```gdscript
|
||||||
menu.open() # shows, emits opened
|
menu.open() # shows, emits opened
|
||||||
@@ -78,6 +54,67 @@ Signals: `opened`, `closed`.
|
|||||||
|
|
||||||
All new menus that need standard show/hide behaviour should extend `ClosableMenu`.
|
All new menus that need standard show/hide behaviour should extend `ClosableMenu`.
|
||||||
|
|
||||||
|
## ConfirmDialog
|
||||||
|
|
||||||
|
Reusable "Yes / No" confirmation overlay at `res://ui/component/ConfirmDialog.gd`. Extends `ClosableMenu`.
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
dialog.open() # shows, focuses No (safe default), emits opened
|
||||||
|
dialog.confirmed # signal — fires after Yes is pressed, before close
|
||||||
|
```
|
||||||
|
|
||||||
|
**Focus locking:** `focus_neighbor_top/bottom` on both buttons is wired so controller navigation cannot escape the dialog to elements behind it.
|
||||||
|
|
||||||
|
**`ui_cancel`** closes the dialog the same as pressing No.
|
||||||
|
|
||||||
|
**Subclassing:**
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
class_name MyConfirmDialog extends ConfirmDialog
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
super._ready()
|
||||||
|
confirmed.connect(func(): do_the_thing())
|
||||||
|
```
|
||||||
|
|
||||||
|
`QuitConfirmDialog` uses this pattern — it extends `ConfirmDialog` and connects `confirmed → get_tree().quit()` in its own `_ready()`.
|
||||||
|
|
||||||
|
**Adding a new confirm dialog:**
|
||||||
|
1. Create a `.tscn` with `ConfirmDialog.gd` as script; set label text in the scene
|
||||||
|
2. Add `btnYes`/`btnNo` node path exports pointing to your two buttons
|
||||||
|
3. Add the instance to `RootUI.tscn` after `PauseMenu` (so it renders on top)
|
||||||
|
4. Register it with the backdrop in `RootUI._ready()`: `modalBackdrop.register(myDialog)`
|
||||||
|
5. Expose via `RootUI.gd` export + `UISingleton.gd` accessor if other systems need it
|
||||||
|
6. Connect `myDialog.confirmed` wherever the action should fire
|
||||||
|
|
||||||
|
## ModalBackdrop
|
||||||
|
|
||||||
|
`res://ui/component/ModalBackdrop.gd` — a single fullscreen semi-transparent `ColorRect` shared across all modal overlays.
|
||||||
|
|
||||||
|
**How it works:** each registered overlay's `opened`/`closed` signals are connected. When any overlay opens, the backdrop becomes visible and `move_child()`s itself in the scene tree to sit immediately before the highest-index open overlay. When all overlays close, it hides.
|
||||||
|
|
||||||
|
**Result:** only one backdrop is ever visible, and it always sits between the game world (or lower overlays) and the frontmost open overlay — even when overlays are stacked (e.g. `QuitConfirmDialog` over `PauseMenu`).
|
||||||
|
|
||||||
|
**Registering a new overlay:**
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
# In RootUI._ready() — overlay must be a direct child of RootUI and have opened/closed signals
|
||||||
|
modalBackdrop.register(myOverlay)
|
||||||
|
```
|
||||||
|
|
||||||
|
`PauseMenu`, `QuitConfirmDialog`, and `MainMenuConfirmDialog` are all registered.
|
||||||
|
|
||||||
|
## AdvancedRichText
|
||||||
|
|
||||||
|
`RichTextLabel` subclass (`@tool`) used inside world-space dialogue textboxes. 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`.
|
||||||
|
|
||||||
## Game menu
|
## 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()`.
|
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()`.
|
||||||
@@ -107,17 +144,39 @@ To add a new tab: add a value to `GameMenu.Tab`, create a tab scene/script under
|
|||||||
|
|
||||||
## Pause menu
|
## Pause menu
|
||||||
|
|
||||||
`PauseMenu` wraps `PauseMain` (item list) and `PauseSettings` (settings tabs).
|
`PauseMenu` wraps `PauseMain` (button list) and `PauseSettings` (settings tabs). Opening it calls `get_tree().paused = true`; closing restores it.
|
||||||
|
|
||||||
| Method | Effect |
|
| Method | Effect |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `PauseMenu.open()` | Shows container, opens PauseMain |
|
| `PauseMenu.open()` | Pauses tree, shows container, opens PauseMain, emits `opened` |
|
||||||
| `PauseMenu.close()` | Hides everything |
|
| `PauseMenu.close()` | Unpauses tree, hides everything, emits `closed` |
|
||||||
| `PauseMenu.isOpen() -> bool` | Visibility state |
|
| `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.
|
**`ui_cancel` behaviour inside PauseMenu:**
|
||||||
|
- If `QuitConfirmDialog` or `MainMenuConfirmDialog` is open → ignored (the dialog handles it)
|
||||||
|
- If `PauseSettings` is open → closes settings, reopens PauseMain
|
||||||
|
- Otherwise → closes PauseMenu
|
||||||
|
|
||||||
> **Stub:** `Pause.gd` (the singleton) has its logic commented out. Pause menu is not yet wired to actual game-pause state.
|
**PauseMain buttons:**
|
||||||
|
|
||||||
|
| Button | Behaviour |
|
||||||
|
|---|---|
|
||||||
|
| Resume | Closes PauseMenu |
|
||||||
|
| Settings | Opens PauseSettings |
|
||||||
|
| Main Menu | Opens `MainMenuConfirmDialog`; on confirm → `SCENE.setScene(INITIAL)` |
|
||||||
|
| Quit Game | Opens `QuitConfirmDialog`; on confirm → `get_tree().quit()` |
|
||||||
|
|
||||||
|
When either confirm dialog cancels, focus returns to the button that opened it.
|
||||||
|
|
||||||
|
**Cannot open on main menu:** `Pause.gd` checks `SCENE.currentScene == INITIAL` and skips opening when already closed.
|
||||||
|
|
||||||
|
## Main menu
|
||||||
|
|
||||||
|
`res://ui/mainmenu/MainMenu.tscn`. Buttons: **New Game**, **Settings**, **Quit Game**.
|
||||||
|
|
||||||
|
- New Game → `SCENE.setScene(OVERWORLD)` + `OVERWORLD.mapChange(...)`
|
||||||
|
- Settings → opens the `MainMenuSettings` overlay
|
||||||
|
- Quit Game → opens `UI.QUIT_DIALOG`; on cancel, focus returns to the Quit button
|
||||||
|
|
||||||
## Settings menu
|
## Settings menu
|
||||||
|
|
||||||
@@ -134,7 +193,7 @@ To add a new tab: add a value to `GameMenu.Tab`, create a tab scene/script under
|
|||||||
| Cooking | `SCENE.setScene(COOKING)` |
|
| Cooking | `SCENE.setScene(COOKING)` |
|
||||||
| Initial | `SCENE.setScene(INITIAL)` |
|
| Initial | `SCENE.setScene(INITIAL)` |
|
||||||
|
|
||||||
Access via `UI.DEBUG_MENU`. Starts hidden; `isClosed` getter/setter controls visibility.
|
Access via `UI.DEBUG_MENU`. Starts hidden.
|
||||||
|
|
||||||
## Theme & assets
|
## Theme & assets
|
||||||
|
|
||||||
@@ -145,6 +204,7 @@ Access via `UI.DEBUG_MENU`. Starts hidden; `isClosed` getter/setter controls vis
|
|||||||
## Adding a new menu
|
## Adding a new menu
|
||||||
|
|
||||||
1. Create a scene whose root extends `ClosableMenu` (or `Control` if open/close isn't needed)
|
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`
|
2. Add it as a child of `RootUI.tscn` — position after `PauseMenu` if it should render above it
|
||||||
3. Export a typed reference on `RootUI.gd` and wire it in the Inspector
|
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)
|
4. Expose via a getter on `UISingleton.gd` if other systems need access (follow the `PAUSE_MENU` / `GAME_MENU` pattern)
|
||||||
|
5. If it needs a backdrop, register it: `modalBackdrop.register(myMenu)` in `RootUI._ready()`
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ static func dialogueCallable(params:Dictionary) -> int:
|
|||||||
else _TextboxGd.AdvancementMode.PLAYER
|
else _TextboxGd.AdvancementMode.PLAYER
|
||||||
)
|
)
|
||||||
|
|
||||||
|
DialogueManager.dialogue_started.emit(resource)
|
||||||
var line:DialogueLine = await DialogueManager.get_next_dialogue_line(resource, title, extraStates)
|
var line:DialogueLine = await DialogueManager.get_next_dialogue_line(resource, title, extraStates)
|
||||||
while line != null:
|
while line != null:
|
||||||
var entity:Entity = OVERWORLD.getEntityByDialogueName(line.character)
|
var entity:Entity = OVERWORLD.getEntityByDialogueName(line.character)
|
||||||
@@ -48,6 +49,8 @@ static func dialogueCallable(params:Dictionary) -> int:
|
|||||||
else:
|
else:
|
||||||
line = await DialogueManager.get_next_dialogue_line(resource, line.next_id, extraStates)
|
line = await DialogueManager.get_next_dialogue_line(resource, line.next_id, extraStates)
|
||||||
|
|
||||||
|
DialogueManager.dialogue_ended.emit(resource)
|
||||||
|
|
||||||
if mode == DialogueMode.CONVERSATION:
|
if mode == DialogueMode.CONVERSATION:
|
||||||
UI.activeConversation = false
|
UI.activeConversation = false
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,15 @@ var _freeTimer:float = 0.0
|
|||||||
var _mouseDelta:Vector2 = Vector2.ZERO
|
var _mouseDelta:Vector2 = Vector2.ZERO
|
||||||
var _rightMouseHeld:bool = false
|
var _rightMouseHeld:bool = false
|
||||||
|
|
||||||
|
func _canOrbit() -> bool:
|
||||||
|
return not UI.activeConversation
|
||||||
|
|
||||||
func _input(event:InputEvent) -> void:
|
func _input(event:InputEvent) -> void:
|
||||||
|
if not _canOrbit():
|
||||||
|
if _rightMouseHeld:
|
||||||
|
_rightMouseHeld = false
|
||||||
|
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||||
|
return
|
||||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT:
|
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT:
|
||||||
_rightMouseHeld = event.pressed
|
_rightMouseHeld = event.pressed
|
||||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED if _rightMouseHeld else Input.MOUSE_MODE_VISIBLE
|
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED if _rightMouseHeld else Input.MOUSE_MODE_VISIBLE
|
||||||
@@ -56,13 +64,17 @@ func _process(delta:float) -> void:
|
|||||||
var xMult:float = -1.0 if SETTINGS.invertCameraX else 1.0
|
var xMult:float = -1.0 if SETTINGS.invertCameraX else 1.0
|
||||||
var yMult:float = 1.0 if SETTINGS.invertCameraY else -1.0
|
var yMult:float = 1.0 if SETTINGS.invertCameraY else -1.0
|
||||||
|
|
||||||
var orbitInput:Vector2 = Input.get_vector(
|
var orbitInput:Vector2 = Vector2.ZERO
|
||||||
"camera_orbit_left", "camera_orbit_right",
|
var mouseActive:bool = false
|
||||||
"camera_orbit_up", "camera_orbit_down"
|
|
||||||
)
|
if _canOrbit():
|
||||||
|
orbitInput = Input.get_vector(
|
||||||
|
"camera_orbit_left", "camera_orbit_right",
|
||||||
|
"camera_orbit_up", "camera_orbit_down"
|
||||||
|
)
|
||||||
|
mouseActive = _mouseDelta.length_squared() > 0.0
|
||||||
|
|
||||||
var controllerActive:bool = orbitInput.length() > 0.01
|
var controllerActive:bool = orbitInput.length() > 0.01
|
||||||
var mouseActive:bool = _mouseDelta.length_squared() > 0.0
|
|
||||||
|
|
||||||
# Any manual camera input returns to FREE and resets the centering timer
|
# Any manual camera input returns to FREE and resets the centering timer
|
||||||
if controllerActive or mouseActive:
|
if controllerActive or mouseActive:
|
||||||
@@ -83,10 +95,10 @@ func _process(delta:float) -> void:
|
|||||||
if mouseActive:
|
if mouseActive:
|
||||||
_yaw += _mouseDelta.x * mouseSensitivity * SETTINGS.cameraSpeedMouse * xMult
|
_yaw += _mouseDelta.x * mouseSensitivity * SETTINGS.cameraSpeedMouse * xMult
|
||||||
_pitch += _mouseDelta.y * mouseSensitivity * SETTINGS.cameraSpeedMouse * yMult
|
_pitch += _mouseDelta.y * mouseSensitivity * SETTINGS.cameraSpeedMouse * yMult
|
||||||
_mouseDelta = Vector2.ZERO
|
_mouseDelta = Vector2.ZERO
|
||||||
|
|
||||||
# center_camera input → switch to MANUAL_CENTER immediately
|
# center_camera input → switch to MANUAL_CENTER immediately
|
||||||
if Input.is_action_just_pressed("center_camera"):
|
if _canOrbit() and Input.is_action_just_pressed("center_camera"):
|
||||||
_mode = CameraMode.MANUAL_CENTER
|
_mode = CameraMode.MANUAL_CENTER
|
||||||
|
|
||||||
# In FREE mode, accumulate time toward auto-centering while the player is moving
|
# In FREE mode, accumulate time toward auto-centering while the player is moving
|
||||||
|
|||||||
@@ -22,11 +22,36 @@ func _exit_tree() -> void:
|
|||||||
self.area_entered.disconnect(_onAreaEntered)
|
self.area_entered.disconnect(_onAreaEntered)
|
||||||
self.area_exited.disconnect(_onAreaExited)
|
self.area_exited.disconnect(_onAreaExited)
|
||||||
|
|
||||||
|
func _process(_delta:float) -> void:
|
||||||
|
if entity.movementType != Entity.MovementType.PLAYER:
|
||||||
|
return
|
||||||
|
if UI.INTERACT_INDICATOR and UI.INTERACT_INDICATOR.visible:
|
||||||
|
UI.INTERACT_INDICATOR.updateWorldPosition()
|
||||||
|
|
||||||
|
func _getBestInteractable() -> Entity:
|
||||||
|
for area in interactableAreas:
|
||||||
|
if area.isInteractable():
|
||||||
|
return area.entity
|
||||||
|
return null
|
||||||
|
|
||||||
|
func _updateIndicator() -> void:
|
||||||
|
if entity.movementType != Entity.MovementType.PLAYER:
|
||||||
|
return
|
||||||
|
if UI.INTERACT_INDICATOR == null:
|
||||||
|
return
|
||||||
|
var best:Entity = _getBestInteractable()
|
||||||
|
if best:
|
||||||
|
UI.INTERACT_INDICATOR.setEntity(best)
|
||||||
|
else:
|
||||||
|
UI.INTERACT_INDICATOR.clear()
|
||||||
|
|
||||||
func _onAreaEntered(area:Area3D) -> void:
|
func _onAreaEntered(area:Area3D) -> void:
|
||||||
if area is EntityInteractableArea:
|
if area is EntityInteractableArea:
|
||||||
if area.entity == entity:
|
if area.entity == entity:
|
||||||
return
|
return
|
||||||
interactableAreas.append(area)
|
interactableAreas.append(area)
|
||||||
|
_updateIndicator()
|
||||||
|
|
||||||
func _onAreaExited(area:Area3D) -> void:
|
func _onAreaExited(area:Area3D) -> void:
|
||||||
interactableAreas.erase(area)
|
interactableAreas.erase(area)
|
||||||
|
_updateIndicator()
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ ui_cancel={
|
|||||||
ui_left={
|
ui_left={
|
||||||
"deadzone": 0.5,
|
"deadzone": 0.5,
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194319,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194319,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
|
||||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":13,"pressure":0.0,"pressed":false,"script":null)
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":13,"pressure":0.0,"pressed":false,"script":null)
|
||||||
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":-1.0,"script":null)
|
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":-1.0,"script":null)
|
||||||
]
|
]
|
||||||
@@ -88,6 +89,7 @@ ui_left={
|
|||||||
ui_right={
|
ui_right={
|
||||||
"deadzone": 0.5,
|
"deadzone": 0.5,
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194321,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194321,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
|
||||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":14,"pressure":0.0,"pressed":false,"script":null)
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":14,"pressure":0.0,"pressed":false,"script":null)
|
||||||
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":1.0,"script":null)
|
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":1.0,"script":null)
|
||||||
]
|
]
|
||||||
@@ -95,6 +97,7 @@ ui_right={
|
|||||||
ui_up={
|
ui_up={
|
||||||
"deadzone": 0.5,
|
"deadzone": 0.5,
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194320,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194320,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
|
||||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":11,"pressure":0.0,"pressed":false,"script":null)
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":11,"pressure":0.0,"pressed":false,"script":null)
|
||||||
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":-1.0,"script":null)
|
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":-1.0,"script":null)
|
||||||
]
|
]
|
||||||
@@ -102,6 +105,7 @@ ui_up={
|
|||||||
ui_down={
|
ui_down={
|
||||||
"deadzone": 0.5,
|
"deadzone": 0.5,
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194322,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194322,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
|
||||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":12,"pressure":0.0,"pressed":false,"script":null)
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":12,"pressure":0.0,"pressed":false,"script":null)
|
||||||
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":1.0,"script":null)
|
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":1.0,"script":null)
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ func _unhandled_input(event:InputEvent) -> void:
|
|||||||
var menu:PauseMenu = UI.PAUSE_MENU
|
var menu:PauseMenu = UI.PAUSE_MENU
|
||||||
if menu == null:
|
if menu == null:
|
||||||
return
|
return
|
||||||
|
if SCENE.currentScene == SceneSingleton.SceneType.INITIAL and !menu.isOpen():
|
||||||
|
return
|
||||||
if menu.isOpen():
|
if menu.isOpen():
|
||||||
menu.close()
|
menu.close()
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ var invertCameraX:bool = false
|
|||||||
var invertCameraY:bool = false
|
var invertCameraY:bool = false
|
||||||
var cameraSpeedController:float = 1.0
|
var cameraSpeedController:float = 1.0
|
||||||
var cameraSpeedMouse:float = 1.0
|
var cameraSpeedMouse:float = 1.0
|
||||||
|
var textSpeed:float = 1.0
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ class_name RootUI extends Control
|
|||||||
@export var debugMenu:DebugMenu
|
@export var debugMenu:DebugMenu
|
||||||
@export var gameMenu:GameMenu
|
@export var gameMenu:GameMenu
|
||||||
@export var pauseMenu:PauseMenu
|
@export var pauseMenu:PauseMenu
|
||||||
|
@export var quitConfirmDialog:QuitConfirmDialog
|
||||||
|
@export var mainMenuConfirmDialog:ConfirmDialog
|
||||||
|
@export var modalBackdrop:ModalBackdrop
|
||||||
@export var chatBoxContainer:Control
|
@export var chatBoxContainer:Control
|
||||||
|
|
||||||
func _enter_tree() -> void:
|
func _enter_tree() -> void:
|
||||||
@@ -11,3 +14,8 @@ func _enter_tree() -> void:
|
|||||||
func _exit_tree() -> void:
|
func _exit_tree() -> void:
|
||||||
if UI.rootUi == self:
|
if UI.rootUi == self:
|
||||||
UI.rootUi = null
|
UI.rootUi = null
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
modalBackdrop.register(pauseMenu)
|
||||||
|
modalBackdrop.register(quitConfirmDialog)
|
||||||
|
modalBackdrop.register(mainMenuConfirmDialog)
|
||||||
|
|||||||
+37
-7
@@ -1,11 +1,15 @@
|
|||||||
[gd_scene load_steps=5 format=3 uid="uid://baos0arpiskbp"]
|
[gd_scene load_steps=9 format=3 uid="uid://baos0arpiskbp"]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://dq3qyyayugt5l" path="res://ui/RootUI.gd" id="1_son71"]
|
[ext_resource type="Script" uid="uid://dq3qyyayugt5l" path="res://ui/RootUI.gd" id="1_son71"]
|
||||||
[ext_resource type="PackedScene" uid="uid://c0i5e2dj11d8c" path="res://ui/pause/PauseMenu.tscn" id="2_atyu8"]
|
[ext_resource type="PackedScene" uid="uid://c0i5e2dj11d8c" path="res://ui/pause/PauseMenu.tscn" id="2_atyu8"]
|
||||||
[ext_resource type="PackedScene" uid="uid://b38dr0wkix76t" path="res://ui/debugmenu/DebugMenu.tscn" id="4_u132g"]
|
[ext_resource type="PackedScene" uid="uid://b38dr0wkix76t" path="res://ui/debugmenu/DebugMenu.tscn" id="4_u132g"]
|
||||||
[ext_resource type="PackedScene" uid="uid://bv5r2x9m4k7n1" path="res://ui/gamemenu/GameMenu.tscn" id="5_gmenu"]
|
[ext_resource type="PackedScene" uid="uid://bv5r2x9m4k7n1" path="res://ui/gamemenu/GameMenu.tscn" id="5_gmenu"]
|
||||||
|
[ext_resource type="PackedScene" path="res://ui/component/InteractIndicator.tscn" id="6_iind"]
|
||||||
|
[ext_resource type="Script" path="res://ui/component/ModalBackdrop.gd" id="7_mbdp"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://cqdf1x7m2canp" path="res://ui/component/QuitConfirmDialog.tscn" id="8_qcd"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://bmmc3x8n1d7qp" path="res://ui/component/MainMenuConfirmDialog.tscn" id="9_mmcd"]
|
||||||
|
|
||||||
[node name="RootUI" type="Control" node_paths=PackedStringArray("debugMenu", "gameMenu", "pauseMenu", "chatBoxContainer")]
|
[node name="RootUI" type="Control" node_paths=PackedStringArray("debugMenu", "gameMenu", "pauseMenu", "quitConfirmDialog", "mainMenuConfirmDialog", "modalBackdrop", "chatBoxContainer")]
|
||||||
layout_mode = 3
|
layout_mode = 3
|
||||||
anchors_preset = 15
|
anchors_preset = 15
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
@@ -17,6 +21,9 @@ script = ExtResource("1_son71")
|
|||||||
debugMenu = NodePath("DebugMenu")
|
debugMenu = NodePath("DebugMenu")
|
||||||
gameMenu = NodePath("GameMenu")
|
gameMenu = NodePath("GameMenu")
|
||||||
pauseMenu = NodePath("PauseMenu")
|
pauseMenu = NodePath("PauseMenu")
|
||||||
|
quitConfirmDialog = NodePath("QuitConfirmDialog")
|
||||||
|
mainMenuConfirmDialog = NodePath("MainMenuConfirmDialog")
|
||||||
|
modalBackdrop = NodePath("ModalBackdrop")
|
||||||
chatBoxContainer = NodePath("ChatBoxContainer")
|
chatBoxContainer = NodePath("ChatBoxContainer")
|
||||||
metadata/_custom_type_script = "uid://dq3qyyayugt5l"
|
metadata/_custom_type_script = "uid://dq3qyyayugt5l"
|
||||||
|
|
||||||
@@ -24,11 +31,6 @@ metadata/_custom_type_script = "uid://dq3qyyayugt5l"
|
|||||||
visible = false
|
visible = false
|
||||||
layout_mode = 1
|
layout_mode = 1
|
||||||
|
|
||||||
[node name="PauseMenu" parent="." instance=ExtResource("2_atyu8")]
|
|
||||||
visible = false
|
|
||||||
layout_mode = 1
|
|
||||||
process_mode = 3
|
|
||||||
|
|
||||||
[node name="GameMenu" parent="." instance=ExtResource("5_gmenu")]
|
[node name="GameMenu" parent="." instance=ExtResource("5_gmenu")]
|
||||||
visible = false
|
visible = false
|
||||||
layout_mode = 1
|
layout_mode = 1
|
||||||
@@ -41,3 +43,31 @@ anchor_bottom = 1.0
|
|||||||
grow_horizontal = 2
|
grow_horizontal = 2
|
||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="InteractIndicator" parent="ChatBoxContainer" instance=ExtResource("6_iind")]
|
||||||
|
|
||||||
|
[node name="ModalBackdrop" type="ColorRect" parent="."]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 1
|
||||||
|
process_mode = 3
|
||||||
|
color = Color(0, 0, 0, 0.5)
|
||||||
|
script = ExtResource("7_mbdp")
|
||||||
|
|
||||||
|
[node name="PauseMenu" parent="." instance=ExtResource("2_atyu8")]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 1
|
||||||
|
process_mode = 3
|
||||||
|
|
||||||
|
[node name="QuitConfirmDialog" parent="." instance=ExtResource("8_qcd")]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 1
|
||||||
|
|
||||||
|
[node name="MainMenuConfirmDialog" parent="." instance=ExtResource("9_mmcd")]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 1
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
var rootUi:RootUI = null
|
var rootUi:RootUI = null
|
||||||
|
var interactIndicator:InteractIndicator = null
|
||||||
|
|
||||||
# True whenever any dialogue resource is being processed by DialogueManager.
|
# True whenever any dialogue resource is being processed by DialogueManager.
|
||||||
# Driven by DialogueManager.dialogue_started / dialogue_ended signals.
|
# Driven by DialogueManager.dialogue_started / dialogue_ended signals.
|
||||||
@@ -19,6 +20,9 @@ func _onDialogueStarted(_resource:DialogueResource) -> void:
|
|||||||
func _onDialogueEnded(_resource:DialogueResource) -> void:
|
func _onDialogueEnded(_resource:DialogueResource) -> void:
|
||||||
dialogueActive = false
|
dialogueActive = false
|
||||||
|
|
||||||
|
var INTERACT_INDICATOR:InteractIndicator:
|
||||||
|
get(): return interactIndicator
|
||||||
|
|
||||||
var chatBoxContainer:Control:
|
var chatBoxContainer:Control:
|
||||||
get():
|
get():
|
||||||
if rootUi:
|
if rootUi:
|
||||||
@@ -42,3 +46,21 @@ var PAUSE_MENU:PauseMenu:
|
|||||||
if rootUi:
|
if rootUi:
|
||||||
return rootUi.pauseMenu
|
return rootUi.pauseMenu
|
||||||
return null
|
return null
|
||||||
|
|
||||||
|
var QUIT_DIALOG:QuitConfirmDialog:
|
||||||
|
get():
|
||||||
|
if rootUi:
|
||||||
|
return rootUi.quitConfirmDialog
|
||||||
|
return null
|
||||||
|
|
||||||
|
var MAIN_MENU_DIALOG:ConfirmDialog:
|
||||||
|
get():
|
||||||
|
if rootUi:
|
||||||
|
return rootUi.mainMenuConfirmDialog
|
||||||
|
return null
|
||||||
|
|
||||||
|
var BACKDROP:ModalBackdrop:
|
||||||
|
get():
|
||||||
|
if rootUi:
|
||||||
|
return rootUi.modalBackdrop
|
||||||
|
return null
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
class_name ConfirmDialog extends ClosableMenu
|
||||||
|
|
||||||
|
signal confirmed
|
||||||
|
|
||||||
|
@export var btnYes:Button
|
||||||
|
@export var btnNo:Button
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
close()
|
||||||
|
btnYes.pressed.connect(_onYes)
|
||||||
|
btnNo.pressed.connect(close)
|
||||||
|
btnYes.focus_neighbor_top = btnNo.get_path()
|
||||||
|
btnYes.focus_neighbor_bottom = btnNo.get_path()
|
||||||
|
btnNo.focus_neighbor_top = btnYes.get_path()
|
||||||
|
btnNo.focus_neighbor_bottom = btnYes.get_path()
|
||||||
|
|
||||||
|
func _onYes() -> void:
|
||||||
|
close()
|
||||||
|
confirmed.emit()
|
||||||
|
|
||||||
|
func open() -> void:
|
||||||
|
super.open()
|
||||||
|
btnNo.grab_focus()
|
||||||
|
|
||||||
|
func _unhandled_input(event:InputEvent) -> void:
|
||||||
|
if !isOpen:
|
||||||
|
return
|
||||||
|
if event.is_action_pressed("ui_cancel"):
|
||||||
|
close()
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
@@ -5,8 +5,7 @@ const SCENE:PackedScene = preload("res://ui/component/DialogueTextbox.tscn")
|
|||||||
enum AdvancementMode { PLAYER, TIMED }
|
enum AdvancementMode { PLAYER, TIMED }
|
||||||
|
|
||||||
const LINES_PER_PAGE:int = 4
|
const LINES_PER_PAGE:int = 4
|
||||||
const CHARS_PER_SECOND:float = 20.0
|
const CHARS_PER_SECOND:float = 24.0
|
||||||
const SPEEDUP_MULTIPLIER:float = 4.0
|
|
||||||
const PAUSE_COMMA:float = 0.15
|
const PAUSE_COMMA:float = 0.15
|
||||||
const PAUSE_SENTENCE:float = 0.4
|
const PAUSE_SENTENCE:float = 0.4
|
||||||
const PAUSE_ELLIPSIS_DOT:float = 0.3
|
const PAUSE_ELLIPSIS_DOT:float = 0.3
|
||||||
@@ -24,7 +23,6 @@ var _pauseTimer:float = 0.0
|
|||||||
var _autoAdvanceTimer:float = 0.0
|
var _autoAdvanceTimer:float = 0.0
|
||||||
var _isRevealing:bool = false
|
var _isRevealing:bool = false
|
||||||
var _isWaitingForInput:bool = false
|
var _isWaitingForInput:bool = false
|
||||||
var _hasLetGoOfInteract:bool = true
|
|
||||||
var _advancementMode:AdvancementMode = AdvancementMode.PLAYER
|
var _advancementMode:AdvancementMode = AdvancementMode.PLAYER
|
||||||
|
|
||||||
@onready var _speakerLabel:Label = $VBoxContainer/SpeakerLabel
|
@onready var _speakerLabel:Label = $VBoxContainer/SpeakerLabel
|
||||||
@@ -68,7 +66,6 @@ func setup(line:DialogueLine, entity:Entity, mode:AdvancementMode = AdvancementM
|
|||||||
_autoAdvanceTimer = 0.0
|
_autoAdvanceTimer = 0.0
|
||||||
_isRevealing = true
|
_isRevealing = true
|
||||||
_isWaitingForInput = false
|
_isWaitingForInput = false
|
||||||
_hasLetGoOfInteract = !Input.is_action_pressed("interact")
|
|
||||||
|
|
||||||
_updateWorldPosition()
|
_updateWorldPosition()
|
||||||
visible = true
|
visible = true
|
||||||
@@ -85,9 +82,10 @@ func _process(delta:float) -> void:
|
|||||||
return
|
return
|
||||||
|
|
||||||
_updateWorldPosition()
|
_updateWorldPosition()
|
||||||
_advanceIndicator.visible = _isWaitingForInput
|
|
||||||
if _isWaitingForInput:
|
if _isWaitingForInput:
|
||||||
_advanceIndicator.modulate.a = 0.5 + 0.5 * sin(Time.get_ticks_msec() / 300.0)
|
_advanceIndicator.modulate.a = 0.5 + 0.5 * sin(Time.get_ticks_msec() / 300.0)
|
||||||
|
else:
|
||||||
|
_advanceIndicator.modulate.a = 0.0
|
||||||
|
|
||||||
if _isRevealing:
|
if _isRevealing:
|
||||||
_processReveal(delta)
|
_processReveal(delta)
|
||||||
@@ -119,16 +117,13 @@ func _buildPreWrappedText(parsed:String) -> String:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
func _processReveal(delta:float) -> void:
|
func _processReveal(delta:float) -> void:
|
||||||
if Input.is_action_just_released("interact"):
|
var scaledDelta:float = delta * SETTINGS.textSpeed
|
||||||
_hasLetGoOfInteract = true
|
|
||||||
|
|
||||||
var speedMult:float = SPEEDUP_MULTIPLIER if (_hasLetGoOfInteract and Input.is_action_pressed("interact")) else 1.0
|
|
||||||
|
|
||||||
if _pauseTimer > 0.0:
|
if _pauseTimer > 0.0:
|
||||||
_pauseTimer -= delta * speedMult
|
_pauseTimer -= scaledDelta
|
||||||
return
|
return
|
||||||
|
|
||||||
_revealTimer += delta * speedMult
|
_revealTimer += scaledDelta
|
||||||
|
|
||||||
while _revealTimer >= 1.0 / CHARS_PER_SECOND:
|
while _revealTimer >= 1.0 / CHARS_PER_SECOND:
|
||||||
_revealTimer -= 1.0 / CHARS_PER_SECOND
|
_revealTimer -= 1.0 / CHARS_PER_SECOND
|
||||||
@@ -185,12 +180,6 @@ func _onRevealComplete() -> void:
|
|||||||
_isWaitingForInput = true
|
_isWaitingForInput = true
|
||||||
|
|
||||||
func _processAdvanceInput() -> void:
|
func _processAdvanceInput() -> void:
|
||||||
if Input.is_action_just_released("interact"):
|
|
||||||
_hasLetGoOfInteract = true
|
|
||||||
|
|
||||||
if not _hasLetGoOfInteract:
|
|
||||||
return
|
|
||||||
|
|
||||||
if Input.is_action_just_pressed("interact"):
|
if Input.is_action_just_pressed("interact"):
|
||||||
_advance()
|
_advance()
|
||||||
|
|
||||||
@@ -200,8 +189,7 @@ func _processAutoAdvance(delta:float) -> void:
|
|||||||
_advance()
|
_advance()
|
||||||
|
|
||||||
func _advance() -> void:
|
func _advance() -> void:
|
||||||
_advanceIndicator.visible = false
|
_advanceIndicator.modulate.a = 0.0
|
||||||
_advanceIndicator.modulate.a = 1.0
|
|
||||||
var totalLines:int = _parsedText.count("\n") + 1
|
var totalLines:int = _parsedText.count("\n") + 1
|
||||||
var hasMorePages:bool = _startLine + _linesPerPage < totalLines
|
var hasMorePages:bool = _startLine + _linesPerPage < totalLines
|
||||||
if hasMorePages:
|
if hasMorePages:
|
||||||
|
|||||||
@@ -28,6 +28,6 @@ autowrap_mode = 3
|
|||||||
|
|
||||||
[node name="AdvanceIndicator" type="Label" parent="VBoxContainer"]
|
[node name="AdvanceIndicator" type="Label" parent="VBoxContainer"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
modulate = Color(1, 1, 1, 0)
|
||||||
text = "▼"
|
text = "▼"
|
||||||
horizontal_alignment = 2
|
horizontal_alignment = 2
|
||||||
visible = false
|
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
class_name InteractIndicator extends PanelContainer
|
||||||
|
|
||||||
|
var _entity:Entity = null
|
||||||
|
|
||||||
|
func _enter_tree() -> void:
|
||||||
|
UI.interactIndicator = self
|
||||||
|
|
||||||
|
func _exit_tree() -> void:
|
||||||
|
if UI.interactIndicator == self:
|
||||||
|
UI.interactIndicator = null
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
visible = false
|
||||||
|
DialogueManager.dialogue_started.connect(_onDialogueStarted)
|
||||||
|
DialogueManager.dialogue_ended.connect(_onDialogueEnded)
|
||||||
|
|
||||||
|
func setEntity(entity:Entity) -> void:
|
||||||
|
if is_instance_valid(_entity):
|
||||||
|
_entity.tree_exiting.disconnect(_onEntityExiting)
|
||||||
|
_entity = entity
|
||||||
|
_entity.tree_exiting.connect(_onEntityExiting)
|
||||||
|
visible = _canShow()
|
||||||
|
if visible:
|
||||||
|
updateWorldPosition()
|
||||||
|
|
||||||
|
func clear() -> void:
|
||||||
|
if is_instance_valid(_entity):
|
||||||
|
_entity.tree_exiting.disconnect(_onEntityExiting)
|
||||||
|
_entity = null
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
func _canShow() -> bool:
|
||||||
|
return _entity != null and not UI.dialogueActive
|
||||||
|
|
||||||
|
func _onEntityExiting() -> void:
|
||||||
|
_entity = null
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
func _onDialogueStarted(_resource:DialogueResource) -> void:
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
func _onDialogueEnded(_resource:DialogueResource) -> void:
|
||||||
|
visible = _canShow()
|
||||||
|
if visible:
|
||||||
|
updateWorldPosition()
|
||||||
|
|
||||||
|
func updateWorldPosition() -> void:
|
||||||
|
var camera:Camera3D = get_viewport().get_camera_3d()
|
||||||
|
if camera == null:
|
||||||
|
return
|
||||||
|
var worldPos:Vector3 = _entity.global_position + Vector3(0, 2.5, 0)
|
||||||
|
var screenPos:Vector2 = camera.unproject_position(worldPos)
|
||||||
|
var viewportSize:Vector2 = get_viewport().get_visible_rect().size
|
||||||
|
position = screenPos - size * 0.5
|
||||||
|
position.x = clamp(position.x, 0.0, viewportSize.x - size.x)
|
||||||
|
position.y = clamp(position.y, 0.0, viewportSize.y - size.y)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://xrcb2e7jwlm0
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[gd_scene load_steps=3 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Theme" path="res://ui/UI Theme.tres" id="1"]
|
||||||
|
[ext_resource type="Script" path="res://ui/component/InteractIndicator.gd" id="2"]
|
||||||
|
|
||||||
|
[node name="InteractIndicator" type="PanelContainer"]
|
||||||
|
mouse_filter = 2
|
||||||
|
theme = ExtResource("1")
|
||||||
|
script = ExtResource("2")
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "INTERACT"
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://bmmc3x8n1d7qp"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://ui/component/ConfirmDialog.gd" id="1_mmcd"]
|
||||||
|
|
||||||
|
[node name="MainMenuConfirmDialog" type="Control" node_paths=PackedStringArray("btnYes", "btnNo")]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
process_mode = 3
|
||||||
|
script = ExtResource("1_mmcd")
|
||||||
|
btnYes = NodePath("VBoxContainer/Yes")
|
||||||
|
btnNo = NodePath("VBoxContainer/No")
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||||
|
anchors_preset = 8
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_top = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
anchor_bottom = 0.5
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Return to main menu?"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="Yes" type="Button" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Yes"
|
||||||
|
|
||||||
|
[node name="No" type="Button" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "No"
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
class_name ModalBackdrop extends ColorRect
|
||||||
|
|
||||||
|
# Tracks which overlays are currently open. Each entry must be a direct sibling
|
||||||
|
# (child of the same parent). The backdrop repositions itself in the scene tree
|
||||||
|
# to sit immediately below whichever open overlay has the highest tree index,
|
||||||
|
# so only one backdrop is ever visible regardless of how many overlays are open.
|
||||||
|
var _openOverlays:Array[Control] = []
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
func register(overlay:Control) -> void:
|
||||||
|
assert(overlay.get_parent() == get_parent(), "ModalBackdrop: overlay must be a sibling")
|
||||||
|
assert(overlay.has_signal("opened") and overlay.has_signal("closed"),
|
||||||
|
"ModalBackdrop: overlay must have opened/closed signals")
|
||||||
|
overlay.connect("opened", func(): _onOpened(overlay))
|
||||||
|
overlay.connect("closed", func(): _onClosed(overlay))
|
||||||
|
|
||||||
|
func _onOpened(overlay:Control) -> void:
|
||||||
|
if overlay not in _openOverlays:
|
||||||
|
_openOverlays.append(overlay)
|
||||||
|
_reposition()
|
||||||
|
|
||||||
|
func _onClosed(overlay:Control) -> void:
|
||||||
|
_openOverlays.erase(overlay)
|
||||||
|
_reposition()
|
||||||
|
|
||||||
|
func _reposition() -> void:
|
||||||
|
if _openOverlays.is_empty():
|
||||||
|
visible = false
|
||||||
|
return
|
||||||
|
visible = true
|
||||||
|
var top := _topOverlay()
|
||||||
|
var topIdx := top.get_index()
|
||||||
|
var myIdx := get_index()
|
||||||
|
if myIdx == topIdx - 1:
|
||||||
|
return
|
||||||
|
if myIdx < topIdx:
|
||||||
|
get_parent().move_child(self, topIdx - 1)
|
||||||
|
else:
|
||||||
|
get_parent().move_child(self, topIdx)
|
||||||
|
|
||||||
|
func _topOverlay() -> Control:
|
||||||
|
var top:Control = _openOverlays[0]
|
||||||
|
for overlay in _openOverlays:
|
||||||
|
if overlay.get_index() > top.get_index():
|
||||||
|
top = overlay
|
||||||
|
return top
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
class_name QuitConfirmDialog extends ConfirmDialog
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
super._ready()
|
||||||
|
confirmed.connect(func(): get_tree().quit())
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://deov3ob0lojyo
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://cqdf1x7m2canp"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://ui/component/QuitConfirmDialog.gd" id="1_qcd"]
|
||||||
|
|
||||||
|
[node name="QuitConfirmDialog" type="Control" node_paths=PackedStringArray("btnYes", "btnNo")]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
process_mode = 3
|
||||||
|
script = ExtResource("1_qcd")
|
||||||
|
btnYes = NodePath("VBoxContainer/Yes")
|
||||||
|
btnNo = NodePath("VBoxContainer/No")
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||||
|
anchors_preset = 8
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_top = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
anchor_bottom = 0.5
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Quit to desktop?"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="Yes" type="Button" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Yes"
|
||||||
|
|
||||||
|
[node name="No" type="Button" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "No"
|
||||||
@@ -2,12 +2,14 @@ class_name MainMenu extends Control
|
|||||||
|
|
||||||
@export var btnNewGame:Button
|
@export var btnNewGame:Button
|
||||||
@export var btnSettings:Button
|
@export var btnSettings:Button
|
||||||
|
@export var btnQuit:Button
|
||||||
@export var settingsMenu:ClosableMenu
|
@export var settingsMenu:ClosableMenu
|
||||||
@export_file("*.tscn") var newGameScene:String
|
@export_file("*.tscn") var newGameScene:String
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
btnNewGame.pressed.connect(onNewGamePressed)
|
btnNewGame.pressed.connect(onNewGamePressed)
|
||||||
btnSettings.pressed.connect(onSettingsPressed)
|
btnSettings.pressed.connect(onSettingsPressed)
|
||||||
|
btnQuit.pressed.connect(_onQuitPressed)
|
||||||
settingsMenu.opened.connect(_onSettingsOpened)
|
settingsMenu.opened.connect(_onSettingsOpened)
|
||||||
settingsMenu.closed.connect(_onSettingsClosed)
|
settingsMenu.closed.connect(_onSettingsClosed)
|
||||||
btnNewGame.grab_focus()
|
btnNewGame.grab_focus()
|
||||||
@@ -26,6 +28,13 @@ func _unhandled_input(event:InputEvent) -> void:
|
|||||||
settingsMenu.close()
|
settingsMenu.close()
|
||||||
get_viewport().set_input_as_handled()
|
get_viewport().set_input_as_handled()
|
||||||
|
|
||||||
|
func _onQuitPressed() -> void:
|
||||||
|
UI.QUIT_DIALOG.closed.connect(_onQuitDialogClosed, CONNECT_ONE_SHOT)
|
||||||
|
UI.QUIT_DIALOG.open()
|
||||||
|
|
||||||
|
func _onQuitDialogClosed() -> void:
|
||||||
|
btnQuit.grab_focus()
|
||||||
|
|
||||||
func onNewGamePressed() -> void:
|
func onNewGamePressed() -> void:
|
||||||
SCENE.setScene(SceneSingleton.SceneType.OVERWORLD)
|
SCENE.setScene(SceneSingleton.SceneType.OVERWORLD)
|
||||||
OVERWORLD.mapChange(newGameScene, "PlayerSpawnPoint")
|
OVERWORLD.mapChange(newGameScene, "PlayerSpawnPoint")
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
[ext_resource type="Script" uid="uid://bcjfv6dw0ugvo" path="res://ui/component/ClosableMenu.gd" id="2_f3vro"]
|
[ext_resource type="Script" uid="uid://bcjfv6dw0ugvo" path="res://ui/component/ClosableMenu.gd" id="2_f3vro"]
|
||||||
[ext_resource type="PackedScene" uid="uid://d3f31lli1ahts" path="res://ui/settings/SettingsMenu.tscn" id="3_44i87"]
|
[ext_resource type="PackedScene" uid="uid://d3f31lli1ahts" path="res://ui/settings/SettingsMenu.tscn" id="3_44i87"]
|
||||||
|
|
||||||
[node name="Main Menu" type="Control" node_paths=PackedStringArray("btnNewGame", "btnSettings", "settingsMenu")]
|
[node name="Main Menu" type="Control" node_paths=PackedStringArray("btnNewGame", "btnSettings", "btnQuit", "settingsMenu")]
|
||||||
layout_mode = 3
|
layout_mode = 3
|
||||||
anchors_preset = 15
|
anchors_preset = 15
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
@@ -14,6 +14,7 @@ grow_vertical = 2
|
|||||||
script = ExtResource("1_vp3lc")
|
script = ExtResource("1_vp3lc")
|
||||||
btnNewGame = NodePath("VBoxContainer/NewGame")
|
btnNewGame = NodePath("VBoxContainer/NewGame")
|
||||||
btnSettings = NodePath("VBoxContainer/Settings")
|
btnSettings = NodePath("VBoxContainer/Settings")
|
||||||
|
btnQuit = NodePath("VBoxContainer/Quit")
|
||||||
settingsMenu = NodePath("MainMenuSettings")
|
settingsMenu = NodePath("MainMenuSettings")
|
||||||
newGameScene = "uid://d0ywgijpuqy0r"
|
newGameScene = "uid://d0ywgijpuqy0r"
|
||||||
metadata/_custom_type_script = "uid://btfeuku41py2b"
|
metadata/_custom_type_script = "uid://btfeuku41py2b"
|
||||||
@@ -35,6 +36,10 @@ text = "New Game"
|
|||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = "Settings"
|
text = "Settings"
|
||||||
|
|
||||||
|
[node name="Quit" type="Button" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Quit Game"
|
||||||
|
|
||||||
[node name="MainMenuSettings" type="Control" parent="."]
|
[node name="MainMenuSettings" type="Control" parent="."]
|
||||||
visible = false
|
visible = false
|
||||||
layout_mode = 1
|
layout_mode = 1
|
||||||
|
|||||||
+15
-22
@@ -2,44 +2,37 @@ class_name PauseMain extends VBoxContainer
|
|||||||
|
|
||||||
signal resumeRequested
|
signal resumeRequested
|
||||||
signal settingsRequested
|
signal settingsRequested
|
||||||
signal mainMenuRequested
|
|
||||||
signal quitRequested
|
|
||||||
|
|
||||||
@export var btnResume:Button
|
@export var btnResume:Button
|
||||||
@export var btnSettings:Button
|
@export var btnSettings:Button
|
||||||
@export var btnMainMenu:Button
|
@export var btnMainMenu:Button
|
||||||
@export var btnQuit:Button
|
@export var btnQuit:Button
|
||||||
@export var mainButtons:VBoxContainer
|
|
||||||
@export var confirmQuit:VBoxContainer
|
|
||||||
@export var btnQuitConfirm:Button
|
|
||||||
@export var btnQuitCancel:Button
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
visible = false
|
visible = false
|
||||||
btnResume.pressed.connect(resumeRequested.emit)
|
btnResume.pressed.connect(resumeRequested.emit)
|
||||||
btnSettings.pressed.connect(settingsRequested.emit)
|
btnSettings.pressed.connect(settingsRequested.emit)
|
||||||
btnMainMenu.pressed.connect(mainMenuRequested.emit)
|
btnMainMenu.pressed.connect(_showMainMenuConfirm)
|
||||||
btnQuit.pressed.connect(_showConfirm)
|
btnQuit.pressed.connect(_showQuitConfirm)
|
||||||
btnQuitConfirm.pressed.connect(quitRequested.emit)
|
UI.QUIT_DIALOG.closed.connect(_onQuitDialogClosed)
|
||||||
btnQuitCancel.pressed.connect(cancelConfirm)
|
UI.MAIN_MENU_DIALOG.closed.connect(_onMainMenuDialogClosed)
|
||||||
|
|
||||||
func _showConfirm() -> void:
|
func _showQuitConfirm() -> void:
|
||||||
mainButtons.visible = false
|
UI.QUIT_DIALOG.open()
|
||||||
confirmQuit.visible = true
|
|
||||||
btnQuitCancel.grab_focus()
|
|
||||||
|
|
||||||
func cancelConfirm() -> void:
|
func _showMainMenuConfirm() -> void:
|
||||||
mainButtons.visible = true
|
UI.MAIN_MENU_DIALOG.open()
|
||||||
confirmQuit.visible = false
|
|
||||||
btnQuit.grab_focus()
|
|
||||||
|
|
||||||
func isConfirming() -> bool:
|
func _onQuitDialogClosed() -> void:
|
||||||
return confirmQuit.visible
|
if isOpen():
|
||||||
|
btnQuit.grab_focus()
|
||||||
|
|
||||||
|
func _onMainMenuDialogClosed() -> void:
|
||||||
|
if isOpen():
|
||||||
|
btnMainMenu.grab_focus()
|
||||||
|
|
||||||
func open() -> void:
|
func open() -> void:
|
||||||
visible = true
|
visible = true
|
||||||
if isConfirming():
|
|
||||||
cancelConfirm()
|
|
||||||
btnResume.grab_focus()
|
btnResume.grab_focus()
|
||||||
|
|
||||||
func close() -> void:
|
func close() -> void:
|
||||||
|
|||||||
+1
-22
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[ext_resource type="Script" uid="uid://c7kvg0jw6w340" path="res://ui/pause/PauseMain.gd" id="1_b5xfl"]
|
[ext_resource type="Script" uid="uid://c7kvg0jw6w340" path="res://ui/pause/PauseMain.gd" id="1_b5xfl"]
|
||||||
|
|
||||||
[node name="PauseMain" type="VBoxContainer" node_paths=PackedStringArray("btnResume", "btnSettings", "btnMainMenu", "btnQuit", "mainButtons", "confirmQuit", "btnQuitConfirm", "btnQuitCancel")]
|
[node name="PauseMain" type="VBoxContainer" node_paths=PackedStringArray("btnResume", "btnSettings", "btnMainMenu", "btnQuit")]
|
||||||
anchors_preset = 8
|
anchors_preset = 8
|
||||||
anchor_left = 0.5
|
anchor_left = 0.5
|
||||||
anchor_top = 0.5
|
anchor_top = 0.5
|
||||||
@@ -16,10 +16,6 @@ btnResume = NodePath("MainButtons/Resume")
|
|||||||
btnSettings = NodePath("MainButtons/Settings")
|
btnSettings = NodePath("MainButtons/Settings")
|
||||||
btnMainMenu = NodePath("MainButtons/MainMenu")
|
btnMainMenu = NodePath("MainButtons/MainMenu")
|
||||||
btnQuit = NodePath("MainButtons/Quit")
|
btnQuit = NodePath("MainButtons/Quit")
|
||||||
mainButtons = NodePath("MainButtons")
|
|
||||||
confirmQuit = NodePath("ConfirmQuit")
|
|
||||||
btnQuitConfirm = NodePath("ConfirmQuit/Yes")
|
|
||||||
btnQuitCancel = NodePath("ConfirmQuit/No")
|
|
||||||
|
|
||||||
[node name="Title" type="Label" parent="."]
|
[node name="Title" type="Label" parent="."]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
@@ -44,20 +40,3 @@ text = "Main Menu"
|
|||||||
[node name="Quit" type="Button" parent="MainButtons"]
|
[node name="Quit" type="Button" parent="MainButtons"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = "Quit Game"
|
text = "Quit Game"
|
||||||
|
|
||||||
[node name="ConfirmQuit" type="VBoxContainer" parent="."]
|
|
||||||
layout_mode = 2
|
|
||||||
visible = false
|
|
||||||
|
|
||||||
[node name="Label" type="Label" parent="ConfirmQuit"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "Quit to desktop?"
|
|
||||||
horizontal_alignment = 1
|
|
||||||
|
|
||||||
[node name="Yes" type="Button" parent="ConfirmQuit"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "Yes"
|
|
||||||
|
|
||||||
[node name="No" type="Button" parent="ConfirmQuit"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "No"
|
|
||||||
|
|||||||
+16
-11
@@ -1,5 +1,8 @@
|
|||||||
class_name PauseMenu extends Control
|
class_name PauseMenu extends Control
|
||||||
|
|
||||||
|
signal opened
|
||||||
|
signal closed
|
||||||
|
|
||||||
@export var MAIN:PauseMain
|
@export var MAIN:PauseMain
|
||||||
@export var settingsPanel:PauseSettings
|
@export var settingsPanel:PauseSettings
|
||||||
|
|
||||||
@@ -7,8 +10,7 @@ func _ready() -> void:
|
|||||||
close()
|
close()
|
||||||
MAIN.resumeRequested.connect(close)
|
MAIN.resumeRequested.connect(close)
|
||||||
MAIN.settingsRequested.connect(_openSettings)
|
MAIN.settingsRequested.connect(_openSettings)
|
||||||
MAIN.mainMenuRequested.connect(_goToMainMenu)
|
UI.MAIN_MENU_DIALOG.confirmed.connect(_goToMainMenu)
|
||||||
MAIN.quitRequested.connect(func(): get_tree().quit())
|
|
||||||
|
|
||||||
func isOpen() -> bool:
|
func isOpen() -> bool:
|
||||||
return visible
|
return visible
|
||||||
@@ -17,12 +19,14 @@ func open() -> void:
|
|||||||
visible = true
|
visible = true
|
||||||
get_tree().paused = true
|
get_tree().paused = true
|
||||||
MAIN.open()
|
MAIN.open()
|
||||||
|
opened.emit()
|
||||||
|
|
||||||
func close() -> void:
|
func close() -> void:
|
||||||
get_tree().paused = false
|
get_tree().paused = false
|
||||||
visible = false
|
visible = false
|
||||||
MAIN.close()
|
MAIN.close()
|
||||||
settingsPanel.close()
|
settingsPanel.close()
|
||||||
|
closed.emit()
|
||||||
|
|
||||||
func _openSettings() -> void:
|
func _openSettings() -> void:
|
||||||
MAIN.close()
|
MAIN.close()
|
||||||
@@ -35,12 +39,13 @@ func _goToMainMenu() -> void:
|
|||||||
func _unhandled_input(event:InputEvent) -> void:
|
func _unhandled_input(event:InputEvent) -> void:
|
||||||
if !visible:
|
if !visible:
|
||||||
return
|
return
|
||||||
if event.is_action_pressed("ui_cancel"):
|
if !event.is_action_pressed("ui_cancel"):
|
||||||
if MAIN.isConfirming():
|
return
|
||||||
MAIN.cancelConfirm()
|
if (UI.QUIT_DIALOG != null and UI.QUIT_DIALOG.isOpen) or (UI.MAIN_MENU_DIALOG != null and UI.MAIN_MENU_DIALOG.isOpen):
|
||||||
elif settingsPanel.isOpen():
|
return
|
||||||
settingsPanel.close()
|
if settingsPanel.isOpen():
|
||||||
MAIN.open()
|
settingsPanel.close()
|
||||||
else:
|
MAIN.open()
|
||||||
close()
|
else:
|
||||||
get_viewport().set_input_as_handled()
|
close()
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
class_name SettingsMenu extends Control
|
class_name SettingsMenu extends Control
|
||||||
|
|
||||||
|
const TEXT_SPEED_VALUES:Array[float] = [0.2, 1.0, 2.0]
|
||||||
|
|
||||||
@export var tabs:TabBar
|
@export var tabs:TabBar
|
||||||
@export var tabControls:Array[Control]
|
@export var tabControls:Array[Control]
|
||||||
@export var checkInvertX:CheckBox
|
@export var checkInvertX:CheckBox
|
||||||
@export var checkInvertY:CheckBox
|
@export var checkInvertY:CheckBox
|
||||||
@export var sliderControllerSpeed:HSlider
|
@export var sliderControllerSpeed:HSlider
|
||||||
@export var sliderMouseSpeed:HSlider
|
@export var sliderMouseSpeed:HSlider
|
||||||
|
@export var optionTextSpeed:OptionButton
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
tabs.tab_changed.connect(onTabChanged)
|
tabs.tab_changed.connect(onTabChanged)
|
||||||
@@ -17,8 +20,16 @@ func _ready() -> void:
|
|||||||
sliderMouseSpeed.value = SETTINGS.cameraSpeedMouse
|
sliderMouseSpeed.value = SETTINGS.cameraSpeedMouse
|
||||||
sliderControllerSpeed.value_changed.connect(func(v:float): SETTINGS.cameraSpeedController = v)
|
sliderControllerSpeed.value_changed.connect(func(v:float): SETTINGS.cameraSpeedController = v)
|
||||||
sliderMouseSpeed.value_changed.connect(func(v:float): SETTINGS.cameraSpeedMouse = v)
|
sliderMouseSpeed.value_changed.connect(func(v:float): SETTINGS.cameraSpeedMouse = v)
|
||||||
|
optionTextSpeed.select(_textSpeedToIndex(SETTINGS.textSpeed))
|
||||||
|
optionTextSpeed.item_selected.connect(func(idx:int): SETTINGS.textSpeed = TEXT_SPEED_VALUES[idx])
|
||||||
onTabChanged(tabs.current_tab)
|
onTabChanged(tabs.current_tab)
|
||||||
|
|
||||||
|
func _textSpeedToIndex(speed:float) -> int:
|
||||||
|
match speed:
|
||||||
|
0.2: return 0
|
||||||
|
2.0: return 2
|
||||||
|
_: return 1
|
||||||
|
|
||||||
func _notification(what:int) -> void:
|
func _notification(what:int) -> void:
|
||||||
if what == NOTIFICATION_VISIBILITY_CHANGED and visible:
|
if what == NOTIFICATION_VISIBILITY_CHANGED and visible:
|
||||||
tabs.grab_focus()
|
tabs.grab_focus()
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[ext_resource type="Script" uid="uid://efmr0xkbw1py" path="res://ui/settings/SettingsMenu.gd" id="1_4lnig"]
|
[ext_resource type="Script" uid="uid://efmr0xkbw1py" path="res://ui/settings/SettingsMenu.gd" id="1_4lnig"]
|
||||||
|
|
||||||
[node name="SettingsMenu" type="Control" node_paths=PackedStringArray("tabs", "tabControls", "checkInvertX", "checkInvertY", "sliderControllerSpeed", "sliderMouseSpeed")]
|
[node name="SettingsMenu" type="Control" node_paths=PackedStringArray("tabs", "tabControls", "checkInvertX", "checkInvertY", "sliderControllerSpeed", "sliderMouseSpeed", "optionTextSpeed")]
|
||||||
layout_mode = 3
|
layout_mode = 3
|
||||||
anchors_preset = 15
|
anchors_preset = 15
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
@@ -11,11 +11,12 @@ grow_horizontal = 2
|
|||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
script = ExtResource("1_4lnig")
|
script = ExtResource("1_4lnig")
|
||||||
tabs = NodePath("VBoxContainer/TabBar")
|
tabs = NodePath("VBoxContainer/TabBar")
|
||||||
tabControls = [NodePath("VBoxContainer/ScrollContainer/LabelGameplay"), NodePath("VBoxContainer/ScrollContainer/LabelSound"), NodePath("VBoxContainer/ScrollContainer/LabelGraphics"), NodePath("VBoxContainer/ScrollContainer/PanelControls")]
|
tabControls = [NodePath("VBoxContainer/ScrollContainer/PanelGameplay"), NodePath("VBoxContainer/ScrollContainer/LabelSound"), NodePath("VBoxContainer/ScrollContainer/LabelGraphics"), NodePath("VBoxContainer/ScrollContainer/PanelControls")]
|
||||||
checkInvertX = NodePath("VBoxContainer/ScrollContainer/PanelControls/CheckInvertX")
|
checkInvertX = NodePath("VBoxContainer/ScrollContainer/PanelControls/CheckInvertX")
|
||||||
checkInvertY = NodePath("VBoxContainer/ScrollContainer/PanelControls/CheckInvertY")
|
checkInvertY = NodePath("VBoxContainer/ScrollContainer/PanelControls/CheckInvertY")
|
||||||
sliderControllerSpeed = NodePath("VBoxContainer/ScrollContainer/PanelControls/SliderControllerSpeed")
|
sliderControllerSpeed = NodePath("VBoxContainer/ScrollContainer/PanelControls/SliderControllerSpeed")
|
||||||
sliderMouseSpeed = NodePath("VBoxContainer/ScrollContainer/PanelControls/SliderMouseSpeed")
|
sliderMouseSpeed = NodePath("VBoxContainer/ScrollContainer/PanelControls/SliderMouseSpeed")
|
||||||
|
optionTextSpeed = NodePath("VBoxContainer/ScrollContainer/PanelGameplay/OptionTextSpeed")
|
||||||
|
|
||||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||||
layout_mode = 1
|
layout_mode = 1
|
||||||
@@ -48,10 +49,24 @@ visible = false
|
|||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = "Sound"
|
text = "Sound"
|
||||||
|
|
||||||
[node name="LabelGameplay" type="Label" parent="VBoxContainer/ScrollContainer"]
|
[node name="PanelGameplay" type="VBoxContainer" parent="VBoxContainer/ScrollContainer"]
|
||||||
visible = false
|
visible = false
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = "Gameplay"
|
|
||||||
|
[node name="LabelTextSpeed" type="Label" parent="VBoxContainer/ScrollContainer/PanelGameplay"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Text Speed"
|
||||||
|
|
||||||
|
[node name="OptionTextSpeed" type="OptionButton" parent="VBoxContainer/ScrollContainer/PanelGameplay"]
|
||||||
|
layout_mode = 2
|
||||||
|
focus_mode = 2
|
||||||
|
item_count = 3
|
||||||
|
item_0/text = "Slow"
|
||||||
|
item_0/id = 0
|
||||||
|
item_1/text = "Normal"
|
||||||
|
item_1/id = 1
|
||||||
|
item_2/text = "Fast"
|
||||||
|
item_2/id = 2
|
||||||
|
|
||||||
[node name="PanelControls" type="VBoxContainer" parent="VBoxContainer/ScrollContainer"]
|
[node name="PanelControls" type="VBoxContainer" parent="VBoxContainer/ScrollContainer"]
|
||||||
visible = false
|
visible = false
|
||||||
|
|||||||
Reference in New Issue
Block a user