From a605f4053187384a5e18c28eaa57f9ce2264b285 Mon Sep 17 00:00:00 2001 From: Paul Joannon Date: Sat, 21 Sep 2019 14:17:35 +0200 Subject: [PATCH 01/10] actually use godot resources for json files --- addons/paulloz.ink/PaullozDotInk.cs | 7 +++++ addons/paulloz.ink/Story.cs | 47 +++++++++-------------------- addons/paulloz.ink/import_ink.gd | 39 ++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 33 deletions(-) create mode 100644 addons/paulloz.ink/import_ink.gd diff --git a/addons/paulloz.ink/PaullozDotInk.cs b/addons/paulloz.ink/PaullozDotInk.cs index fc90126..13a7d75 100644 --- a/addons/paulloz.ink/PaullozDotInk.cs +++ b/addons/paulloz.ink/PaullozDotInk.cs @@ -13,8 +13,13 @@ public class PaullozDotInk : EditorPlugin private NodePath dockScene = $"{addonBasePath}/InkDock.tscn"; private Control dock; + private EditorImportPlugin importPlugin; + public override void _EnterTree() { + importPlugin = (ResourceLoader.Load($"{addonBasePath}/import_ink.gd") as GDScript).New() as EditorImportPlugin; + AddImportPlugin(importPlugin); + AddCustomType("Story", "Node", ResourceLoader.Load(customTypeScriptPath) as Script, ResourceLoader.Load(customTypeIconPath) as Texture); dock = (ResourceLoader.Load(dockScene) as PackedScene).Instance() as Control; @@ -26,6 +31,8 @@ public override void _ExitTree() RemoveControlFromDocks(dock); RemoveCustomType("Story"); + + RemoveImportPlugin(importPlugin); } } #endif \ No newline at end of file diff --git a/addons/paulloz.ink/Story.cs b/addons/paulloz.ink/Story.cs index 71e0aaa..cf73d86 100644 --- a/addons/paulloz.ink/Story.cs +++ b/addons/paulloz.ink/Story.cs @@ -20,7 +20,7 @@ private String ObservedVariableSignalName(String name) // All the exported variables [Export] public Boolean AutoLoadStory = false; - [Export] public String InkFilePath = null; + [Export] public TextFile InkFile = null; // All the public variables public String CurrentText = ""; @@ -55,48 +55,24 @@ public override void _Ready() this.LoadStory(); } - public void LoadStory() - { - this.LoadStory(this.InkFilePath); - } - - public Boolean LoadStory(String inkFilePath) + public Boolean LoadStory() { this.reset(); - try - { - if (inkFilePath == null) - throw new System.IO.FileNotFoundException(String.Format("Unable to find {0}.", null)); - - this.InkFilePath = inkFilePath; - - String path = this.InkFilePath.StartsWith("res://") ? this.InkFilePath : String.Format("res://{0}", this.InkFilePath); - File file = new File(); - if (file.FileExists(path)) - { - // Load the story - file.Open(path, (int)File.ModeFlags.Read); - this.story = new Ink.Runtime.Story(file.GetAsText()); - file.Close(); - } - else - throw new System.IO.FileNotFoundException(String.Format("Unable to find {0}.", path)); - } - catch (System.IO.FileNotFoundException e) - { - GD.PrintErr(e.ToString()); - + if (!this.isJSONFileValid()) return false; - } + this.story = new Ink.Runtime.Story(this.InkFile.GetMeta("content") as String); return true; } - public void LoadStory(String inkFilePath, String state) + public Boolean LoadStory(String state) { - if (this.LoadStory(inkFilePath)) + if (this.LoadStory()) this.SetState(state); + else + return false; + return true; } public String Continue() @@ -271,4 +247,9 @@ public void LoadStateFromDisk(File file) this.story.state.LoadJson(file.GetAsText()); } } + + private Boolean isJSONFileValid() + { + return this.InkFile != null && this.InkFile.HasMeta("content"); + } } diff --git a/addons/paulloz.ink/import_ink.gd b/addons/paulloz.ink/import_ink.gd new file mode 100644 index 0000000..356fb82 --- /dev/null +++ b/addons/paulloz.ink/import_ink.gd @@ -0,0 +1,39 @@ +tool +extends EditorImportPlugin + +func get_importer_name(): + return "inkjson"; + +func get_visible_name(): + return "JSON ink story"; + +func get_recognized_extensions(): + return [ "json" ]; + +func get_save_extension(): + return "res"; + +func get_resource_type(): + return "TextFile"; + +func get_import_options(preset): + return [] + +func get_preset_count(): + return 0 + +func import(source_file, save_path, options, r_platform_variants, r_gen_files): + var file = File.new() + var err = file.open(source_file, File.READ) + if err != OK: + return err + var raw_content = file.get_as_text() + file.close() + + var parsed_content = parse_json(raw_content) + if !parsed_content.has("inkVersion"): + return ERR_FILE_UNRECOGNIZED + + var resource = TextFile.new() + resource.set_meta("content", raw_content); + return ResourceSaver.save("%s.%s" % [save_path, get_save_extension()], resource) From 6afda77c92a0334de4010ff5c948f27a28156172 Mon Sep 17 00:00:00 2001 From: Paul Joannon Date: Sat, 21 Sep 2019 14:29:18 +0200 Subject: [PATCH 02/10] fix dock so it works with new resources --- addons/paulloz.ink/InkDock.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/addons/paulloz.ink/InkDock.cs b/addons/paulloz.ink/InkDock.cs index 26774c2..bcc6722 100644 --- a/addons/paulloz.ink/InkDock.cs +++ b/addons/paulloz.ink/InkDock.cs @@ -75,7 +75,7 @@ private void onFileDialogHide() else { fileSelect.Select(2); - storyNode.Set("InkFilePath", currentFilePath.Remove(0, 6)); + storyNode.Set("InkFile", ResourceLoader.Load(currentFilePath)); storyNode.Call("LoadStory"); continueStoryMaximally(); } @@ -84,7 +84,16 @@ private void onFileDialogHide() private void continueStoryMaximally() { while ((bool)storyNode.Get("CanContinue")) - storyNode.Call("Continue"); + { + try + { + storyNode.Call("Continue"); + } + catch (Ink.Runtime.StoryException e) + { + onStoryContinued(e.ToString(), new String[] { }); + } + } } private void onStoryContinued(String text, String[] tags) From 805a485b19609ec691cf1ca47f496b39c3315931 Mon Sep 17 00:00:00 2001 From: Paul Joannon Date: Sat, 21 Sep 2019 14:29:34 +0200 Subject: [PATCH 03/10] :newspaper: README.md --- README.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 232e600..1daa7f3 100644 --- a/README.md +++ b/README.md @@ -38,10 +38,6 @@ If you're having trouble enabling the editor plugin, it's probably because the ` Depending on the version of Godot you're using, you might still have issues with the editor plugin. Do not worry, you don't actually need to enable it to use **godot-ink**. If you don't want to bother with extensive troubleshooting, all you have to do is attach `addons/paulloz.ink/Story.cs` to a node (or use it as a singleton). This node will become the `Story` node for the rest of this documentation. -### Game export - -As your `.json` files aren't Godot resources, you'll need to manually tell the engine to include them in the exported package. You can read more about that in the [documentation](https://godot.readthedocs.io/en/latest/getting_started/workflow/export/exporting_projects.html?highlight=export#export-mode). - ## How to use You'll need to put `ink-engine-runtime.dll` at the root of your Godot project. @@ -52,11 +48,12 @@ If nothing is specified, the **C#** usage is the same as the **GDScript** one. ### Loading the story +First you should navigate to your JSON ink file and import it as a `JSON ink story` in Godot. To do that, select the file in Godot, go to `Import`, select `JSON ink story` under `Import As:` and click `ReImport`. + To load your story, you can: -* Point the `InkFilePath` exported variable to the location of your JSON Ink file and check the `AutoLoadStory` checkbox in the inspector. -* Point the `InkFilePath` exported variable to the location of your JSON Ink file (in the inspector or via a script) and call `story.LoadStory()`. -* Call `story.LoadStory(path)` with path pointing to the location of your JSON Ink file. +* Point the `InkFile` exported variable to your JSON ink file and check the `AutoLoadStory` checkbox in the inspector. +* Point the `InkFile` exported variable to your JSON ink file (in the inspector or via a script) and call `story.LoadStory()`. ### Running the story and making choices From adc416f1d616f1fd0b4da7da477aa64430701e90 Mon Sep 17 00:00:00 2001 From: Paul Joannon Date: Sat, 21 Sep 2019 18:54:59 +0200 Subject: [PATCH 04/10] change icon.svg --- addons/paulloz.ink/icon.svg | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/addons/paulloz.ink/icon.svg b/addons/paulloz.ink/icon.svg index 3becfa4..dc29217 100644 --- a/addons/paulloz.ink/icon.svg +++ b/addons/paulloz.ink/icon.svg @@ -1,14 +1,7 @@ - - - - + + + + + + + \ No newline at end of file From 00ba6f2e6aef61db3aa4f4140fb054438c93e64f Mon Sep 17 00:00:00 2001 From: Paul Joannon Date: Sat, 21 Sep 2019 18:56:11 +0200 Subject: [PATCH 05/10] rename Story -> InkStory --- addons/paulloz.ink/InkDock.cs | 2 +- addons/paulloz.ink/InkDock.tscn | 2 +- addons/paulloz.ink/{Story.cs => InkStory.cs} | 2 +- addons/paulloz.ink/PaullozDotInk.cs | 9 +++++---- 4 files changed, 8 insertions(+), 7 deletions(-) rename addons/paulloz.ink/{Story.cs => InkStory.cs} (99%) diff --git a/addons/paulloz.ink/InkDock.cs b/addons/paulloz.ink/InkDock.cs index bcc6722..2e2ea1f 100644 --- a/addons/paulloz.ink/InkDock.cs +++ b/addons/paulloz.ink/InkDock.cs @@ -23,11 +23,11 @@ public override void _Ready() fileDialog.Connect("popup_hide", this, nameof(onFileDialogHide)); storyNode = GetNode("Story"); - storyNode.SetScript(ResourceLoader.Load("res://addons/paulloz.ink/Story.cs") as Script); storyNode.Connect(nameof(Story.InkContinued), this, nameof(onStoryContinued)); storyNode.Connect(nameof(Story.InkChoices), this, nameof(onStoryChoices)); storyText = GetNode("VBoxContainer/StoryText"); storyChoices = GetNode("VBoxContainer/StoryChoices"); + storyNode.SetScript(ResourceLoader.Load("res://addons/paulloz.ink/InkStory.cs") as Script); } private void resetFileSelectItems() diff --git a/addons/paulloz.ink/InkDock.tscn b/addons/paulloz.ink/InkDock.tscn index 02eb98b..70f2896 100755 --- a/addons/paulloz.ink/InkDock.tscn +++ b/addons/paulloz.ink/InkDock.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=3 format=2] [ext_resource path="res://addons/paulloz.ink/InkDock.cs" type="Script" id=1] -[ext_resource path="res://addons/paulloz.ink/Story.cs" type="Script" id=2] +[ext_resource path="res://addons/paulloz.ink/InkStory.cs" type="Script" id=2] [node name="Ink" type="Control"] anchor_right = 1.0 diff --git a/addons/paulloz.ink/Story.cs b/addons/paulloz.ink/InkStory.cs similarity index 99% rename from addons/paulloz.ink/Story.cs rename to addons/paulloz.ink/InkStory.cs index cf73d86..ac3dc3d 100644 --- a/addons/paulloz.ink/Story.cs +++ b/addons/paulloz.ink/InkStory.cs @@ -5,7 +5,7 @@ #if TOOLS [Tool] #endif -public class Story : Node +public class InkStory : Node { // All the signals we'll need [Signal] public delegate void InkContinued(String text, String[] tags); diff --git a/addons/paulloz.ink/PaullozDotInk.cs b/addons/paulloz.ink/PaullozDotInk.cs index 13a7d75..fbc534e 100644 --- a/addons/paulloz.ink/PaullozDotInk.cs +++ b/addons/paulloz.ink/PaullozDotInk.cs @@ -7,20 +7,21 @@ public class PaullozDotInk : EditorPlugin { private const String addonBasePath = "res://addons/paulloz.ink"; - private NodePath customTypeScriptPath = $"{addonBasePath}/Story.cs"; + private NodePath customTypeScriptPath = $"{addonBasePath}/InkStory.cs"; private NodePath customTypeIconPath = $"{addonBasePath}/icon.svg"; private NodePath dockScene = $"{addonBasePath}/InkDock.tscn"; private Control dock; + private NodePath importPluginScriptPath = $"{addonBasePath}/import_ink.gd"; private EditorImportPlugin importPlugin; public override void _EnterTree() { - importPlugin = (ResourceLoader.Load($"{addonBasePath}/import_ink.gd") as GDScript).New() as EditorImportPlugin; + importPlugin = (ResourceLoader.Load(importPluginScriptPath) as GDScript).New() as EditorImportPlugin; AddImportPlugin(importPlugin); - AddCustomType("Story", "Node", ResourceLoader.Load(customTypeScriptPath) as Script, ResourceLoader.Load(customTypeIconPath) as Texture); + AddCustomType("Ink Story", "Node", ResourceLoader.Load(customTypeScriptPath) as Script, ResourceLoader.Load(customTypeIconPath) as Texture); dock = (ResourceLoader.Load(dockScene) as PackedScene).Instance() as Control; AddControlToDock(EditorPlugin.DockSlot.RightUl, dock); @@ -30,7 +31,7 @@ public override void _ExitTree() { RemoveControlFromDocks(dock); - RemoveCustomType("Story"); + RemoveCustomType("Ink Story"); RemoveImportPlugin(importPlugin); } From 7cdc4df3096ee60260d9edbdeca70854cb980a6b Mon Sep 17 00:00:00 2001 From: Paul Joannon Date: Sat, 21 Sep 2019 18:56:16 +0200 Subject: [PATCH 06/10] better dock --- addons/paulloz.ink/InkDock.cs | 42 ++++++++++++++----- addons/paulloz.ink/InkDock.tscn | 63 +++++++++++++++++++++-------- addons/paulloz.ink/PaullozDotInk.cs | 5 ++- 3 files changed, 81 insertions(+), 29 deletions(-) diff --git a/addons/paulloz.ink/InkDock.cs b/addons/paulloz.ink/InkDock.cs index 2e2ea1f..94579ae 100644 --- a/addons/paulloz.ink/InkDock.cs +++ b/addons/paulloz.ink/InkDock.cs @@ -11,23 +11,26 @@ public class InkDock : Control private String currentFilePath; private Node storyNode; - private RichTextLabel storyText; + private VBoxContainer storyText; private VBoxContainer storyChoices; + + private ScrollBar scrollbar; public override void _Ready() { - fileSelect = GetNode("VBoxContainer/GridContainer/OptionButton"); + fileSelect = GetNode("Container/Top/OptionButton"); fileDialog = GetNode("FileDialog"); fileSelect.Connect("item_selected", this, nameof(onFileSelectItemSelected)); fileDialog.Connect("file_selected", this, nameof(onFileDialogFileSelected)); fileDialog.Connect("popup_hide", this, nameof(onFileDialogHide)); storyNode = GetNode("Story"); - storyNode.Connect(nameof(Story.InkContinued), this, nameof(onStoryContinued)); - storyNode.Connect(nameof(Story.InkChoices), this, nameof(onStoryChoices)); - storyText = GetNode("VBoxContainer/StoryText"); - storyChoices = GetNode("VBoxContainer/StoryChoices"); storyNode.SetScript(ResourceLoader.Load("res://addons/paulloz.ink/InkStory.cs") as Script); + storyNode.Connect(nameof(InkStory.InkChoices), this, nameof(onStoryChoices)); + storyText = GetNode("Container/Bottom/Scroll/Margin/StoryText"); + storyChoices = GetNode("Container/Bottom/StoryChoices"); + + scrollbar = this.GetNode("Container/Bottom/Scroll").GetVScrollbar(); } private void resetFileSelectItems() @@ -38,7 +41,18 @@ private void resetFileSelectItems() private void resetStoryContent() { - storyText.Text = ""; + this.removeAllStoryContent(); + this.removeAllChoices(); + } + + private void removeAllStoryContent() + { + foreach (Node n in storyText.GetChildren()) + storyText.RemoveChild(n); + } + + private void removeAllChoices() + { foreach (Node n in storyChoices.GetChildren()) storyChoices.RemoveChild(n); } @@ -77,28 +91,35 @@ private void onFileDialogHide() fileSelect.Select(2); storyNode.Set("InkFile", ResourceLoader.Load(currentFilePath)); storyNode.Call("LoadStory"); + resetStoryContent(); continueStoryMaximally(); } } - private void continueStoryMaximally() + private async void continueStoryMaximally() { while ((bool)storyNode.Get("CanContinue")) { try { storyNode.Call("Continue"); + onStoryContinued(storyNode.Get("CurrentText") as String, new String[] { }); } catch (Ink.Runtime.StoryException e) { onStoryContinued(e.ToString(), new String[] { }); } } + await ToSignal(GetTree(), "idle_frame"); + this.scrollbar.Value = this.scrollbar.MaxValue; } private void onStoryContinued(String text, String[] tags) { - storyText.Text = (storyText.Text + text).TrimStart(new char[] { ' ', '\n' }); + Label newLine = new Label(); + newLine.Autowrap = true; + newLine.Text = text.Trim(new char[] { ' ', '\n' }); + this.storyText.AddChild(newLine); } private void onStoryChoices(String[] choices) @@ -116,8 +137,9 @@ private void onStoryChoices(String[] choices) private void clickChoice(int idx) { - resetStoryContent(); storyNode.Callv("ChooseChoiceIndex", new Godot.Collections.Array() { idx }); + this.removeAllChoices(); + this.storyText.AddChild(new HSeparator()); continueStoryMaximally(); } } diff --git a/addons/paulloz.ink/InkDock.tscn b/addons/paulloz.ink/InkDock.tscn index 70f2896..d515701 100755 --- a/addons/paulloz.ink/InkDock.tscn +++ b/addons/paulloz.ink/InkDock.tscn @@ -1,52 +1,82 @@ -[gd_scene load_steps=3 format=2] +[gd_scene load_steps=4 format=2] [ext_resource path="res://addons/paulloz.ink/InkDock.cs" type="Script" id=1] [ext_resource path="res://addons/paulloz.ink/InkStory.cs" type="Script" id=2] +[sub_resource type="StyleBoxFlat" id=1] +bg_color = Color( 0.137255, 0.160784, 0.184314, 1 ) + [node name="Ink" type="Control"] anchor_right = 1.0 anchor_bottom = 1.0 +rect_min_size = Vector2( 0, 200 ) +size_flags_horizontal = 3 +size_flags_vertical = 3 script = ExtResource( 1 ) -[node name="VBoxContainer" type="VBoxContainer" parent="."] +[node name="Container" type="VBoxContainer" parent="."] anchor_right = 1.0 -margin_bottom = 24.0 +anchor_bottom = 1.0 size_flags_horizontal = 3 size_flags_vertical = 3 -[node name="GridContainer" type="GridContainer" parent="VBoxContainer"] +[node name="Top" type="HBoxContainer" parent="Container"] +editor/display_folded = true margin_right = 1024.0 margin_bottom = 20.0 -size_flags_horizontal = 3 -size_flags_vertical = 0 -columns = 2 -[node name="Label" type="Label" parent="VBoxContainer/GridContainer"] +[node name="Label" type="Label" parent="Container/Top"] margin_top = 3.0 margin_right = 40.0 margin_bottom = 17.0 text = "Story :" -[node name="OptionButton" type="OptionButton" parent="VBoxContainer/GridContainer"] +[node name="OptionButton" type="OptionButton" parent="Container/Top"] margin_left = 44.0 margin_right = 85.0 margin_bottom = 20.0 items = [ "", null, false, 0, null, "Load", null, false, 1, null ] selected = 0 -[node name="StoryText" type="RichTextLabel" parent="VBoxContainer"] +[node name="Bottom" type="HBoxContainer" parent="Container"] margin_top = 24.0 margin_right = 1024.0 -margin_bottom = 624.0 -rect_min_size = Vector2( 100, 600 ) +margin_bottom = 600.0 size_flags_horizontal = 3 size_flags_vertical = 3 -custom_colors/default_color = Color( 1, 1, 1, 1 ) -[node name="StoryChoices" type="VBoxContainer" parent="VBoxContainer"] -margin_top = 628.0 +[node name="Scroll" type="ScrollContainer" parent="Container/Bottom"] +margin_right = 820.0 +margin_bottom = 576.0 +size_flags_horizontal = 11 +size_flags_vertical = 3 +custom_styles/bg = SubResource( 1 ) +scroll_horizontal_enabled = false + +[node name="Margin" type="MarginContainer" parent="Container/Bottom/Scroll"] +margin_right = 820.0 +margin_bottom = 576.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +custom_constants/margin_right = 10 +custom_constants/margin_top = 10 +custom_constants/margin_left = 10 +custom_constants/margin_bottom = 10 + +[node name="StoryText" type="VBoxContainer" parent="Container/Bottom/Scroll/Margin"] +margin_left = 10.0 +margin_top = 10.0 +margin_right = 810.0 +margin_bottom = 566.0 +size_flags_horizontal = 11 +size_flags_vertical = 3 + +[node name="StoryChoices" type="VBoxContainer" parent="Container/Bottom"] +margin_left = 824.0 margin_right = 1024.0 -margin_bottom = 628.0 +margin_bottom = 576.0 +rect_min_size = Vector2( 200, 0 ) +custom_constants/separation = 10 [node name="FileDialog" type="FileDialog" parent="."] margin_right = 461.0 @@ -57,4 +87,3 @@ filters = PoolStringArray( "*.json" ) [node name="Story" type="Node" parent="."] script = ExtResource( 2 ) - diff --git a/addons/paulloz.ink/PaullozDotInk.cs b/addons/paulloz.ink/PaullozDotInk.cs index fbc534e..fec371f 100644 --- a/addons/paulloz.ink/PaullozDotInk.cs +++ b/addons/paulloz.ink/PaullozDotInk.cs @@ -24,12 +24,13 @@ public override void _EnterTree() AddCustomType("Ink Story", "Node", ResourceLoader.Load(customTypeScriptPath) as Script, ResourceLoader.Load(customTypeIconPath) as Texture); dock = (ResourceLoader.Load(dockScene) as PackedScene).Instance() as Control; - AddControlToDock(EditorPlugin.DockSlot.RightUl, dock); + AddControlToBottomPanel(dock, "Ink"); } public override void _ExitTree() { - RemoveControlFromDocks(dock); + RemoveControlFromBottomPanel(dock); + dock.Free(); RemoveCustomType("Ink Story"); From d9bd6ff125a01156d126ead34048f3fb980bd534 Mon Sep 17 00:00:00 2001 From: Paul Joannon Date: Sat, 21 Sep 2019 19:20:55 +0200 Subject: [PATCH 07/10] :newspaper: README.md --- README.md | 48 +++++++++++++++++++++------------------ import_screenshot.png | Bin 0 -> 6315 bytes inspector_screenshot.png | Bin 0 -> 31278 bytes 3 files changed, 26 insertions(+), 22 deletions(-) create mode 100644 import_screenshot.png create mode 100644 inspector_screenshot.png diff --git a/README.md b/README.md index 1daa7f3..b14fa4e 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,6 @@ # godot-ink -**This project is heavily under construction as C# support in Godot is a WIP.** - -[Ink](https://github.com/inkle/ink) integration for [Godot Engine](https://github.com/godotengine/godot). -As C# supprt in Godot is still in its early stages, you might need to manually add the addon files to your `.csproj`. +An [ink](https://github.com/inkle/ink) integration for [Godot Engine](https://github.com/godotengine/godot). ### Currently supported features: * Running an Ink story and branching with choice indexes @@ -14,31 +11,17 @@ As C# supprt in Godot is still in its early stages, you might need to manually a * Observing Ink variables (Inklists aren't supported yet) * External function bindings * Read/Visit counts +* Story viewer inside the editor ### TODO: * Getting/Setting/Observing InkLists * On the fly Ink to JSON compilation -## Troubleshooting - -If you're having trouble enabling the editor plugin, it's probably because the `.cs` files aren't compiling with your project. You can solve the issue by adding this `ItemGroup` to your `.csproj` file. - -```xml - - - - - - $(ProjectDir)/ink-engine-runtime.dll - False - - -``` +## How to use -Depending on the version of Godot you're using, you might still have issues with the editor plugin. -Do not worry, you don't actually need to enable it to use **godot-ink**. If you don't want to bother with extensive troubleshooting, all you have to do is attach `addons/paulloz.ink/Story.cs` to a node (or use it as a singleton). This node will become the `Story` node for the rest of this documentation. +When the plugin is properly loaded, you should be able to use the new ink panel to inspect your story. -## How to use +![](inspector_screenshot.png) You'll need to put `ink-engine-runtime.dll` at the root of your Godot project. @@ -50,6 +33,8 @@ If nothing is specified, the **C#** usage is the same as the **GDScript** one. First you should navigate to your JSON ink file and import it as a `JSON ink story` in Godot. To do that, select the file in Godot, go to `Import`, select `JSON ink story` under `Import As:` and click `ReImport`. +![](import_screenshot.png) + To load your story, you can: * Point the `InkFile` exported variable to your JSON ink file and check the `AutoLoadStory` checkbox in the inspector. @@ -175,6 +160,25 @@ You can know how many times a knot/stitch has been visited with `.VisitCountPath print(story.VisitCountPathString("mycoolknot.myradstitch")) ``` +## Troubleshooting + +If you're having trouble enabling the editor plugin, it's probably because the `.cs` files aren't compiling with your project. You can solve the issue by adding this `ItemGroup` to your `.csproj` file. + +```xml + + + + + + $(ProjectDir)/ink-engine-runtime.dll + False + + +``` + +Depending on the version of Godot you're using, you might still have issues with the editor plugin. +Do not worry, you don't actually need to enable it to use **godot-ink**. If you don't want to bother with extensive troubleshooting, all you have to do is attach `addons/paulloz.ink/Story.cs` to a node (or use it as a singleton). This node will become the `Story` node for the rest of this documentation. + ## License **godot-ink** is released under MIT license (see the [LICENSE](/LICENSE) file for more information). diff --git a/import_screenshot.png b/import_screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..90794741ec1e49f8fd37f93a551db7a0de8a47fb GIT binary patch literal 6315 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 MO<4<3u4sY%FMe*Y7ytkO literal 0 HcmV?d00001 diff --git a/inspector_screenshot.png b/inspector_screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..3683f443108076b31061cdbbf4cb3b7106932bf2 GIT binary patch literal 31278 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 vkp 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 Date: Sat, 21 Sep 2019 21:27:58 +0200 Subject: [PATCH 08/10] can directly import ink files .ink files will be compiled to .json if the inklecate path is set correctly in the project settings --- addons/paulloz.ink/PaullozDotInk.cs | 28 +++++++++++++++++ addons/paulloz.ink/import_ink.gd | 47 ++++++++++++++++++++++++----- 2 files changed, 68 insertions(+), 7 deletions(-) diff --git a/addons/paulloz.ink/PaullozDotInk.cs b/addons/paulloz.ink/PaullozDotInk.cs index fec371f..12dc0f0 100644 --- a/addons/paulloz.ink/PaullozDotInk.cs +++ b/addons/paulloz.ink/PaullozDotInk.cs @@ -1,10 +1,12 @@ #if TOOLS using Godot; +using Godot.Collections; using System; [Tool] public class PaullozDotInk : EditorPlugin { + private Dictionary settings = new Dictionary() { {"inklecate_path", "---"} }; private const String addonBasePath = "res://addons/paulloz.ink"; private NodePath customTypeScriptPath = $"{addonBasePath}/InkStory.cs"; @@ -18,23 +20,49 @@ public class PaullozDotInk : EditorPlugin public override void _EnterTree() { + // Settings + foreach (String key in settings.Keys) + { + String property_name = $"ink/{key}"; + if (!ProjectSettings.HasSetting(property_name)) + { + ProjectSettings.SetSetting(property_name, settings[key]); + ProjectSettings.SetInitialValue(property_name, settings[key]); + } + } + ProjectSettings.Save(); + + // Resources importPlugin = (ResourceLoader.Load(importPluginScriptPath) as GDScript).New() as EditorImportPlugin; AddImportPlugin(importPlugin); + // Custom types AddCustomType("Ink Story", "Node", ResourceLoader.Load(customTypeScriptPath) as Script, ResourceLoader.Load(customTypeIconPath) as Texture); + // Editor dock = (ResourceLoader.Load(dockScene) as PackedScene).Instance() as Control; AddControlToBottomPanel(dock, "Ink"); } public override void _ExitTree() { + // Editor RemoveControlFromBottomPanel(dock); dock.Free(); + // Custom types RemoveCustomType("Ink Story"); + // Resources RemoveImportPlugin(importPlugin); + + // Settings + foreach (String key in settings.Keys) + { + String property_name = $"ink/{key}"; + if (ProjectSettings.HasSetting(property_name)) + ProjectSettings.SetSetting(property_name, null); + } } } #endif \ No newline at end of file diff --git a/addons/paulloz.ink/import_ink.gd b/addons/paulloz.ink/import_ink.gd index 356fb82..3f6a04d 100644 --- a/addons/paulloz.ink/import_ink.gd +++ b/addons/paulloz.ink/import_ink.gd @@ -8,7 +8,7 @@ func get_visible_name(): return "JSON ink story"; func get_recognized_extensions(): - return [ "json" ]; + return [ "json", "ink" ]; func get_save_extension(): return "res"; @@ -23,12 +23,33 @@ func get_preset_count(): return 0 func import(source_file, save_path, options, r_platform_variants, r_gen_files): - var file = File.new() - var err = file.open(source_file, File.READ) - if err != OK: - return err - var raw_content = file.get_as_text() - file.close() + match source_file.split(".")[-1].to_lower(): + "ink": + return import_from_ink(source_file, save_path) + "json": + return import_from_json(source_file, save_path) + +func import_from_ink(source_file, save_path): + if ProjectSettings.has_setting("ink/inklecate_path"): + var inklecate = ProjectSettings.get_setting("ink/inklecate_path") + if inklecate != "---": + var new_file = "%d.json" % int(randf() * 100000) + + var err = OS.execute(inklecate, [ + "-o", "%s/%s" % [OS.get_user_data_dir(), new_file], + ProjectSettings.globalize_path(source_file) + ], true) + + new_file = "user://%s" % new_file + if !File.new().file_exists(new_file): + return ERR_FILE_UNRECOGNIZED + var ret = import_from_json(new_file, save_path) + + Directory.new().remove(new_file) + return ret + +func import_from_json(source_file, save_path): + var raw_content = get_source_file_content(source_file) var parsed_content = parse_json(raw_content) if !parsed_content.has("inkVersion"): @@ -36,4 +57,16 @@ func import(source_file, save_path, options, r_platform_variants, r_gen_files): var resource = TextFile.new() resource.set_meta("content", raw_content); + return ResourceSaver.save("%s.%s" % [save_path, get_save_extension()], resource) + +func get_source_file_content(source_file): + var file = File.new() + var err = file.open(source_file, File.READ) + if err != OK: + return err + + var raw_content = file.get_as_text() + + file.close() + return raw_content \ No newline at end of file From e112cc259b386c722d865029e0546992aec5f17b Mon Sep 17 00:00:00 2001 From: Paul Joannon Date: Sat, 21 Sep 2019 21:42:10 +0200 Subject: [PATCH 09/10] rename importer --- addons/paulloz.ink/import_ink.gd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/paulloz.ink/import_ink.gd b/addons/paulloz.ink/import_ink.gd index 3f6a04d..376fef0 100644 --- a/addons/paulloz.ink/import_ink.gd +++ b/addons/paulloz.ink/import_ink.gd @@ -2,10 +2,10 @@ tool extends EditorImportPlugin func get_importer_name(): - return "inkjson"; + return "ink"; func get_visible_name(): - return "JSON ink story"; + return "Ink story"; func get_recognized_extensions(): return [ "json", "ink" ]; From 59e77afa0c3e7fc3868a1b7d8de2bfb679256d1b Mon Sep 17 00:00:00 2001 From: Paul Joannon Date: Sat, 21 Sep 2019 21:42:17 +0200 Subject: [PATCH 10/10] :newspaper: README.md --- README.md | 34 +++++++++++++--------------------- import_screenshot.png | Bin 6315 -> 7477 bytes 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index b14fa4e..596cdfb 100644 --- a/README.md +++ b/README.md @@ -2,43 +2,30 @@ An [ink](https://github.com/inkle/ink) integration for [Godot Engine](https://github.com/godotengine/godot). -### Currently supported features: -* Running an Ink story and branching with choice indexes -* Saving and loading Ink state -* Tags -* Knot/Stitch jumping -* Getting/Setting Ink variables (InkLists aren't supported yet) -* Observing Ink variables (Inklists aren't supported yet) -* External function bindings -* Read/Visit counts -* Story viewer inside the editor - -### TODO: -* Getting/Setting/Observing InkLists -* On the fly Ink to JSON compilation - ## How to use When the plugin is properly loaded, you should be able to use the new ink panel to inspect your story. ![](inspector_screenshot.png) -You'll need to put `ink-engine-runtime.dll` at the root of your Godot project. +You'll also see a new `ink` section in your project settings. If you want to be able to compile your .ink files on the fly you can input the path to the inklecate binary here. +The last thing you'll need to do in order to get going is to put `ink-engine-runtime.dll` at the root of your Godot project. -Everything revolves around the `Story` node. +--- +Everything is handled in a `Story` node. If nothing is specified, the **C#** usage is the same as the **GDScript** one. ### Loading the story -First you should navigate to your JSON ink file and import it as a `JSON ink story` in Godot. To do that, select the file in Godot, go to `Import`, select `JSON ink story` under `Import As:` and click `ReImport`. +First you should navigate to your `.json` or `.ink` file and import it as an `Ink story` in Godot. To do that, select the file in Godot, go to `Import`, select `Ink story` under `Import As:` and click `ReImport`. ![](import_screenshot.png) To load your story, you can: -* Point the `InkFile` exported variable to your JSON ink file and check the `AutoLoadStory` checkbox in the inspector. -* Point the `InkFile` exported variable to your JSON ink file (in the inspector or via a script) and call `story.LoadStory()`. +* Point the `InkFile` exported variable to your `.json`/`.ink` file and check the `AutoLoadStory` checkbox in the inspector. +* Point the `InkFile` exported variable to your `.json`/`.ink` file (in the inspector or via a script) and call `story.LoadStory()`. ### Running the story and making choices @@ -93,7 +80,8 @@ You get and set the json state by calling `.GetState()` and `.SetState(String)`. story.SetState(story.GetState()) ``` -Alternatively you can save and load directly from disk (either by passing a path or a file as argument) with `.LoadStateFromDisk` and `.SaveStateOnDisk`. +Alternatively you can save and load directly from disk (either by passing a path or a file as argument) with `.LoadStateFromDisk` and `.SaveStateOnDisk`. +When using a path, the default behaviour is to use the `user://` folder. You can bypass this by passing a full path to the functions (e.g. `res://my_dope_save_file.json`). ```GDScript story.SaveStateOnDisk("save.json") @@ -179,6 +167,10 @@ If you're having trouble enabling the editor plugin, it's probably because the ` Depending on the version of Godot you're using, you might still have issues with the editor plugin. Do not worry, you don't actually need to enable it to use **godot-ink**. If you don't want to bother with extensive troubleshooting, all you have to do is attach `addons/paulloz.ink/Story.cs` to a node (or use it as a singleton). This node will become the `Story` node for the rest of this documentation. +### TODO: +* Getting/Setting/Observing InkLists +* On the fly Ink to JSON compilation (works on Windows, need some tweaking for Linux and Mac OS support) + ## License **godot-ink** is released under MIT license (see the [LICENSE](/LICENSE) file for more information). diff --git a/import_screenshot.png b/import_screenshot.png index 90794741ec1e49f8fd37f93a551db7a0de8a47fb..bb40015799eb8af3e8f66f1acd20b2b0d141d391 100644 GIT binary patch literal 7477 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 literal 6315 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 MO<4<3u4sY%FMe*Y7ytkO