Add some UI
This commit is contained in:
@@ -13,10 +13,11 @@ Never instantiate these — access only via the global handle.
|
|||||||
| `OVERWORLD` | Map switching with threaded loading |
|
| `OVERWORLD` | Map switching with threaded loading |
|
||||||
| `COOKING` | Cooking mini-game lifecycle |
|
| `COOKING` | Cooking mini-game lifecycle |
|
||||||
| `SAVE` | Persistence (stub) |
|
| `SAVE` | Persistence (stub) |
|
||||||
|
| `SETTINGS` | Runtime settings — `invertCameraX:bool`, `invertCameraY:bool` |
|
||||||
| `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` | Root UI accessor — `UI.TEXTBOX`, `UI.DEBUG_MENU`, `UI.GAME_MENU` |
|
||||||
|
|
||||||
## Scene Graph
|
## Scene Graph
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ 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
|
└─ VNTextbox, DebugMenu, PauseMenu, GameMenu
|
||||||
```
|
```
|
||||||
|
|
||||||
`RootScene` listens to `SCENE.sceneChanged` and shows/hides the correct sub-tree.
|
`RootScene` listens to `SCENE.sceneChanged` and shows/hides the correct sub-tree.
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
# Overworld System
|
||||||
|
|
||||||
|
## Scene structure
|
||||||
|
|
||||||
|
```
|
||||||
|
OverworldScene (Node3D) ← always resident, managed by SCENE singleton
|
||||||
|
└─ Map (Node3D) ← emptied and repopulated on each map change
|
||||||
|
|
||||||
|
TestMap (Node3D) ← typical map root, extends Node3D
|
||||||
|
├─ Player (Entity) ← movementType = PLAYER, entityId = "player"
|
||||||
|
├─ NPC/object (Entity) ... ← interactType drives what happens on interact
|
||||||
|
├─ TestMapBase (StaticBody3D) ← reusable terrain plane (200×200, collision layer 1)
|
||||||
|
└─ Camera3D (OverworldCamera) ← targetNode → Player
|
||||||
|
```
|
||||||
|
|
||||||
|
## Map transitions
|
||||||
|
|
||||||
|
Use `OVERWORLD.mapChange(path, destinationNodeName)` to switch maps.
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
OVERWORLD.mapChange("res://overworld/map/SomeMap.tscn", "SpawnPoint")
|
||||||
|
```
|
||||||
|
|
||||||
|
Flow: fade-out begins → map loads on a background thread → when both complete, `OVERWORLD.mapChanged` fires → `OverworldScene` clears `Map` children and instances the new map → fade-in begins. The `destinationNodeName` is passed with the signal for the new map to use as a spawn point (not yet wired to player placement — see [stubs](stubs.md)).
|
||||||
|
|
||||||
|
## Entity
|
||||||
|
|
||||||
|
All overworld objects (player, NPCs, items, triggers) are instances of [entity/Entity.tscn](../../overworld/entity/Entity.tscn) with different export values.
|
||||||
|
|
||||||
|
| Export | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `entityId:String` | UUID; use the Inspector button to regenerate |
|
||||||
|
| `movementType:MovementType` | `NONE` (static), `DISABLED`, or `PLAYER` (input-driven) |
|
||||||
|
| `interactType:InteractType` | What happens when the player presses Interact nearby |
|
||||||
|
| `dialogueResource:DialogueResource` | `.dialogue` file — required for `CONVERSATION` |
|
||||||
|
| `dialogueTitle:String` | Dialogue section to start from (default `"start"`) |
|
||||||
|
| `oneTimeItem:ItemResource` | Item granted on interact — required for `ONE_TIME_ITEM` |
|
||||||
|
| `cutscene:CutsceneResource` | Cutscene to run — required for `CUTSCENE` |
|
||||||
|
|
||||||
|
### Interaction types
|
||||||
|
|
||||||
|
| `InteractType` | Behaviour |
|
||||||
|
|---|---|
|
||||||
|
| `NONE` | Not interactable |
|
||||||
|
| `CONVERSATION` | Runs `dialogueResource` from `dialogueTitle` via `DialogueAction` |
|
||||||
|
| `ONE_TIME_ITEM` | Grants `oneTimeItem`, then frees the entity |
|
||||||
|
| `CUTSCENE` | Queues and starts `cutscene` |
|
||||||
|
| `BATTLE_TEST` | Starts a test battle (hardcoded enemy, for dev use) |
|
||||||
|
|
||||||
|
To add a new interaction type: add a value to `Entity.InteractType`, then add the matching `match` branch in `EntityInteractableArea.onInteract()` ([entity/EntityInteractableArea.gd](../../overworld/entity/EntityInteractableArea.gd)).
|
||||||
|
|
||||||
|
### Collision layers
|
||||||
|
|
||||||
|
| Area | Layer | Mask | Purpose |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `EntityInteractingArea` | 0 | 2 | Player's reach — detects nearby interactables |
|
||||||
|
| `EntityInteractableArea` | 2 | 0 | Entity's surface — detected by other reaches |
|
||||||
|
|
||||||
|
The asymmetric setup means entities never trigger themselves.
|
||||||
|
|
||||||
|
## Movement
|
||||||
|
|
||||||
|
`EntityMovement` (a child `Node` under `Components`) handles all physics each frame:
|
||||||
|
|
||||||
|
1. Apply gravity if airborne
|
||||||
|
2. Apply friction (`velocity.x/z *= delta * FRICTION`)
|
||||||
|
3. If `_canMove()` and `movementType == PLAYER`: read input, compute camera-relative direction, set velocity
|
||||||
|
4. `move_and_slide()`
|
||||||
|
|
||||||
|
Movement is blocked (`_canMove() → false`) when `UI.dialogueActive`, `UI.TEXTBOX` is open, or `UI.GAME_MENU.isOpen()`.
|
||||||
|
|
||||||
|
Camera-relative direction is derived from the active `Camera3D`'s basis — the camera's Y-zeroed and renormalized X/Z axes map input axes to world axes. The entity faces (`look_at`) the movement direction each frame.
|
||||||
|
|
||||||
|
## Camera
|
||||||
|
|
||||||
|
`OverworldCamera` orbits around `targetNode` using yaw/pitch angles driven by `camera_orbit_*` inputs.
|
||||||
|
|
||||||
|
| Export | Default | Purpose |
|
||||||
|
|---|---|---|
|
||||||
|
| `targetNode:Node3D` | — | Node to orbit (assign Player in scene) |
|
||||||
|
| `pivotOffset:Vector3` | `(0, 1.2, 0)` | Orbit point above entity origin |
|
||||||
|
| `distance:float` | `10.0` | Orbit radius |
|
||||||
|
| `pitchMin/Max:float` | `-10° / 70°` | Vertical clamp |
|
||||||
|
| `orbitSensitivity:float` | `120.0` | Degrees/sec at full input |
|
||||||
|
| `collisionMask:int` | — | Layers the camera avoids (terrain = layer 1) |
|
||||||
|
|
||||||
|
If the ray from pivot to desired camera position hits `collisionMask`, the camera is pulled in to just in front of the hit point (with `COLLISION_MARGIN = 0.3`), clamped to `minDistance`.
|
||||||
|
|
||||||
|
## Adding a new map
|
||||||
|
|
||||||
|
1. Create a new scene (`Node3D` root) in `overworld/map/`
|
||||||
|
2. Add `Entity` instances, set `interactType` and relevant exports in the Inspector
|
||||||
|
3. Instance `TestMapBase` (or your own terrain) as a child
|
||||||
|
4. Add a `Camera3D` with `OverworldCamera` script; set `targetNode` to the Player entity
|
||||||
|
5. Add a `Player` entity with `movementType = PLAYER` and `entityId = "player"`
|
||||||
|
6. Switch to it with `OVERWORLD.mapChange("res://overworld/map/YourMap.tscn", "")`
|
||||||
@@ -9,6 +9,8 @@
|
|||||||
|
|
||||||
## Entities (Overworld)
|
## Entities (Overworld)
|
||||||
|
|
||||||
|
See [Overworld](overworld.md) for the full reference (scene structure, interaction types, camera, map transitions).
|
||||||
|
|
||||||
- All interactable world objects extend `Entity` (CharacterBody3D)
|
- All interactable world objects extend `Entity` (CharacterBody3D)
|
||||||
- Interaction type set via `@export var interactType:InteractType`
|
- Interaction type set via `@export var interactType:InteractType`
|
||||||
- Interaction routing lives in `EntityInteractableArea.onInteract()` — add new `InteractType` values there
|
- Interaction routing lives in `EntityInteractableArea.onInteract()` — add new `InteractType` values there
|
||||||
@@ -21,6 +23,8 @@
|
|||||||
|
|
||||||
## UI
|
## UI
|
||||||
|
|
||||||
|
See [UI](ui.md) for the full reference (VNTextbox, ClosableMenu, pause/debug/settings menus, AdvancedRichText, adding new menus).
|
||||||
|
|
||||||
- `UI.TEXTBOX.setTextAndWait(text)` — show dialogue and await player dismiss (use `await`)
|
- `UI.TEXTBOX.setTextAndWait(text)` — show dialogue and await player dismiss (use `await`)
|
||||||
- Movement is blocked automatically when `UI.TEXTBOX` is visible (`EntityMovement._canMove()` checks this)
|
- Movement is blocked automatically when `UI.TEXTBOX` is visible (`EntityMovement._canMove()` checks this)
|
||||||
- Menus extend `ClosableMenu` for open/close/toggle + `closed`/`opened` signals
|
- Menus extend `ClosableMenu` for open/close/toggle + `closed`/`opened` signals
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
# 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)
|
||||||
@@ -11,9 +11,13 @@ Detailed reference lives in [.claude/docs/](.claude/docs/):
|
|||||||
- [Systems](.claude/docs/systems.md) — battle, entities, items, UI conventions
|
- [Systems](.claude/docs/systems.md) — battle, entities, items, UI conventions
|
||||||
- [Dialogue](.claude/docs/dialogue.md) — DialogueManager integration, writing .dialogue files, DialogueAction
|
- [Dialogue](.claude/docs/dialogue.md) — DialogueManager integration, writing .dialogue files, DialogueAction
|
||||||
- [Stubs](.claude/docs/stubs.md) — incomplete / placeholder systems to avoid relying on
|
- [Stubs](.claude/docs/stubs.md) — incomplete / placeholder systems to avoid relying on
|
||||||
|
- [Overworld](.claude/docs/overworld.md) — map transitions, Entity exports, interaction types, camera, movement
|
||||||
|
- [UI](.claude/docs/ui.md) — UI singleton, VNTextbox, ClosableMenu, pause/debug/settings menus, AdvancedRichText
|
||||||
|
|
||||||
@.claude/docs/code-style.md
|
@.claude/docs/code-style.md
|
||||||
@.claude/docs/architecture.md
|
@.claude/docs/architecture.md
|
||||||
@.claude/docs/systems.md
|
@.claude/docs/systems.md
|
||||||
@.claude/docs/dialogue.md
|
@.claude/docs/dialogue.md
|
||||||
@.claude/docs/stubs.md
|
@.claude/docs/stubs.md
|
||||||
|
@.claude/docs/overworld.md
|
||||||
|
@.claude/docs/ui.md
|
||||||
|
|||||||
@@ -27,9 +27,10 @@ func _process(delta:float) -> void:
|
|||||||
"camera_orbit_left", "camera_orbit_right",
|
"camera_orbit_left", "camera_orbit_right",
|
||||||
"camera_orbit_up", "camera_orbit_down"
|
"camera_orbit_up", "camera_orbit_down"
|
||||||
)
|
)
|
||||||
_yaw += orbitInput.x * orbitSensitivity * delta
|
var xMult:float = -1.0 if SETTINGS.invertCameraX else 1.0
|
||||||
# Invert Y so stick-up = camera rises (bird's-eye)
|
var yMult:float = 1.0 if SETTINGS.invertCameraY else -1.0
|
||||||
_pitch -= orbitInput.y * orbitSensitivity * delta
|
_yaw += orbitInput.x * orbitSensitivity * delta * xMult
|
||||||
|
_pitch += orbitInput.y * orbitSensitivity * delta * yMult
|
||||||
_pitch = clamp(_pitch, pitchMin, pitchMax)
|
_pitch = clamp(_pitch, pitchMin, pitchMax)
|
||||||
|
|
||||||
var pivot:Vector3 = targetNode.global_transform.origin + pivotOffset
|
var pivot:Vector3 = targetNode.global_transform.origin + pivotOffset
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ func _canMove() -> bool:
|
|||||||
return false
|
return false
|
||||||
if !UI.TEXTBOX.isClosed:
|
if !UI.TEXTBOX.isClosed:
|
||||||
return false
|
return false
|
||||||
|
if UI.GAME_MENU && UI.GAME_MENU.isOpen():
|
||||||
|
return false
|
||||||
return true
|
return true
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|||||||
+9
-1
@@ -26,6 +26,7 @@ BATTLE="*res://battle/Battle.gd"
|
|||||||
PARTY="*res://party/Party.gd"
|
PARTY="*res://party/Party.gd"
|
||||||
COOKING="*res://cooking/Cooking.gd"
|
COOKING="*res://cooking/Cooking.gd"
|
||||||
SAVE="*res://save/Save.gd"
|
SAVE="*res://save/Save.gd"
|
||||||
|
SETTINGS="*res://scene/Settings.gd"
|
||||||
CUTSCENE="*res://cutscene/CutsceneSingleton.gd"
|
CUTSCENE="*res://cutscene/CutsceneSingleton.gd"
|
||||||
UI="*res://ui/UISingleton.gd"
|
UI="*res://ui/UISingleton.gd"
|
||||||
ControllerIcons="*res://addons/controller_icons/ControllerIcons.gd"
|
ControllerIcons="*res://addons/controller_icons/ControllerIcons.gd"
|
||||||
@@ -106,11 +107,12 @@ interact={
|
|||||||
pause={
|
pause={
|
||||||
"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":0,"physical_keycode":4194305,"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":0,"physical_keycode":4194305,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":6,"pressure":0.0,"pressed":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
debug={
|
debug={
|
||||||
"deadzone": 0.5,
|
"deadzone": 0.5,
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194306,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194332,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
run={
|
run={
|
||||||
@@ -118,6 +120,12 @@ run={
|
|||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
menu={
|
||||||
|
"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":0,"physical_keycode":4194306,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":3,"pressure":0.0,"pressed":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
camera_orbit_left={
|
camera_orbit_left={
|
||||||
"deadzone": 0.15,
|
"deadzone": 0.15,
|
||||||
"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":2,"axis_value":-1.0,"script":null)
|
"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":2,"axis_value":-1.0,"script":null)
|
||||||
|
|||||||
+13
-28
@@ -1,31 +1,16 @@
|
|||||||
class_name PauseSingleton extends Node
|
class_name PauseSingleton extends Node
|
||||||
|
|
||||||
# var cutscenePaused:bool = false
|
func _ready() -> void:
|
||||||
|
process_mode = Node.PROCESS_MODE_ALWAYS
|
||||||
|
|
||||||
# func cutscenePause() -> void:
|
func _unhandled_input(event:InputEvent) -> void:
|
||||||
# cutscenePaused = true
|
if !event.is_action_pressed("pause"):
|
||||||
|
return
|
||||||
# func cutsceneResume() -> void:
|
var menu:PauseMenu = UI.PAUSE_MENU
|
||||||
# cutscenePaused = false
|
if menu == null:
|
||||||
|
return
|
||||||
# func isMovementPaused() -> bool:
|
if menu.isOpen():
|
||||||
# if cutscenePaused:
|
menu.close()
|
||||||
# return true
|
else:
|
||||||
|
menu.open()
|
||||||
# if !UI.TEXTBOX.isClosed:
|
get_viewport().set_input_as_handled()
|
||||||
# return true
|
|
||||||
|
|
||||||
# if UI.PAUSE.isOpen():
|
|
||||||
# return true
|
|
||||||
|
|
||||||
# if OVERWORLD.isMapChanging():
|
|
||||||
# return true
|
|
||||||
|
|
||||||
# return false
|
|
||||||
|
|
||||||
# func menuPause() -> void:
|
|
||||||
# # if UI.PAUSE.isOpen():
|
|
||||||
# # UI.PAUSE.close()
|
|
||||||
# # else:
|
|
||||||
# # UI.PAUSE.open()
|
|
||||||
# pass
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
var invertCameraX:bool = false
|
||||||
|
var invertCameraY:bool = false
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cvrjh5g0gy883
|
||||||
@@ -2,6 +2,8 @@ class_name RootUI extends Control
|
|||||||
|
|
||||||
@export var debugMenu:DebugMenu
|
@export var debugMenu:DebugMenu
|
||||||
@export var textBox:VNTextbox
|
@export var textBox:VNTextbox
|
||||||
|
@export var gameMenu:GameMenu
|
||||||
|
@export var pauseMenu:PauseMenu
|
||||||
|
|
||||||
func _enter_tree() -> void:
|
func _enter_tree() -> void:
|
||||||
UI.rootUi = self
|
UI.rootUi = self
|
||||||
|
|||||||
+10
-2
@@ -1,11 +1,12 @@
|
|||||||
[gd_scene load_steps=5 format=3 uid="uid://baos0arpiskbp"]
|
[gd_scene load_steps=6 format=3 uid="uid://baos0arpiskbp"]
|
||||||
|
|
||||||
[ext_resource type="PackedScene" uid="uid://bkx3l0kckf4a8" path="res://ui/component/VNTextbox.tscn" id="1_1mtk3"]
|
[ext_resource type="PackedScene" uid="uid://bkx3l0kckf4a8" path="res://ui/component/VNTextbox.tscn" id="1_1mtk3"]
|
||||||
[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"]
|
||||||
|
|
||||||
[node name="RootUI" type="Control" node_paths=PackedStringArray("debugMenu", "textBox")]
|
[node name="RootUI" type="Control" node_paths=PackedStringArray("debugMenu", "textBox", "gameMenu", "pauseMenu")]
|
||||||
layout_mode = 3
|
layout_mode = 3
|
||||||
anchors_preset = 15
|
anchors_preset = 15
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
@@ -16,6 +17,8 @@ mouse_filter = 2
|
|||||||
script = ExtResource("1_son71")
|
script = ExtResource("1_son71")
|
||||||
debugMenu = NodePath("DebugMenu")
|
debugMenu = NodePath("DebugMenu")
|
||||||
textBox = NodePath("VNTextbox")
|
textBox = NodePath("VNTextbox")
|
||||||
|
gameMenu = NodePath("GameMenu")
|
||||||
|
pauseMenu = NodePath("PauseMenu")
|
||||||
metadata/_custom_type_script = "uid://dq3qyyayugt5l"
|
metadata/_custom_type_script = "uid://dq3qyyayugt5l"
|
||||||
|
|
||||||
[node name="DebugMenu" parent="." instance=ExtResource("4_u132g")]
|
[node name="DebugMenu" parent="." instance=ExtResource("4_u132g")]
|
||||||
@@ -25,6 +28,11 @@ layout_mode = 1
|
|||||||
[node name="PauseMenu" parent="." instance=ExtResource("2_atyu8")]
|
[node name="PauseMenu" parent="." instance=ExtResource("2_atyu8")]
|
||||||
visible = false
|
visible = false
|
||||||
layout_mode = 1
|
layout_mode = 1
|
||||||
|
process_mode = 3
|
||||||
|
|
||||||
|
[node name="GameMenu" parent="." instance=ExtResource("5_gmenu")]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 1
|
||||||
|
|
||||||
[node name="VNTextbox" parent="." instance=ExtResource("1_1mtk3")]
|
[node name="VNTextbox" parent="." instance=ExtResource("1_1mtk3")]
|
||||||
visible = false
|
visible = false
|
||||||
|
|||||||
+13
-1
@@ -16,4 +16,16 @@ var TEXTBOX:
|
|||||||
get():
|
get():
|
||||||
if rootUi && rootUi.textBox:
|
if rootUi && rootUi.textBox:
|
||||||
return rootUi.textBox
|
return rootUi.textBox
|
||||||
return null
|
return null
|
||||||
|
|
||||||
|
var GAME_MENU:
|
||||||
|
get():
|
||||||
|
if rootUi && rootUi.gameMenu:
|
||||||
|
return rootUi.gameMenu
|
||||||
|
return null
|
||||||
|
|
||||||
|
var PAUSE_MENU:
|
||||||
|
get():
|
||||||
|
if rootUi && rootUi.pauseMenu:
|
||||||
|
return rootUi.pauseMenu
|
||||||
|
return null
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
class_name GameMenu extends Control
|
||||||
|
|
||||||
|
enum Tab { PARTY, ITEMS }
|
||||||
|
|
||||||
|
@export var SIDEBAR:ItemList
|
||||||
|
@export var PARTY_TAB:GameMenuPartyTab
|
||||||
|
@export var ITEMS_TAB:GameMenuItemsTab
|
||||||
|
|
||||||
|
var _currentTab:Tab = Tab.PARTY
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
visible = false
|
||||||
|
SIDEBAR.item_selected.connect(_onTabSelected)
|
||||||
|
|
||||||
|
func open() -> void:
|
||||||
|
visible = true
|
||||||
|
_selectTab(_currentTab)
|
||||||
|
SIDEBAR.select(_currentTab)
|
||||||
|
SIDEBAR.grab_focus()
|
||||||
|
|
||||||
|
func close() -> void:
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
func isOpen() -> bool:
|
||||||
|
return visible
|
||||||
|
|
||||||
|
func _onTabSelected(index:int) -> void:
|
||||||
|
_selectTab(index as Tab)
|
||||||
|
|
||||||
|
func _selectTab(tab:Tab) -> void:
|
||||||
|
_currentTab = tab
|
||||||
|
PARTY_TAB.visible = (tab == Tab.PARTY)
|
||||||
|
ITEMS_TAB.visible = (tab == Tab.ITEMS)
|
||||||
|
match tab:
|
||||||
|
Tab.PARTY:
|
||||||
|
PARTY_TAB.refresh()
|
||||||
|
Tab.ITEMS:
|
||||||
|
ITEMS_TAB.refresh()
|
||||||
|
|
||||||
|
func _unhandled_input(event:InputEvent) -> void:
|
||||||
|
if event.is_action_pressed("menu"):
|
||||||
|
if visible:
|
||||||
|
close()
|
||||||
|
elif !UI.dialogueActive && UI.TEXTBOX.isClosed:
|
||||||
|
open()
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
return
|
||||||
|
if !visible:
|
||||||
|
return
|
||||||
|
if event.is_action_pressed("ui_cancel"):
|
||||||
|
close()
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dvqu7spul754e
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
[gd_scene load_steps=5 format=3 uid="uid://bv5r2x9m4k7n1"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://dn8p3y6a1s5t2" path="res://ui/gamemenu/GameMenu.gd" id="1_gmgd"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://br9c7x4t1n6q2" path="res://ui/gamemenu/GameMenuPartyTab.tscn" id="2_ptab"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://bq4m7v2k9d3c1" path="res://ui/gamemenu/GameMenuItemsTab.tscn" id="3_itab"]
|
||||||
|
|
||||||
|
[node name="GameMenu" type="Control" node_paths=PackedStringArray("SIDEBAR", "PARTY_TAB", "ITEMS_TAB")]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 0
|
||||||
|
script = ExtResource("1_gmgd")
|
||||||
|
SIDEBAR = NodePath("MarginContainer/HBoxContainer/SidebarPanel/VBoxContainer/Sidebar")
|
||||||
|
PARTY_TAB = NodePath("MarginContainer/HBoxContainer/ContentPanel/GameMenuPartyTab")
|
||||||
|
ITEMS_TAB = NodePath("MarginContainer/HBoxContainer/ContentPanel/GameMenuItemsTab")
|
||||||
|
|
||||||
|
[node name="ColorRect" type="ColorRect" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
color = Color(0, 0, 0, 0.75)
|
||||||
|
|
||||||
|
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
theme_override_constants/margin_left = 16
|
||||||
|
theme_override_constants/margin_right = 16
|
||||||
|
theme_override_constants/margin_top = 12
|
||||||
|
theme_override_constants/margin_bottom = 12
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 8
|
||||||
|
|
||||||
|
[node name="SidebarPanel" type="PanelContainer" parent="MarginContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
custom_minimum_size = Vector2(120, 0)
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/HBoxContainer/SidebarPanel"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 4
|
||||||
|
|
||||||
|
[node name="MenuTitle" type="Label" parent="MarginContainer/HBoxContainer/SidebarPanel/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
horizontal_alignment = 1
|
||||||
|
text = "MENU"
|
||||||
|
|
||||||
|
[node name="Sidebar" type="ItemList" parent="MarginContainer/HBoxContainer/SidebarPanel/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
auto_width = true
|
||||||
|
item_count = 2
|
||||||
|
item_0/text = "Party"
|
||||||
|
item_1/text = "Items"
|
||||||
|
|
||||||
|
[node name="ContentPanel" type="PanelContainer" parent="MarginContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="GameMenuPartyTab" parent="MarginContainer/HBoxContainer/ContentPanel" instance=ExtResource("2_ptab")]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="GameMenuItemsTab" parent="MarginContainer/HBoxContainer/ContentPanel" instance=ExtResource("3_itab")]
|
||||||
|
layout_mode = 2
|
||||||
|
visible = false
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
class_name GameMenuItemsTab extends Control
|
||||||
|
|
||||||
|
func refresh() -> void:
|
||||||
|
var container = $ScrollContainer/ItemContainer
|
||||||
|
for child in container.get_children():
|
||||||
|
child.queue_free()
|
||||||
|
|
||||||
|
var items = PARTY.BACKPACK.items
|
||||||
|
if items.is_empty():
|
||||||
|
var empty = Label.new()
|
||||||
|
empty.text = "No items."
|
||||||
|
container.add_child(empty)
|
||||||
|
return
|
||||||
|
|
||||||
|
for stack in items:
|
||||||
|
container.add_child(_makeItemRow(stack))
|
||||||
|
|
||||||
|
func _makeItemRow(stack:ItemStack) -> Control:
|
||||||
|
var panel = PanelContainer.new()
|
||||||
|
var hbox = HBoxContainer.new()
|
||||||
|
panel.add_child(hbox)
|
||||||
|
|
||||||
|
var nameLabel = Label.new()
|
||||||
|
nameLabel.text = Item.getItemHandle(stack.item).capitalize()
|
||||||
|
nameLabel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
hbox.add_child(nameLabel)
|
||||||
|
|
||||||
|
var qtyLabel = Label.new()
|
||||||
|
qtyLabel.text = "x%d" % stack.quantity
|
||||||
|
hbox.add_child(qtyLabel)
|
||||||
|
|
||||||
|
return panel
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bmhnyuhkwti7i
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://bq4m7v2k9d3c1"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://cx6h2j9r4b8w5" path="res://ui/gamemenu/GameMenuItemsTab.gd" id="1_itabgd"]
|
||||||
|
|
||||||
|
[node name="GameMenuItemsTab" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1_itabgd")
|
||||||
|
|
||||||
|
[node name="ScrollContainer" type="ScrollContainer" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="ItemContainer" type="VBoxContainer" parent="ScrollContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_constants/separation = 4
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
class_name GameMenuPartyTab extends Control
|
||||||
|
|
||||||
|
func refresh() -> void:
|
||||||
|
var list = $ScrollContainer/MemberList
|
||||||
|
for child in list.get_children():
|
||||||
|
child.queue_free()
|
||||||
|
for member in PARTY.getFullParty():
|
||||||
|
list.add_child(_makeMemberCard(member))
|
||||||
|
|
||||||
|
func _makeMemberCard(member:PartyMember) -> Control:
|
||||||
|
var panel = PanelContainer.new()
|
||||||
|
var margin = MarginContainer.new()
|
||||||
|
panel.add_child(margin)
|
||||||
|
margin.add_theme_constant_override("margin_left", 8)
|
||||||
|
margin.add_theme_constant_override("margin_right", 8)
|
||||||
|
margin.add_theme_constant_override("margin_top", 6)
|
||||||
|
margin.add_theme_constant_override("margin_bottom", 6)
|
||||||
|
|
||||||
|
var vbox = VBoxContainer.new()
|
||||||
|
margin.add_child(vbox)
|
||||||
|
|
||||||
|
# Name + status row
|
||||||
|
var headerRow = HBoxContainer.new()
|
||||||
|
vbox.add_child(headerRow)
|
||||||
|
|
||||||
|
var nameLabel = Label.new()
|
||||||
|
nameLabel.text = member.name
|
||||||
|
nameLabel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
headerRow.add_child(nameLabel)
|
||||||
|
|
||||||
|
var statusLabel = Label.new()
|
||||||
|
statusLabel.text = "DEAD" if member.status == BattleFighter.Status.DEAD else "OK"
|
||||||
|
headerRow.add_child(statusLabel)
|
||||||
|
|
||||||
|
# HP row
|
||||||
|
var hpRow = HBoxContainer.new()
|
||||||
|
hpRow.add_theme_constant_override("separation", 6)
|
||||||
|
vbox.add_child(hpRow)
|
||||||
|
var hpKey = Label.new()
|
||||||
|
hpKey.text = "HP"
|
||||||
|
hpKey.custom_minimum_size = Vector2(30, 0)
|
||||||
|
hpRow.add_child(hpKey)
|
||||||
|
var hpVal = Label.new()
|
||||||
|
hpVal.text = "%d / %d" % [member.health, member.maxHealth]
|
||||||
|
hpRow.add_child(hpVal)
|
||||||
|
|
||||||
|
# MP row
|
||||||
|
var mpRow = HBoxContainer.new()
|
||||||
|
mpRow.add_theme_constant_override("separation", 6)
|
||||||
|
vbox.add_child(mpRow)
|
||||||
|
var mpKey = Label.new()
|
||||||
|
mpKey.text = "MP"
|
||||||
|
mpKey.custom_minimum_size = Vector2(30, 0)
|
||||||
|
mpRow.add_child(mpKey)
|
||||||
|
var mpVal = Label.new()
|
||||||
|
mpVal.text = "%d / %d" % [member.mp, member.maxMp]
|
||||||
|
mpRow.add_child(mpVal)
|
||||||
|
|
||||||
|
# Stats row
|
||||||
|
var statsRow = HBoxContainer.new()
|
||||||
|
statsRow.add_theme_constant_override("separation", 14)
|
||||||
|
vbox.add_child(statsRow)
|
||||||
|
for pair in [["ATK", member.attack], ["DEF", member.defense], ["SPD", member.speed], ["MAG", member.magic], ["LCK", member.luck]]:
|
||||||
|
var label = Label.new()
|
||||||
|
label.text = "%s %d" % [pair[0], pair[1]]
|
||||||
|
statsRow.add_child(label)
|
||||||
|
|
||||||
|
return panel
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bfklpf0grikg5
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://br9c7x4t1n6q2"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://dw3n5k8m2p1a7" path="res://ui/gamemenu/GameMenuPartyTab.gd" id="1_ptabgd"]
|
||||||
|
|
||||||
|
[node name="GameMenuPartyTab" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1_ptabgd")
|
||||||
|
|
||||||
|
[node name="ScrollContainer" type="ScrollContainer" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="MemberList" type="VBoxContainer" parent="ScrollContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_constants/separation = 6
|
||||||
+12
-6
@@ -1,19 +1,25 @@
|
|||||||
class_name PauseMain extends VBoxContainer
|
class_name PauseMain extends VBoxContainer
|
||||||
|
|
||||||
|
signal resumeRequested
|
||||||
|
signal settingsRequested
|
||||||
|
signal quitRequested
|
||||||
|
|
||||||
|
@export var btnResume:Button
|
||||||
|
@export var btnSettings:Button
|
||||||
|
@export var btnQuit:Button
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
visible = false
|
visible = false
|
||||||
$HBoxContainer/ItemList.item_selected.connect(onItemSelected)
|
btnResume.pressed.connect(resumeRequested.emit)
|
||||||
|
btnSettings.pressed.connect(settingsRequested.emit)
|
||||||
|
btnQuit.pressed.connect(quitRequested.emit)
|
||||||
|
|
||||||
func open() -> void:
|
func open() -> void:
|
||||||
visible = true
|
visible = true
|
||||||
$HBoxContainer/ItemList.clear()
|
btnResume.grab_focus()
|
||||||
$HBoxContainer/ItemList.grab_focus()
|
|
||||||
|
|
||||||
func close() -> void:
|
func close() -> void:
|
||||||
visible = false
|
visible = false
|
||||||
|
|
||||||
func isOpen() -> bool:
|
func isOpen() -> bool:
|
||||||
return visible
|
return visible
|
||||||
|
|
||||||
func onItemSelected(index:int) -> void:
|
|
||||||
print("Selected item index: ", index)
|
|
||||||
|
|||||||
+18
-36
@@ -2,51 +2,33 @@
|
|||||||
|
|
||||||
[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 name="PauseMain" type="VBoxContainer" node_paths=PackedStringArray("btnResume", "btnSettings", "btnQuit")]
|
||||||
anchors_preset = 15
|
anchors_preset = 8
|
||||||
anchor_right = 1.0
|
anchor_left = 0.5
|
||||||
anchor_bottom = 1.0
|
anchor_top = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
anchor_bottom = 0.5
|
||||||
grow_horizontal = 2
|
grow_horizontal = 2
|
||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
script = ExtResource("1_b5xfl")
|
script = ExtResource("1_b5xfl")
|
||||||
metadata/_custom_type_script = "uid://c7kvg0jw6w340"
|
metadata/_custom_type_script = "uid://c7kvg0jw6w340"
|
||||||
|
btnResume = NodePath("Resume")
|
||||||
|
btnSettings = NodePath("Settings")
|
||||||
|
btnQuit = NodePath("Quit")
|
||||||
|
|
||||||
[node name="HBoxContainer" type="HBoxContainer" parent="."]
|
[node name="Title" type="Label" parent="."]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_vertical = 3
|
text = "Paused"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
[node name="PanelContainer" type="PanelContainer" parent="HBoxContainer"]
|
[node name="Resume" type="Button" parent="."]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 3
|
text = "Resume"
|
||||||
|
|
||||||
[node name="ItemList" type="ItemList" parent="HBoxContainer"]
|
[node name="Settings" type="Button" parent="."]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
auto_width = true
|
text = "Settings"
|
||||||
item_count = 6
|
|
||||||
item_0/text = "Equpiment"
|
|
||||||
item_1/text = "Abilities"
|
|
||||||
item_2/text = "Items"
|
|
||||||
item_3/text = "Quests"
|
|
||||||
item_4/text = "Settings"
|
|
||||||
item_5/text = "Save"
|
|
||||||
|
|
||||||
[node name="HBoxContainer2" type="HBoxContainer" parent="."]
|
[node name="Quit" type="Button" parent="."]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
text = "Quit Game"
|
||||||
[node name="PanelContainer" type="PanelContainer" parent="HBoxContainer2"]
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 3
|
|
||||||
|
|
||||||
[node name="HBoxContainer" type="GridContainer" parent="HBoxContainer2/PanelContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 8
|
|
||||||
theme_override_constants/h_separation = 8
|
|
||||||
columns = 2
|
|
||||||
|
|
||||||
[node name="Money" type="Label" parent="HBoxContainer2/PanelContainer/HBoxContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "%MONEY%"
|
|
||||||
|
|
||||||
[node name="Playtime" type="Label" parent="HBoxContainer2/PanelContainer/HBoxContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "%PLAYTIME%"
|
|
||||||
|
|||||||
+13
-4
@@ -1,29 +1,38 @@
|
|||||||
class_name PauseMenu extends Control
|
class_name PauseMenu extends Control
|
||||||
|
|
||||||
@export var MAIN:PauseMain
|
@export var MAIN:PauseMain
|
||||||
@export var SETTINGS:PauseSettings
|
@export var settingsPanel:PauseSettings
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
close()
|
close()
|
||||||
|
MAIN.resumeRequested.connect(close)
|
||||||
|
MAIN.settingsRequested.connect(_openSettings)
|
||||||
|
MAIN.quitRequested.connect(func(): get_tree().quit())
|
||||||
|
|
||||||
func isOpen() -> bool:
|
func isOpen() -> bool:
|
||||||
return visible
|
return visible
|
||||||
|
|
||||||
func open() -> void:
|
func open() -> void:
|
||||||
visible = true
|
visible = true
|
||||||
|
get_tree().paused = true
|
||||||
MAIN.open()
|
MAIN.open()
|
||||||
|
|
||||||
func close() -> void:
|
func close() -> void:
|
||||||
|
get_tree().paused = false
|
||||||
visible = false
|
visible = false
|
||||||
MAIN.close()
|
MAIN.close()
|
||||||
SETTINGS.close()
|
settingsPanel.close()
|
||||||
|
|
||||||
|
func _openSettings() -> void:
|
||||||
|
MAIN.close()
|
||||||
|
settingsPanel.open()
|
||||||
|
|
||||||
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 SETTINGS.isOpen():
|
if settingsPanel.isOpen():
|
||||||
SETTINGS.close()
|
settingsPanel.close()
|
||||||
MAIN.open()
|
MAIN.open()
|
||||||
else:
|
else:
|
||||||
close()
|
close()
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
[ext_resource type="Script" uid="uid://cgvf34t5qgwbm" path="res://ui/pause/PauseMenu.gd" id="1_82qxy"]
|
[ext_resource type="Script" uid="uid://cgvf34t5qgwbm" path="res://ui/pause/PauseMenu.gd" id="1_82qxy"]
|
||||||
[ext_resource type="PackedScene" uid="uid://qgk5trrh6dfd" path="res://ui/pause/PauseSettings.tscn" id="2_3djnw"]
|
[ext_resource type="PackedScene" uid="uid://qgk5trrh6dfd" path="res://ui/pause/PauseSettings.tscn" id="2_3djnw"]
|
||||||
|
|
||||||
[node name="PauseMenu" type="Control" node_paths=PackedStringArray("MAIN", "SETTINGS")]
|
[node name="PauseMenu" type="Control" node_paths=PackedStringArray("MAIN", "settingsPanel")]
|
||||||
layout_mode = 3
|
layout_mode = 3
|
||||||
anchors_preset = 15
|
anchors_preset = 15
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
@@ -13,7 +13,7 @@ grow_horizontal = 2
|
|||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
script = ExtResource("1_82qxy")
|
script = ExtResource("1_82qxy")
|
||||||
MAIN = NodePath("PauseMain")
|
MAIN = NodePath("PauseMain")
|
||||||
SETTINGS = NodePath("PauseSettings")
|
settingsPanel = NodePath("PauseSettings")
|
||||||
|
|
||||||
[node name="PauseSettings" parent="." instance=ExtResource("2_3djnw")]
|
[node name="PauseSettings" parent="." instance=ExtResource("2_3djnw")]
|
||||||
visible = false
|
visible = false
|
||||||
|
|||||||
@@ -2,9 +2,15 @@ class_name SettingsMenu extends Control
|
|||||||
|
|
||||||
@export var tabs:TabBar
|
@export var tabs:TabBar
|
||||||
@export var tabControls:Array[Control]
|
@export var tabControls:Array[Control]
|
||||||
|
@export var checkInvertX:CheckBox
|
||||||
|
@export var checkInvertY:CheckBox
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
tabs.tab_changed.connect(onTabChanged)
|
tabs.tab_changed.connect(onTabChanged)
|
||||||
|
checkInvertX.button_pressed = SETTINGS.invertCameraX
|
||||||
|
checkInvertY.button_pressed = SETTINGS.invertCameraY
|
||||||
|
checkInvertX.toggled.connect(func(v:bool): SETTINGS.invertCameraX = v)
|
||||||
|
checkInvertY.toggled.connect(func(v:bool): SETTINGS.invertCameraY = v)
|
||||||
onTabChanged(tabs.current_tab)
|
onTabChanged(tabs.current_tab)
|
||||||
|
|
||||||
func _notification(what:int) -> void:
|
func _notification(what:int) -> void:
|
||||||
@@ -16,3 +22,10 @@ func onTabChanged(tabIndex:int) -> void:
|
|||||||
control.visible = false
|
control.visible = false
|
||||||
if tabIndex >= 0 and tabIndex < tabControls.size():
|
if tabIndex >= 0 and tabIndex < tabControls.size():
|
||||||
tabControls[tabIndex].visible = true
|
tabControls[tabIndex].visible = true
|
||||||
|
_focusFirstIn(tabControls[tabIndex])
|
||||||
|
|
||||||
|
func _focusFirstIn(container:Control) -> void:
|
||||||
|
for child in container.get_children():
|
||||||
|
if child is Control and child.focus_mode != Control.FOCUS_NONE:
|
||||||
|
child.grab_focus()
|
||||||
|
return
|
||||||
|
|||||||
@@ -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")]
|
[node name="SettingsMenu" type="Control" node_paths=PackedStringArray("tabs", "tabControls", "checkInvertX", "checkInvertY")]
|
||||||
layout_mode = 3
|
layout_mode = 3
|
||||||
anchors_preset = 15
|
anchors_preset = 15
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
@@ -11,7 +11,9 @@ 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")]
|
tabControls = [NodePath("VBoxContainer/ScrollContainer/LabelGameplay"), NodePath("VBoxContainer/ScrollContainer/LabelSound"), NodePath("VBoxContainer/ScrollContainer/LabelGraphics"), NodePath("VBoxContainer/ScrollContainer/PanelControls")]
|
||||||
|
checkInvertX = NodePath("VBoxContainer/ScrollContainer/PanelControls/CheckInvertX")
|
||||||
|
checkInvertY = NodePath("VBoxContainer/ScrollContainer/PanelControls/CheckInvertY")
|
||||||
|
|
||||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||||
layout_mode = 1
|
layout_mode = 1
|
||||||
@@ -24,10 +26,11 @@ grow_vertical = 2
|
|||||||
[node name="TabBar" type="TabBar" parent="VBoxContainer"]
|
[node name="TabBar" type="TabBar" parent="VBoxContainer"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
current_tab = 0
|
current_tab = 0
|
||||||
tab_count = 3
|
tab_count = 4
|
||||||
tab_0/title = "Gameplay"
|
tab_0/title = "Gameplay"
|
||||||
tab_1/title = "Sound"
|
tab_1/title = "Sound"
|
||||||
tab_2/title = "Graphics"
|
tab_2/title = "Graphics"
|
||||||
|
tab_3/title = "Controls"
|
||||||
|
|
||||||
[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer"]
|
[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
@@ -47,3 +50,21 @@ text = "Sound"
|
|||||||
visible = false
|
visible = false
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = "Gameplay"
|
text = "Gameplay"
|
||||||
|
|
||||||
|
[node name="PanelControls" type="VBoxContainer" parent="VBoxContainer/ScrollContainer"]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="LabelController" type="Label" parent="VBoxContainer/ScrollContainer/PanelControls"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Controller"
|
||||||
|
|
||||||
|
[node name="CheckInvertX" type="CheckBox" parent="VBoxContainer/ScrollContainer/PanelControls"]
|
||||||
|
layout_mode = 2
|
||||||
|
focus_mode = 2
|
||||||
|
text = "Invert Camera X"
|
||||||
|
|
||||||
|
[node name="CheckInvertY" type="CheckBox" parent="VBoxContainer/ScrollContainer/PanelControls"]
|
||||||
|
layout_mode = 2
|
||||||
|
focus_mode = 2
|
||||||
|
text = "Invert Camera Y"
|
||||||
|
|||||||
Reference in New Issue
Block a user