Improved UI textbox
This commit is contained in:
+46
-24
@@ -102,7 +102,7 @@ The speaker name in a `.dialogue` line (e.g. `Guard: Halt!`) is matched against
|
||||
- The **display name** shown to the player is a separate property and can differ (e.g. `dialogueName = "guard_captain"` but display name = `"???"` before the character has revealed their name). This lets you write dialogue that references a speaker before the player knows who they are.
|
||||
- **Every dialogue line must have a speaker.** There is no narrator/no-speaker path — if a line has no natural speaker, use a named invisible anchor Entity in the scene.
|
||||
|
||||
> **Not yet implemented:** the entity-lookup and 3D→UI projection logic. Currently `DialogueAction` passes a target node manually.
|
||||
> **Implemented:** Entity lookup is live — `OVERWORLD.getEntityByDialogueName(line.character)` resolves the speaker. 3D→UI projection runs every frame in `DialogueTextbox._updateWorldPosition()`.
|
||||
|
||||
### All conversation routes through DialogueManager
|
||||
|
||||
@@ -114,7 +114,7 @@ Whether a line of dialogue is triggered by:
|
||||
|
||||
…it is always written in a `.dialogue` file and played back via `DialogueManager.get_next_dialogue_line()`. There is no separate "dumb string" path for simple one-liners.
|
||||
|
||||
> **Migration needed:** the current `CHATBOX` interact type (WorldChatBox, plain string) should be replaced with a one-line `.dialogue` file going through the same pipeline.
|
||||
> **Done:** `CHATBOX` interact type removed. All NPC text now routes through DialogueManager.
|
||||
|
||||
### Dialogue and movement control
|
||||
|
||||
@@ -125,7 +125,7 @@ Dialogue does **not** automatically block movement. Each dialogue sequence decla
|
||||
|
||||
This is configured per `DialogueAction` call, not per line.
|
||||
|
||||
> **Partially implemented:** `DialogueAction` always sets `UI.dialogueActive`. The non-blocking path does not exist yet.
|
||||
> **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.
|
||||
|
||||
### Text reveal (scrolling)
|
||||
|
||||
@@ -139,11 +139,13 @@ Body text is revealed character-by-character at a speed approximating natural hu
|
||||
|
||||
Holding the **Interact** bind speeds up reveal to near-instant. Once all characters on the current page are revealed, pressing Interact advances to the next page or the next dialogue line.
|
||||
|
||||
The `[wait=N]` and `[speed=N]` BBCode tags from DialogueManager can override reveal behaviour inline for specific moments (e.g. a shocked line flashing by at `[speed=4]`).
|
||||
The plugin returns `DialogueLine.text` as a plain BBCode string — it does **not** handle punctuation pauses or `...` detection. All reveal pacing is implemented in our textbox, not the plugin.
|
||||
|
||||
**ADA note:** fast reveal speeds (via `[speed=N]` or hold-to-skip) should not flash entire blocks of text in a way that could trigger photosensitive responses. Avoid using `[speed=N]` values so high that multiple lines appear simultaneously. The hold-to-skip path is safe since it requires sustained player input.
|
||||
The `[wait=N]` and `[speed=N]` BBCode tags are extracted from the text by the plugin during line resolution (stored in `DialogueLine.speeds` and inline mutation data). Our textbox must read these to adjust reveal behaviour inline — e.g. a shocked line using `[speed=4]` to flash past quickly.
|
||||
|
||||
> **Partially implemented:** `VNTextbox` has character-by-character reveal and hold-to-skip. Punctuation pause logic is not yet implemented.
|
||||
**ADA note:** fast reveal speeds (via `[speed=N]` or hold-to-skip) should not flash entire blocks of text in a way that could trigger photosensitive responses. Avoid `[speed=N]` values so high that multiple lines appear simultaneously. The hold-to-skip path is safe since it requires sustained player input.
|
||||
|
||||
> **Implemented:** `DialogueTextbox` has character-by-character reveal, punctuation pauses, `...` detection, and hold-to-skip. `[wait=N]` / `[speed=N]` BBCode tag support is not yet wired.
|
||||
|
||||
### Advancement modes
|
||||
|
||||
@@ -151,12 +153,12 @@ Each dialogue sequence uses one of two advancement modes:
|
||||
|
||||
| Mode | Behaviour |
|
||||
|---|---|
|
||||
| `PLAYER` (default) | Player presses Interact to advance to the next page or line once reveal is complete. |
|
||||
| `TIMED` | Each line auto-advances after a delay estimated from line length (reading speed). A per-line `[next=N]` override is supported for lines that need a specific timing. Player input is ignored. |
|
||||
| `PLAYER` (default) | Player presses Interact to advance to the next page or line once reveal is complete. Used by `CONVERSATION` and `NARRATION` modes. |
|
||||
| `TIMED` | Each line auto-advances after a delay estimated from line length (reading speed). A per-line `[next=N]` tag overrides the estimated delay. Player input is ignored. Used by `AMBIENT` mode. |
|
||||
|
||||
Mixed sequences (some lines timed, some player-advanced) are not supported — the mode is set per conversation, not per line.
|
||||
Mixed sequences (some lines timed, some player-advanced) are not supported — advancement mode is determined by `DialogueMode`, not per line.
|
||||
|
||||
> **Not yet implemented:** `TIMED` mode. Currently all dialogue requires player input.
|
||||
> **Implemented:** `TIMED` auto-advance is wired in `DialogueTextbox._processAutoAdvance`. Delay is estimated from line length (characters / `READING_CHARS_PER_SECOND`). Per-line `[next=N]` override is not yet wired.
|
||||
|
||||
### Response / choice UI
|
||||
|
||||
@@ -164,7 +166,7 @@ When a `DialogueLine` has a non-empty `responses` array, reveal pauses and a cho
|
||||
|
||||
The choice textbox follows the same world-space anchor and screen-edge clamping rules as all other textboxes.
|
||||
|
||||
> **Not yet implemented:** `DialogueAction` currently auto-selects the first allowed response. Replace the auto-select block with a call to the response UI and await its selection signal.
|
||||
> **Implemented:** `DialogueChoiceBox` is shown when allowed responses exist, anchored to the player entity via `OVERWORLD.getPlayerEntity()`. Player navigates with ui_up/ui_down or move_forward/move_back, confirms with Interact.
|
||||
|
||||
### Trigger types
|
||||
|
||||
@@ -179,21 +181,37 @@ The choice textbox follows the same world-space anchor and screen-edge clamping
|
||||
|
||||
---
|
||||
|
||||
## DialogueMode enum
|
||||
|
||||
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 |
|
||||
|---|---|---|---|
|
||||
| `CONVERSATION` | Blocked | Player (Interact) | NPC interactions, cutscene dialogue |
|
||||
| `NARRATION` | Non-blocking | Player (Interact) | Item pickups, announcements the player can dismiss when ready |
|
||||
| `AMBIENT` | Non-blocking | 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.
|
||||
|
||||
More modes can be added to this enum as new use cases arise.
|
||||
|
||||
---
|
||||
|
||||
## DialogueAction — the Cutscene bridge
|
||||
|
||||
[cutscene/dialogue/DialogueAction.gd](../../cutscene/dialogue/DialogueAction.gd) is the glue between the `Cutscene` queue and `DialogueManager`. It fetches lines via `get_next_dialogue_line()`, displays them in the world-space textbox, and returns `CUTSCENE_CONTINUE` when the last line is dismissed.
|
||||
|
||||
```gdscript
|
||||
# Add a blocking dialogue step to any Cutscene
|
||||
# Add a dialogue step to any Cutscene
|
||||
cutscene.addCallable(DialogueAction.getDialogueCallable(
|
||||
load("res://dialogue/npc/test.dialogue"),
|
||||
"start", # section to begin from
|
||||
[entity], # extra_game_states exposed to {{variables}} in the file
|
||||
true # blocking — pauses player movement
|
||||
"res://dialogue/npc/guard", # base path — no locale suffix, no extension
|
||||
"start", # section to begin from
|
||||
[entity], # extra_game_states exposed to {{variables}} in the file
|
||||
DialogueAction.DialogueMode.CONVERSATION
|
||||
))
|
||||
```
|
||||
|
||||
The `blockMovement` argument (fourth param, default `true`) controls whether `UI.dialogueActive` is set for the duration.
|
||||
`DialogueAction` resolves the base path to the correct locale file at runtime (e.g. `guard.en.dialogue`), falling back to `en` if the active locale file is missing.
|
||||
|
||||
---
|
||||
|
||||
@@ -266,10 +284,12 @@ Set these exports on an Entity node whose `interactType = CONVERSATION`:
|
||||
|
||||
| Export | Purpose |
|
||||
|---|---|
|
||||
| `dialogueResource:DialogueResource` | The `.dialogue` file to run |
|
||||
| `dialogueBasePath:String` | Base path to the dialogue file, without locale suffix or extension (e.g. `"res://dialogue/npc/guard"`) |
|
||||
| `dialogueTitle:String` | Section to start from (default `"start"`) |
|
||||
| `dialogueName:String` | Key matched against `.dialogue` speaker names to find this entity's textbox anchor |
|
||||
| `displayName:String` | Player-visible name shown in the textbox speaker label (can differ from `dialogueName`, e.g. `"???"`) |
|
||||
|
||||
The entity's `entityId` must match the speaker name used in the dialogue file so the textbox anchor resolves correctly.
|
||||
`dialogueName` must match the speaker name used in the dialogue file. `entityId` is unrelated to dialogue lookup.
|
||||
|
||||
---
|
||||
|
||||
@@ -277,9 +297,9 @@ The entity's `entityId` must match the speaker name used in the dialogue file so
|
||||
|
||||
| File | Used by |
|
||||
|---|---|
|
||||
| `dialogue/npc/test.dialogue` | TestMap NPC (NotPlayer) |
|
||||
| `dialogue/item/pickup.dialogue` | `ItemAction` — item pickup text with `{{item_name}}` and `{{quantity}}` |
|
||||
| `dialogue/battle/narration.dialogue` | `BattleCutsceneAction` — move announcements, victory/defeat |
|
||||
| `dialogue/npc/test.en.dialogue` | TestMap NPC (NotPlayer / "Stranger") |
|
||||
| `dialogue/item/pickup.en.dialogue` | `ItemAction` — item pickup text with `{{item_name}}` and `{{quantity}}` |
|
||||
| `dialogue/battle/narration.en.dialogue` | `BattleCutsceneAction` — move announcements, victory/defeat |
|
||||
|
||||
---
|
||||
|
||||
@@ -292,11 +312,13 @@ dialogue/npc/test.en.dialogue
|
||||
dialogue/npc/test.ja.dialogue
|
||||
```
|
||||
|
||||
`DialogueAction.getDialogueCallable()` accepts a base path (without locale suffix) and resolves the active locale automatically, falling back to `en` if the locale file is missing.
|
||||
`DialogueAction.getDialogueCallable()` accepts a base path (no locale suffix, no extension — e.g. `"res://dialogue/npc/test"`) and resolves the active locale automatically via `TranslationServer.get_locale()` (e.g. → `test.en.dialogue`), falling back to `en` if the locale file is missing.
|
||||
|
||||
To switch language at runtime: `TranslationServer.set_locale("ja")` — all subsequent dialogue loads will use the new locale. The project default is configured in Godot's **Project Settings → Localization** tab.
|
||||
|
||||
Do not hardcode any visible text outside of `.dialogue` files. All player-facing strings — including item pickup messages, battle narration, and UI prompts — must live in a dialogue file so they are covered by the locale system.
|
||||
|
||||
> **Not yet implemented:** locale resolution in `DialogueAction`. Currently loads the resource directly.
|
||||
> **Implemented:** `DialogueAction._loadLocaleResource(basePath)` resolves the active locale via `TranslationServer.get_locale().left(2)`, falling back to `en` if the locale file is missing.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user