Envoi sur GitHub du projet updated
This commit is contained in:
@@ -5,6 +5,9 @@ namespace DialogueManagerRuntime
|
||||
{
|
||||
public partial class ExampleBalloon : CanvasLayer
|
||||
{
|
||||
[Export] public Resource DialogueResource;
|
||||
[Export] public string StartFromTitle = "";
|
||||
[Export] public bool AutoStart = false;
|
||||
[Export] public string NextAction = "ui_accept";
|
||||
[Export] public string SkipAction = "ui_cancel";
|
||||
|
||||
@@ -13,8 +16,8 @@ namespace DialogueManagerRuntime
|
||||
RichTextLabel characterLabel;
|
||||
RichTextLabel dialogueLabel;
|
||||
VBoxContainer responsesMenu;
|
||||
Polygon2D progress;
|
||||
|
||||
Resource resource;
|
||||
Array<Variant> temporaryGameStates = new Array<Variant>();
|
||||
bool isWaitingForInput = false;
|
||||
bool willHideBalloon = false;
|
||||
@@ -25,21 +28,27 @@ namespace DialogueManagerRuntime
|
||||
get => dialogueLine;
|
||||
set
|
||||
{
|
||||
isWaitingForInput = false;
|
||||
balloon.FocusMode = Control.FocusModeEnum.All;
|
||||
balloon.GrabFocus();
|
||||
|
||||
// Dialogue has finished so close the balloon
|
||||
if (value == null)
|
||||
{
|
||||
QueueFree();
|
||||
if (Owner == null)
|
||||
{
|
||||
QueueFree();
|
||||
}
|
||||
else
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
dialogueLine = value;
|
||||
UpdateDialogue();
|
||||
ApplyDialogueLine();
|
||||
}
|
||||
}
|
||||
|
||||
Timer MutationCooldown = new Timer();
|
||||
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
@@ -47,6 +56,7 @@ namespace DialogueManagerRuntime
|
||||
characterLabel = GetNode<RichTextLabel>("%CharacterLabel");
|
||||
dialogueLabel = GetNode<RichTextLabel>("%DialogueLabel");
|
||||
responsesMenu = GetNode<VBoxContainer>("%ResponsesMenu");
|
||||
progress = GetNode<Polygon2D>("%Progress");
|
||||
|
||||
balloon.Hide();
|
||||
|
||||
@@ -88,7 +98,28 @@ namespace DialogueManagerRuntime
|
||||
Next(response.NextId);
|
||||
}));
|
||||
|
||||
|
||||
// Hide the balloon when a mutation is running
|
||||
MutationCooldown.Timeout += () =>
|
||||
{
|
||||
if (willHideBalloon)
|
||||
{
|
||||
willHideBalloon = false;
|
||||
balloon.Hide();
|
||||
}
|
||||
};
|
||||
AddChild(MutationCooldown);
|
||||
|
||||
DialogueManager.Mutated += OnMutated;
|
||||
|
||||
if (AutoStart)
|
||||
{
|
||||
if (!IsInstanceValid(DialogueResource))
|
||||
{
|
||||
throw new System.Exception(DialogueManager.GetErrorMessage(143));
|
||||
}
|
||||
Start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -111,7 +142,7 @@ namespace DialogueManagerRuntime
|
||||
if (what == NotificationTranslationChanged && IsInstanceValid(dialogueLabel))
|
||||
{
|
||||
float visibleRatio = dialogueLabel.VisibleRatio;
|
||||
DialogueLine = await DialogueManager.GetNextDialogueLine(resource, DialogueLine.Id, temporaryGameStates);
|
||||
DialogueLine = await DialogueManager.GetNextDialogueLine(DialogueResource, DialogueLine.Id, temporaryGameStates);
|
||||
if (visibleRatio < 1.0f)
|
||||
{
|
||||
dialogueLabel.Call("skip_typing");
|
||||
@@ -120,36 +151,52 @@ namespace DialogueManagerRuntime
|
||||
}
|
||||
|
||||
|
||||
public async void Start(Resource dialogueResource, string title, Array<Variant> extraGameStates = null)
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (!IsNodeReady())
|
||||
base._Process(delta);
|
||||
|
||||
if (IsInstanceValid(dialogueLine))
|
||||
{
|
||||
await ToSignal(this, SignalName.Ready);
|
||||
progress.Visible = !(bool)dialogueLabel.Get("is_typing") && dialogueLine.Responses.Count == 0 && !dialogueLine.HasTag("voice");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async void Start(Resource dialogueResource = null, string title = "", Array<Variant> extraGameStates = null)
|
||||
{
|
||||
temporaryGameStates = new Array<Variant> { this } + (extraGameStates ?? new Array<Variant>());
|
||||
isWaitingForInput = false;
|
||||
|
||||
if (IsInstanceValid(dialogueResource))
|
||||
{
|
||||
DialogueResource = dialogueResource;
|
||||
}
|
||||
if (title != "")
|
||||
{
|
||||
StartFromTitle = title;
|
||||
}
|
||||
|
||||
temporaryGameStates = extraGameStates ?? new Array<Variant>();
|
||||
isWaitingForInput = false;
|
||||
resource = dialogueResource;
|
||||
|
||||
DialogueLine = await DialogueManager.GetNextDialogueLine(resource, title, temporaryGameStates);
|
||||
DialogueLine = await DialogueManager.GetNextDialogueLine(DialogueResource, StartFromTitle, temporaryGameStates);
|
||||
Show();
|
||||
}
|
||||
|
||||
|
||||
public async void Next(string nextId)
|
||||
{
|
||||
DialogueLine = await DialogueManager.GetNextDialogueLine(resource, nextId, temporaryGameStates);
|
||||
DialogueLine = await DialogueManager.GetNextDialogueLine(DialogueResource, nextId, temporaryGameStates);
|
||||
}
|
||||
|
||||
|
||||
#region Helpers
|
||||
|
||||
|
||||
private async void UpdateDialogue()
|
||||
private async void ApplyDialogueLine()
|
||||
{
|
||||
if (!IsNodeReady())
|
||||
{
|
||||
await ToSignal(this, SignalName.Ready);
|
||||
}
|
||||
MutationCooldown.Stop();
|
||||
|
||||
isWaitingForInput = false;
|
||||
balloon.FocusMode = Control.FocusModeEnum.All;
|
||||
balloon.GrabFocus();
|
||||
|
||||
// Set up the character name
|
||||
characterLabel.Visible = !string.IsNullOrEmpty(dialogueLine.Character);
|
||||
@@ -208,14 +255,7 @@ namespace DialogueManagerRuntime
|
||||
{
|
||||
isWaitingForInput = false;
|
||||
willHideBalloon = true;
|
||||
GetTree().CreateTimer(0.1f).Timeout += () =>
|
||||
{
|
||||
if (willHideBalloon)
|
||||
{
|
||||
willHideBalloon = false;
|
||||
balloon.Hide();
|
||||
}
|
||||
};
|
||||
MutationCooldown.Start(0.1f);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://5b3w40kwakl3
|
||||
@@ -1,14 +1,25 @@
|
||||
class_name DialogueManagerExampleBalloon extends CanvasLayer
|
||||
#class_name DialogueManagerExampleBalloon extends CanvasLayer
|
||||
## A basic dialogue balloon for use with Dialogue Manager.
|
||||
|
||||
extends CanvasLayer
|
||||
|
||||
## The dialogue resource
|
||||
@export var dialogue_resource: DialogueResource
|
||||
|
||||
## Start from a given title when using balloon as a [Node] in a scene.
|
||||
@export var start_from_title: String = ""
|
||||
|
||||
## If running as a [Node] in a scene then auto start the dialogue.
|
||||
@export var auto_start: bool = false
|
||||
|
||||
## The action to use for advancing the dialogue
|
||||
@export var next_action: StringName = &"ui_accept"
|
||||
|
||||
## The action to use to skip typing the dialogue
|
||||
@export var skip_action: StringName = &"ui_cancel"
|
||||
|
||||
## The dialogue resource
|
||||
var resource: DialogueResource
|
||||
## A sound player for voice lines (if they exist).
|
||||
@onready var audio_stream_player: AudioStreamPlayer = %AudioStreamPlayer
|
||||
|
||||
## Temporary game states
|
||||
var temporary_game_states: Array = []
|
||||
@@ -26,55 +37,22 @@ var _locale: String = TranslationServer.get_locale()
|
||||
|
||||
## The current line
|
||||
var dialogue_line: DialogueLine:
|
||||
set(next_dialogue_line):
|
||||
is_waiting_for_input = false
|
||||
balloon.focus_mode = Control.FOCUS_ALL
|
||||
balloon.grab_focus()
|
||||
|
||||
# The dialogue has finished so close the balloon
|
||||
if not next_dialogue_line:
|
||||
queue_free()
|
||||
return
|
||||
|
||||
# If the node isn't ready yet then none of the labels will be ready yet either
|
||||
if not is_node_ready():
|
||||
await ready
|
||||
|
||||
dialogue_line = next_dialogue_line
|
||||
|
||||
character_label.visible = not dialogue_line.character.is_empty()
|
||||
character_label.text = tr(dialogue_line.character, "dialogue")
|
||||
|
||||
dialogue_label.hide()
|
||||
dialogue_label.dialogue_line = dialogue_line
|
||||
|
||||
responses_menu.hide()
|
||||
responses_menu.set_responses(dialogue_line.responses)
|
||||
|
||||
# Show our balloon
|
||||
balloon.show()
|
||||
will_hide_balloon = false
|
||||
|
||||
dialogue_label.show()
|
||||
if not dialogue_line.text.is_empty():
|
||||
dialogue_label.type_out()
|
||||
await dialogue_label.finished_typing
|
||||
|
||||
# Wait for input
|
||||
if dialogue_line.responses.size() > 0:
|
||||
balloon.focus_mode = Control.FOCUS_NONE
|
||||
responses_menu.show()
|
||||
elif dialogue_line.time != "":
|
||||
var time = dialogue_line.text.length() * 0.02 if dialogue_line.time == "auto" else dialogue_line.time.to_float()
|
||||
await get_tree().create_timer(time).timeout
|
||||
next(dialogue_line.next_id)
|
||||
set(value):
|
||||
if value:
|
||||
dialogue_line = value
|
||||
apply_dialogue_line()
|
||||
else:
|
||||
is_waiting_for_input = true
|
||||
balloon.focus_mode = Control.FOCUS_ALL
|
||||
balloon.grab_focus()
|
||||
# The dialogue has finished so close the balloon
|
||||
if owner == null:
|
||||
queue_free()
|
||||
else:
|
||||
hide()
|
||||
get:
|
||||
return dialogue_line
|
||||
|
||||
## A cooldown timer for delaying the balloon hide when encountering a mutation.
|
||||
var mutation_cooldown: Timer = Timer.new()
|
||||
|
||||
## The base balloon anchor
|
||||
@onready var balloon: Control = %Balloon
|
||||
|
||||
@@ -87,6 +65,9 @@ var dialogue_line: DialogueLine:
|
||||
## The menu of responses
|
||||
@onready var responses_menu: DialogueResponsesMenu = %ResponsesMenu
|
||||
|
||||
## Indicator to show that player can progress dialogue.
|
||||
@onready var progress: Polygon2D = %Progress
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
balloon.hide()
|
||||
@@ -96,6 +77,19 @@ func _ready() -> void:
|
||||
if responses_menu.next_action.is_empty():
|
||||
responses_menu.next_action = next_action
|
||||
|
||||
mutation_cooldown.timeout.connect(_on_mutation_cooldown_timeout)
|
||||
add_child(mutation_cooldown)
|
||||
|
||||
if auto_start:
|
||||
if not is_instance_valid(dialogue_resource):
|
||||
assert(false, DMConstants.get_error_message(DMConstants.ERR_MISSING_RESOURCE_FOR_AUTOSTART))
|
||||
start()
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if is_instance_valid(dialogue_line):
|
||||
progress.visible = not dialogue_label.is_typing and dialogue_line.responses.size() == 0 and not dialogue_line.has_tag("voice")
|
||||
|
||||
|
||||
func _unhandled_input(_event: InputEvent) -> void:
|
||||
# Only the balloon is allowed to handle input while it's showing
|
||||
@@ -107,37 +101,88 @@ func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_TRANSLATION_CHANGED and _locale != TranslationServer.get_locale() and is_instance_valid(dialogue_label):
|
||||
_locale = TranslationServer.get_locale()
|
||||
var visible_ratio = dialogue_label.visible_ratio
|
||||
self.dialogue_line = await resource.get_next_dialogue_line(dialogue_line.id)
|
||||
dialogue_line = await dialogue_resource.get_next_dialogue_line(dialogue_line.id)
|
||||
if visible_ratio < 1:
|
||||
dialogue_label.skip_typing()
|
||||
|
||||
|
||||
## Start some dialogue
|
||||
func start(dialogue_resource: DialogueResource, title: String, extra_game_states: Array = []) -> void:
|
||||
if not is_node_ready():
|
||||
await ready
|
||||
temporary_game_states = [self] + extra_game_states
|
||||
func start(with_dialogue_resource: DialogueResource = null, title: String = "", extra_game_states: Array = []) -> void:
|
||||
temporary_game_states = [self] + extra_game_states
|
||||
is_waiting_for_input = false
|
||||
resource = dialogue_resource
|
||||
self.dialogue_line = await resource.get_next_dialogue_line(title, temporary_game_states)
|
||||
if is_instance_valid(with_dialogue_resource):
|
||||
dialogue_resource = with_dialogue_resource
|
||||
if not title.is_empty():
|
||||
start_from_title = title
|
||||
dialogue_line = await dialogue_resource.get_next_dialogue_line(start_from_title, temporary_game_states)
|
||||
show()
|
||||
|
||||
|
||||
## Apply any changes to the balloon given a new [DialogueLine].
|
||||
func apply_dialogue_line() -> void:
|
||||
mutation_cooldown.stop()
|
||||
|
||||
progress.hide()
|
||||
is_waiting_for_input = false
|
||||
balloon.focus_mode = Control.FOCUS_ALL
|
||||
balloon.grab_focus()
|
||||
|
||||
character_label.visible = not dialogue_line.character.is_empty()
|
||||
character_label.text = tr(dialogue_line.character, "dialogue")
|
||||
|
||||
dialogue_label.hide()
|
||||
dialogue_label.dialogue_line = dialogue_line
|
||||
|
||||
responses_menu.hide()
|
||||
responses_menu.responses = dialogue_line.responses
|
||||
|
||||
# Show our balloon
|
||||
balloon.show()
|
||||
will_hide_balloon = false
|
||||
|
||||
dialogue_label.show()
|
||||
if not dialogue_line.text.is_empty():
|
||||
dialogue_label.type_out()
|
||||
await dialogue_label.finished_typing
|
||||
|
||||
# Wait for next line
|
||||
if dialogue_line.has_tag("voice"):
|
||||
audio_stream_player.stream = load(dialogue_line.get_tag_value("voice"))
|
||||
audio_stream_player.play()
|
||||
await audio_stream_player.finished
|
||||
next(dialogue_line.next_id)
|
||||
elif dialogue_line.responses.size() > 0:
|
||||
balloon.focus_mode = Control.FOCUS_NONE
|
||||
responses_menu.show()
|
||||
elif dialogue_line.time != "":
|
||||
var time = dialogue_line.text.length() * 0.02 if dialogue_line.time == "auto" else dialogue_line.time.to_float()
|
||||
await get_tree().create_timer(time).timeout
|
||||
next(dialogue_line.next_id)
|
||||
else:
|
||||
is_waiting_for_input = true
|
||||
balloon.focus_mode = Control.FOCUS_ALL
|
||||
balloon.grab_focus()
|
||||
|
||||
|
||||
## Go to the next line
|
||||
func next(next_id: String) -> void:
|
||||
self.dialogue_line = await resource.get_next_dialogue_line(next_id, temporary_game_states)
|
||||
dialogue_line = await dialogue_resource.get_next_dialogue_line(next_id, temporary_game_states)
|
||||
|
||||
|
||||
#region Signals
|
||||
|
||||
|
||||
func _on_mutation_cooldown_timeout() -> void:
|
||||
if will_hide_balloon:
|
||||
will_hide_balloon = false
|
||||
balloon.hide()
|
||||
|
||||
|
||||
func _on_mutated(_mutation: Dictionary) -> void:
|
||||
is_waiting_for_input = false
|
||||
will_hide_balloon = true
|
||||
get_tree().create_timer(0.1).timeout.connect(func():
|
||||
if will_hide_balloon:
|
||||
will_hide_balloon = false
|
||||
balloon.hide()
|
||||
)
|
||||
if not _mutation.is_inline:
|
||||
is_waiting_for_input = false
|
||||
will_hide_balloon = true
|
||||
mutation_cooldown.start(0.1)
|
||||
|
||||
|
||||
func _on_balloon_gui_input(event: InputEvent) -> void:
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://d1wt4ma6055l8
|
||||
@@ -1,8 +1,178 @@
|
||||
[gd_scene load_steps=9 format=3 uid="uid://73jm5qjy52vq"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogue_manager/example_balloon/example_balloon.gd" id="1_36de5"]
|
||||
[ext_resource type="PackedScene" uid="uid://ckvgyvclnwggo" path="res://addons/dialogue_manager/dialogue_label.tscn" id="2_a8ve6"]
|
||||
[ext_resource type="Script" path="res://addons/dialogue_manager/dialogue_reponses_menu.gd" id="3_72ixx"]
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_i0vys"]
|
||||
script/source = "class_name DialogueManagerExampleBalloon extends CanvasLayer
|
||||
## A basic dialogue balloon for use with Dialogue Manager.
|
||||
|
||||
## The action to use for advancing the dialogue
|
||||
@export var next_action: StringName = &\"ui_accept\"
|
||||
|
||||
## The action to use to skip typing the dialogue
|
||||
@export var skip_action: StringName = &\"ui_cancel\"
|
||||
|
||||
## The dialogue resource
|
||||
var resource: DialogueResource
|
||||
|
||||
## Temporary game states
|
||||
var temporary_game_states: Array = []
|
||||
|
||||
## See if we are waiting for the player
|
||||
var is_waiting_for_input: bool = false
|
||||
|
||||
## See if we are running a long mutation and should hide the balloon
|
||||
var will_hide_balloon: bool = false
|
||||
|
||||
## A dictionary to store any ephemeral variables
|
||||
var locals: Dictionary = {}
|
||||
|
||||
var _locale: String = TranslationServer.get_locale()
|
||||
|
||||
## The current line
|
||||
var dialogue_line: DialogueLine:
|
||||
set(next_dialogue_line):
|
||||
is_waiting_for_input = false
|
||||
balloon.focus_mode = Control.FOCUS_ALL
|
||||
balloon.grab_focus()
|
||||
|
||||
# The dialogue has finished so close the balloon
|
||||
if not next_dialogue_line:
|
||||
queue_free()
|
||||
return
|
||||
|
||||
# If the node isn't ready yet then none of the labels will be ready yet either
|
||||
if not is_node_ready():
|
||||
await ready
|
||||
|
||||
dialogue_line = next_dialogue_line
|
||||
|
||||
character_label.visible = not dialogue_line.character.is_empty()
|
||||
character_label.text = tr(dialogue_line.character, \"dialogue\")
|
||||
|
||||
dialogue_label.hide()
|
||||
dialogue_label.dialogue_line = dialogue_line
|
||||
|
||||
responses_menu.hide()
|
||||
responses_menu.set_responses(dialogue_line.responses)
|
||||
|
||||
# Show our balloon
|
||||
balloon.show()
|
||||
will_hide_balloon = false
|
||||
|
||||
dialogue_label.show()
|
||||
if not dialogue_line.text.is_empty():
|
||||
dialogue_label.type_out()
|
||||
await dialogue_label.finished_typing
|
||||
|
||||
# Wait for input
|
||||
if dialogue_line.responses.size() > 0:
|
||||
balloon.focus_mode = Control.FOCUS_NONE
|
||||
responses_menu.show()
|
||||
elif dialogue_line.time != \"\":
|
||||
var time = dialogue_line.text.length() * 0.02 if dialogue_line.time == \"auto\" else dialogue_line.time.to_float()
|
||||
await get_tree().create_timer(time).timeout
|
||||
next(dialogue_line.next_id)
|
||||
else:
|
||||
is_waiting_for_input = true
|
||||
balloon.focus_mode = Control.FOCUS_ALL
|
||||
balloon.grab_focus()
|
||||
get:
|
||||
return dialogue_line
|
||||
|
||||
## The base balloon anchor
|
||||
@onready var balloon: Control = %Balloon
|
||||
|
||||
## The label showing the name of the currently speaking character
|
||||
@onready var character_label: RichTextLabel = %CharacterLabel
|
||||
|
||||
## The label showing the currently spoken dialogue
|
||||
@onready var dialogue_label: DialogueLabel = %DialogueLabel
|
||||
|
||||
## The menu of responses
|
||||
@onready var responses_menu: DialogueResponsesMenu = %ResponsesMenu
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
balloon.hide()
|
||||
Engine.get_singleton(\"DialogueManager\").mutated.connect(_on_mutated)
|
||||
|
||||
# If the responses menu doesn't have a next action set, use this one
|
||||
if responses_menu.next_action.is_empty():
|
||||
responses_menu.next_action = next_action
|
||||
|
||||
|
||||
func _unhandled_input(_event: InputEvent) -> void:
|
||||
# Only the balloon is allowed to handle input while it's showing
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
## Detect a change of locale and update the current dialogue line to show the new language
|
||||
if what == NOTIFICATION_TRANSLATION_CHANGED and _locale != TranslationServer.get_locale() and is_instance_valid(dialogue_label):
|
||||
_locale = TranslationServer.get_locale()
|
||||
var visible_ratio = dialogue_label.visible_ratio
|
||||
self.dialogue_line = await resource.get_next_dialogue_line(dialogue_line.id)
|
||||
if visible_ratio < 1:
|
||||
dialogue_label.skip_typing()
|
||||
|
||||
|
||||
## Start some dialogue
|
||||
func start(dialogue_resource: DialogueResource, title: String, extra_game_states: Array = []) -> void:
|
||||
if not is_node_ready():
|
||||
await ready
|
||||
temporary_game_states = [self] + extra_game_states
|
||||
is_waiting_for_input = false
|
||||
resource = dialogue_resource
|
||||
self.dialogue_line = await resource.get_next_dialogue_line(title, temporary_game_states)
|
||||
|
||||
|
||||
## Go to the next line
|
||||
func next(next_id: String) -> void:
|
||||
self.dialogue_line = await resource.get_next_dialogue_line(next_id, temporary_game_states)
|
||||
|
||||
|
||||
#region Signals
|
||||
|
||||
|
||||
func _on_mutated(_mutation: Dictionary) -> void:
|
||||
is_waiting_for_input = false
|
||||
will_hide_balloon = true
|
||||
get_tree().create_timer(0.1).timeout.connect(func():
|
||||
if will_hide_balloon:
|
||||
will_hide_balloon = false
|
||||
balloon.hide()
|
||||
)
|
||||
|
||||
|
||||
func _on_balloon_gui_input(event: InputEvent) -> void:
|
||||
# See if we need to skip typing of the dialogue
|
||||
if dialogue_label.is_typing:
|
||||
var mouse_was_clicked: bool = event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed()
|
||||
var skip_button_was_pressed: bool = event.is_action_pressed(skip_action)
|
||||
if mouse_was_clicked or skip_button_was_pressed:
|
||||
get_viewport().set_input_as_handled()
|
||||
dialogue_label.skip_typing()
|
||||
return
|
||||
|
||||
if not is_waiting_for_input: return
|
||||
if dialogue_line.responses.size() > 0: return
|
||||
|
||||
# When there are no response options the balloon itself is the clickable thing
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
if event is InputEventMouseButton and event.is_pressed() and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
next(dialogue_line.next_id)
|
||||
elif event.is_action_pressed(next_action) and get_viewport().gui_get_focus_owner() == balloon:
|
||||
next(dialogue_line.next_id)
|
||||
|
||||
|
||||
func _on_responses_menu_response_selected(response: DialogueResponse) -> void:
|
||||
next(response.next_id)
|
||||
|
||||
|
||||
#endregion
|
||||
"
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_spyqn"]
|
||||
bg_color = Color(0, 0, 0, 1)
|
||||
@@ -63,9 +233,154 @@ MarginContainer/constants/margin_right = 30
|
||||
MarginContainer/constants/margin_top = 15
|
||||
Panel/styles/panel = SubResource("StyleBoxFlat_uy0d5")
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_v0j00"]
|
||||
script/source = "@icon(\"./assets/responses_menu.svg\")
|
||||
|
||||
## A [Container] for dialogue responses provided by [b]Dialogue Manager[/b].
|
||||
class_name DialogueResponsesMenu extends Container
|
||||
|
||||
|
||||
## Emitted when a response is selected.
|
||||
signal response_selected(response)
|
||||
|
||||
|
||||
## Optionally specify a control to duplicate for each response
|
||||
@export var response_template: Control
|
||||
|
||||
## The action for accepting a response (is possibly overridden by parent dialogue balloon).
|
||||
@export var next_action: StringName = &\"\"
|
||||
|
||||
## The list of dialogue responses.
|
||||
var responses: Array = []:
|
||||
get:
|
||||
return responses
|
||||
set(value):
|
||||
responses = value
|
||||
|
||||
# Remove any current items
|
||||
for item in get_children():
|
||||
if item == response_template: continue
|
||||
|
||||
remove_child(item)
|
||||
item.queue_free()
|
||||
|
||||
# Add new items
|
||||
if responses.size() > 0:
|
||||
for response in responses:
|
||||
var item: Control
|
||||
if is_instance_valid(response_template):
|
||||
item = response_template.duplicate(DUPLICATE_GROUPS | DUPLICATE_SCRIPTS | DUPLICATE_SIGNALS)
|
||||
item.show()
|
||||
else:
|
||||
item = Button.new()
|
||||
item.name = \"Response%d\" % get_child_count()
|
||||
if not response.is_allowed:
|
||||
item.name = String(item.name) + \"Disallowed\"
|
||||
item.disabled = true
|
||||
|
||||
# If the item has a response property then use that
|
||||
if \"response\" in item:
|
||||
item.response = response
|
||||
# Otherwise assume we can just set the text
|
||||
else:
|
||||
item.text = response.text
|
||||
|
||||
item.set_meta(\"response\", response)
|
||||
|
||||
add_child(item)
|
||||
|
||||
_configure_focus()
|
||||
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
visibility_changed.connect(func():
|
||||
if visible and get_menu_items().size() > 0:
|
||||
get_menu_items()[0].grab_focus()
|
||||
)
|
||||
|
||||
if is_instance_valid(response_template):
|
||||
response_template.hide()
|
||||
|
||||
|
||||
## Get the selectable items in the menu.
|
||||
func get_menu_items() -> Array:
|
||||
var items: Array = []
|
||||
for child in get_children():
|
||||
if not child.visible: continue
|
||||
if \"Disallowed\" in child.name: continue
|
||||
items.append(child)
|
||||
|
||||
return items
|
||||
|
||||
|
||||
## [b]DEPRECATED[/b]. Do not use.
|
||||
func set_responses(next_responses: Array) -> void:
|
||||
self.responses = next_responses
|
||||
|
||||
|
||||
#region Internal
|
||||
|
||||
|
||||
# Prepare the menu for keyboard and mouse navigation.
|
||||
func _configure_focus() -> void:
|
||||
var items = get_menu_items()
|
||||
for i in items.size():
|
||||
var item: Control = items[i]
|
||||
|
||||
item.focus_mode = Control.FOCUS_ALL
|
||||
|
||||
item.focus_neighbor_left = item.get_path()
|
||||
item.focus_neighbor_right = item.get_path()
|
||||
|
||||
if i == 0:
|
||||
item.focus_neighbor_top = item.get_path()
|
||||
item.focus_previous = item.get_path()
|
||||
else:
|
||||
item.focus_neighbor_top = items[i - 1].get_path()
|
||||
item.focus_previous = items[i - 1].get_path()
|
||||
|
||||
if i == items.size() - 1:
|
||||
item.focus_neighbor_bottom = item.get_path()
|
||||
item.focus_next = item.get_path()
|
||||
else:
|
||||
item.focus_neighbor_bottom = items[i + 1].get_path()
|
||||
item.focus_next = items[i + 1].get_path()
|
||||
|
||||
item.mouse_entered.connect(_on_response_mouse_entered.bind(item))
|
||||
item.gui_input.connect(_on_response_gui_input.bind(item, item.get_meta(\"response\")))
|
||||
|
||||
items[0].grab_focus()
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Signals
|
||||
|
||||
|
||||
func _on_response_mouse_entered(item: Control) -> void:
|
||||
if \"Disallowed\" in item.name: return
|
||||
|
||||
item.grab_focus()
|
||||
|
||||
|
||||
func _on_response_gui_input(event: InputEvent, item: Control, response) -> void:
|
||||
if \"Disallowed\" in item.name: return
|
||||
|
||||
if event is InputEventMouseButton and event.is_pressed() and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
get_viewport().set_input_as_handled()
|
||||
response_selected.emit(response)
|
||||
elif event.is_action_pressed(&\"ui_accept\" if next_action.is_empty() else next_action) and item in get_menu_items():
|
||||
get_viewport().set_input_as_handled()
|
||||
response_selected.emit(response)
|
||||
|
||||
|
||||
#endregion
|
||||
"
|
||||
|
||||
[node name="ExampleBalloon" type="CanvasLayer"]
|
||||
layer = 100
|
||||
script = ExtResource("1_36de5")
|
||||
script = SubResource("GDScript_i0vys")
|
||||
|
||||
[node name="Balloon" type="Control" parent="."]
|
||||
unique_name_in_owner = true
|
||||
@@ -132,7 +447,7 @@ unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 8
|
||||
theme_override_constants/separation = 2
|
||||
script = ExtResource("3_72ixx")
|
||||
script = SubResource("GDScript_v0j00")
|
||||
response_template = NodePath("ResponseExample")
|
||||
|
||||
[node name="ResponseExample" type="Button" parent="Balloon/Responses/ResponsesMenu"]
|
||||
@@ -140,4 +455,3 @@ layout_mode = 2
|
||||
text = "Response example"
|
||||
|
||||
[connection signal="gui_input" from="Balloon" to="." method="_on_balloon_gui_input"]
|
||||
[connection signal="response_selected" from="Balloon/Responses/ResponsesMenu" to="." method="_on_responses_menu_response_selected"]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[gd_scene load_steps=10 format=3 uid="uid://13s5spsk34qu"]
|
||||
[gd_scene load_steps=12 format=3 uid="uid://13s5spsk34qu"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogue_manager/example_balloon/example_balloon.gd" id="1_s2gbs"]
|
||||
[ext_resource type="Script" uid="uid://d1wt4ma6055l8" path="res://addons/dialogue_manager/example_balloon/example_balloon.gd" id="1_s2gbs"]
|
||||
[ext_resource type="PackedScene" uid="uid://ckvgyvclnwggo" path="res://addons/dialogue_manager/dialogue_label.tscn" id="2_hfvdi"]
|
||||
[ext_resource type="Script" path="res://addons/dialogue_manager/dialogue_reponses_menu.gd" id="3_1j1j0"]
|
||||
[ext_resource type="Script" uid="uid://bb52rsfwhkxbn" path="res://addons/dialogue_manager/dialogue_responses_menu.gd" id="3_1j1j0"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_235ry"]
|
||||
content_margin_left = 6.0
|
||||
@@ -66,7 +66,7 @@ corner_radius_top_right = 3
|
||||
corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_uy0d5"]
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_i6nbm"]
|
||||
bg_color = Color(0, 0, 0, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
@@ -87,7 +87,28 @@ MarginContainer/constants/margin_bottom = 4
|
||||
MarginContainer/constants/margin_left = 8
|
||||
MarginContainer/constants/margin_right = 8
|
||||
MarginContainer/constants/margin_top = 4
|
||||
Panel/styles/panel = SubResource("StyleBoxFlat_uy0d5")
|
||||
PanelContainer/styles/panel = SubResource("StyleBoxFlat_i6nbm")
|
||||
|
||||
[sub_resource type="Animation" id="Animation_i6nbm"]
|
||||
resource_name = "progress"
|
||||
loop_mode = 1
|
||||
step = 0.1
|
||||
tracks/0/type = "bezier"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath("Progress:position:y")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"handle_modes": PackedInt32Array(0, 0, 0, 0, 0),
|
||||
"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0, 0, -0.25, 0, 0.2, -0.0157438, -1, -0.2, 0.0112069, 0.25, 0, -1, -0.25, 0, 0.25, 0, 0, -0.2, 0.00299701, 0.25, 0),
|
||||
"times": PackedFloat32Array(0, 0.1, 0.5, 0.6, 1)
|
||||
}
|
||||
|
||||
[sub_resource type="AnimationLibrary" id="AnimationLibrary_6b6c8"]
|
||||
_data = {
|
||||
&"progress": SubResource("Animation_i6nbm")
|
||||
}
|
||||
|
||||
[node name="ExampleBalloon" type="CanvasLayer"]
|
||||
layer = 100
|
||||
@@ -103,31 +124,32 @@ grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme = SubResource("Theme_qq3yp")
|
||||
|
||||
[node name="Panel" type="Panel" parent="Balloon"]
|
||||
[node name="MarginContainer" type="MarginContainer" parent="Balloon"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 12
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 3.0
|
||||
offset_top = -62.0
|
||||
offset_right = -4.0
|
||||
offset_bottom = -4.0
|
||||
offset_top = -71.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
|
||||
[node name="Dialogue" type="MarginContainer" parent="Balloon/Panel"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
[node name="PanelContainer" type="PanelContainer" parent="Balloon/MarginContainer"]
|
||||
clip_children = 2
|
||||
layout_mode = 2
|
||||
mouse_filter = 1
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Balloon/Panel/Dialogue"]
|
||||
[node name="MarginContainer" type="MarginContainer" parent="Balloon/MarginContainer/PanelContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="CharacterLabel" type="RichTextLabel" parent="Balloon/Panel/Dialogue/VBoxContainer"]
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Balloon/MarginContainer/PanelContainer/MarginContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="CharacterLabel" type="RichTextLabel" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
modulate = Color(1, 1, 1, 0.501961)
|
||||
layout_mode = 2
|
||||
@@ -137,37 +159,52 @@ text = "Character"
|
||||
fit_content = true
|
||||
scroll_active = false
|
||||
|
||||
[node name="DialogueLabel" parent="Balloon/Panel/Dialogue/VBoxContainer" instance=ExtResource("2_hfvdi")]
|
||||
[node name="DialogueLabel" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer/VBoxContainer" instance=ExtResource("2_hfvdi")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
text = "Dialogue..."
|
||||
skip_pause_at_abbreviations = PackedStringArray("Mr", "Mrs", "Ms", "Dr", "etc", "eg", "ex")
|
||||
|
||||
[node name="Responses" type="MarginContainer" parent="Balloon"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 7
|
||||
anchor_left = 0.5
|
||||
anchor_top = 1.0
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -124.0
|
||||
offset_top = -218.0
|
||||
offset_right = 125.0
|
||||
offset_bottom = -50.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
|
||||
[node name="ResponsesMenu" type="VBoxContainer" parent="Balloon/Responses"]
|
||||
unique_name_in_owner = true
|
||||
[node name="Control" type="Control" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer"]
|
||||
custom_minimum_size = Vector2(5, 3)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 8
|
||||
|
||||
[node name="Progress" type="Polygon2D" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer/Control"]
|
||||
unique_name_in_owner = true
|
||||
position = Vector2(0, -0.455998)
|
||||
polygon = PackedVector2Array(0, 0, 2.5, 3, 5, 0)
|
||||
|
||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer/Control"]
|
||||
libraries = {
|
||||
&"": SubResource("AnimationLibrary_6b6c8")
|
||||
}
|
||||
autoplay = "progress"
|
||||
|
||||
[node name="ResponsesMenu" type="VBoxContainer" parent="Balloon"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -116.5
|
||||
offset_top = -9.0
|
||||
offset_right = 116.5
|
||||
offset_bottom = 9.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_vertical = 8
|
||||
theme_override_constants/separation = 2
|
||||
script = ExtResource("3_1j1j0")
|
||||
|
||||
[node name="ResponseExample" type="Button" parent="Balloon/Responses/ResponsesMenu"]
|
||||
[node name="ResponseExample" type="Button" parent="Balloon/ResponsesMenu"]
|
||||
layout_mode = 2
|
||||
text = "Response Example"
|
||||
|
||||
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[connection signal="gui_input" from="Balloon" to="." method="_on_balloon_gui_input"]
|
||||
[connection signal="response_selected" from="Balloon/Responses/ResponsesMenu" to="." method="_on_responses_menu_response_selected"]
|
||||
[connection signal="response_selected" from="Balloon/ResponsesMenu" to="." method="_on_responses_menu_response_selected"]
|
||||
|
||||
Reference in New Issue
Block a user