Added dialogue manager plugin
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
using System;
|
||||
|
||||
namespace DialogueManagerRuntime
|
||||
{
|
||||
/// <summary>
|
||||
/// A special <see cref="Area2D"/> node to hold information about starting dialogue.
|
||||
///
|
||||
/// Assuming <c>DialogueResource</c> and <c>DialogueCue</c> have been configured you can
|
||||
/// call <c>Action()</c> on this node at runtime to start dialogue.
|
||||
/// </summary>
|
||||
[Tool, Icon("uid://ja67jst5iqyy")]
|
||||
public partial class DialogueActionable2D : Area2D
|
||||
{
|
||||
/// <summary>
|
||||
/// Emitted when this <see cref="DialogueActionable2D"/> has <c>Action()</c> called on it.
|
||||
/// </summary>
|
||||
[Signal] public delegate void ActionedEventHandler();
|
||||
|
||||
/// <summary>
|
||||
/// Emitted when the dialogue resource associated with this <see cref="DialogueActionable2D"/> ends. NOTE: The
|
||||
/// signal is also emitted if the same resource is used for multiple <see cref="DialogueActionable2D"/> nodes in the tree.
|
||||
/// </summary>
|
||||
[Signal] public delegate void DialogueEndedEventHandler();
|
||||
|
||||
|
||||
private Resource dialogueResource = null;
|
||||
|
||||
/// <summary>
|
||||
/// The dialogue resource to use when starting dialogue.
|
||||
/// </summary>
|
||||
[Export]
|
||||
public Resource DialogueResource
|
||||
{
|
||||
get => dialogueResource;
|
||||
set
|
||||
{
|
||||
dialogueResource = value;
|
||||
if (dialogueResource == null)
|
||||
{
|
||||
DialogueCue = "";
|
||||
}
|
||||
NotifyPropertyListChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The target cue to start dialogue from.
|
||||
/// </summary>
|
||||
[Export] public string DialogueCue = "";
|
||||
|
||||
/// <summary>
|
||||
/// The dialogue balloon that was last used by calling <c>Action()</c> (if there was one).
|
||||
/// </summary>
|
||||
public Node DialogueBalloon;
|
||||
|
||||
/// <summary>
|
||||
/// The method used to start dialogue when <c>Action()</c> is called. Override if you need different logic.
|
||||
/// </summary>
|
||||
public static Func<Resource, string, Array<Variant>, Node> StartDialogue = (withDialogueResource, fromCue, extraGameStates) =>
|
||||
DialogueManager.ShowDialogueBalloon(withDialogueResource, fromCue, extraGameStates);
|
||||
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
if (!Engine.IsEditorHint())
|
||||
{
|
||||
AddToGroup("dialogue_actionables");
|
||||
// Touch the instance to make sure the DialogueManager bridge signals are connected
|
||||
_ = DialogueManager.Instance;
|
||||
DialogueManager.DialogueEnded += OnDialogueEnded;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
if (!Engine.IsEditorHint())
|
||||
{
|
||||
DialogueManager.DialogueEnded -= OnDialogueEnded;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region Public
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Action this <see cref="DialogueActionable2D"/>. If a dialogue resource and cue have been set on this node then it will start dialogue.
|
||||
/// </summary>
|
||||
public void Action()
|
||||
{
|
||||
if (IsInstanceValid(DialogueResource) && !string.IsNullOrEmpty(DialogueCue))
|
||||
{
|
||||
DialogueBalloon = StartDialogue(DialogueResource, DialogueCue, new Array<Variant> { new Dictionary { { "actionable", this } }, Owner });
|
||||
}
|
||||
EmitSignal(SignalName.Actioned);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Find the nearest <see cref="DialogueActionable2D"/> to a given position.
|
||||
/// </summary>
|
||||
public static DialogueActionable2D GetNearestActionableTo(Vector2 targetPosition)
|
||||
{
|
||||
float nearestDistance = float.PositiveInfinity;
|
||||
DialogueActionable2D nearestActionable = null;
|
||||
foreach (Node node in ((SceneTree)Engine.GetMainLoop()).GetNodesInGroup("dialogue_actionables"))
|
||||
{
|
||||
if (node is not DialogueActionable2D actionable) continue;
|
||||
|
||||
float distance = actionable.GlobalPosition.DistanceSquaredTo(targetPosition);
|
||||
if (distance < nearestDistance)
|
||||
{
|
||||
nearestDistance = distance;
|
||||
nearestActionable = actionable;
|
||||
}
|
||||
}
|
||||
|
||||
return nearestActionable;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Signals
|
||||
|
||||
|
||||
private void OnDialogueEnded(Resource endingDialogueResource)
|
||||
{
|
||||
if (endingDialogueResource == DialogueResource)
|
||||
{
|
||||
EmitSignal(SignalName.DialogueEnded);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://d0sxb0wcim1wy
|
||||
@@ -0,0 +1,141 @@
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
using System;
|
||||
|
||||
namespace DialogueManagerRuntime
|
||||
{
|
||||
/// <summary>
|
||||
/// A special <see cref="Area3D"/> node to hold information about starting dialogue.
|
||||
///
|
||||
/// Assuming <c>DialogueResource</c> and <c>DialogueCue</c> have been configured you can
|
||||
/// call <c>Action()</c> on this node at runtime to start dialogue.
|
||||
/// </summary>
|
||||
[Tool, Icon("uid://dppsuhlvbcbjc")]
|
||||
public partial class DialogueActionable3D : Area3D
|
||||
{
|
||||
/// <summary>
|
||||
/// Emitted when this <see cref="DialogueActionable3D"/> has <c>Action()</c> called on it.
|
||||
/// </summary>
|
||||
[Signal] public delegate void ActionedEventHandler();
|
||||
|
||||
/// <summary>
|
||||
/// Emitted when the dialogue resource associated with this <see cref="DialogueActionable3D"/> ends. NOTE: The
|
||||
/// signal is also emitted if the same resource is used for multiple <see cref="DialogueActionable3D"/> nodes in the tree.
|
||||
/// </summary>
|
||||
[Signal] public delegate void DialogueEndedEventHandler();
|
||||
|
||||
|
||||
private Resource dialogueResource = null;
|
||||
|
||||
/// <summary>
|
||||
/// The dialogue resource to use when starting dialogue.
|
||||
/// </summary>
|
||||
[Export]
|
||||
public Resource DialogueResource
|
||||
{
|
||||
get => dialogueResource;
|
||||
set
|
||||
{
|
||||
dialogueResource = value;
|
||||
if (dialogueResource == null)
|
||||
{
|
||||
DialogueCue = "";
|
||||
}
|
||||
NotifyPropertyListChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The target cue to start dialogue from.
|
||||
/// </summary>
|
||||
[Export] public string DialogueCue = "";
|
||||
|
||||
/// <summary>
|
||||
/// The dialogue balloon that was last used by calling <c>Action()</c> (if there was one).
|
||||
/// </summary>
|
||||
public Node DialogueBalloon;
|
||||
|
||||
/// <summary>
|
||||
/// The method used to start dialogue when <c>Action()</c> is called. Override if you need different logic.
|
||||
/// </summary>
|
||||
public static Func<Resource, string, Array<Variant>, Node> StartDialogue = (withDialogueResource, fromCue, extraGameStates) =>
|
||||
DialogueManager.ShowDialogueBalloon(withDialogueResource, fromCue, extraGameStates);
|
||||
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
if (!Engine.IsEditorHint())
|
||||
{
|
||||
AddToGroup("dialogue_actionables");
|
||||
// Touch the instance to make sure the DialogueManager bridge signals are connected
|
||||
_ = DialogueManager.Instance;
|
||||
DialogueManager.DialogueEnded += OnDialogueEnded;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
if (!Engine.IsEditorHint())
|
||||
{
|
||||
DialogueManager.DialogueEnded -= OnDialogueEnded;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region Public
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Action this <see cref="DialogueActionable3D"/>. If a dialogue resource and cue have been set on this node then it will start dialogue.
|
||||
/// </summary>
|
||||
public void Action()
|
||||
{
|
||||
if (IsInstanceValid(DialogueResource) && !string.IsNullOrEmpty(DialogueCue))
|
||||
{
|
||||
DialogueBalloon = StartDialogue(DialogueResource, DialogueCue, new Array<Variant> { new Dictionary { { "actionable", this } }, Owner });
|
||||
}
|
||||
EmitSignal(SignalName.Actioned);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Find the nearest <see cref="DialogueActionable3D"/> to a given position.
|
||||
/// </summary>
|
||||
public static DialogueActionable3D GetNearestActionableTo(Vector3 targetPosition)
|
||||
{
|
||||
float nearestDistance = float.PositiveInfinity;
|
||||
DialogueActionable3D nearestActionable = null;
|
||||
foreach (Node node in ((SceneTree)Engine.GetMainLoop()).GetNodesInGroup("dialogue_actionables"))
|
||||
{
|
||||
if (node is not DialogueActionable3D actionable) continue;
|
||||
|
||||
float distance = actionable.GlobalPosition.DistanceSquaredTo(targetPosition);
|
||||
if (distance < nearestDistance)
|
||||
{
|
||||
nearestDistance = distance;
|
||||
nearestActionable = actionable;
|
||||
}
|
||||
}
|
||||
|
||||
return nearestActionable;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Signals
|
||||
|
||||
|
||||
private void OnDialogueEnded(Resource endingDialogueResource)
|
||||
{
|
||||
if (endingDialogueResource == DialogueResource)
|
||||
{
|
||||
EmitSignal(SignalName.DialogueEnded);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://5skdphbgu68t
|
||||
@@ -0,0 +1,82 @@
|
||||
@tool
|
||||
|
||||
@icon("uid://ja67jst5iqyy")
|
||||
|
||||
## A special [Area2D] node to hold information about starting dialogue.
|
||||
##
|
||||
## Assuming [code]dialogue_resource[/code] and [code]cue[/code] have been configured you can
|
||||
## call [code]action()[/code] on this node at runtime to start dialogue.
|
||||
class_name DialogueActionable2D extends Area2D
|
||||
|
||||
|
||||
## Emitted when this [DialogueActionable2D] has [code]action()[/code] called on it.
|
||||
signal actioned()
|
||||
|
||||
## Emitted when the [DialogueResource] associated with this [DialogueActionable2D] ends. [b]NOTE:[/b] The
|
||||
## signal is also emitted if the same resource is used for multiple [DialogueActionable2D] nodes in the tree.
|
||||
signal dialogue_ended()
|
||||
|
||||
|
||||
## The [DialogueResource] to use when starting dialogue.
|
||||
@export var dialogue_resource: DialogueResource = null:
|
||||
set(value):
|
||||
dialogue_resource = value
|
||||
if dialogue_resource == null:
|
||||
dialogue_cue = ""
|
||||
notify_property_list_changed()
|
||||
get:
|
||||
return dialogue_resource
|
||||
|
||||
## The target cue to start dialogue from.
|
||||
@export var dialogue_cue: String = ""
|
||||
|
||||
## The dialogue balloon that was last used by calling [code]action()[/code] (if there was one).
|
||||
var dialogue_balloon: Node
|
||||
|
||||
## The method used to start dialogue action [code]action()[/code] is called. Override if you need different logic.
|
||||
static var start_dialogue: Callable = func(with_dialogue_resource: DialogueResource, from_cue: String, extra_game_states: Array) -> Node:
|
||||
return Engine.get_singleton("DialogueManager").show_dialogue_balloon(with_dialogue_resource, from_cue, extra_game_states)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not Engine.is_editor_hint():
|
||||
add_to_group("dialogue_actionables")
|
||||
Engine.get_singleton("DialogueManager").dialogue_ended.connect(_on_dialogue_ended)
|
||||
|
||||
|
||||
#region Public
|
||||
|
||||
|
||||
## Action this [DialogueActionable2D]. If a [DialogueResource] and cue have been set on this node then
|
||||
## it will start dialogue.
|
||||
func action() -> void:
|
||||
if is_instance_valid(dialogue_resource) and not dialogue_cue.is_empty():
|
||||
dialogue_balloon = start_dialogue.call(dialogue_resource, dialogue_cue, [{ actionable = self }, owner])
|
||||
actioned.emit()
|
||||
|
||||
|
||||
## Find the nearest [DialogueActionable2D] to a given position.
|
||||
static func get_nearest_actionable_to(target_position: Vector2) -> DialogueActionable2D:
|
||||
var nearest_distance: float = INF
|
||||
var nearest_actionable: DialogueActionable2D = null
|
||||
var actionables: Array[Node] = (Engine.get_main_loop() as SceneTree).get_nodes_in_group("dialogue_actionables")
|
||||
for actionable: DialogueActionable2D in actionables:
|
||||
var distance: float = actionable.global_position.distance_squared_to(target_position)
|
||||
if distance < nearest_distance:
|
||||
nearest_distance = distance
|
||||
nearest_actionable = actionable
|
||||
|
||||
return nearest_actionable
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Signals
|
||||
|
||||
|
||||
func _on_dialogue_ended(ending_dialogue_resource: DialogueResource) -> void:
|
||||
if ending_dialogue_resource == dialogue_resource:
|
||||
dialogue_ended.emit()
|
||||
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1 @@
|
||||
uid://cqcur1rm7433g
|
||||
Binary file not shown.
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ja67jst5iqyy"
|
||||
path="res://.godot/imported/dialogue_actionable_2d.svg-fab4e58d6b3980b100fb8a5494b4d5cb.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/dialogue_manager/nodes/actionable/dialogue_actionable_2d.svg"
|
||||
dest_files=["res://.godot/imported/dialogue_actionable_2d.svg-fab4e58d6b3980b100fb8a5494b4d5cb.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
||||
@@ -0,0 +1,82 @@
|
||||
@tool
|
||||
|
||||
@icon("uid://dppsuhlvbcbjc")
|
||||
|
||||
## A special [Area3D] node to hold information about starting dialogue.
|
||||
##
|
||||
## Assuming [code]dialogue_resource[/code] and [code]cue[/code] have been configured you can
|
||||
## call [code]action()[/code] on this node at runtime to start dialogue.
|
||||
class_name DialogueActionable3D extends Area3D
|
||||
|
||||
|
||||
## Emitted when this [DialogueActionable3D] has [code]action()[/code] called on it.
|
||||
signal actioned()
|
||||
|
||||
## Emitted when the [DialogueResource] associated with this [DialogueActionable2D] ends. [b]NOTE:[/b] The
|
||||
## signal is also emitted if the same resource is used for multiple [DialogueActionable2D] nodes in the tree.
|
||||
signal dialogue_ended()
|
||||
|
||||
|
||||
## The [DialogueResource] to use when starting dialogue.
|
||||
@export var dialogue_resource: DialogueResource = null:
|
||||
set(value):
|
||||
dialogue_resource = value
|
||||
if dialogue_resource == null:
|
||||
dialogue_cue = ""
|
||||
notify_property_list_changed()
|
||||
get:
|
||||
return dialogue_resource
|
||||
|
||||
## The target cue to start dialogue from.
|
||||
@export var dialogue_cue: String = ""
|
||||
|
||||
## The dialogue balloon that was last used by calling [code]action()[/code] (if there was one).
|
||||
var dialogue_balloon: Node
|
||||
|
||||
## The method used to start dialogue action [code]action()[/code] is called. Override if you need different logic.
|
||||
static var start_dialogue: Callable = func(with_dialogue_resource: DialogueResource, from_cue: String, extra_game_states: Array) -> Node:
|
||||
return Engine.get_singleton("DialogueManager").show_dialogue_balloon(with_dialogue_resource, from_cue, extra_game_states)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not Engine.is_editor_hint():
|
||||
add_to_group("dialogue_actionables")
|
||||
Engine.get_singleton("DialogueManager").dialogue_ended.connect(_on_dialogue_ended)
|
||||
|
||||
|
||||
#region Public
|
||||
|
||||
|
||||
## Action this [DialogueActionable3D]. If a [DialogueResource] and cue have been set on this node then
|
||||
## it will start dialogue.
|
||||
func action() -> void:
|
||||
if is_instance_valid(dialogue_resource) and not dialogue_cue.is_empty():
|
||||
dialogue_balloon = start_dialogue.call(dialogue_resource, dialogue_cue, [{ actionable = self }, owner])
|
||||
actioned.emit()
|
||||
|
||||
|
||||
## Find the nearest [DialogueActionable3D] to a given position.
|
||||
static func get_nearest_actionable_to(target_position: Vector3) -> DialogueActionable3D:
|
||||
var nearest_distance: float = INF
|
||||
var nearest_actionable: DialogueActionable3D = null
|
||||
var actionables: Array[Node] = (Engine.get_main_loop() as SceneTree).get_nodes_in_group("dialogue_actionables")
|
||||
for actionable: DialogueActionable3D in actionables:
|
||||
var distance: float = actionable.global_position.distance_squared_to(target_position)
|
||||
if distance < nearest_distance:
|
||||
nearest_distance = distance
|
||||
nearest_actionable = actionable
|
||||
|
||||
return nearest_actionable
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Signals
|
||||
|
||||
|
||||
func _on_dialogue_ended(ending_dialogue_resource: DialogueResource) -> void:
|
||||
if ending_dialogue_resource == dialogue_resource:
|
||||
dialogue_ended.emit()
|
||||
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1 @@
|
||||
uid://c5mlp6r4mts0r
|
||||
Binary file not shown.
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dppsuhlvbcbjc"
|
||||
path="res://.godot/imported/dialogue_actionable_3d.svg-66fe4924d1964b29285ae978ab731ec3.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/dialogue_manager/nodes/actionable/dialogue_actionable_3d.svg"
|
||||
dest_files=["res://.godot/imported/dialogue_actionable_3d.svg-66fe4924d1964b29285ae978ab731ec3.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
||||
Reference in New Issue
Block a user