diff --git a/.gitignore b/.gitignore index 15ad0ac..a0de551 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ !.vscode/launch.json !.vscode/extensions.json *.code-workspace - +Generated/ ## VisualStudio .vs/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 103be53..582d1b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,26 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [0.3.0] Pending + +* Updated YarnSpinner DLLs to support version 3 of the Yarn Language, which supports many new features, similar to the feature set described for the Unity plugin here: + * https://www.yarnspinner.dev/blog/yarn-spinner-3-beta-1 + * The samples have been updated to demonstrate these features, including enums, node groups, line groups, smart variables, shadow lines, generated variable storage code, `<>` and `<>`. +* `[YarnCommand`] and `[YarnFunction]` have been updated to generate a class called `YarnSpinnerGodot.Generated.ActionRegistration`. This class will register all of your commands and functions without relying on reflection, which was the cause of some performance hiccups when starting a scene with a DialogueRunner in v0.2.* of the plugin +* To enable new features for an existing .yarnproject, edit the .yarnproject file to change the projectFileVersion to 3. +* If you keep your .yarnproject at projectFileVersion 2, you will have to re-compile the scripts in your yarn project to work with this version of the plugin. +* Update views to be async Task based, like the Unity plugin version 3. +* The existing `DialogueViewBase` interface is deprecated in favor of `AsyncDialogueViewBased`. Updated example views are provided. +* GDScript nodes that have methods that are the `snake_case` version of methods on `AsyncDialogueViewBase` can be added directly in `YarnProject.dialogueViews` +without requiring GDScriptViewAdapter. Supported methods: `on_dialogue_start_async() -> void`, `on_dialogue_complete_async() -> void`, `run_line_async(line: Dictionary) -> void`, `run_options_async(options: Array, on_option_selected: Callable) -> void`. You can still use `await` statements in your GDScript view methods. `AddCommandHandlerCallable` on `DialogueRunner` has been re-tested to ensure that commands can still be registered from GDScript. Also, the GDScriptIntegration sample has been updated with these changes. +* ⚠ Breaking change: LineProviderBehaviour has changed to require different methods and fields. If you have a custom line provider, please see the updated TextLineProvider as an * example of how to implement the updated class. Also notice that you must now set the yarn project on `TextLineProvider` instances, either via script or in the inspector. +* ⚠ Breaking change: Use DialogueRunner.VariableStorage, not DialogueRunner.variableStorage, nor DialogueRunner.SetDialogueViews (removed) to get/set the variable storage associated with a DialogueRunner. Previously there were two properties with different case that were both publicly accessible. +* ⚠ Breaking change: MarkupPalette renamed ColourMarkers to FormatMarkers, supporting new functionality like bold, underline, italics, as a way to demonstrate the updated formatting +* ⚠ Breaking change: the field DialogueRunner.verboseLogging has been removed. +* New functionality on YarnProject - optionally generate a C# variable storage class which has getters and setters for each variable declared in your yarn scripts. You can control the class that the generated file inherits from, the namespace it will be in, and the name of the class and file. + + + ## [0.2.14] 2024-11-02 * GDScript: Add GDScriptViewAdapter, a C# Script which allows you to write custom dialogue views in GDScript. See GDScriptViewAdapter.cs for more details. * GDScript: Add new method AddCommandHandlerCallable to DialogueRunner, allowing commands to be registered from GDScript. GDScript command handlers that use asynchronous `await` functionality are also supported as blocking YarnSpinner commands, similar to using `async Task` commands in C#. @@ -30,7 +50,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [0.2.9] 2024-06-30 * Set LineView's MouseFilter to 'ignore' to avoid interfering with clicks. -* Fix an issue where 'use fade effect' would cause the ConvertBBCodeToHTML feature to stop working while the text was fading out. +* Fix an issue where 'use fade effect' would cause the ConvertHTMLToBBCode feature to stop working while the text was fading out. * Set the LineView to Visible=False when marking its alpha as 0. ## [0.2.8] 2024-05-24 diff --git a/Samples/Characters/Dialogue/CharacterSample.yarn b/Samples/Characters/Dialogue/CharacterSample.yarn new file mode 100644 index 0000000..603d286 --- /dev/null +++ b/Samples/Characters/Dialogue/CharacterSample.yarn @@ -0,0 +1,93 @@ +title: Barry +when: once +--- +// This node can only ever be run once, and this is the only content for this +// character. The game won't show the 'interactable' indicator if the character +// has no content available. +Barry: Hi, I'm Barry! You can only talk to me this one time! +=== +title: Larry +--- +<> + // Greet the player the first time. + Larry: Hi, I'm Larry! You can talk to me several times! +<> + +/// The number of times we have turned the switch on or off. +<> + +/// Whether the switch is currently turned on or off. +<> + +<> + <> + // Directly thank the player the first time + Larry: Hey, thanks for turning that switch on for me! + <> + // Run general 'you did it' barks in other circumstances. + => Larry: Sure is nice having that switch on! + => Larry: Being on is a great thing for a switch to be! + => Larry: Wow! Never knew how good a switch being turned on was. + <> +< 0 and not $switch_on>> + // We've turned the switch on in the past, but it's not on now. Complain + // about it. + => Larry: Don't be a jerk! Turn that switch back on! + => Larry: Hey! Can you turn that switch back on? + => Larry: Aw, I loved it when the switch was on. +<> + // The switch isn't on. + <> + // We haven't turned the switch on before, so be really direct about it + Larry: Hey, can you turn that switch on for me? + <> + // Be a bit more passive-aggressive about it the rest of the time. + => Larry: Sure wish someone would turn that switch on. + => Larry: Man, how amazing would it be for that switch to be on... + => Larry: My only dream is for the switch to be on... + <> +<> +=== +title: Switch +--- +// Toggle the switch on or off. +<> + +// Increment the number of times we've interacted with it. +<> + +// Play an animation that shows the switch turning on or off. +<> + <> +<> + <> +<> +=== +title: Garry +--- + + +Garry: Emotions test! + +<> +Garry: Smiling! + +<> +Garry: Frowning! + +<> +Garry: Neutral! + +Garry: Let's try animating it! + +<> +Garry: Smiling! + +<> +Garry: Frowning! + +<> +Garry: Neutral! + + +=== \ No newline at end of file diff --git a/Samples/Characters/Dialogue/CharacterSample.yarn.import b/Samples/Characters/Dialogue/CharacterSample.yarn.import new file mode 100644 index 0000000..06eb82d --- /dev/null +++ b/Samples/Characters/Dialogue/CharacterSample.yarn.import @@ -0,0 +1,14 @@ +[remap] + +importer="yarnscript" +type="Resource" +uid="uid://bvucc8lvq2xbc" +path="res://.godot/imported/CharacterSample.yarn-ed78dc65c09d526de320ad4d21190854.tres" + +[deps] + +source_file="res://Samples/Characters/Dialogue/CharacterSample.yarn" +dest_files=["res://.godot/imported/CharacterSample.yarn-ed78dc65c09d526de320ad4d21190854.tres"] + +[params] + diff --git a/Samples/Characters/Dialogue/CharacterSample.yarnproject b/Samples/Characters/Dialogue/CharacterSample.yarnproject new file mode 100644 index 0000000..5489f58 --- /dev/null +++ b/Samples/Characters/Dialogue/CharacterSample.yarnproject @@ -0,0 +1,11 @@ +{ + "projectFileVersion": 3, + "sourceFiles": [ + "**/*.yarn" + ], + "excludeFiles": [ + "**/*~/*" + ], + "localisation": {}, + "baseLanguage": "en" +} \ No newline at end of file diff --git a/Samples/Characters/Dialogue/CharacterSample.yarnproject.import b/Samples/Characters/Dialogue/CharacterSample.yarnproject.import new file mode 100644 index 0000000..c982427 --- /dev/null +++ b/Samples/Characters/Dialogue/CharacterSample.yarnproject.import @@ -0,0 +1,14 @@ +[remap] + +importer="yarnproject" +type="Resource" +uid="uid://b3qu6dw2x8nls" +path="res://.godot/imported/CharacterSample.yarnproject-c10833699b5605472ed6a79ae0501e1a.tres" + +[deps] + +source_file="res://Samples/Characters/Dialogue/CharacterSample.yarnproject" +dest_files=["res://.godot/imported/CharacterSample.yarnproject-c10833699b5605472ed6a79ae0501e1a.tres"] + +[params] + diff --git a/Samples/Characters/Dialogue/YarnVariables.cs b/Samples/Characters/Dialogue/YarnVariables.cs new file mode 100644 index 0000000..7a350cb --- /dev/null +++ b/Samples/Characters/Dialogue/YarnVariables.cs @@ -0,0 +1,25 @@ +namespace MyGame; + +using YarnSpinnerGodot; + +[System.CodeDom.Compiler.GeneratedCode("YarnSpinner", "3.0.0.0")] +public partial class YarnVariables : YarnSpinnerGodot.InMemoryVariableStorage, YarnSpinnerGodot.IGeneratedVariableStorage { + // Accessor for Number $times_interacted_with_switch + /// + /// The number of times we have turned the switch on or off. + /// + public float TimesInteractedWithSwitch { + get => this.GetValueOrDefault("$times_interacted_with_switch"); + set => this.SetValue("$times_interacted_with_switch", value); + } + + // Accessor for Bool $switch_on + /// + /// Whether the switch is currently turned on or off. + /// + public bool SwitchOn { + get => this.GetValueOrDefault("$switch_on"); + set => this.SetValue("$switch_on", value); + } + +} \ No newline at end of file diff --git a/Samples/GDScriptIntegration/CrossLanguageScriptingExample.gd b/Samples/GDScriptIntegration/CrossLanguageScriptingExample.gd index d61a09a..37789d1 100644 --- a/Samples/GDScriptIntegration/CrossLanguageScriptingExample.gd +++ b/Samples/GDScriptIntegration/CrossLanguageScriptingExample.gd @@ -4,7 +4,8 @@ extends Control @export var dialogue_runner: Node @export var logo: Control -@export var yarn_project: YarnProject +@export var yarn_project: Resource + func _ready() -> void: dialogue_runner.AddCommandHandlerCallable("show_logo", show_logo) dialogue_runner.onDialogueComplete.connect(on_dialogue_complete) diff --git a/Samples/GDScriptIntegration/Dialogue/GDScriptIntegration.yarn b/Samples/GDScriptIntegration/Dialogue/GDScriptIntegration.yarn index 583a1c4..a894141 100644 --- a/Samples/GDScriptIntegration/Dialogue/GDScriptIntegration.yarn +++ b/Samples/GDScriptIntegration/Dialogue/GDScriptIntegration.yarn @@ -5,18 +5,23 @@ tags: Gary: So, can I use [fx type="wave"]GDScript[/fx] with YarnSpinner? #my_metadata #line:02fa0cd Derrick: Many features can be used with GDScript. For example, you can make a custom view like the one in this scene. #line:09c1c2c Derrick: You can also interact with DialogueRunner and variable storage nodes with cross-language scripting. #line:0b086af -Derrick: And you can also write commands in GDScript by using AddCommandHandlerCallable. Your GDScript commands can even use 'await'. Look: #line:0c22e97 +Derrick: And you can also write commands in GDScript by using AddCommandHandlerCallable. Your GDScript commands can even use 'await'. Look! #line:0c22e97 <> -Derrick: Do you know about options, too? You can make an "OptionsListView" with GDScript, too. --> Yes - Derrick: Nice. +Derrick: Do you know about options, too? You can make an "OptionsListView" with GDScript, too. #line:0cc7452 +-> Yes #line:0cd81c1 + Derrick: Nice. #line:0b3ca20 <> --> No - Derrick: Well, you can prompt the player to pick an option. +-> No #line:0f03ac3 + Derrick: Well, you can prompt the player to pick an option. #line:01e20f4 <> === title: GDScriptIntegrationFinish --- Gary: {$myVariableSetFromGDScript} That's cool. I will look at the GDScriptIntegration sample code to learn about this. #line:020bb2b +Derrick: Awesome. Which language do you think you will end up using YarnSpinner for Godot with? #line:0166369 +-> GDScript #line:07cc6d2 + Derrick: That's cool. #line:0862705 +-> C\# #line:0eb6bfe + Derrick: That's cool. #shadow:0862705 === \ No newline at end of file diff --git a/Samples/GDScriptIntegration/GDScriptIntegration.yarnproject b/Samples/GDScriptIntegration/Dialogue/GDScriptIntegration.yarnproject similarity index 100% rename from Samples/GDScriptIntegration/GDScriptIntegration.yarnproject rename to Samples/GDScriptIntegration/Dialogue/GDScriptIntegration.yarnproject diff --git a/Samples/GDScriptIntegration/Dialogue/GDScriptIntegration.yarnproject.import b/Samples/GDScriptIntegration/Dialogue/GDScriptIntegration.yarnproject.import new file mode 100644 index 0000000..bc813cc --- /dev/null +++ b/Samples/GDScriptIntegration/Dialogue/GDScriptIntegration.yarnproject.import @@ -0,0 +1,14 @@ +[remap] + +importer="yarnproject" +type="Resource" +uid="uid://c0qdj0h48fu6a" +path="res://.godot/imported/GDScriptIntegration.yarnproject-3447483eda8ff87e2d80c8bc61bf66c1.tres" + +[deps] + +source_file="res://Samples/GDScriptIntegration/Dialogue/GDScriptIntegration.yarnproject" +dest_files=["res://.godot/imported/GDScriptIntegration.yarnproject-3447483eda8ff87e2d80c8bc61bf66c1.tres"] + +[params] + diff --git a/Samples/GDScriptIntegration/GDScriptIntegration.yarnproject.import b/Samples/GDScriptIntegration/GDScriptIntegration.yarnproject.import deleted file mode 100644 index 7d456e9..0000000 --- a/Samples/GDScriptIntegration/GDScriptIntegration.yarnproject.import +++ /dev/null @@ -1,14 +0,0 @@ -[remap] - -importer="yarnproject" -type="Resource" -uid="uid://c0qdj0h48fu6a" -path="res://.godot/imported/GDScriptIntegration.yarnproject-dbec9ba712db1ca78d4a6bcd0bf67c57.tres" - -[deps] - -source_file="res://Samples/GDScriptIntegration/GDScriptIntegration.yarnproject" -dest_files=["res://.godot/imported/GDScriptIntegration.yarnproject-dbec9ba712db1ca78d4a6bcd0bf67c57.tres"] - -[params] - diff --git a/Samples/GDScriptIntegration/GDScriptIntegrationSample.tscn b/Samples/GDScriptIntegration/GDScriptIntegrationSample.tscn index 52ee862..3c41cf3 100644 --- a/Samples/GDScriptIntegration/GDScriptIntegrationSample.tscn +++ b/Samples/GDScriptIntegration/GDScriptIntegrationSample.tscn @@ -1,9 +1,8 @@ -[gd_scene load_steps=11 format=3 uid="uid://hvtydqjfdlf5"] +[gd_scene load_steps=10 format=3 uid="uid://hvtydqjfdlf5"] [ext_resource type="Script" path="res://addons/YarnSpinner-Godot/Runtime/DialogueRunner.cs" id="1_1er8s"] -[ext_resource type="Resource" uid="uid://c0qdj0h48fu6a" path="res://Samples/GDScriptIntegration/GDScriptIntegration.yarnproject" id="2_15buf"] +[ext_resource type="Resource" uid="uid://c0qdj0h48fu6a" path="res://Samples/GDScriptIntegration/Dialogue/GDScriptIntegration.yarnproject" id="2_15buf"] [ext_resource type="Script" path="res://Samples/GDScriptIntegration/CrossLanguageScriptingExample.gd" id="3_05a7h"] -[ext_resource type="Script" path="res://addons/YarnSpinner-Godot/Runtime/Views/GDScriptViewAdapter.cs" id="3_8do86"] [ext_resource type="Script" path="res://Samples/ReturnOnComplete.cs" id="4_6b5el"] [ext_resource type="Script" path="res://Samples/GDScriptIntegration/SimpleGDScriptLineView.gd" id="4_ixyx4"] [ext_resource type="Script" path="res://addons/YarnSpinner-Godot/Runtime/InMemoryVariableStorage.cs" id="8_fijq0"] @@ -11,7 +10,7 @@ [ext_resource type="Script" path="res://Samples/GDScriptIntegration/SimpleGDScriptOptionsListView.gd" id="9_dujw4"] [ext_resource type="PackedScene" uid="uid://bwrwry811vrgh" path="res://Samples/GDScriptIntegration/SimpleGDScriptOptionView.tscn" id="10_s0v7h"] -[node name="PaletteSample" type="Node2D"] +[node name="GDScriptIntegrationSample" type="Node2D"] [node name="CanvasLayer" type="CanvasLayer" parent="."] layer = 0 @@ -29,7 +28,7 @@ color = Color(0.00260118, 0.121715, 0.193433, 1) [node name="GDScriptYarnSpinnerCanvasLayer" type="CanvasLayer" parent="."] -[node name="DialogueRunner" type="Control" parent="GDScriptYarnSpinnerCanvasLayer" node_paths=PackedStringArray("variableStorage", "dialogueViews", "lineProvider")] +[node name="DialogueRunner" type="Control" parent="GDScriptYarnSpinnerCanvasLayer" node_paths=PackedStringArray("variableStorage", "lineProvider", "dialogueViews")] layout_mode = 3 anchors_preset = 15 anchor_right = 1.0 @@ -40,9 +39,10 @@ mouse_filter = 2 script = ExtResource("1_1er8s") yarnProject = ExtResource("2_15buf") variableStorage = NodePath("../InMemoryVariableStorage") -dialogueViews = [NodePath("../LineViewAdapter"), NodePath("../OptionsListView")] -startNode = "GDScriptIntegration" lineProvider = NodePath("../TextLineProvider") +dialogueViews = [NodePath("../LineView"), NodePath("../OptionsListView")] +autoStart = true +startNode = "GDScriptIntegration" [node name="CrossLanguageScriptingExample" type="Control" parent="GDScriptYarnSpinnerCanvasLayer" node_paths=PackedStringArray("dialogue_runner", "logo")] layout_mode = 3 @@ -56,7 +56,7 @@ yarn_project = ExtResource("2_15buf") [node name="VariableDebugText" type="RichTextLabel" parent="GDScriptYarnSpinnerCanvasLayer"] -[node name="LineViewAdapter" type="Control" parent="GDScriptYarnSpinnerCanvasLayer" node_paths=PackedStringArray("GDScriptView")] +[node name="LineView" type="Control" parent="GDScriptYarnSpinnerCanvasLayer" node_paths=PackedStringArray("continue_button", "character_name_label", "line_text_label", "view_control")] layout_mode = 3 anchors_preset = 15 anchor_right = 1.0 @@ -64,10 +64,13 @@ anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 mouse_filter = 2 -script = ExtResource("3_8do86") -GDScriptView = NodePath("SimpleGDScriptLineView") +script = ExtResource("4_ixyx4") +continue_button = NodePath("ViewControl/ContinueButton") +character_name_label = NodePath("ViewControl/CharacterNameText") +line_text_label = NodePath("ViewControl/LineText") +view_control = NodePath("ViewControl") -[node name="ViewControl" type="Control" parent="GDScriptYarnSpinnerCanvasLayer/LineViewAdapter"] +[node name="ViewControl" type="Control" parent="GDScriptYarnSpinnerCanvasLayer/LineView"] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 @@ -75,7 +78,7 @@ anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 -[node name="LineText" type="RichTextLabel" parent="GDScriptYarnSpinnerCanvasLayer/LineViewAdapter/ViewControl"] +[node name="LineText" type="RichTextLabel" parent="GDScriptYarnSpinnerCanvasLayer/LineView/ViewControl"] layout_mode = 1 anchors_preset = 8 anchor_left = 0.5 @@ -96,7 +99,7 @@ theme_override_font_sizes/bold_font_size = 36 bbcode_enabled = true text = "The dialogue text should appear here!" -[node name="ColorRect" type="ColorRect" parent="GDScriptYarnSpinnerCanvasLayer/LineViewAdapter/ViewControl/LineText"] +[node name="ColorRect" type="ColorRect" parent="GDScriptYarnSpinnerCanvasLayer/LineView/ViewControl/LineText"] modulate = Color(0.203922, 0.192157, 0.192157, 0.458824) show_behind_parent = true layout_mode = 1 @@ -109,7 +112,7 @@ offset_bottom = -158.0 grow_horizontal = 2 grow_vertical = 2 -[node name="CharacterNameText" type="RichTextLabel" parent="GDScriptYarnSpinnerCanvasLayer/LineViewAdapter/ViewControl"] +[node name="CharacterNameText" type="RichTextLabel" parent="GDScriptYarnSpinnerCanvasLayer/LineView/ViewControl"] self_modulate = Color(0.321569, 0.87451, 0.254902, 1) layout_mode = 1 anchors_preset = 8 @@ -131,14 +134,14 @@ theme_override_font_sizes/bold_font_size = 36 bbcode_enabled = true text = "Character Name" -[node name="ColorRect" type="ColorRect" parent="GDScriptYarnSpinnerCanvasLayer/LineViewAdapter/ViewControl/CharacterNameText"] +[node name="ColorRect" type="ColorRect" parent="GDScriptYarnSpinnerCanvasLayer/LineView/ViewControl/CharacterNameText"] modulate = Color(0.203922, 0.192157, 0.192157, 0.458824) show_behind_parent = true layout_mode = 0 anchor_right = 1.0 anchor_bottom = 1.0 -[node name="ContinueButton" type="Button" parent="GDScriptYarnSpinnerCanvasLayer/LineViewAdapter/ViewControl"] +[node name="ContinueButton" type="Button" parent="GDScriptYarnSpinnerCanvasLayer/LineView/ViewControl"] layout_mode = 1 anchors_preset = 7 anchor_left = 0.5 @@ -155,16 +158,7 @@ mouse_default_cursor_shape = 2 theme_override_font_sizes/font_size = 36 text = "Continue" -[node name="SimpleGDScriptLineView" type="Control" parent="GDScriptYarnSpinnerCanvasLayer/LineViewAdapter" node_paths=PackedStringArray("continue_button", "character_name_label", "line_text_label")] -anchors_preset = 0 -offset_right = 40.0 -offset_bottom = 40.0 -script = ExtResource("4_ixyx4") -continue_button = NodePath("../ViewControl/ContinueButton") -character_name_label = NodePath("../ViewControl/CharacterNameText") -line_text_label = NodePath("../ViewControl/LineText") - -[node name="OptionsListView" type="Control" parent="GDScriptYarnSpinnerCanvasLayer" node_paths=PackedStringArray("GDScriptView")] +[node name="OptionsListView" type="Control" parent="GDScriptYarnSpinnerCanvasLayer" node_paths=PackedStringArray("options_container", "view_control")] layout_mode = 3 anchors_preset = 15 anchor_right = 1.0 @@ -172,8 +166,10 @@ anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 mouse_filter = 2 -script = ExtResource("3_8do86") -GDScriptView = NodePath("SimpleGDScriptOptionsListView") +script = ExtResource("9_dujw4") +option_view_prefab = ExtResource("10_s0v7h") +options_container = NodePath("ViewControl/VBoxContainer") +view_control = NodePath("ViewControl") [node name="ViewControl" type="Control" parent="GDScriptYarnSpinnerCanvasLayer/OptionsListView"] layout_mode = 1 @@ -198,20 +194,12 @@ offset_bottom = 287.0 grow_horizontal = 2 grow_vertical = 2 -[node name="SimpleGDScriptOptionsListView" type="Control" parent="GDScriptYarnSpinnerCanvasLayer/OptionsListView" node_paths=PackedStringArray("options_container", "view_control")] -anchors_preset = 0 -offset_right = 40.0 -offset_bottom = 40.0 -script = ExtResource("9_dujw4") -option_view_prefab = ExtResource("10_s0v7h") -options_container = NodePath("../ViewControl/VBoxContainer") -view_control = NodePath("../ViewControl") - [node name="InMemoryVariableStorage" type="Node" parent="GDScriptYarnSpinnerCanvasLayer"] script = ExtResource("8_fijq0") [node name="TextLineProvider" type="Node2D" parent="GDScriptYarnSpinnerCanvasLayer"] script = ExtResource("9_a8pi7") +YarnProject = ExtResource("2_15buf") [node name="Logo" type="TextureRect" parent="GDScriptYarnSpinnerCanvasLayer"] visible = false diff --git a/Samples/GDScriptIntegration/SimpleGDScriptLineView.gd b/Samples/GDScriptIntegration/SimpleGDScriptLineView.gd index da97d89..6abb3e1 100644 --- a/Samples/GDScriptIntegration/SimpleGDScriptLineView.gd +++ b/Samples/GDScriptIntegration/SimpleGDScriptLineView.gd @@ -6,13 +6,18 @@ extends Node @export var continue_button : Button @export var character_name_label : RichTextLabel @export var line_text_label : RichTextLabel +@export var view_control: Node -var on_line_finished : Callable +var line_finished: bool -func dialogue_started() -> void: +func _ready() -> void: + view_control.visible = false + +func on_dialogue_start_async() -> void: print("Dialogue started ") -func run_line(line: Dictionary, on_dialogue_line_finished: Callable) -> void: + +func run_line_async(line: Dictionary) -> void: # line is a Dictionary converted from the LocalizedLine C# Class # example: # {"metadata":["my_metadata"], @@ -26,9 +31,11 @@ func run_line(line: Dictionary, on_dialogue_line_finished: Callable) -> void: # "text_without_character_name":"So, can I use GDScript with YarnSpinner?" # } # } + view_control.visible = true + line_finished = false print('Line: ' + JSON.stringify(line)) continue_button.pressed.connect(continue_line) - self.on_line_finished = on_dialogue_line_finished + line_text_label.text = line["text"]["text_without_character_name"] var character_name : String = "" var character_name_offset: int = 0 @@ -49,10 +56,13 @@ func run_line(line: Dictionary, on_dialogue_line_finished: Callable) -> void: if not (character_name): character_name_label.visible = false - + while !line_finished: + await get_tree().process_frame + func continue_line() -> void: continue_button.pressed.disconnect(continue_line) - self.on_line_finished.call() + line_finished = true -func dialogue_complete() -> void: +func dialogue_complete_async() -> void: print("Dialogue complete ") + view_control.visible = false diff --git a/Samples/GDScriptIntegration/SimpleGDScriptOptionsListView.gd b/Samples/GDScriptIntegration/SimpleGDScriptOptionsListView.gd index 5e5ed17..af871b1 100644 --- a/Samples/GDScriptIntegration/SimpleGDScriptOptionsListView.gd +++ b/Samples/GDScriptIntegration/SimpleGDScriptOptionsListView.gd @@ -38,10 +38,18 @@ func _ready() -> void: #"text_id": "line:9bcbf175" #} #] -func run_options(options: Array, on_option_selected: Callable) -> void: +func run_options_async(options: Array, on_option_selected: Callable) -> void: print("Options: %s" % JSON.stringify(options)) + + # You can do await statements here if you want. + await get_tree().process_frame option_selected_handler = on_option_selected + while options_container.get_child_count() >0: + options_container.remove_child(options_container.get_child(0)) for option in options: + if not option["is_available"]: + # don't render unvailable options + continue var option_view: SimpleGDScriptOptionView = option_view_prefab.instantiate() option_view.set_option(option, select_option) options_container.add_child(option_view) diff --git a/Samples/MarkupPalette/Palette-dialogue.yarn b/Samples/MarkupPalette/Palette-dialogue.yarn index dba0f6d..79ca8db 100644 --- a/Samples/MarkupPalette/Palette-dialogue.yarn +++ b/Samples/MarkupPalette/Palette-dialogue.yarn @@ -1,6 +1,6 @@ title: Start --- -Alice: this is a [calm]quick[/calm] demonstration of using the [hype]new[/hype] MarkupPalette feature +Alice: this is a [calm]quick[/calm] demonstration of using the [hype]new[/hype] [b]MarkupPalette[/b] feature Bob: [calm]oh[/calm], cool, [hype]colours[/hype]! Alice: yes indeed, [turbohype]colours[/turbohype]. @@ -11,7 +11,7 @@ Bob: uh huh Alice: and then [turbohype]configure[/turbohype] the colours how you like Bob: right [calm]ok[/calm] and then Alice: and then [hype]just[/hype] give it to your dialogue views -Alice: then you add the [calm]\[markup\][/calm] to your lines +Alice: then you add the [calm]\[markup\][/calm] [u]to your lines[/u] Alice: and th[hype]en yo[/hype]u are done! Bob: [calm]wowsers[/calm] Alice: [turbohype]wowsers[/turbohype] indeed diff --git a/Samples/MarkupPalette/Palette.yarnproject b/Samples/MarkupPalette/Palette.yarnproject index f6d4fc9..96aafdd 100644 --- a/Samples/MarkupPalette/Palette.yarnproject +++ b/Samples/MarkupPalette/Palette.yarnproject @@ -1,5 +1,5 @@ { - "projectFileVersion": 2, + "projectFileVersion": 3, "sourceFiles": [ "**/*.yarn" ], diff --git a/Samples/MarkupPalette/PaletteSample.tscn b/Samples/MarkupPalette/PaletteSample.tscn index 554b380..63125b0 100644 --- a/Samples/MarkupPalette/PaletteSample.tscn +++ b/Samples/MarkupPalette/PaletteSample.tscn @@ -1,9 +1,11 @@ -[gd_scene load_steps=5 format=3 uid="uid://uxc1jm6ayoar"] +[gd_scene load_steps=7 format=3 uid="uid://uxc1jm6ayoar"] [ext_resource type="PackedScene" uid="uid://bv42g323prh5f" path="res://addons/YarnSpinner-Godot/Scenes/DefaultDialogueSystem.tscn" id="1_luv5t"] [ext_resource type="Resource" uid="uid://rr8xlqj5fkjd" path="res://Samples/MarkupPalette/Palette.yarnproject" id="2_yfv6n"] [ext_resource type="Resource" uid="uid://c631us202ijmk" path="res://Samples/MarkupPalette/example_markup_palette.tres" id="3_1wwmk"] +[ext_resource type="Script" path="res://addons/YarnSpinner-Godot/Runtime/Async/PaletteMarkerProcessor.cs" id="4_fg4ac"] [ext_resource type="Script" path="res://Samples/ReturnOnComplete.cs" id="4_q6j15"] +[ext_resource type="Script" path="res://addons/YarnSpinner-Godot/Runtime/Async/LineAdvancer.cs" id="5_2oaav"] [node name="PaletteSample" type="Node2D"] @@ -23,16 +25,24 @@ color = Color(0.0509804, 0.0235294, 0.0509804, 1) [node name="YarnSpinnerCanvasLayer" parent="." instance=ExtResource("1_luv5t")] -[node name="DialogueRunner" parent="YarnSpinnerCanvasLayer" index="0"] +[node name="DialogueRunner" parent="YarnSpinnerCanvasLayer" index="0" node_paths=PackedStringArray("dialogueViews")] yarnProject = ExtResource("2_yfv6n") +dialogueViews = [NodePath("../AsyncLineView"), NodePath("../AsyncOptionsView"), NodePath("../LineAdvancer")] +autoStart = true startNode = "Start" -startAutomatically = true -[node name="LineView" parent="YarnSpinnerCanvasLayer" index="2"] -palette = ExtResource("3_1wwmk") +[node name="TextLineProvider" parent="YarnSpinnerCanvasLayer" index="5"] +YarnProject = ExtResource("2_yfv6n") -[node name="OptionsListView" parent="YarnSpinnerCanvasLayer" index="3"] +[node name="PaletteMarkerProcessor" type="Node2D" parent="YarnSpinnerCanvasLayer" node_paths=PackedStringArray("lineProvider")] +script = ExtResource("4_fg4ac") palette = ExtResource("3_1wwmk") +lineProvider = NodePath("../TextLineProvider") + +[node name="LineAdvancer" type="Node2D" parent="YarnSpinnerCanvasLayer" node_paths=PackedStringArray("runner")] +script = ExtResource("5_2oaav") +runner = NodePath("../DialogueRunner") +nextLineAction = "" [node name="ReturnOnComplete" type="Node2D" parent="." node_paths=PackedStringArray("dialogueRunner")] script = ExtResource("4_q6j15") diff --git a/Samples/MarkupPalette/example_markup_palette.tres b/Samples/MarkupPalette/example_markup_palette.tres index e8e9c3c..0b423ba 100644 --- a/Samples/MarkupPalette/example_markup_palette.tres +++ b/Samples/MarkupPalette/example_markup_palette.tres @@ -1,11 +1,53 @@ -[gd_resource type="Resource" script_class="MarkupPalette" load_steps=2 format=3 uid="uid://c631us202ijmk"] +[gd_resource type="Resource" script_class="MarkupPalette" load_steps=8 format=3 uid="uid://c631us202ijmk"] [ext_resource type="Script" path="res://addons/YarnSpinner-Godot/Runtime/Views/MarkupPalette.cs" id="1_6b5jh"] +[ext_resource type="Script" path="res://addons/YarnSpinner-Godot/Runtime/Views/FormatMarker.cs" id="1_ir7mg"] + +[sub_resource type="Resource" id="Resource_my8im"] +script = ExtResource("1_ir7mg") +Marker = "b" +Color = Color(1, 1, 1, 1) +Boldened = true +Italicised = false +Underlined = false +Strikedthrough = false + +[sub_resource type="Resource" id="Resource_lbgvk"] +script = ExtResource("1_ir7mg") +Marker = "u" +Color = Color(1, 1, 1, 1) +Boldened = false +Italicised = false +Underlined = true +Strikedthrough = false + +[sub_resource type="Resource" id="Resource_ni5um"] +script = ExtResource("1_ir7mg") +Marker = "calm" +Color = Color(0.136169, 0.566038, 0.184834, 1) +Boldened = false +Italicised = false +Underlined = false +Strikedthrough = false + +[sub_resource type="Resource" id="Resource_8bx5h"] +script = ExtResource("1_ir7mg") +Marker = "hype" +Color = Color(0.754717, 0.162608, 0.0747597, 1) +Boldened = false +Italicised = false +Underlined = false +Strikedthrough = false + +[sub_resource type="Resource" id="Resource_ctqap"] +script = ExtResource("1_ir7mg") +Marker = "turbohype" +Color = Color(0.735849, 0.0173549, 0.722466, 1) +Boldened = false +Italicised = false +Underlined = false +Strikedthrough = false [resource] script = ExtResource("1_6b5jh") -ColourMarkers = { -"calm": Color(0.136169, 0.566038, 0.184834, 1), -"hype": Color(0.754717, 0.162608, 0.0747597, 1), -"turbohype": Color(0.735849, 0.0173549, 0.722466, 1) -} +FormatMarkers = Array[Object]([SubResource("Resource_my8im"), SubResource("Resource_lbgvk"), SubResource("Resource_ni5um"), SubResource("Resource_8bx5h"), SubResource("Resource_ctqap")]) diff --git a/Samples/PausingTypewriter/PauseResponder.cs b/Samples/PausingTypewriter/PauseResponder.cs index b234bee..3bc19db 100644 --- a/Samples/PausingTypewriter/PauseResponder.cs +++ b/Samples/PausingTypewriter/PauseResponder.cs @@ -7,7 +7,7 @@ public partial class PauseResponder : Control [Export] public TextureRect face; [Export] public Texture2D thinkingFace; [Export] public Texture2D talkingFace; - [Export] public LineView lineView; + [Export] public AsyncLineView lineView; public override void _Ready() { diff --git a/Samples/PausingTypewriter/PauseSample.tscn b/Samples/PausingTypewriter/PauseSample.tscn index f5be7d1..baff6bc 100644 --- a/Samples/PausingTypewriter/PauseSample.tscn +++ b/Samples/PausingTypewriter/PauseSample.tscn @@ -1,10 +1,12 @@ -[gd_scene load_steps=7 format=3 uid="uid://ckp616grs7xrs"] +[gd_scene load_steps=9 format=3 uid="uid://ckp616grs7xrs"] [ext_resource type="PackedScene" uid="uid://bv42g323prh5f" path="res://addons/YarnSpinner-Godot/Scenes/DefaultDialogueSystem.tscn" id="1_m0dlq"] [ext_resource type="Resource" uid="uid://21gon4fidoq8" path="res://Samples/PausingTypewriter/PauseProj.yarnproject" id="2_j3415"] [ext_resource type="Texture2D" uid="uid://crtrls05kcbu5" path="res://Samples/PausingTypewriter/sprites/talking.png" id="2_njmvd"] +[ext_resource type="Script" path="res://addons/YarnSpinner-Godot/Runtime/Async/AsyncLineView.cs" id="3_22yb1"] [ext_resource type="Script" path="res://Samples/PausingTypewriter/PauseResponder.cs" id="3_uqanb"] [ext_resource type="Texture2D" uid="uid://dk84dy4vqquds" path="res://Samples/PausingTypewriter/sprites/thinking.png" id="4_1i87u"] +[ext_resource type="Script" path="res://addons/YarnSpinner-Godot/Runtime/Async/AsyncOptionsView.cs" id="4_5arf7"] [ext_resource type="Script" path="res://Samples/ReturnOnComplete.cs" id="6_otpsy"] [node name="PauseSample" type="Panel"] @@ -18,8 +20,30 @@ grow_vertical = 2 [node name="DialogueRunner" parent="YarnSpinnerCanvasLayer" index="0"] yarnProject = ExtResource("2_j3415") +autoStart = true startNode = "Start" -startAutomatically = true + +[node name="LineView" parent="YarnSpinnerCanvasLayer" index="2" node_paths=PackedStringArray("dialogueRunner", "viewControl", "lineText", "characterNameContainer")] +script = ExtResource("3_22yb1") +dialogueRunner = NodePath("../DialogueRunner") +viewControl = NodePath("ViewControl") +lineText = NodePath("ViewControl/LineText") +characterNameContainer = NodePath("ViewControl/CharacterNameText") +fadeUpDuration = 0.25 +fadeDownDuration = 0.1 +autoAdvanceDelay = 1.0 +typewriterEffectSpeed = 60 + +[node name="OptionsListView" parent="YarnSpinnerCanvasLayer" index="3" node_paths=PackedStringArray("lastLineText", "lastLineContainer", "lastLineCharacterNameText", "lastLineCharacterNameContainer")] +script = ExtResource("4_5arf7") +showsLastLine = false +lastLineText = NodePath("../LineView/ViewControl/LineText") +lastLineContainer = NodePath("../LineView/ViewControl") +lastLineCharacterNameText = NodePath("../LineView/ViewControl/CharacterNameText") +lastLineCharacterNameContainer = NodePath("../LineView/ViewControl/CharacterNameText") + +[node name="TextLineProvider" parent="YarnSpinnerCanvasLayer" index="5"] +YarnProject = ExtResource("2_j3415") [node name="ColorRect" type="ColorRect" parent="."] layout_mode = 1 diff --git a/Samples/PausingTypewriter/sprites/talking.png.meta b/Samples/PausingTypewriter/sprites/talking.png.meta deleted file mode 100644 index a474501..0000000 --- a/Samples/PausingTypewriter/sprites/talking.png.meta +++ /dev/null @@ -1,123 +0,0 @@ -fileFormatVersion: 2 -guid: b3e0998ed09cf4c26aa5a77e27d55baa -TextureImporter: - internalIDToNameTable: [] - externalObjects: {} - serializedVersion: 12 - mipmaps: - mipMapMode: 0 - enableMipMap: 0 - sRGBTexture: 1 - linearTexture: 0 - fadeOut: 0 - borderMipMap: 0 - mipMapsPreserveCoverage: 0 - alphaTestReferenceValue: 0.5 - mipMapFadeDistanceStart: 1 - mipMapFadeDistanceEnd: 3 - bumpmap: - convertToNormalMap: 0 - externalNormalMap: 0 - heightScale: 0.25 - normalMapFilter: 0 - isReadable: 0 - streamingMipmaps: 0 - streamingMipmapsPriority: 0 - vTOnly: 0 - ignoreMasterTextureLimit: 0 - grayScaleToAlpha: 0 - generateCubemap: 6 - cubemapConvolution: 0 - seamlessCubemap: 0 - textureFormat: 1 - maxTextureSize: 2048 - textureSettings: - serializedVersion: 2 - filterMode: 1 - aniso: 1 - mipBias: 0 - wrapU: 1 - wrapV: 1 - wrapW: 0 - nPOTScale: 0 - lightmap: 0 - compressionQuality: 50 - spriteMode: 1 - spriteExtrude: 1 - spriteMeshType: 1 - alignment: 0 - spritePivot: {x: 0.5, y: 0.5} - spritePixelsToUnits: 100 - spriteBorder: {x: 0, y: 0, z: 0, w: 0} - spriteGenerateFallbackPhysicsShape: 1 - alphaUsage: 1 - alphaIsTransparency: 1 - spriteTessellationDetail: -1 - textureType: 8 - textureShape: 1 - singleChannelComponent: 0 - flipbookRows: 1 - flipbookColumns: 1 - maxTextureSizeSet: 0 - compressionQualitySet: 0 - textureFormatSet: 0 - ignorePngGamma: 0 - applyGammaDecoding: 0 - cookieLightType: 0 - platformSettings: - - serializedVersion: 3 - buildTarget: DefaultTexturePlatform - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - - serializedVersion: 3 - buildTarget: Standalone - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - - serializedVersion: 3 - buildTarget: Server - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - spriteSheet: - serializedVersion: 2 - sprites: [] - outline: [] - physicsShape: [] - bones: [] - spriteID: 5e97eb03825dee720800000000000000 - internalID: 0 - vertices: [] - indices: - edges: [] - weights: [] - secondaryTextures: [] - nameFileIdTable: {} - spritePackingTag: - pSDRemoveMatte: 0 - pSDShowRemoveMatteOption: 0 - userData: - assetBundleName: - assetBundleVariant: diff --git a/Samples/PausingTypewriter/sprites/thinking.png.meta b/Samples/PausingTypewriter/sprites/thinking.png.meta deleted file mode 100644 index 28e4a80..0000000 --- a/Samples/PausingTypewriter/sprites/thinking.png.meta +++ /dev/null @@ -1,123 +0,0 @@ -fileFormatVersion: 2 -guid: f6f94f4d324494fe5881822d16a40f4c -TextureImporter: - internalIDToNameTable: [] - externalObjects: {} - serializedVersion: 12 - mipmaps: - mipMapMode: 0 - enableMipMap: 0 - sRGBTexture: 1 - linearTexture: 0 - fadeOut: 0 - borderMipMap: 0 - mipMapsPreserveCoverage: 0 - alphaTestReferenceValue: 0.5 - mipMapFadeDistanceStart: 1 - mipMapFadeDistanceEnd: 3 - bumpmap: - convertToNormalMap: 0 - externalNormalMap: 0 - heightScale: 0.25 - normalMapFilter: 0 - isReadable: 0 - streamingMipmaps: 0 - streamingMipmapsPriority: 0 - vTOnly: 0 - ignoreMasterTextureLimit: 0 - grayScaleToAlpha: 0 - generateCubemap: 6 - cubemapConvolution: 0 - seamlessCubemap: 0 - textureFormat: 1 - maxTextureSize: 2048 - textureSettings: - serializedVersion: 2 - filterMode: 1 - aniso: 1 - mipBias: 0 - wrapU: 1 - wrapV: 1 - wrapW: 0 - nPOTScale: 0 - lightmap: 0 - compressionQuality: 50 - spriteMode: 1 - spriteExtrude: 1 - spriteMeshType: 1 - alignment: 0 - spritePivot: {x: 0.5, y: 0.5} - spritePixelsToUnits: 100 - spriteBorder: {x: 0, y: 0, z: 0, w: 0} - spriteGenerateFallbackPhysicsShape: 1 - alphaUsage: 1 - alphaIsTransparency: 1 - spriteTessellationDetail: -1 - textureType: 8 - textureShape: 1 - singleChannelComponent: 0 - flipbookRows: 1 - flipbookColumns: 1 - maxTextureSizeSet: 0 - compressionQualitySet: 0 - textureFormatSet: 0 - ignorePngGamma: 0 - applyGammaDecoding: 0 - cookieLightType: 0 - platformSettings: - - serializedVersion: 3 - buildTarget: DefaultTexturePlatform - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - - serializedVersion: 3 - buildTarget: Standalone - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - - serializedVersion: 3 - buildTarget: Server - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - spriteSheet: - serializedVersion: 2 - sprites: [] - outline: [] - physicsShape: [] - bones: [] - spriteID: 5e97eb03825dee720800000000000000 - internalID: 0 - vertices: [] - indices: - edges: [] - weights: [] - secondaryTextures: [] - nameFileIdTable: {} - spritePackingTag: - pSDRemoveMatte: 0 - pSDShowRemoveMatteOption: 0 - userData: - assetBundleName: - assetBundleVariant: diff --git a/Samples/RoundedViews/RoundedSample.tscn b/Samples/RoundedViews/RoundedSample.tscn index 302b63f..c240765 100644 --- a/Samples/RoundedViews/RoundedSample.tscn +++ b/Samples/RoundedViews/RoundedSample.tscn @@ -10,7 +10,10 @@ [node name="DialogueRunner" parent="RoundedYarnSpinnerCanvasLayer" index="0"] yarnProject = ExtResource("2_s6ssf") -startAutomatically = true +autoStart = true + +[node name="TextLineProvider" parent="RoundedYarnSpinnerCanvasLayer" index="5"] +YarnProject = ExtResource("2_s6ssf") [node name="ReturnOnComplete" type="Node2D" parent="." node_paths=PackedStringArray("dialogueRunner")] script = ExtResource("3_cw7ad") diff --git a/Samples/SQLiteVariableStorage/SQLSample.tscn b/Samples/SQLiteVariableStorage/SQLSample.tscn index 4defade..816bcd4 100644 --- a/Samples/SQLiteVariableStorage/SQLSample.tscn +++ b/Samples/SQLiteVariableStorage/SQLSample.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=5 format=3 uid="uid://dt33g06nhtxn2"] [ext_resource type="PackedScene" uid="uid://dqe5yshlcmnpg" path="res://addons/YarnSpinner-Godot/Scenes/RoundedDialogueSystem.tscn" id="1_8wwci"] -[ext_resource type="Resource" uid="uid://tsj53mnkwps3" path="res://Samples/SQLiteVariableStorage/SQLSample.yarnproject" id="2_73843"] +[ext_resource type="Resource" uid="uid://tsj53mnkwps3" path="res://Samples/SQLiteVariableStorage/SQLSample.yarnproject" id="2_bai3o"] [ext_resource type="Script" path="res://Samples/SQLiteVariableStorage/SQLVariableStorage.cs" id="3_71oeg"] [ext_resource type="Script" path="res://Samples/ReturnOnComplete.cs" id="4_lgfx4"] @@ -9,18 +9,16 @@ [node name="RoundedYarnSpinnerCanvasLayer" parent="." instance=ExtResource("1_8wwci")] -[node name="DialogueRunner" parent="RoundedYarnSpinnerCanvasLayer" index="0" node_paths=PackedStringArray("variableStorage")] -yarnProject = ExtResource("2_73843") -variableStorage = NodePath("../SQLVariableStorage") +[node name="DialogueRunner" parent="RoundedYarnSpinnerCanvasLayer" index="0"] +yarnProject = ExtResource("2_bai3o") +autoStart = true startNode = "SqlSample" -startAutomatically = true [node name="LineView" parent="RoundedYarnSpinnerCanvasLayer" index="2"] useFadeEffect = true -fadeInTime = 0.2 -[node name="OptionsListView" parent="RoundedYarnSpinnerCanvasLayer" index="3"] -fadeTime = 0.2 +[node name="TextLineProvider" parent="RoundedYarnSpinnerCanvasLayer" index="5"] +YarnProject = ExtResource("2_bai3o") [node name="ColorRect" type="ColorRect" parent="RoundedYarnSpinnerCanvasLayer"] z_index = -6 diff --git a/Samples/SQLiteVariableStorage/SQLSample.yarnproject b/Samples/SQLiteVariableStorage/SQLSample.yarnproject index 5cbeb26..b3bc90d 100644 --- a/Samples/SQLiteVariableStorage/SQLSample.yarnproject +++ b/Samples/SQLiteVariableStorage/SQLSample.yarnproject @@ -1,5 +1,5 @@ { - "projectFileVersion": 2, + "projectFileVersion": 3, "sourceFiles": [ "**/*.yarn" ], diff --git a/Samples/SQLiteVariableStorage/SqlSample.yarn b/Samples/SQLiteVariableStorage/SqlSample.yarn index 29ba08d..c7caeb5 100644 --- a/Samples/SQLiteVariableStorage/SqlSample.yarn +++ b/Samples/SQLiteVariableStorage/SqlSample.yarn @@ -1,41 +1,56 @@ title: SqlSample tags: --- -<> -<> +<> + <> + <> +<> + +<> + <> + <> + <> + <> + <> +<> + +<> +<> <> -Guide: Alright, I've been waiting for my last applicant of the day. -Guide: Hmm, how do you say your name!? It says "S-Q-L". --> Eskew-well - <> --> Seekwill - <> -{$characterName}: My name is {$characterName}. -{$characterName}: I have trained for one thousand years to store the most variables of all time. -Guide: Wow. Impressive! Just how many variables do you think you could store?? --> There's no limit. - <> - Guide: Wh- what? Unlimited?? - Guide: Gahhh!!! - The guide ran away. +<> +<> +Guide: Alright, you're my [ordinal value={$nthApplicant} one="%st" two="%nd" few="%rd" other="%th" /] this morning. #line:0d2bca2 +Guide: Hmm, how do you say your name!? It says "S-Q-L". #line:0e6b788 +-> Eskew-well #line:0f551f3 + <> +-> Seekwill #line:07187e4 + <> +{$characterName}: My name is {$characterName}. #line:0d98ec4 +{$characterName}: I have trained for one thousand years to store the most variables of all time. #line:089db20 +Guide: Wow. Impressive! Just how many variables do you think you could store?? #line:0930b53 +-> There's no limit. #line:03bddee + <> + Guide: Wh- what? Unlimited?? #line:09af09f + Guide: Gahhh!!! #line:02fbbb2 + The guide ran away. #line:0806472 <> --> Sixty two thousand. - <> - Guide: Formidable... imagine the heights you could reach under our guidance. +-> Sixty two thousand. #line:0961fda + <> + Guide: Formidable... imagine the heights you could reach under our guidance. #line:0aed054 <> --> One thousand. - <> - Guide: Hmm. That's not bad. +-> One thousand. #line:0332341 + <> + Guide: Hmm. That's not bad. #line:09737bd <> --> Three. - <> - Guide: I ...see. +-> Three. #line:0ef43c6 + <> + Guide: I ...see. #line:0f34784 <> -Guide: Alright then, {$characterName}. You can store {$numVariables} variables... +Guide: Alright then, {$characterName}. You can store {$numVariables} variables... #line:06ab48d <> -Guide: Welcome to Variable Academy. +Guide: Welcome to Variable Academy. #line:07f7e2a <> -Guide: I'm afraid you're not cut out for Variable Academy. +Guide: I'm afraid you're not cut out for Variable Academy. #line:02b6a1f <> === \ No newline at end of file diff --git a/Samples/SampleEntryPoint.tscn b/Samples/SampleEntryPoint.tscn index c1f1633..921a33c 100644 --- a/Samples/SampleEntryPoint.tscn +++ b/Samples/SampleEntryPoint.tscn @@ -1,7 +1,8 @@ -[gd_scene load_steps=4 format=3 uid="uid://dnyy064638ly1"] +[gd_scene load_steps=5 format=3 uid="uid://dnyy064638ly1"] [ext_resource type="Script" path="res://Samples/SampleEntryPoint.cs" id="1"] [ext_resource type="Theme" uid="uid://b2mp0b1wvnu8s" path="res://Samples/sample_default_theme.tres" id="2"] +[ext_resource type="Script" path="res://Samples/SampleVersionNumberLabel.cs" id="2_shsu2"] [ext_resource type="Texture2D" uid="uid://pbrr5yyepbx8" path="res://addons/YarnSpinner-Godot/Editor/Icons/YarnSpinnerLogo.png" id="3_bcudv"] [node name="SampleEntryPoint" type="CanvasLayer" node_paths=PackedStringArray("_spaceButton", "_visualNovelButton", "_markupPaletteButton", "_pausingTypewriterButton", "_roundedViewsButton", "_sqliteButton", "_gdscriptButton")] @@ -36,6 +37,39 @@ theme_override_font_sizes/normal_font_size = 48 bbcode_enabled = true text = "[u]YarnSpinner Samples[/u]" +[node name="PluginVersionLabel" type="RichTextLabel" parent="."] +clip_contents = false +anchors_preset = 5 +anchor_left = 0.5 +anchor_right = 0.5 +offset_left = -502.0 +offset_top = 257.0 +offset_right = 89.0 +offset_bottom = 364.0 +grow_horizontal = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_font_sizes/normal_font_size = 28 +bbcode_enabled = true +text = "Plugin Version" + +[node name="PluginVersionValue" type="RichTextLabel" parent="."] +clip_contents = false +anchors_preset = 5 +anchor_left = 0.5 +anchor_right = 0.5 +offset_left = -294.0 +offset_top = 257.0 +offset_right = 297.0 +offset_bottom = 364.0 +grow_horizontal = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_font_sizes/normal_font_size = 28 +bbcode_enabled = true +text = "0.3.0" +script = ExtResource("2_shsu2") + [node name="Logo" type="TextureRect" parent="."] offset_left = 76.0 offset_top = 55.0 @@ -52,8 +86,8 @@ anchor_right = 0.5 anchor_bottom = 0.5 offset_left = -696.0 offset_top = -64.0 -offset_right = 696.0 -offset_bottom = 340.0 +offset_right = 744.0 +offset_bottom = 397.0 grow_horizontal = 2 grow_vertical = 2 size_flags_vertical = 8 @@ -79,7 +113,9 @@ theme_override_font_sizes/normal_font_size = 28 bbcode_enabled = true text = "Basic dialogue demo Talking to NPCs -" +Line Groups +Node Groups +Shadow lines" scroll_active = false [node name="VBoxContainer2" type="VBoxContainer" parent="HBoxContainer"] diff --git a/Samples/SampleVersionNumberLabel.cs b/Samples/SampleVersionNumberLabel.cs new file mode 100644 index 0000000..3a70797 --- /dev/null +++ b/Samples/SampleVersionNumberLabel.cs @@ -0,0 +1,23 @@ +using Godot; +using System.IO; +using System.Text.RegularExpressions; +using FileAccess = Godot.FileAccess; + +[Tool] +public partial class SampleVersionNumberLabel : RichTextLabel +{ + // Called when the node enters the scene tree for the first time. + public override void _Ready() + { + using var pluginFile = FileAccess.Open("res://addons/YarnSpinner-Godot/plugin.cfg", FileAccess.ModeFlags.Read); + var pluginCfgText = pluginFile.GetAsText(); + var versionNumber = new Regex("version=\"(.*)\""); + var versionMatch = versionNumber.Match(pluginCfgText); + if (!versionMatch.Success) + { + return; + } + + Text = versionMatch.Groups[1].ToString(); + } +} \ No newline at end of file diff --git a/Samples/Space/Dialogue/Sally.yarn b/Samples/Space/Dialogue/Sally.yarn index ab8f7df..82a5f61 100644 --- a/Samples/Space/Dialogue/Sally.yarn +++ b/Samples/Space/Dialogue/Sally.yarn @@ -3,18 +3,32 @@ tags: colorID: 0 position: 524,111 --- -<> +<> +< 3>> +<> Player: Hey, Sally. #line:794945 // these HTML-style tags will only work with the ConvertHTMLToBBCode setting - // enabled on the provided example LineView + // enabled on the provided example AsyncLineView Sally: Uah! #line:2dc39b Sally: You snuck up on me! #line:34de2f Sally: Don't do that. #line:dcc2bc -<> - Player: Hey. #line:a8e70c - Sally: Hi. #line:0af7f86 -<> +<> +<> +=== +title: Sally_Give_Quest +--- +<> +// smart variable example +<> +<> +Sally: Hey! Stop talking to me and talk to the ship already! #line:02aa0c8 +Sally: Sigh... #line:0255785 +<> +<> + => Sally: Hi. #line:0af7f86 + => Sally: What's up. #line:077a332 + => Sally: Yo. #line:0b00ed4 -> Anything exciting happen on your watch? <> #line:5d7a7c <> -> Sorry about the console. <> #line:0a7e39 @@ -31,6 +45,7 @@ position: 512,430 Sally: Not really. #line:8c3f98 Sally: Same old nebula, doing the same old thing. #line:24c418 Sally: Oh, Ship wanted to see you. Go say hi to it. #line:df4eaf +Sally: Bye. #shadow:60c282 <> <> Player: Already done! #line:1fea6c @@ -51,6 +66,8 @@ tags: colorID: 0 position: 827,439 --- +<> Sally: Yeah. Don't do it again. #line:d7df49 +<> === diff --git a/Samples/Space/Dialogue/Ship.yarn b/Samples/Space/Dialogue/Ship.yarn index 02c5794..f3f60b5 100644 --- a/Samples/Space/Dialogue/Ship.yarn +++ b/Samples/Space/Dialogue/Ship.yarn @@ -3,7 +3,7 @@ tags: colorID: 0 position: 721,130 --- -<> +<> Ship: Hey, friend. #line:5837f2 #firstline Player: Hi, Ship. #line:ship09 Player: How's space? #line:ship10 @@ -11,13 +11,12 @@ position: 721,130 <> Ship: It's HUGE! #line:ship12 <> -<> +<> + +<> <> Ship: Hey!! #line:ship13 <> -<> - -<> Player: Sally said you wanted to see me? #line:ship01 <> Ship: She totally did!! #line:ship03 @@ -30,5 +29,60 @@ position: 721,130 <> Player: Uh. #line:ship07 <> +<> +<> <> === + +// Node groups example. YarnSpinner will pick from these same-named +// nodes based on the saliency strategy set on dialogueRunner.Dialogue.ContentSaliencyStrategy +// They must have a when: tag to be in a node group. + +title: ShipChat +when: always +--- +<> +Ship: Remember that alien made of crystals that we met? That was cool. +=== + +title: ShipChat +when: always +--- +Ship: Remind me again, are you a shapeshifting species? +Ship: Oh that's right, you're just a human. How, um, exciting. +<> +=== + +title: ShipChat +when: always +--- +<> +Ship: I was built in the Gamma Quadrant. It's still my favorite quadrant. +=== + + +title: ShipChat +when: $apologized_to_sally +--- +<> +Ship: Don't feel too bad about the console. She probably didn't mean the thing about the flaying. +<> +Ship: I mean she's only done that twice ever. And only once to a crew member. +=== + +title: ShipChat +when: always +--- +<> +Ship: Did I ever tell you about the psychic giant cyclops of Beltridion Blue? +<> +Ship: Easily in the top ten cyclops planets I've ever been to. +=== + +title: Cyclops +--- +Ship: Of course as a machine, they were unable to read my mind. +Ship: But they could certainly read Sally's, and boy did they make her embarrassed. +Ship: That's why I was surprised when she gifted them our archives on how to manufacture monocles. +Ship: Their King, Iris, said I was an 'amazing device'. +=== \ No newline at end of file diff --git a/Samples/Space/Dialogue/SpaceYarnProject.yarnproject b/Samples/Space/Dialogue/SpaceYarnProject.yarnproject index f6d4fc9..96aafdd 100644 --- a/Samples/Space/Dialogue/SpaceYarnProject.yarnproject +++ b/Samples/Space/Dialogue/SpaceYarnProject.yarnproject @@ -1,5 +1,5 @@ { - "projectFileVersion": 2, + "projectFileVersion": 3, "sourceFiles": [ "**/*.yarn" ], diff --git a/Samples/Space/Dialogue/SpaceYarnVariables.cs b/Samples/Space/Dialogue/SpaceYarnVariables.cs new file mode 100644 index 0000000..0784219 --- /dev/null +++ b/Samples/Space/Dialogue/SpaceYarnVariables.cs @@ -0,0 +1,46 @@ +using YarnSpinnerGodot; + +[System.CodeDom.Compiler.GeneratedCode("YarnSpinner", "0.3.0")] +public partial class SpaceYarnVariables : YarnSpinnerGodot.InMemoryVariableStorage, YarnSpinnerGodot.IGeneratedVariableStorage { + // Accessor for String $sampleName + public string SampleName { + get => this.GetValueOrDefault("$sampleName"); + set => this.SetValue("$sampleName", value); + } + + // Accessor for Number $times_talked_sally_before_ship + public float TimesTalkedSallyBeforeShip { + get => this.GetValueOrDefault("$times_talked_sally_before_ship"); + set => this.SetValue("$times_talked_sally_before_ship", value); + } + + // Accessor for Bool $player_avoided_ship + public bool PlayerAvoidedShip { + get => this.GetValueOrDefault("$player_avoided_ship"); + } + + // Accessor for Bool $apologized_to_sally + public bool ApologizedToSally { + get => this.GetValueOrDefault("$apologized_to_sally"); + set => this.SetValue("$apologized_to_sally", value); + } + + // Accessor for Bool $should_see_ship + /// + /// Ship.yarn, node Ship, line 14 + /// + public bool ShouldSeeShip { + get => this.GetValueOrDefault("$should_see_ship"); + set => this.SetValue("$should_see_ship", value); + } + + // Accessor for Bool $sally_warning + /// + /// Ship.yarn, node Ship, line 14 + /// + public bool SallyWarning { + get => this.GetValueOrDefault("$sally_warning"); + set => this.SetValue("$sally_warning", value); + } + +} diff --git a/Samples/Space/DialogueTarget.cs b/Samples/Space/DialogueTarget.cs index 6a35119..faff295 100644 --- a/Samples/Space/DialogueTarget.cs +++ b/Samples/Space/DialogueTarget.cs @@ -1,13 +1,12 @@ #nullable disable using Godot; using System; -namespace Samples.Space +namespace Samples.Space; + +public partial class DialogueTarget : Node { - public partial class DialogueTarget : Node - { - /// - /// Node name if the player talks to this NPC - /// - [Export] public string nodeName; - } + /// + /// Node name if the player talks to this NPC + /// + [Export] public string nodeName; } \ No newline at end of file diff --git a/Samples/Space/Player.cs b/Samples/Space/Player.cs index f302979..8ab917b 100644 --- a/Samples/Space/Player.cs +++ b/Samples/Space/Player.cs @@ -2,102 +2,101 @@ using Godot; using Godot.Collections; using YarnSpinnerGodot; -namespace Samples.Space +namespace Samples.Space; + +/// +/// Script for controlling the player and triggering dialogue +/// in the space sample +/// +public partial class Player : CharacterBody2D { + /// - /// Script for controlling the player and triggering dialogue - /// in the space sample + /// Collision shape used to check for NPCs to chat with /// - public partial class Player : CharacterBody2D - { + [Export] public CollisionShape2D IntersectShape; - /// - /// Collision shape used to check for NPCs to chat with - /// - [Export] public CollisionShape2D IntersectShape; + [Export] public DialogueRunner DialogueRunner; + // we start the intro dialogue automatically, so prevent the player from moving at first + private bool _dialoguePlaying = true; - [Export] public DialogueRunner DialogueRunner; - // we start the intro dialogue automatically, so prevent the player from moving at first - private bool _dialoguePlaying = true; + private const float X_SPEED = 480f; + private const int NPC_LAYER = 2; // NPCs in the demo are in layer 2 - private const float X_SPEED = 480f; - private const int NPC_LAYER = 2; // NPCs in the demo are in layer 2 - - public override void _Ready() + public override void _Ready() + { + DialogueRunner.onDialogueComplete += SetDialogueNotPlaying; + DialogueRunner.onDialogueStart += DialogueStarted; + DialogueRunner.onNodeComplete += NodeCompleted; + } + public override void _PhysicsProcess(double delta) + { + if (_dialoguePlaying) { - DialogueRunner.onDialogueComplete += SetDialogueNotPlaying; - DialogueRunner.onDialogueStart += DialogueStarted; - DialogueRunner.onNodeComplete += NodeCompleted; + return; } - public override void _PhysicsProcess(double delta) - { - if (_dialoguePlaying) - { - return; - } - if (Input.IsActionPressed("right")) - { - Velocity = new Vector2(X_SPEED, 0f); + if (Input.IsActionPressed("right")) + { + Velocity = new Vector2(X_SPEED, 0f); - } - else if (Input.IsActionPressed("left")) - { - Velocity = new Vector2(-X_SPEED, 0f); - } - else + } + else if (Input.IsActionPressed("left")) + { + Velocity = new Vector2(-X_SPEED, 0f); + } + else + { + Velocity *= 0.1f; // skid to stop + } + MoveAndSlide(); + if (Input.IsActionPressed("interact")) + { + var world = GetWorld2D(); + var spaceState = world.DirectSpaceState; + var hitCollider = spaceState.IntersectShape(new PhysicsShapeQueryParameters2D() { - Velocity *= 0.1f; // skid to stop - } - MoveAndSlide(); - if (Input.IsActionPressed("interact")) + CollisionMask = 1 << (NPC_LAYER - 1), + Margin = 40, + ShapeRid = IntersectShape.Shape.GetRid(), + Transform = IntersectShape.GlobalTransform, + CollideWithAreas = true, + CollideWithBodies = true + }); + foreach (Dictionary colliderCheck in hitCollider) { - var world = GetWorld2D(); - var spaceState = world.DirectSpaceState; - var hitCollider = spaceState.IntersectShape(new PhysicsShapeQueryParameters2D() - { - CollisionMask = 1 << (NPC_LAYER - 1), - Margin = 40, - ShapeRid = IntersectShape.Shape.GetRid(), - Transform = IntersectShape.GlobalTransform, - CollideWithAreas = true, - CollideWithBodies = true - }); - foreach (Dictionary colliderCheck in hitCollider) + if (colliderCheck.ContainsKey("collider")) { - if (colliderCheck.ContainsKey("collider")) + var colliderNode = ((Node)colliderCheck["collider"]); + if (colliderNode.HasNode(nameof(DialogueTarget))) { - var colliderNode = ((Node)colliderCheck["collider"]); - if (colliderNode.HasNode(nameof(DialogueTarget))) - { - var target = colliderNode.GetNode(nameof(DialogueTarget)); - _dialoguePlaying = true; - DialogueRunner.StartDialogue(target.nodeName); - break; - } + var target = colliderNode.GetNode(nameof(DialogueTarget)); + _dialoguePlaying = true; + DialogueRunner.StartDialogue(target.nodeName); + break; } } } } + } - /// - /// Example of connecting to onDialogueStart from - /// - private void DialogueStarted() - { - GD.Print("Dialogue started in space sample"); - } + /// + /// Example of connecting to onDialogueStart from + /// + private void DialogueStarted() + { + GD.Print("Dialogue started in space sample"); + } - /// - /// Example of connecting to onNodeComplete from - /// - private void NodeCompleted(string nodeName) - { - GD.Print($"Dialogue node {nodeName} completed in space sample"); - } - private void SetDialogueNotPlaying() - { - _dialoguePlaying = false; - } + /// + /// Example of connecting to onNodeComplete from + /// + private void NodeCompleted(string nodeName) + { + GD.Print($"Dialogue node {nodeName} completed in space sample"); + } + private void SetDialogueNotPlaying() + { + _dialoguePlaying = false; } } \ No newline at end of file diff --git a/Samples/Space/Scripts/SpaceSample.cs b/Samples/Space/Scripts/SpaceSample.cs index be37820..36c016f 100644 --- a/Samples/Space/Scripts/SpaceSample.cs +++ b/Samples/Space/Scripts/SpaceSample.cs @@ -1,36 +1,46 @@ #nullable disable using Godot; using YarnSpinnerGodot; + public partial class SpaceSample : Node { - [Export] public Texture2D ShipHappySprite; - [Export] public Texture2D ShipNeutralSprite; - [Export] public DialogueRunner dialogueRunner; - [Export] public Sprite2D shipFace; - private static SpaceSample _instance; - // Called when the node enters the scene tree for the first time. - public override void _Ready() - { - _instance = this; - dialogueRunner.onDialogueComplete += OnDialogueComplete; - } + [Export] public Texture2D ShipHappySprite; + [Export] public Texture2D ShipNeutralSprite; + [Export] public DialogueRunner dialogueRunner; + [Export] public Sprite2D shipFace; + + private static SpaceSample _instance; - [YarnCommand("setsprite")] - public static void SetSprite(string character, string spriteName) - { - // assume ShipFace character as only one character uses this command right now - if (spriteName.ToLower().Equals("happy")) - { - _instance.shipFace.Texture = _instance.ShipHappySprite; - } else if (spriteName.ToLower().Equals("neutral")) - { - _instance.shipFace.Texture = _instance.ShipNeutralSprite; - } - } + // Called when the node enters the scene tree for the first time. + public override void _Ready() + { + _instance = this; + dialogueRunner.onDialogueComplete += OnDialogueComplete; + dialogueRunner.Dialogue.ContentSaliencyStrategy = + new Yarn.Saliency.RandomBestLeastRecentlyViewedSalienceStrategy(dialogueRunner.VariableStorage); + } - private void OnDialogueComplete() - { - GD.Print("Space sample has completed!"); - } + public static void Test(global::YarnSpinnerGodot.IActionRegistration target) + { + target.AddCommandHandler("test", SetSprite); + } + + [YarnCommand("setsprite")] + public static void SetSprite(string character, string spriteName) + { + // assume ShipFace character as only one character uses this command right now + if (spriteName.ToLower().Equals("happy")) + { + _instance.shipFace.Texture = _instance.ShipHappySprite; + } + else if (spriteName.ToLower().Equals("neutral")) + { + _instance.shipFace.Texture = _instance.ShipNeutralSprite; + } + } -} + private void OnDialogueComplete() + { + GD.Print("Space sample has completed!"); + } +} \ No newline at end of file diff --git a/Samples/Space/SpaceCamera.cs b/Samples/Space/SpaceCamera.cs index 17920a3..dfb142e 100644 --- a/Samples/Space/SpaceCamera.cs +++ b/Samples/Space/SpaceCamera.cs @@ -7,22 +7,20 @@ /// public partial class SpaceCamera : Camera2D { - [Export] public NodePath followTargetPath; + [Export] public Node2D followTarget; [Export] public int MinX; [Export] public int MaxX; - private Node2D _followTarget; private int _cameraWidth; public override void _Ready() { - _followTarget = GetNode(followTargetPath); _cameraWidth = (int)((int)ProjectSettings.GetSetting("display/window/size/viewport_width") / Zoom.X); } public override void _PhysicsProcess(double delta) { - var idealX = _followTarget.GlobalPosition.X - _cameraWidth * 0.5f; + var idealX = followTarget.GlobalPosition.X - _cameraWidth * 0.5f; idealX = Math.Min(idealX, MaxX); idealX = Math.Max(idealX, MinX); GlobalPosition = new Vector2(idealX, GlobalPosition.Y); diff --git a/Samples/Space/SpaceSample.tscn b/Samples/Space/SpaceSample.tscn index 6b0d4b1..388b642 100644 --- a/Samples/Space/SpaceSample.tscn +++ b/Samples/Space/SpaceSample.tscn @@ -1,7 +1,6 @@ -[gd_scene load_steps=18 format=3 uid="uid://ddbq27wcm6emj"] +[gd_scene load_steps=20 format=3 uid="uid://ddbq27wcm6emj"] -[ext_resource type="Resource" uid="uid://d2lkhhnqha4qi" path="res://Samples/Space/Dialogue/SpaceYarnProject.yarnproject" id="1"] -[ext_resource type="Texture2D" uid="uid://delt5hkois0xf" path="res://Samples/Space/Sprites/Ship-Face-Happy.png" id="2_3dm8d"] +[ext_resource type="Texture2D" uid="uid://delt5hkois0xf" path="res://Samples/Space/Sprites/Ship-Face-Happy.png" id="2_b8xl3"] [ext_resource type="Script" path="res://Samples/Space/Scripts/SpaceSample.cs" id="3"] [ext_resource type="Texture2D" uid="uid://dsxwawdra77cw" path="res://Samples/Space/Sprites/Ship-Face-Neutral.png" id="3_b3htj"] [ext_resource type="PackedScene" uid="uid://bv42g323prh5f" path="res://addons/YarnSpinner-Godot/Scenes/DefaultDialogueSystem.tscn" id="4"] @@ -10,10 +9,13 @@ [ext_resource type="Texture2D" uid="uid://u2fbx0x644y6" path="res://Samples/Space/Sprites/Background.jpg" id="6_lstjv"] [ext_resource type="Texture2D" uid="uid://bof87dtlnbrt0" path="res://Samples/Space/Sprites/Wall.png" id="7_ld3rh"] [ext_resource type="Texture2D" uid="uid://d3utr21wvs3n6" path="res://Samples/Space/Sprites/Player.png" id="8_18hb1"] +[ext_resource type="Resource" uid="uid://d2lkhhnqha4qi" path="res://Samples/Space/Dialogue/SpaceYarnProject.yarnproject" id="12_qnfx0"] [ext_resource type="Script" path="res://Samples/Space/Player.cs" id="14"] [ext_resource type="Script" path="res://Samples/Space/Scripts/StopButton.cs" id="14_1b0fg"] [ext_resource type="Script" path="res://Samples/Space/SpaceCamera.cs" id="15"] [ext_resource type="Script" path="res://Samples/Space/DialogueTarget.cs" id="16"] +[ext_resource type="Script" path="res://Samples/Space/Dialogue/SpaceYarnVariables.cs" id="16_i6fc8"] +[ext_resource type="Script" path="res://addons/YarnSpinner-Godot/Runtime/Async/LineAdvancer.cs" id="17_wrty4"] [sub_resource type="CapsuleShape2D" id="3"] radius = 40.02 @@ -28,7 +30,7 @@ size = Vector2(314, 2122) [node name="SpaceSample" type="Node2D" node_paths=PackedStringArray("dialogueRunner", "shipFace")] script = ExtResource("3") -ShipHappySprite = ExtResource("2_3dm8d") +ShipHappySprite = ExtResource("2_b8xl3") ShipNeutralSprite = ExtResource("3_b3htj") dialogueRunner = NodePath("YarnSpinnerCanvasLayer/DialogueRunner") shipFace = NodePath("Ship/ShipFaceSprite") @@ -131,29 +133,27 @@ offset_right = 4096.0 offset_bottom = 1536.0 texture = ExtResource("7_ld3rh") -[node name="SpaceCamera" type="Camera2D" parent="."] +[node name="SpaceCamera" type="Camera2D" parent="." node_paths=PackedStringArray("followTarget")] position = Vector2(3, 630) anchor_mode = 0 script = ExtResource("15") -followTargetPath = NodePath("../Player") +followTarget = NodePath("../Player") MinX = 3 MaxX = 1200 [node name="YarnSpinnerCanvasLayer" parent="." instance=ExtResource("4")] -[node name="DialogueRunner" parent="YarnSpinnerCanvasLayer" index="0"] -yarnProject = ExtResource("1") +[node name="DialogueRunner" parent="YarnSpinnerCanvasLayer" index="0" node_paths=PackedStringArray("dialogueViews")] +yarnProject = ExtResource("12_qnfx0") +dialogueViews = [NodePath("../AsyncLineView"), NodePath("../AsyncOptionsView"), NodePath("../../LineAdvancer")] +autoStart = true startNode = "SpaceIntro" -startAutomatically = true -[node name="LineView" parent="YarnSpinnerCanvasLayer" index="2"] -ConvertHTMLToBBCode = true - -[node name="LineText" parent="YarnSpinnerCanvasLayer/LineView/ViewControl" index="0"] -theme_override_font_sizes/normal_font_size = 34 +[node name="InMemoryVariableStorage" parent="YarnSpinnerCanvasLayer" index="4"] +script = ExtResource("16_i6fc8") [node name="TextLineProvider" parent="YarnSpinnerCanvasLayer" index="5"] -textLanguageCode = "en" +YarnProject = ExtResource("12_qnfx0") [node name="StopButton" type="Button" parent="YarnSpinnerCanvasLayer" node_paths=PackedStringArray("DialogueRunner")] self_modulate = Color(1, 0, 0, 1) @@ -166,4 +166,9 @@ text = "Stop Dialogue" script = ExtResource("14_1b0fg") DialogueRunner = NodePath("../DialogueRunner") +[node name="LineAdvancer" type="Node2D" parent="." node_paths=PackedStringArray("runner")] +script = ExtResource("17_wrty4") +runner = NodePath("../YarnSpinnerCanvasLayer/DialogueRunner") +hurryUpAction = "dialogue_hurry" + [editable path="YarnSpinnerCanvasLayer"] diff --git a/Samples/VisualNovel/Dialogue/VNExampleDialogue.yarn b/Samples/VisualNovel/Dialogue/VNExampleDialogue.yarn index d21ad55..b8139bb 100644 --- a/Samples/VisualNovel/Dialogue/VNExampleDialogue.yarn +++ b/Samples/VisualNovel/Dialogue/VNExampleDialogue.yarn @@ -6,34 +6,34 @@ position: 0,0 // sets background image to sprite called "bg_office" // note: this command uses the [YarnCommand] attribute instead of AddCommandHandler, so -// the node name must be specified - in this case VisualNovelManager -<> +// the node name must be specified - in this case VNManager +<> // start playing audioclip "ambient_birds" at 50% volume, loop forever -<> +<> A VISUAL NOVEL EXAMPLE #line:07ee50d // adds actor named "Eve" using sprite "cool-girl", placed in left-half + center of screen, with green text label -<> +<> Eve: Finally, a quiet day at the office. Maybe I'll be able to get some work done. #line:03a9f6d // adds actor "Adam" with sprite "biz-guy" off-screen right, with blue-ish text label -<> +<> // animate Adam into new position in right-half + center, within 0.5 seconds -<> -<> +<> +<> Adam: Hey Eve! I have a question! #line:07f4eac Eve: Oh no... #line:0721506 // horizontally flips Eve actor to face Adam -<> +<> // Yarn shortcut choices, an easy way to make branching options -> Eve gets upset with Adam #line:0b52843 - <> + <> Eve: WHAT DO YOU WANT??? #line:09f21f4 Adam: I just wanted to see if you liked using YarnSpinner. #line:0b7be61 Eve: ... It's fine. Thanks. Now leave me alone. #line:01942dd @@ -43,29 +43,29 @@ Eve: Oh no... #line:0721506 Adam: Oh, I didn't realize you were busy. Sorry. #line:0cb4316 // hide Adam actor and remove from the scene -<> +<> // Stop all sound playback -<> +<> Eve: Ok, now to finally write that visual novel. #line:0794753 -<> -<> +<> +<> Eve: I can finally write my epic romance about - #line:0c39b43 -<> -<> +<> +<> <> -<> +<> Eve: Wait... where's my laptop? It's not here. #line:04432b5 -<> +<> Eve: Adam! Have you seen my laptop??? #line:0a21105 -<> -<> -<> +<> +<> +<> Eve: ADDDDAAAMMMMMMM!!! #line:062467b // fade to black, from 0% opacity to 100% opacity, in 2.0 seconds -<> +<> THE END. #line:0d7e78e === \ No newline at end of file diff --git a/Samples/VisualNovel/Dialogue/VisualNovel.yarnproject b/Samples/VisualNovel/Dialogue/VisualNovel.yarnproject index 02d1e97..fcdb9a6 100644 --- a/Samples/VisualNovel/Dialogue/VisualNovel.yarnproject +++ b/Samples/VisualNovel/Dialogue/VisualNovel.yarnproject @@ -1,5 +1,5 @@ { - "projectFileVersion": 2, + "projectFileVersion": 3, "sourceFiles": [ "**/*.yarn" ], diff --git a/Samples/VisualNovel/Localization/VisualNovel_es.translation b/Samples/VisualNovel/Localization/VisualNovel_es.translation index a487fe6..3dda890 100644 Binary files a/Samples/VisualNovel/Localization/VisualNovel_es.translation and b/Samples/VisualNovel/Localization/VisualNovel_es.translation differ diff --git a/Samples/VisualNovel/Localization/VisualNovel_ja.translation b/Samples/VisualNovel/Localization/VisualNovel_ja.translation index eae7fde..04003df 100644 Binary files a/Samples/VisualNovel/Localization/VisualNovel_ja.translation and b/Samples/VisualNovel/Localization/VisualNovel_ja.translation differ diff --git a/Samples/VisualNovel/Scripts/VisualNovelManager.cs b/Samples/VisualNovel/Scripts/VisualNovelManager.cs index 1b8f19f..d40c3d1 100644 --- a/Samples/VisualNovel/Scripts/VisualNovelManager.cs +++ b/Samples/VisualNovel/Scripts/VisualNovelManager.cs @@ -47,14 +47,8 @@ public override void _Ready() StartDialogue("ja"); _colorOverlay = GetNode(_colorOverlayPath); _dialogueRunner = GetNode(_dialogueRunnerPath); - _dialogueRunner.AddCommandHandler("PlayAudio", PlayAudio); - _dialogueRunner.AddCommandHandler("Act", SetActor); - _dialogueRunner.AddCommandHandler("Move", new Func(MoveSprite)); - _dialogueRunner.AddCommandHandler("Flip", FlipSprite); - _dialogueRunner.AddCommandHandler("Shake", new Func(ShakeSprite)); - _dialogueRunner.AddCommandHandler("Hide", new Action(HideSprite)); - _dialogueRunner.AddCommandHandler("StopAudioAll", StopAudioAll); - _dialogueRunner.AddCommandHandler("Fade", Fade); + + _dialogueRunner.onDialogueComplete += OnDialogueComplete; _englishButton.GrabFocus(); } @@ -62,10 +56,10 @@ public override void _Ready() public void StartDialogue(string locale) { TranslationServer.SetLocale(locale); - ((TextLineProvider)_dialogueRunner.lineProvider).textLanguageCode = locale; + ((TextLineProvider) _dialogueRunner.lineProvider).textLanguageCode = locale; _dialogueStartUi.Visible = false; _dialogueCanvas.Visible = true; - _dialogueRunner.StartDialogue(_dialogueRunner.startNode); + _dialogueRunner.StartDialogue("Start"); } private void OnDialogueComplete() @@ -112,28 +106,34 @@ public void Scene(string backgroundImage) private List _audioPlayers = new(); - private async void PlayAudio(string streamName, float volume = 1.0f, string doLoop = "loop") + [YarnCommand] + public void PlayAudio(string streamName, float volume = 1.0f, string doLoop = "loop") { - if (!_audioShortNameToUuid.ContainsKey(streamName)) + async YarnTask PlayAudioAsync() { - GD.PrintErr($"The audio stream name {streamName} was not defined in {nameof(_audioShortNameToUuid)}"); - return; - } + if (!_audioShortNameToUuid.ContainsKey(streamName)) + { + GD.PrintErr($"The audio stream name {streamName} was not defined in {nameof(_audioShortNameToUuid)}"); + return; + } - var stream = ResourceLoader.Load(_audioShortNameToUuid[streamName]); - var player = new AudioStreamPlayer2D(); - player.VolumeDb = Mathf.LinearToDb(volume); - player.Stream = stream; - _audioPlayers.Add(player); - AddChild(player); - player.Play(); - if (doLoop != "loop") - { - await DefaultActions.Wait(stream.GetLength()); - player.Stop(); - _audioPlayers.Remove(player); - player.QueueFree(); + var stream = ResourceLoader.Load(_audioShortNameToUuid[streamName]); + var player = new AudioStreamPlayer2D(); + player.VolumeDb = Mathf.LinearToDb(volume); + player.Stream = stream; + _audioPlayers.Add(player); + AddChild(player); + player.Play(); + if (doLoop != "loop") + { + await DefaultActions.Wait(stream.GetLength()); + player.Stop(); + _audioPlayers.Remove(player); + player.QueueFree(); + } } + + PlayAudioAsync().Forget(); } private class Actor @@ -214,6 +214,7 @@ private static float GetCoordinate(string coordinate) // screenPosY=0.5, moveTime=1.0>> screenPosX and screenPosY are // normalized screen coordinates (0.0 - 1.0) moveTime is the time // in seconds it will take to reach that position + [YarnCommand("Move")] public async Task MoveSprite(string actorOrSpriteName, string screenPosX = "0.5", string screenPosY = "0.5", float moveTime = 1) { @@ -230,7 +231,7 @@ public async Task MoveSprite(string actorOrSpriteName, string screenPosX = "0.5" // calculate the sprite movement this frame, // trying to normalize it based on framerate var timeRatio = delta / moveTime; - var movement = new Vector2((float)timeRatio * distance.X, (float)timeRatio * distance.Y); + var movement = new Vector2((float) timeRatio * distance.X, (float) timeRatio * distance.Y); actor.Rect.Position += movement; elapsed += delta; await DefaultActions.Wait(delta); // wait a frame @@ -241,6 +242,7 @@ public async Task MoveSprite(string actorOrSpriteName, string screenPosX = "0.5" } // shake a sprite + [YarnCommand("Shake")] public async Task ShakeSprite(string actorOrSpriteName, float moveTime) { var actor = _actors[actorOrSpriteName]; @@ -279,6 +281,7 @@ Vector2 GetRandomDestination() actor.Rect.Position = initialPos; } + [YarnCommand("Act")] public void SetActor(string actorName, string spriteName, string positionX = "", string positionY = "", string colorHex = "") { @@ -299,12 +302,14 @@ public void SetActor(string actorName, string spriteName, string positionX = "", MoveChild(rect, 1); } - public void HideSprite(String actorOrSpriteName) + [YarnCommand("Hide")] + public void HideSprite(string actorOrSpriteName) { _actors[actorOrSpriteName].Rect.Visible = false; } /// flip a sprite, or force the sprite to face a direction + [YarnCommand("Flip")] public void FlipSprite(string actorOrSpriteName, string xDirection = null) { bool newFlip; @@ -332,7 +337,8 @@ public void FlipSprite(string actorOrSpriteName, string xDirection = null) rect.FlipH = newFlip; } - private void StopAudioAll() + [YarnCommand] + public void StopAudioAll() { foreach (var player in _audioPlayers) { @@ -348,6 +354,7 @@ private void StopAudioAll() /// typical screen fade effect, good for transitions? /// usage: Fade( #hexcolor, startAlpha, endAlpha, fadeTime /// ) + [YarnCommand] public async Task Fade(string fadeColorHex, float startAlpha = 0, float endAlpha = 1, float fadeTime = 1) { var elapsed = 0d; @@ -359,7 +366,7 @@ public async Task Fade(string fadeColorHex, float startAlpha = 0, float endAlpha while (elapsed < fadeTime && Mathf.Abs(endAlpha - newColor.A) > 0.001) { var timeRatio = elapsed / fadeTime; - newColor.A = (float)(startAlpha + timeRatio * colorDifference); + newColor.A = (float) (startAlpha + timeRatio * colorDifference); _colorOverlay.Color = newColor; elapsed += delay / 1000d; await Task.Delay(delay); diff --git a/Samples/VisualNovel/VisualNovelSample.tscn b/Samples/VisualNovel/VisualNovelSample.tscn index 5ebda8c..6cb3fb9 100644 --- a/Samples/VisualNovel/VisualNovelSample.tscn +++ b/Samples/VisualNovel/VisualNovelSample.tscn @@ -15,7 +15,7 @@ anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 -[node name="VisualNovelManager" type="Control" parent="."] +[node name="VNManager" type="Control" parent="."] anchors_preset = 0 anchor_right = 1.0 anchor_bottom = 1.0 @@ -29,12 +29,12 @@ _spanishButtonPath = NodePath("DialogueStartUI/VBoxContainer/SpanishButton") _japaneseButtonPath = NodePath("DialogueStartUI/VBoxContainer/JapaneseButton") _dialogueCanvasPath = NodePath("../YarnSpinnerCanvasLayer") -[node name="BackgroundImage" type="TextureRect" parent="VisualNovelManager"] +[node name="BackgroundImage" type="TextureRect" parent="VNManager"] layout_mode = 0 anchor_right = 1.0 anchor_bottom = 1.0 -[node name="DialogueStartUI" type="Control" parent="VisualNovelManager"] +[node name="DialogueStartUI" type="Control" parent="VNManager"] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 @@ -42,7 +42,7 @@ anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 -[node name="VBoxContainer" type="VBoxContainer" parent="VisualNovelManager/DialogueStartUI"] +[node name="VBoxContainer" type="VBoxContainer" parent="VNManager/DialogueStartUI"] custom_minimum_size = Vector2(600, 0) layout_mode = 0 offset_left = 494.0 @@ -50,13 +50,13 @@ offset_top = 272.0 offset_right = 1094.0 offset_bottom = 407.0 -[node name="PlayTheGameIn" type="RichTextLabel" parent="VisualNovelManager/DialogueStartUI/VBoxContainer"] +[node name="PlayTheGameIn" type="RichTextLabel" parent="VNManager/DialogueStartUI/VBoxContainer"] custom_minimum_size = Vector2(0, 30) layout_mode = 2 bbcode_enabled = true text = "Play the game in...." -[node name="EnglishButton" type="Button" parent="VisualNovelManager/DialogueStartUI/VBoxContainer"] +[node name="EnglishButton" type="Button" parent="VNManager/DialogueStartUI/VBoxContainer"] layout_mode = 2 focus_neighbor_top = NodePath("../JapaneseButton") focus_neighbor_bottom = NodePath("../SpanishButton") @@ -64,7 +64,7 @@ focus_next = NodePath("../SpanishButton") focus_previous = NodePath("../JapaneseButton") text = "English" -[node name="SpanishButton" type="Button" parent="VisualNovelManager/DialogueStartUI/VBoxContainer"] +[node name="SpanishButton" type="Button" parent="VNManager/DialogueStartUI/VBoxContainer"] layout_mode = 2 focus_neighbor_top = NodePath("../EnglishButton") focus_neighbor_bottom = NodePath("../JapaneseButton") @@ -72,7 +72,7 @@ focus_next = NodePath("../JapaneseButton") focus_previous = NodePath("../EnglishButton") text = "Español" -[node name="JapaneseButton" type="Button" parent="VisualNovelManager/DialogueStartUI/VBoxContainer"] +[node name="JapaneseButton" type="Button" parent="VNManager/DialogueStartUI/VBoxContainer"] layout_mode = 2 focus_neighbor_top = NodePath("../SpanishButton") focus_neighbor_bottom = NodePath("../EnglishButton") @@ -82,11 +82,11 @@ text = "日本語" [node name="InMemoryVariableStorage" type="Node" parent="."] script = ExtResource("6") -debugTextViewPath = NodePath("") [node name="TextLineProvider" type="Node2D" parent="."] script = ExtResource("9") textLanguageCode = "en" +YarnProject = ExtResource("2") [node name="YarnSpinnerCanvasLayer" parent="." instance=ExtResource("4_rci1f")] @@ -94,8 +94,8 @@ textLanguageCode = "en" yarnProject = ExtResource("2") startNode = "Start" -[node name="LineText" parent="YarnSpinnerCanvasLayer/LineView/ViewControl" index="0"] -theme_override_font_sizes/normal_font_size = 34 +[node name="TextLineProvider" parent="YarnSpinnerCanvasLayer" index="5"] +YarnProject = ExtResource("2") [node name="ColorOverlay" type="ColorRect" parent="YarnSpinnerCanvasLayer"] z_index = -1 diff --git a/YarnSpinner-Godot.csproj b/YarnSpinner-Godot.csproj index 900a982..5a830c4 100644 --- a/YarnSpinner-Godot.csproj +++ b/YarnSpinner-Godot.csproj @@ -1,10 +1,19 @@ + RS2008 net6.0 true true enable + + $(DefineConstants);YARN_SOURCE_GENERATION_DEBUG_LOGGING;YARNSPINNER_DEBUG + + + + + @@ -12,17 +21,13 @@ + - - + - - - - - - + + \ No newline at end of file diff --git a/YarnSpinner-Godot.sln.DotSettings b/YarnSpinner-Godot.sln.DotSettings index 1063953..a5bb906 100644 --- a/YarnSpinner-Godot.sln.DotSettings +++ b/YarnSpinner-Godot.sln.DotSettings @@ -1,5 +1,6 @@  CSV + GD JSON True True \ No newline at end of file diff --git a/addons/YarnSpinner-Godot/Editor/UI/LineEditWithSubmit.cs b/addons/YarnSpinner-Godot/Editor/UI/LineEditWithSubmit.cs index aa7010b..c292f96 100644 --- a/addons/YarnSpinner-Godot/Editor/UI/LineEditWithSubmit.cs +++ b/addons/YarnSpinner-Godot/Editor/UI/LineEditWithSubmit.cs @@ -2,7 +2,7 @@ #if TOOLS using Godot; -namespace YarnSpinnerGodot.Editor.UI; +namespace YarnSpinnerGodot; /// /// LineEdit component with an associated button. diff --git a/addons/YarnSpinner-Godot/Editor/UI/LocaleDeleteButton.cs b/addons/YarnSpinner-Godot/Editor/UI/LocaleDeleteButton.cs index 07d771a..5bd71d3 100644 --- a/addons/YarnSpinner-Godot/Editor/UI/LocaleDeleteButton.cs +++ b/addons/YarnSpinner-Godot/Editor/UI/LocaleDeleteButton.cs @@ -2,7 +2,7 @@ #if TOOLS using Godot; -namespace YarnSpinnerGodot.Editor.UI; +namespace YarnSpinnerGodot; /// /// This class is used to save the locale code associated diff --git a/addons/YarnSpinner-Godot/Editor/UI/MarkupPaletteAddTagButton.cs b/addons/YarnSpinner-Godot/Editor/UI/MarkupPaletteAddTagButton.cs deleted file mode 100644 index b770f37..0000000 --- a/addons/YarnSpinner-Godot/Editor/UI/MarkupPaletteAddTagButton.cs +++ /dev/null @@ -1,53 +0,0 @@ -#nullable disable -using Godot; - -namespace YarnSpinnerGodot.Editor.UI; - -/// -/// Inspector UI component for adding new markup tags -/// to a -/// -public partial class MarkupPaletteAddTagButton : Button -{ - /// - /// Text entry which contains the name of the new tag to add to - /// the markup palette. - /// - public LineEdit newTagNameInput; - - /// - /// Reference to the MarkupPalette that we are inspecting. - /// - public MarkupPalette palette; - - public override void _Ready() - { - Connect(BaseButton.SignalName.Pressed, Callable.From(OnPressed)); - } - - /// - /// Handler for the Pressed signal on this button. Removes the locale code from - /// the yarn project via the inspector plugin. - /// - private void OnPressed() - { - if (!IsInstanceValid(palette) || - !IsInstanceValid(newTagNameInput)) - { - return; - } - - var newTagName = newTagNameInput.Text? - .Replace("[", "").Replace("]", ""); - if (string.IsNullOrEmpty(newTagName)) - { - // button should be enabled, but just in case - GD.Print( - "Enter a markup tag name in order to add a color mapping."); - return; - } - - palette.ColourMarkers.Add(newTagName, Colors.Black); - palette.NotifyPropertyListChanged(); - } -} \ No newline at end of file diff --git a/addons/YarnSpinner-Godot/Editor/UI/MarkupPaletteColorButton.cs b/addons/YarnSpinner-Godot/Editor/UI/MarkupPaletteColorButton.cs deleted file mode 100644 index 09de5b3..0000000 --- a/addons/YarnSpinner-Godot/Editor/UI/MarkupPaletteColorButton.cs +++ /dev/null @@ -1,37 +0,0 @@ -#nullable disable -using Godot; - -namespace YarnSpinnerGodot.Editor.UI; - -/// -/// Color pickup button used in the markup palette inspector. -/// -public partial class MarkupPaletteColorButton : ColorPickerButton -{ - /// - /// The name of the markup tag this button chooses a color for. - /// - public string tagName; - - /// - /// The markup palette associated with this button. - /// - public MarkupPalette palette; - - public override void _Ready() - { - Connect(ColorPickerButton.SignalName.PopupClosed, Callable.From(OnPopupClosed)); - } - - private void OnPopupClosed() - { - if (!IsInstanceValid(palette)) - { - return; - } - - palette.ColourMarkers[tagName] = Color; - ResourceSaver.Save(palette, palette.ResourcePath); - palette.NotifyPropertyListChanged(); - } -} \ No newline at end of file diff --git a/addons/YarnSpinner-Godot/Editor/UI/MarkupPaletteDeleteTagButton.cs b/addons/YarnSpinner-Godot/Editor/UI/MarkupPaletteDeleteTagButton.cs deleted file mode 100644 index 72be4f9..0000000 --- a/addons/YarnSpinner-Godot/Editor/UI/MarkupPaletteDeleteTagButton.cs +++ /dev/null @@ -1,42 +0,0 @@ -#nullable disable -using Godot; - -namespace YarnSpinnerGodot.Editor.UI; - -/// -/// Inspector UI component to delete a tag from a -/// -/// -public partial class MarkupPaletteDeleteTagButton : Button -{ - /// - /// Reference to the MarkupPalette that we are inspecting. - /// - public MarkupPalette palette; - - /// - /// The name of the tag to remove if this button is pressed. - /// - public string tagName; - - public override void _Ready() - { - Connect(BaseButton.SignalName.Pressed, Callable.From(OnPressed)); - } - - /// - /// Handler for the Pressed signal on this button. Removes the locale code from - /// the yarn project via the inspector plugin. - /// - private void OnPressed() - { - if (!IsInstanceValid(palette)) - { - return; - } - - palette.ColourMarkers.Remove(tagName); - ResourceSaver.Save(palette, palette.ResourcePath); - palette.NotifyPropertyListChanged(); - } -} \ No newline at end of file diff --git a/addons/YarnSpinner-Godot/Editor/UI/SourcePatternAddButton.cs b/addons/YarnSpinner-Godot/Editor/UI/SourcePatternAddButton.cs index 8c67965..6bebc6c 100644 --- a/addons/YarnSpinner-Godot/Editor/UI/SourcePatternAddButton.cs +++ b/addons/YarnSpinner-Godot/Editor/UI/SourcePatternAddButton.cs @@ -3,7 +3,7 @@ using System.Linq; using Godot; -namespace YarnSpinnerGodot.Editor.UI; +namespace YarnSpinnerGodot; /// /// This class is used to save the locale code associated diff --git a/addons/YarnSpinner-Godot/Editor/UI/SourcePatternDeleteButton.cs b/addons/YarnSpinner-Godot/Editor/UI/SourcePatternDeleteButton.cs index 9946943..b86e4de 100644 --- a/addons/YarnSpinner-Godot/Editor/UI/SourcePatternDeleteButton.cs +++ b/addons/YarnSpinner-Godot/Editor/UI/SourcePatternDeleteButton.cs @@ -3,7 +3,7 @@ using System.Linq; using Godot; -namespace YarnSpinnerGodot.Editor.UI; +namespace YarnSpinnerGodot; /// /// This class is used to save the locale code associated diff --git a/addons/YarnSpinner-Godot/Editor/YarnCompileErrorsPropertyEditor.cs b/addons/YarnSpinner-Godot/Editor/YarnCompileErrorsPropertyEditor.cs index 7185fba..d47bb83 100644 --- a/addons/YarnSpinner-Godot/Editor/YarnCompileErrorsPropertyEditor.cs +++ b/addons/YarnSpinner-Godot/Editor/YarnCompileErrorsPropertyEditor.cs @@ -3,7 +3,7 @@ using Godot; using Array = Godot.Collections.Array; -namespace YarnSpinnerGodot.Editor; +namespace YarnSpinnerGodot; [Tool] public partial class YarnCompileErrorsPropertyEditor : EditorProperty diff --git a/addons/YarnSpinner-Godot/Editor/YarnEditorUtility.cs b/addons/YarnSpinner-Godot/Editor/YarnEditorUtility.cs index 272d83e..4568d54 100644 --- a/addons/YarnSpinner-Godot/Editor/YarnEditorUtility.cs +++ b/addons/YarnSpinner-Godot/Editor/YarnEditorUtility.cs @@ -4,7 +4,7 @@ using File = System.IO.File; using Path = System.IO.Path; -namespace YarnSpinnerGodot.Editor; +namespace YarnSpinnerGodot; /// /// Contains utility methods for working with Yarn Spinner content in diff --git a/addons/YarnSpinner-Godot/Editor/YarnImporter.cs b/addons/YarnSpinner-Godot/Editor/YarnImporter.cs index 80851a2..0abbde3 100644 --- a/addons/YarnSpinner-Godot/Editor/YarnImporter.cs +++ b/addons/YarnSpinner-Godot/Editor/YarnImporter.cs @@ -5,7 +5,7 @@ using Godot; using Godot.Collections; -namespace YarnSpinnerGodot.Editor; +namespace YarnSpinnerGodot; /// /// A for Yarn scripts (.yarn files) diff --git a/addons/YarnSpinner-Godot/Editor/YarnMarkupPaletteInspectorPlugin.cs b/addons/YarnSpinner-Godot/Editor/YarnMarkupPaletteInspectorPlugin.cs deleted file mode 100644 index 68fc182..0000000 --- a/addons/YarnSpinner-Godot/Editor/YarnMarkupPaletteInspectorPlugin.cs +++ /dev/null @@ -1,127 +0,0 @@ -#nullable disable -#if TOOLS -using System; -using Godot; -using YarnSpinnerGodot.Editor.UI; - -namespace YarnSpinnerGodot.Editor; - -/// -/// Custom inspector for that allows the user -/// to add / remove markup tags and set their associated colors. -/// -[Tool] -public partial class YarnMarkupPaletteInspectorPlugin : EditorInspectorPlugin -{ - public override bool _CanHandle(GodotObject obj) => obj is MarkupPalette; - - public override bool _ParseProperty(GodotObject @object, Variant.Type type, - string path, - PropertyHint hint, string hintText, PropertyUsageFlags usage, bool wide) - { - if (@object is not MarkupPalette palette) - { - return false; - } - - try - { - if (path == nameof(MarkupPalette.ColourMarkers)) - { - AddCustomControl(new Label - {Text = "Map [markup] tag names to colors"}); - if (palette.ColourMarkers.Count == 0) - { - var noColorsLabel = new Label(); - noColorsLabel.Text = "No colors remapped"; - AddCustomControl(noColorsLabel); - } - else - { - var colorRemapGrid = new GridContainer(); - colorRemapGrid.Columns = 3; - colorRemapGrid.SizeFlagsVertical = Control.SizeFlags.ExpandFill; - colorRemapGrid.SizeFlagsHorizontal = - Control.SizeFlags.ExpandFill; - - var originalHeader = new Label(); - originalHeader.Text = "Markup Tag"; - colorRemapGrid.AddChild(originalHeader); - - var replacementHeader = new Label(); - replacementHeader.Text = "Text Color"; - colorRemapGrid.AddChild(replacementHeader); - - var deleteHeader = new Label(); - deleteHeader.Text = "Delete"; - colorRemapGrid.AddChild(deleteHeader); - const int remapHeight = 4; - foreach (var tagName in palette.ColourMarkers.Keys) - { - colorRemapGrid.AddChild(new Label {Text = tagName}); - - var replacementColorButton = new MarkupPaletteColorButton - {palette = palette, tagName = tagName}; - replacementColorButton.Color = - palette.ColourMarkers[tagName]; - replacementColorButton.Size = new Vector2(0, remapHeight); - replacementColorButton.SizeFlagsHorizontal = - Control.SizeFlags.ExpandFill; - colorRemapGrid.AddChild(replacementColorButton); - - var deleteArea = new HBoxContainer(); - var deleteSpacer = new Label {Text = " "}; - - var deleteButton = new MarkupPaletteDeleteTagButton - { - Text = "X", - tagName = tagName, - palette = palette - }; - deleteButton.Text = "x"; - deleteButton.AddThemeColorOverride("normal", Colors.Red); - deleteButton.Size = new Vector2(4, remapHeight); - deleteButton.SizeFlagsHorizontal = 0; - - deleteArea.AddChild(deleteSpacer); - deleteArea.AddChild(deleteButton); - colorRemapGrid.AddChild(deleteArea); - } - - AddCustomControl(colorRemapGrid); - } - - var newTagRow = new HBoxContainer(); - var addNewTagButton = new MarkupPaletteAddTagButton - {Text = "Add", palette = palette}; - - var newTagNameInput = new LineEditWithSubmit() - { - PlaceholderText = "tag name, without []", - CustomMinimumSize = new Vector2(80, 10), - SubmitButton = addNewTagButton - }; - addNewTagButton.newTagNameInput = newTagNameInput; - newTagNameInput.SizeFlagsHorizontal = Control.SizeFlags.ExpandFill; - addNewTagButton.Disabled = true; - - newTagRow.AddChild(newTagNameInput); - newTagRow.AddChild(addNewTagButton); - newTagRow.SizeFlagsVertical = Control.SizeFlags.ExpandFill; - newTagRow.SizeFlagsHorizontal = Control.SizeFlags.ExpandFill; - AddCustomControl(newTagRow); - - return true; - } - - return false; - } - catch (Exception e) - { - GD.PushError( - $"Error in {nameof(YarnMarkupPaletteInspectorPlugin)}: {e.Message}\n{e.StackTrace}"); - return false; - } - } -} -#endif \ No newline at end of file diff --git a/addons/YarnSpinner-Godot/Editor/YarnProjectEditorUtility.cs b/addons/YarnSpinner-Godot/Editor/YarnProjectEditorUtility.cs index 913b609..2f5e563 100644 --- a/addons/YarnSpinner-Godot/Editor/YarnProjectEditorUtility.cs +++ b/addons/YarnSpinner-Godot/Editor/YarnProjectEditorUtility.cs @@ -1,10 +1,8 @@ -#nullable disable #if TOOLS using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -17,11 +15,14 @@ using Microsoft.Extensions.FileSystemGlobbing; using Yarn; using Yarn.Compiler; +using Yarn.Utility; using File = System.IO.File; -using FileAccess = System.IO.FileAccess; using Path = System.IO.Path; +#if YARNSPINNER_DEBUG +using System.Diagnostics; +#endif -namespace YarnSpinnerGodot.Editor; +namespace YarnSpinnerGodot; [Tool] public static class YarnProjectEditorUtility @@ -36,9 +37,9 @@ public static class YarnProjectEditorUtility /// /// /// - public static string GetDestinationProjectPath(string scriptPath) + public static string? GetDestinationProjectPath(string scriptPath) { - string destinationProjectPath = null; + string? destinationProjectPath = null; var globalScriptPath = Path.GetFullPath(ProjectSettings.GlobalizePath(scriptPath)); var allProjects = FindAllYarnProjects(); foreach (var project in allProjects) @@ -96,7 +97,6 @@ public static void UpdateYarnProject(YarnProject project) return; } - ; if (string.IsNullOrEmpty(project.ResourcePath)) { return; @@ -130,13 +130,7 @@ TimeSpan getTimeDiff() try { - CompileAllScripts(project); - SaveYarnProject(project); - } - catch (Exception e) - { - GD.PushError( - $"Error updating {nameof(YarnProject)} '{project.ResourcePath}': {e.Message}{e.StackTrace}"); + UpdateYarnProjectImmediate(project); } finally { @@ -147,6 +141,39 @@ TimeSpan getTimeDiff() } } + /// + /// Like , but rather than queueing up an update, + /// update it immediately. Call this when you don't anticipate another + /// update request to be triggered within a second or so. + /// + public static void UpdateYarnProjectImmediate(YarnProject project) + { + try + { + var compilationResult = CompileAllScripts(project); + SaveYarnProject(project); + if (project is {generateVariablesSourceFile: true, ResourcePath: not null} + && compilationResult != null) + { + var fileName = project.variablesClassName + ".cs"; + + var generatedSourcePath = + Path.Combine(Path.GetDirectoryName(ProjectSettings.GlobalizePath(project.ResourcePath))!, + fileName); + bool generated = GenerateVariableSource(generatedSourcePath, project, compilationResult); + if (generated) + { + YarnSpinnerPlugin.editorInterface.GetResourceFilesystem().ScanSources(); + } + } + } + catch (Exception e) + { + GD.PushError( + $"Error updating {nameof(YarnProject)} '{project.ResourcePath}': {e.Message}{e.StackTrace}"); + } + } + public static void WriteBaseLanguageStringsCSV(YarnProject project, string path) { UpdateLocalizationFile(project.baseLocalization.GetStringTableEntries(), @@ -172,6 +199,13 @@ public static void UpdateLocalizationCSVs(YarnProject project) continue; } + if (project.baseLocalization == null) + { + GD.PrintErr( + $"Can't update localization for {loc.Key} because it doesn't have a {nameof(project.baseLocalization)}."); + continue; + } + var fileWasChanged = UpdateLocalizationFile(project.baseLocalization.GetStringTableEntries(), loc.Key, loc.Value.Strings); @@ -376,7 +410,7 @@ public static void ClearJSONCache() var updateHandlerType = assembly.GetType("System.Text.Json.JsonSerializerOptionsUpdateHandler"); var clearCacheMethod = updateHandlerType?.GetMethod("ClearCache", BindingFlags.Static | BindingFlags.Public); - clearCacheMethod?.Invoke(null, new object[] {null}); + clearCacheMethod?.Invoke(null, new object?[] {null}); } public static void SaveYarnProject(YarnProject project) @@ -408,7 +442,7 @@ public static void SaveYarnProject(YarnProject project) } } - public static void CompileAllScripts(YarnProject project) + public static CompilationResult? CompileAllScripts(YarnProject project) { lock (project) { @@ -422,29 +456,31 @@ public static void CompileAllScripts(YarnProject project) { GD.Print( $"No .yarn files found matching the {nameof(project.JSONProject.SourceFilePatterns)} in {project.JSONProjectPath}"); - return; + return null; } - var library = new Library(); + var library = Actions.GetLibrary(); IEnumerable errors; project.ProjectErrors = Array.Empty(); - // We now now compile! + // We now compile! var scriptAbsolutePaths = sourceScripts.ToList().Where(s => s != null) .Select(ProjectSettings.GlobalizePath).ToList(); // Store the compiled program - byte[] compiledBytes = null; - CompilationResult? compilationResult = new CompilationResult?(); + byte[]? compiledBytes = null; + CompilationResult? compilationResult = new CompilationResult(); if (scriptAbsolutePaths.Count > 0) { var job = CompilationJob.CreateFromFiles(scriptAbsolutePaths); // job.VariableDeclarations = localDeclarations; job.CompilationType = CompilationJob.Type.FullCompilation; job.Library = library; + job.LanguageVersion = project.JSONProject.FileVersion; + compilationResult = Yarn.Compiler.Compiler.Compile(job); - errors = compilationResult.Value.Diagnostics.Where(d => + errors = compilationResult.Diagnostics.Where(d => d.Severity == Diagnostic.DiagnosticSeverity.Error); if (errors.Any()) @@ -468,14 +504,14 @@ public static void CompileAllScripts(YarnProject project) FileName = ProjectSettings.LocalizePath(e.FileName) }); project.ProjectErrors = projectErrors.ToArray(); - return; + return compilationResult; } - if (compilationResult.Value.Program == null) + if (compilationResult.Program == null) { GD.PushError( "public error: Failed to compile: resulting program was null, but compiler did not report errors."); - return; + return compilationResult; } // Store _all_ declarations - both the ones in this @@ -487,12 +523,12 @@ public static void CompileAllScripts(YarnProject project) // the user. var newDeclarations = new List() //localDeclarations - .Concat(compilationResult.Value.Declarations) + .Concat(compilationResult.Declarations) .Where(decl => !decl.Name.StartsWith("$Yarn.Internal.")) .Where(decl => decl.Type is not FunctionType) .Select(decl => { - SerializedDeclaration existingDeclaration = null; + SerializedDeclaration? existingDeclaration = null; // try to re-use a declaration if one exists to avoid changing the .tres file so much foreach (var existing in project.SerializedDeclarations) { @@ -512,12 +548,12 @@ public static void CompileAllScripts(YarnProject project) // compilation project.ProjectErrors = Array.Empty(); - CreateYarnInternalLocalizationAssets(project, compilationResult.Value); + CreateYarnInternalLocalizationAssets(project, compilationResult); using var memoryStream = new MemoryStream(); using var outputStream = new CodedOutputStream(memoryStream); // Serialize the compiled program to memory - compilationResult.Value.Program.WriteTo(outputStream); + compilationResult.Program.WriteTo(outputStream); outputStream.Flush(); compiledBytes = memoryStream.ToArray(); @@ -531,6 +567,8 @@ public static void CompileAllScripts(YarnProject project) { GD.PushError($"Failed to save updated {nameof(YarnProject)}: {saveErr}"); } + + return compilationResult; } } @@ -583,25 +621,6 @@ public static CompilationResult CompileProgram(FileInfo[] inputs) return compilationResult; } - public static FunctionInfo CreateFunctionInfoFromMethodGroup(MethodInfo method) - { - var returnType = $"-> {method.ReturnType.Name}"; - - var parameters = method.GetParameters(); - var p = new string[parameters.Length]; - for (int i = 0; i < parameters.Length; i++) - { - var q = parameters[i].ParameterType; - p[i] = parameters[i].Name; - } - - var info = new FunctionInfo(); - info.Name = method.Name; - info.ReturnType = returnType; - info.Parameters = p; - return info; - } - /// /// If , will search /// all assemblies that have been defined using an GenerateStringsTable(YarnProject pro { CompilationResult? compilationResult = CompileStringsOnly(project); - if (!compilationResult.HasValue) + if (compilationResult == null) { // We only get no value if we have no scripts to work with. // In this case, return an empty collection - there's no // error, but there's no content either. - return new List(); + return Array.Empty(); } var errors = - compilationResult.Value.Diagnostics.Where(d => d.Severity == Diagnostic.DiagnosticSeverity.Error); + compilationResult.Diagnostics.Where(d => d.Severity == Diagnostic.DiagnosticSeverity.Error); if (errors.Any()) { GD.PrintErr("Can't generate a strings table from a Yarn Project that contains compile errors", null); - return null; + return Array.Empty(); } - return GetStringTableEntries(project, compilationResult.Value); + return GetStringTableEntries(project, compilationResult); } private static CompilationResult? CompileStringsOnly(YarnProject project) @@ -705,36 +724,40 @@ public static IEnumerable GenerateStringsTable(YarnProject pro private static IEnumerable LineMetadataTableEntriesFromCompilationResult( CompilationResult result) { - return result.StringTable.Select(x => - { - var meta = new LineMetadataTableEntry(); - meta.ID = x.Key; - meta.File = ProjectSettings.LocalizePath(x.Value.fileName); - meta.Node = x.Value.nodeName; - meta.LineNumber = x.Value.lineNumber.ToString(); - meta.Metadata = RemoveLineIDFromMetadata(x.Value.metadata).ToArray(); - return meta; + if (result.StringTable == null) + { + return Array.Empty(); + } + + return result.StringTable.Select(x => new LineMetadataTableEntry + { + ID = x.Key, + File = ProjectSettings.LocalizePath(x.Value.fileName), + Node = x.Value.nodeName, + LineNumber = x.Value.lineNumber.ToString(), + Metadata = RemoveLineIDFromMetadata(x.Value.metadata).ToArray() }).Where(x => x.Metadata.Length > 0); } private static IEnumerable GetStringTableEntries(YarnProject project, CompilationResult result) { - return result.StringTable.Select(x => - { - var entry = new StringTableEntry(); + if (result.StringTable == null) + { + return Array.Empty(); + } - entry.ID = x.Key; - entry.Language = project.defaultLanguage; - entry.Text = x.Value.text; - entry.File = ProjectSettings.LocalizePath(x.Value.fileName); - entry.Node = x.Value.nodeName; - entry.LineNumber = x.Value.lineNumber.ToString(); - entry.Lock = YarnImporter.GetHashString(x.Value.text, 8); - entry.Comment = GenerateCommentWithLineMetadata(x.Value.metadata); - return entry; - } - ); + return result.StringTable.Select(x => new StringTableEntry + { + ID = x.Key, + Language = project.defaultLanguage, + Text = x.Value.text, + File = ProjectSettings.LocalizePath(x.Value.fileName), + Node = x.Value.nodeName, + LineNumber = x.Value.lineNumber.ToString(), + Lock = x.Value.text == null ? "" : YarnImporter.GetHashString(x.Value.text, 8), + Comment = GenerateCommentWithLineMetadata(x.Value.metadata) + }); } /// @@ -822,7 +845,9 @@ public static void AddLineTagsToFilesInYarnProject(YarnProject project) return Array.Empty(); } - return result.StringTable.Where(i => i.Value.isImplicitTag == false).Select(i => i.Key); + return result.StringTable == null + ? Array.Empty() + : result.StringTable.Where(i => i.Value.isImplicitTag == false).Select(i => i.Key); }).ToList(); // immediately execute this query so we can determine timing information #if YARNSPINNER_DEBUG @@ -895,5 +920,318 @@ public static List LoadAllYarnProjects() return projects; } + + + /// + /// A regular expression that matches characters following the start of + /// the string or an underscore. + /// + /// + /// Used as part of converting variable names from snake_case to + /// CamelCase when generating C# variable source code. + /// + private static readonly System.Text.RegularExpressions.Regex SnakeCaseToCamelCase = + new System.Text.RegularExpressions.Regex(@"(^|_)(\w)"); + + + private static bool GenerateVariableSource(string outputPath, YarnProject project, + CompilationResult compilationResult) + { + if (!GodotObject.IsInstanceValid(project)) + { + GD.PushError("Can't generate variable source for null project!"); + return false; + } + + string? existingContent = null; + + if (File.Exists(outputPath)) + { + // If the file already exists on disk, read it all in now. We'll + // compare it to what we generated and, if the contents match + // exactly, we don't need to re-import the resulting C# script. + existingContent = File.ReadAllText(outputPath); + } + + if (string.IsNullOrEmpty(project.variablesClassName)) + { + GD.PushError("Can't generate variable interface, because the specified class name is empty."); + return false; + } + + var sb = new StringBuilder(); + int indentLevel = 0; + const int indentSize = 4; + + void WriteLine(string line = "", int offset = 0) + { + if (line.Length > 0) + { + sb.Append(new string(' ', (indentLevel + offset) * indentSize)); + } + + sb.AppendLine(line); + } + + void WriteComment(string comment = "") => WriteLine("// " + comment); + + if (string.IsNullOrEmpty(project.variablesClassNamespace) == false) + { + WriteLine($"namespace {project.variablesClassNamespace} {{"); + WriteLine(); + indentLevel += 1; + } + + WriteLine("using YarnSpinnerGodot;"); + WriteLine(); + + void WriteGeneratedCodeAttribute() + { + var toolName = "YarnSpinner"; + var toolVersion = YarnSpinnerPlugin.VersionString; + WriteLine($"[System.CodeDom.Compiler.GeneratedCode(\"{toolName}\", \"{toolVersion}\")]"); + } + + // For each user-defined enum, create a C# enum type + IEnumerable enumTypes = compilationResult.UserDefinedTypes.OfType(); + + foreach (var type in enumTypes) + { + WriteLine($"/// "); + if (string.IsNullOrEmpty(type.Description) == false) + { + WriteLine($"/// {type.Description}"); + } + else + { + WriteLine($"/// {type.Name}"); + } + + WriteLine($"/// "); + + WriteLine($"/// "); + WriteLine($"/// Automatically generated from Yarn project at {project.ResourcePath}."); + WriteLine($"/// "); + + WriteGeneratedCodeAttribute(); + + if (type.RawType == Types.String) + { + // String-backed enums are represented as CRC32 hashes of + // the raw value, which we store as uints + WriteLine($"public enum {type.Name} : uint {{"); + } + else + { + WriteLine($"public enum {type.Name} {{"); + } + + indentLevel += 1; + + foreach (var enumCase in type.EnumCases) + { + WriteLine(); + + WriteLine($"/// "); + if (string.IsNullOrEmpty(enumCase.Value.Description) == false) + { + WriteLine($"/// {enumCase.Value.Description}"); + } + else + { + WriteLine($"/// {enumCase.Key}"); + } + + WriteLine($"/// "); + + if (type.RawType == Types.Number) + { + WriteLine($"{enumCase.Key} = {enumCase.Value.Value},"); + } + else if (type.RawType == Types.String) + { + WriteLine($"/// "); + WriteLine($"/// Backing value: \"{enumCase.Value.Value}\""); + WriteLine($"/// "); + var stringValue = (string) enumCase.Value.Value; + WriteComment($"\"{stringValue}\""); + WriteLine($"{enumCase.Key} = {CRC32.GetChecksum(stringValue)},"); + } + else + { + WriteComment( + $"Error: enum case {type.Name}.{enumCase.Key} has an invalid raw type {type.RawType.Name}"); + } + } + + indentLevel -= 1; + + WriteLine($"}}"); + WriteLine(); + } + + if (enumTypes.Any()) + { + // Generate an extension class that extends the above enums with + // methods that accesses their backing value + WriteGeneratedCodeAttribute(); + WriteLine($"internal static class {project.variablesClassName}TypeExtensions {{"); + indentLevel += 1; + foreach (var enumType in enumTypes) + { + var backingType = enumType.RawType == Types.Number ? "int" : "string"; + WriteLine($"internal static {backingType} GetBackingValue(this {enumType.Name} enumValue) {{"); + indentLevel += 1; + WriteLine($"switch (enumValue) {{"); + indentLevel += 1; + + foreach (var @case in enumType.EnumCases) + { + WriteLine($"case {enumType.Name}.{@case.Key}:", 1); + if (enumType.RawType == Types.Number) + { + WriteLine($"return {@case.Value.Value};", 2); + } + else if (enumType.RawType == Types.String) + { + WriteLine($"return \"{@case.Value.Value}\";", 2); + } + else + { + throw new System.ArgumentException($"Invalid Yarn enum raw type {enumType.RawType}"); + } + } + + WriteLine("default:", 1); + WriteLine("throw new System.ArgumentException($\"{enumValue} is not a valid enum case.\");"); + + indentLevel -= 1; + WriteLine("}"); + indentLevel -= 1; + WriteLine("}"); + } + + indentLevel -= 1; + WriteLine("}"); + } + + WriteGeneratedCodeAttribute(); + WriteLine( + $"public partial class {project.variablesClassName} : {project.variablesClassParent}, YarnSpinnerGodot.IGeneratedVariableStorage {{"); + + indentLevel += 1; + + var declarationsToGenerate = compilationResult.Declarations + .Where(d => d.IsVariable == true) + .Where(d => d.Name.StartsWith("$Yarn.Internal") == false); + + if (declarationsToGenerate.Count() == 0) + { + WriteComment("This yarn project does not declare any variables."); + } + + foreach (var decl in declarationsToGenerate) + { + string? cSharpTypeName = null; + + if (decl.Type == Yarn.Types.String) + { + cSharpTypeName = "string"; + } + else if (decl.Type == Yarn.Types.Number) + { + cSharpTypeName = "float"; + } + else if (decl.Type == Yarn.Types.Boolean) + { + cSharpTypeName = "bool"; + } + else if (decl.Type is EnumType enumType1) + { + cSharpTypeName = enumType1.Name; + } + else + { + WriteLine( + $"#warning Can't generate a property for variable {decl.Name}, because its type ({decl.Type}) can't be handled."); + WriteLine(); + } + + + WriteComment($"Accessor for {decl.Type} {decl.Name}"); + + // Remove '$' + string cSharpVariableName = decl.Name.TrimStart('$'); + + // Convert snake_case to CamelCase + cSharpVariableName = SnakeCaseToCamelCase.Replace(cSharpVariableName, + (match) => { return match.Groups[2].Value.ToUpperInvariant(); }); + + // Capitalise first letter + cSharpVariableName = + cSharpVariableName.Substring(0, 1).ToUpperInvariant() + cSharpVariableName.Substring(1); + + if (decl.Description != null) + { + WriteLine("/// "); + WriteLine($"/// {decl.Description}"); + WriteLine("/// "); + } + + WriteLine($"public {cSharpTypeName} {cSharpVariableName} {{"); + + indentLevel += 1; + + if (decl.Type is EnumType enumType) + { + WriteLine($"get => this.GetEnumValueOrDefault<{cSharpTypeName}>(\"{decl.Name}\");"); + } + else + { + WriteLine($"get => this.GetValueOrDefault<{cSharpTypeName}>(\"{decl.Name}\");"); + } + + if (decl.IsInlineExpansion == false) + { + // Only generate a setter if it's a variable that can be modified + if (decl.Type is EnumType e) + { + WriteLine($"set => this.SetValue(\"{decl.Name}\", value.GetBackingValue());"); + } + else + { + WriteLine($"set => this.SetValue<{cSharpTypeName}>(\"{decl.Name}\", value);"); + } + } + + indentLevel -= 1; + + WriteLine($"}}"); + + WriteLine(); + } + + indentLevel -= 1; + + WriteLine($"}}"); + + if (string.IsNullOrEmpty(project.variablesClassNamespace) == false) + { + indentLevel -= 1; + WriteLine($"}}"); + } + + if (existingContent != null && existingContent.Equals(sb.ToString(), System.StringComparison.Ordinal)) + { + // What we generated is identical to what's already on disk. + // Don't write it. + return false; + } + + GD.Print($"Writing to {outputPath}"); + File.WriteAllText(outputPath, sb.ToString()); + + return true; + } } #endif \ No newline at end of file diff --git a/addons/YarnSpinner-Godot/Editor/YarnProjectImporter.cs b/addons/YarnSpinner-Godot/Editor/YarnProjectImporter.cs index 27da1cc..b685579 100644 --- a/addons/YarnSpinner-Godot/Editor/YarnProjectImporter.cs +++ b/addons/YarnSpinner-Godot/Editor/YarnProjectImporter.cs @@ -1,11 +1,17 @@ #nullable disable #if TOOLS using System; +using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Text; using Godot; using Godot.Collections; +using Yarn; +using Yarn.Compiler; +using Yarn.Utility; -namespace YarnSpinnerGodot.Editor; +namespace YarnSpinnerGodot; /// /// A for YarnSpinner JSON project files (.yarnproject files) @@ -87,7 +93,9 @@ public override Error _Import( } YarnProjectEditorUtility.UpdateYarnProject(godotProject); - return (int) Error.Ok; + + return (int)Error.Ok; } + } #endif \ No newline at end of file diff --git a/addons/YarnSpinner-Godot/Editor/YarnProjectInspectorPlugin.cs b/addons/YarnSpinner-Godot/Editor/YarnProjectInspectorPlugin.cs index b173083..29e6f77 100644 --- a/addons/YarnSpinner-Godot/Editor/YarnProjectInspectorPlugin.cs +++ b/addons/YarnSpinner-Godot/Editor/YarnProjectInspectorPlugin.cs @@ -8,9 +8,8 @@ using Microsoft.Extensions.FileSystemGlobbing; using Yarn; using Yarn.Compiler; -using YarnSpinnerGodot.Editor.UI; -namespace YarnSpinnerGodot.Editor; +namespace YarnSpinnerGodot; [Tool] public partial class YarnProjectInspectorPlugin : EditorInspectorPlugin @@ -70,6 +69,9 @@ public override bool _ParseProperty(GodotObject @object, Variant.Type type, nameof(YarnProject.baseLocalization), nameof(YarnProject.ImportPath), nameof(YarnProject.JSONProjectPath), + nameof(YarnProject.variablesClassName), + nameof(YarnProject.variablesClassNamespace), + nameof(YarnProject.variablesClassParent), // can't use nameof for private fields here "_baseLocalizationJSON", "_lineMetadataJSON", @@ -161,20 +163,25 @@ public override bool _ParseProperty(GodotObject @object, Variant.Type type, var typeName = declaration.typeName; var defaultValue = ""; - if (typeName == BuiltinTypes.String.Name) + if (typeName == Types.String.Name || typeName.Contains($"Enum ({Types.String.Name})")) { defaultValue = declaration.defaultValueString; } - else if (typeName == BuiltinTypes.Boolean.Name) + else if (typeName == Types.Boolean.Name || typeName.Contains($"Enum ({Types.Boolean.Name})")) { defaultValue = declaration.defaultValueBool.ToString(); } - else if (typeName == BuiltinTypes.Number.Name) + else if (typeName == Types.Number.Name || typeName.Contains($"Enum ({Types.Number.Name})")) { defaultValue = declaration.defaultValueNumber.ToString(CultureInfo.InvariantCulture); } - labelText += $"Default value: {defaultValue}\n"; + if (!string.IsNullOrWhiteSpace(declaration.description)) + { + labelText += $"\n{declaration.description}"; + } + + labelText += $"Default value: {defaultValue}\n\n"; var label = _fileNameLabelScene.Instantiate