added a talking rock

This commit is contained in:
2024-09-07 14:12:04 -04:00
parent 1573768619
commit 55a291e2ae
79 changed files with 14358 additions and 17 deletions

View File

@@ -0,0 +1,219 @@
using Godot;
using Godot.Collections;
namespace DialogueManagerRuntime
{
public partial class ExampleBalloon : CanvasLayer
{
[Export] public string NextAction = "ui_accept";
[Export] public string SkipAction = "ui_cancel";
Control balloon;
RichTextLabel characterLabel;
RichTextLabel dialogueLabel;
VBoxContainer responsesMenu;
Resource resource;
Array<Variant> temporaryGameStates = new Array<Variant>();
bool isWaitingForInput = false;
bool willHideBalloon = false;
DialogueLine dialogueLine;
DialogueLine DialogueLine
{
get => dialogueLine;
set
{
isWaitingForInput = false;
balloon.FocusMode = Control.FocusModeEnum.All;
balloon.GrabFocus();
if (value == null)
{
QueueFree();
return;
}
dialogueLine = value;
UpdateDialogue();
}
}
public override void _Ready()
{
balloon = GetNode<Control>("%Balloon");
characterLabel = GetNode<RichTextLabel>("%CharacterLabel");
dialogueLabel = GetNode<RichTextLabel>("%DialogueLabel");
responsesMenu = GetNode<VBoxContainer>("%ResponsesMenu");
balloon.Hide();
balloon.GuiInput += (@event) =>
{
if ((bool)dialogueLabel.Get("is_typing"))
{
bool mouseWasClicked = @event is InputEventMouseButton && (@event as InputEventMouseButton).ButtonIndex == MouseButton.Left && @event.IsPressed();
bool skipButtonWasPressed = @event.IsActionPressed(SkipAction);
if (mouseWasClicked || skipButtonWasPressed)
{
GetViewport().SetInputAsHandled();
dialogueLabel.Call("skip_typing");
return;
}
}
if (!isWaitingForInput) return;
if (dialogueLine.Responses.Count > 0) return;
GetViewport().SetInputAsHandled();
if (@event is InputEventMouseButton && @event.IsPressed() && (@event as InputEventMouseButton).ButtonIndex == MouseButton.Left)
{
Next(dialogueLine.NextId);
}
else if (@event.IsActionPressed(NextAction) && GetViewport().GuiGetFocusOwner() == balloon)
{
Next(dialogueLine.NextId);
}
};
if (string.IsNullOrEmpty((string)responsesMenu.Get("next_action")))
{
responsesMenu.Set("next_action", NextAction);
}
responsesMenu.Connect("response_selected", Callable.From((DialogueResponse response) =>
{
Next(response.NextId);
}));
DialogueManager.Mutated += OnMutated;
}
public override void _ExitTree()
{
DialogueManager.Mutated -= OnMutated;
}
public override void _UnhandledInput(InputEvent @event)
{
// Only the balloon is allowed to handle input while it's showing
GetViewport().SetInputAsHandled();
}
public override async void _Notification(int what)
{
// Detect a change of locale and update the current dialogue line to show the new language
if (what == NotificationTranslationChanged)
{
float visibleRatio = dialogueLabel.VisibleRatio;
DialogueLine = await DialogueManager.GetNextDialogueLine(resource, DialogueLine.Id, temporaryGameStates);
if (visibleRatio < 1.0f)
{
dialogueLabel.Call("skip_typing");
}
}
}
public async void Start(Resource dialogueResource, string title, Array<Variant> extraGameStates = null)
{
temporaryGameStates = extraGameStates ?? new Array<Variant>();
isWaitingForInput = false;
resource = dialogueResource;
DialogueLine = await DialogueManager.GetNextDialogueLine(resource, title, temporaryGameStates);
}
public async void Next(string nextId)
{
DialogueLine = await DialogueManager.GetNextDialogueLine(resource, nextId, temporaryGameStates);
}
#region Helpers
private async void UpdateDialogue()
{
if (!IsNodeReady())
{
await ToSignal(this, SignalName.Ready);
}
// Set up the character name
characterLabel.Visible = !string.IsNullOrEmpty(dialogueLine.Character);
characterLabel.Text = Tr(dialogueLine.Character, "dialogue");
// Set up the dialogue
dialogueLabel.Hide();
dialogueLabel.Set("dialogue_line", dialogueLine);
// Set up the responses
responsesMenu.Hide();
responsesMenu.Set("responses", dialogueLine.Responses);
// Type out the text
balloon.Show();
willHideBalloon = false;
dialogueLabel.Show();
if (!string.IsNullOrEmpty(dialogueLine.Text))
{
dialogueLabel.Call("type_out");
await ToSignal(dialogueLabel, "finished_typing");
}
// Wait for input
if (dialogueLine.Responses.Count > 0)
{
balloon.FocusMode = Control.FocusModeEnum.None;
responsesMenu.Show();
}
else if (!string.IsNullOrEmpty(dialogueLine.Time))
{
float time = 0f;
if (!float.TryParse(dialogueLine.Time, out time))
{
time = dialogueLine.Text.Length * 0.02f;
}
await ToSignal(GetTree().CreateTimer(time), "timeout");
Next(dialogueLine.NextId);
}
else
{
isWaitingForInput = true;
balloon.FocusMode = Control.FocusModeEnum.All;
balloon.GrabFocus();
}
}
#endregion
#region signals
private void OnMutated(Dictionary _mutation)
{
isWaitingForInput = false;
willHideBalloon = true;
GetTree().CreateTimer(0.1f).Timeout += () =>
{
if (willHideBalloon)
{
willHideBalloon = false;
balloon.Hide();
}
};
}
#endregion
}
}

View File

@@ -0,0 +1,156 @@
extends CanvasLayer
## 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"
@onready var balloon: Control = %Balloon
@onready var character_label: RichTextLabel = %CharacterLabel
@onready var dialogue_label: DialogueLabel = %DialogueLabel
@onready var responses_menu: DialogueResponsesMenu = %ResponsesMenu
## 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
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
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:
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

View File

@@ -0,0 +1,149 @@
[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="StyleBoxFlat" id="StyleBoxFlat_spyqn"]
bg_color = Color(0, 0, 0, 1)
border_width_left = 3
border_width_top = 3
border_width_right = 3
border_width_bottom = 3
border_color = Color(0.329412, 0.329412, 0.329412, 1)
corner_radius_top_left = 5
corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ri4m3"]
bg_color = Color(0.121569, 0.121569, 0.121569, 1)
border_width_left = 3
border_width_top = 3
border_width_right = 3
border_width_bottom = 3
border_color = Color(1, 1, 1, 1)
corner_radius_top_left = 5
corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_e0njw"]
bg_color = Color(0, 0, 0, 1)
border_width_left = 3
border_width_top = 3
border_width_right = 3
border_width_bottom = 3
border_color = Color(0.6, 0.6, 0.6, 1)
corner_radius_top_left = 5
corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_uy0d5"]
bg_color = Color(0, 0, 0, 1)
border_width_left = 3
border_width_top = 3
border_width_right = 3
border_width_bottom = 3
corner_radius_top_left = 5
corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
[sub_resource type="Theme" id="Theme_qq3yp"]
default_font_size = 20
Button/styles/disabled = SubResource("StyleBoxFlat_spyqn")
Button/styles/focus = SubResource("StyleBoxFlat_ri4m3")
Button/styles/hover = SubResource("StyleBoxFlat_e0njw")
Button/styles/normal = SubResource("StyleBoxFlat_e0njw")
MarginContainer/constants/margin_bottom = 15
MarginContainer/constants/margin_left = 30
MarginContainer/constants/margin_right = 30
MarginContainer/constants/margin_top = 15
Panel/styles/panel = SubResource("StyleBoxFlat_uy0d5")
[node name="ExampleBalloon" type="CanvasLayer"]
layer = 100
script = ExtResource("1_36de5")
[node name="Balloon" type="Control" parent="."]
unique_name_in_owner = true
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme = SubResource("Theme_qq3yp")
[node name="Panel" type="Panel" parent="Balloon"]
clip_children = 2
layout_mode = 1
anchors_preset = 12
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 21.0
offset_top = -183.0
offset_right = -19.0
offset_bottom = -19.0
grow_horizontal = 2
grow_vertical = 0
mouse_filter = 1
[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="VBoxContainer" type="VBoxContainer" parent="Balloon/Panel/Dialogue"]
layout_mode = 2
[node name="CharacterLabel" type="RichTextLabel" parent="Balloon/Panel/Dialogue/VBoxContainer"]
unique_name_in_owner = true
modulate = Color(1, 1, 1, 0.501961)
layout_mode = 2
mouse_filter = 1
bbcode_enabled = true
text = "Character"
fit_content = true
scroll_active = false
[node name="DialogueLabel" parent="Balloon/Panel/Dialogue/VBoxContainer" instance=ExtResource("2_a8ve6")]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
text = "Dialogue..."
[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 = -147.0
offset_top = -558.0
offset_right = 494.0
offset_bottom = -154.0
grow_horizontal = 2
grow_vertical = 0
[node name="ResponsesMenu" type="VBoxContainer" parent="Balloon/Responses" node_paths=PackedStringArray("response_template")]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 8
theme_override_constants/separation = 2
script = ExtResource("3_72ixx")
response_template = NodePath("ResponseExample")
[node name="ResponseExample" type="Button" parent="Balloon/Responses/ResponsesMenu"]
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"]

View File

@@ -0,0 +1,173 @@
[gd_scene load_steps=10 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="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"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_235ry"]
content_margin_left = 6.0
content_margin_top = 3.0
content_margin_right = 6.0
content_margin_bottom = 3.0
bg_color = Color(0.0666667, 0.0666667, 0.0666667, 1)
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color(0.345098, 0.345098, 0.345098, 1)
corner_radius_top_left = 3
corner_radius_top_right = 3
corner_radius_bottom_right = 3
corner_radius_bottom_left = 3
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ufjut"]
content_margin_left = 6.0
content_margin_top = 3.0
content_margin_right = 6.0
content_margin_bottom = 3.0
bg_color = Color(0.227451, 0.227451, 0.227451, 1)
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color(1, 1, 1, 1)
corner_radius_top_left = 3
corner_radius_top_right = 3
corner_radius_bottom_right = 3
corner_radius_bottom_left = 3
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_fcbqo"]
content_margin_left = 6.0
content_margin_top = 3.0
content_margin_right = 6.0
content_margin_bottom = 3.0
bg_color = Color(0.0666667, 0.0666667, 0.0666667, 1)
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
corner_radius_top_left = 3
corner_radius_top_right = 3
corner_radius_bottom_right = 3
corner_radius_bottom_left = 3
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_t6i7a"]
content_margin_left = 6.0
content_margin_top = 3.0
content_margin_right = 6.0
content_margin_bottom = 3.0
bg_color = Color(0.0666667, 0.0666667, 0.0666667, 1)
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
corner_radius_top_left = 3
corner_radius_top_right = 3
corner_radius_bottom_right = 3
corner_radius_bottom_left = 3
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_uy0d5"]
bg_color = Color(0, 0, 0, 1)
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
corner_radius_top_left = 3
corner_radius_top_right = 3
corner_radius_bottom_right = 3
corner_radius_bottom_left = 3
[sub_resource type="Theme" id="Theme_qq3yp"]
default_font_size = 8
Button/styles/disabled = SubResource("StyleBoxFlat_235ry")
Button/styles/focus = SubResource("StyleBoxFlat_ufjut")
Button/styles/hover = SubResource("StyleBoxFlat_fcbqo")
Button/styles/normal = SubResource("StyleBoxFlat_t6i7a")
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")
[node name="ExampleBalloon" type="CanvasLayer"]
layer = 100
script = ExtResource("1_s2gbs")
[node name="Balloon" type="Control" parent="."]
unique_name_in_owner = true
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme = SubResource("Theme_qq3yp")
[node name="Panel" type="Panel" 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
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="VBoxContainer" type="VBoxContainer" parent="Balloon/Panel/Dialogue"]
layout_mode = 2
[node name="CharacterLabel" type="RichTextLabel" parent="Balloon/Panel/Dialogue/VBoxContainer"]
unique_name_in_owner = true
modulate = Color(1, 1, 1, 0.501961)
layout_mode = 2
mouse_filter = 1
bbcode_enabled = true
text = "Character"
fit_content = true
scroll_active = false
[node name="DialogueLabel" parent="Balloon/Panel/Dialogue/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
layout_mode = 2
size_flags_vertical = 8
theme_override_constants/separation = 2
script = ExtResource("3_1j1j0")
[node name="ResponseExample" type="Button" parent="Balloon/Responses/ResponsesMenu"]
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"]