initial commit
commit
fb1b447a10
@ -0,0 +1,4 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
# Normalize EOL for all files that Git considers text files.
|
||||||
|
* text=auto eol=lf
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
# Godot 4+ specific ignores
|
||||||
|
.godot/
|
||||||
|
/android/
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
[gd_scene format=3 uid="uid://hac4l25a1g7v"]
|
||||||
|
|
||||||
|
[ext_resource type="Shader" uid="uid://cgfte1tvivngj" path="res://new_shader.gdshader" id="1_gbilv"]
|
||||||
|
[ext_resource type="Script" uid="uid://bfvl27u51p0lf" path="res://character_body_3d.gd" id="2_atw5s"]
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="BoxMesh_4127l"]
|
||||||
|
|
||||||
|
[sub_resource type="CameraTexture" id="CameraTexture_ey7nh"]
|
||||||
|
|
||||||
|
[sub_resource type="Gradient" id="Gradient_atw5s"]
|
||||||
|
|
||||||
|
[sub_resource type="GradientTexture2D" id="GradientTexture2D_atw5s"]
|
||||||
|
gradient = SubResource("Gradient_atw5s")
|
||||||
|
|
||||||
|
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_atw5s"]
|
||||||
|
|
||||||
|
[sub_resource type="ShaderMaterial" id="ShaderMaterial_i0stl"]
|
||||||
|
render_priority = 0
|
||||||
|
shader = ExtResource("1_gbilv")
|
||||||
|
shader_parameter/width = 0.1
|
||||||
|
shader_parameter/height = 0.1
|
||||||
|
shader_parameter/gap_x = 0.02
|
||||||
|
shader_parameter/gap_y = 0.04
|
||||||
|
shader_parameter/sink = -0.5
|
||||||
|
shader_parameter/brick = SubResource("CameraTexture_ey7nh")
|
||||||
|
shader_parameter/wall = SubResource("NoiseTexture2D_atw5s")
|
||||||
|
shader_parameter/gap = SubResource("GradientTexture2D_atw5s")
|
||||||
|
shader_parameter/ratio = 0.5
|
||||||
|
|
||||||
|
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_yp6wb"]
|
||||||
|
radius = 0.12158203
|
||||||
|
height = 0.43318358
|
||||||
|
|
||||||
|
[sub_resource type="Environment" id="Environment_yp6wb"]
|
||||||
|
background_mode = 1
|
||||||
|
background_color = Color(1, 0.5254629, 0.55886984, 1)
|
||||||
|
ambient_light_color = Color(0.71889627, 0.67300373, 0.93241364, 1)
|
||||||
|
|
||||||
|
[node name="Node3D" type="Node3D" unique_id=367464733]
|
||||||
|
|
||||||
|
[node name="CSGMesh3D" type="CSGMesh3D" parent="." unique_id=2026853425]
|
||||||
|
transform = Transform3D(2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0)
|
||||||
|
use_collision = true
|
||||||
|
flip_faces = true
|
||||||
|
mesh = SubResource("BoxMesh_4127l")
|
||||||
|
material = SubResource("ShaderMaterial_i0stl")
|
||||||
|
|
||||||
|
[node name="OmniLight3D" type="OmniLight3D" parent="." unique_id=432683985]
|
||||||
|
|
||||||
|
[node name="CharacterBody3D" type="CharacterBody3D" parent="." unique_id=287273578]
|
||||||
|
script = ExtResource("2_atw5s")
|
||||||
|
|
||||||
|
[node name="Camera3D" type="Camera3D" parent="CharacterBody3D" unique_id=871682469]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.53903836)
|
||||||
|
current = true
|
||||||
|
|
||||||
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="CharacterBody3D" unique_id=1029909573]
|
||||||
|
shape = SubResource("CapsuleShape3D_yp6wb")
|
||||||
|
|
||||||
|
[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=1564628371]
|
||||||
|
environment = SubResource("Environment_yp6wb")
|
||||||
Binary file not shown.
@ -0,0 +1,38 @@
|
|||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<InkVersion>v1.1.1</InkVersion>
|
||||||
|
<!-- Directories -->
|
||||||
|
<GodotDotDirectory>$([System.IO.Path]::Combine($(GodotProjectDir),'.godot'))</GodotDotDirectory>
|
||||||
|
<GodotMonoDirectory>$([System.IO.Path]::Combine($(GodotDotDirectory),'mono'))</GodotMonoDirectory>
|
||||||
|
<UnzipDirectory>$([System.IO.Path]::Combine($(GodotDotDirectory),'ink'))</UnzipDirectory>
|
||||||
|
<InkVersionDirectory>$([System.IO.Path]::Combine($(GodotMonoDirectory),'ink-$(InkVersion)'))</InkVersionDirectory>
|
||||||
|
<!-- File -->
|
||||||
|
<InkZip>inklecate_windows.zip</InkZip>
|
||||||
|
<InkZipPath>$([System.IO.Path]::Combine($(GodotDotDirectory),$(InkZip)))</InkZipPath>
|
||||||
|
<InkUrl>https://github.com/inkle/ink/releases/download/$(InkVersion)/$(InkZip)</InkUrl>
|
||||||
|
<InkCompiler>ink_compiler.dll</InkCompiler>
|
||||||
|
<InkCompilerPath>$([System.IO.Path]::Combine($(InkVersionDirectory),$(InkCompiler)))</InkCompilerPath>
|
||||||
|
<InkRuntime>ink-engine-runtime.dll</InkRuntime>
|
||||||
|
<InkRuntimePath>$([System.IO.Path]::Combine($(InkVersionDirectory),$(InkRuntime)))</InkRuntimePath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<Target Name="DownloadInkDependency" AfterTargets="BeforeBuild" Condition="!Exists('$(InkCompilerPath)') Or !Exists('$(InkRuntimePath)')">
|
||||||
|
<!-- Download and unzip -->
|
||||||
|
<DownloadFile SourceUrl="$(InkUrl)" DestinationFolder="$(GodotDotDirectory)" />
|
||||||
|
<Unzip SourceFiles="$(InkZipPath)" DestinationFolder="$(UnzipDirectory)" />
|
||||||
|
<!-- Extract what we want -->
|
||||||
|
<ItemGroup>
|
||||||
|
<FilesToMove Include="$([System.IO.Path]::Combine($(UnzipDirectory),$(InkCompiler)))" />
|
||||||
|
<FilesToMove Include="$([System.IO.Path]::Combine($(UnzipDirectory),$(InkRuntime)))" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Move SourceFiles="@(FilesToMove)" DestinationFolder="$(InkVersionDirectory)" />
|
||||||
|
<!-- Cleanup -->
|
||||||
|
<Delete Files="$(InkZipPath)" />
|
||||||
|
<RemoveDir Directories="$(UnzipDirectory)" />
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="$(InkCompilerPath)" Condition="'$(Configuration)' == 'Debug'" />
|
||||||
|
<Reference Include="$(InkRuntimePath)" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@ -0,0 +1 @@
|
|||||||
|
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7.3136863 1.0451519c-1.1300539.1561083-2.1797857.4941546-3.1354935 1.1410596-.7783707.5268672-1.4612656 1.2265006-1.9842642 2.0139066-.4973947.7488583-.8815365 1.5944212-1.0603099 2.4819785-.95527094 4.7426384 3.3693827 8.8795764 7.9359446 8.2552864.9504617-.129934 1.8803567-.480581 2.6965237-.988244 3.708275-2.306571 4.338996-7.6732378 1.311294-10.8284044-.862632-.8989506-2.009172-1.5183411-3.1925886-1.8473904-.8120607-.2257936-1.7322548-.34407278-2.5711061-.2281923m5.2049197 3.4095282c.46704.363567.831781.9839945 1.065826 1.527371.318783.7401157.640439 1.6601785.553744 2.4819779-.175511 1.663713-1.809807 3.201643-2.999186 4.205327-.794997.670877-1.8195886 1.458842-2.8846556 1.574906-1.0595319.115463-2.119995-.506003-2.9473638-1.107716-.9414982-.684716-1.7807878-1.511478-2.4020398-2.508741-.2483083-.398596-.5356887-.9868407-.3479044-1.4627967.2247735-.56971 1.3074802-1.0017929 1.872006-1.0710849.2424849-.029766.595381-.0485.7412927.1937196.2551389.4235385-.1340085.9985353-.1083388 1.4491962.050084.8792768.4947129 1.7293578 1.00568 2.4183378.1414647.190746.5055731.837878.7871155.787158.1692126-.03049.1222367-.290441.10892-.405315-.051947-.448112-.1258299-.891399-.1989206-1.33645-.1766914-1.075882-.5697899-2.4158386-.281081-3.5002263.1756643-.659794.8205368-.3899122 1.0788423-.8937651.2031841-.3963332-.2225521-.8682646-.2488562-1.2700106.6383703.1869895 1.2930544.7408713 1.6904095 1.2730551.1582256.2119133.3702727.5401447.231145.8135268-.2186648.4296693-1.4769713.6919585-1.1614379 1.410845.3933613.8962013 1.655226-.2672517 2.2404251-.021508.292853.1229782.08341.4680216-.0042.6607119-.188638.4148282-.282515 1.1481662.270435 1.3574162.571502.216271 1.019879-.972216 1.173052-1.3574162.395401-.9943516.716088-2.1033072.762632-3.1820233.02626-.6084688-.228341-1.4782825.0024-2.0364951z" fill="#cf68ea"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
@ -0,0 +1,43 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://op2d8aagi35f"
|
||||||
|
path="res://.godot/imported/GodotInk.svg-0c8ec83d82b5d336ec3fed92ffcbc8bc.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/GodotInk/GodotInk.svg"
|
||||||
|
dest_files=["res://.godot/imported/GodotInk.svg-0c8ec83d82b5d336ec3fed92ffcbc8bc.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 - 2024 Paul Joannon
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
# GodotInk
|
||||||
|
|
||||||
|
An [ink](https://github.com/inkle/ink) integration for [Godot Engine 4](https://github.com/godotengine/godot).
|
||||||
|
|
||||||
|
If you use and like this project, please consider buying me a coffee.
|
||||||
|
[](https://ko-fi.com/E1E53SKZF)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
You'll find a quick-start guide, and the general documentation on the project's
|
||||||
|
[wiki](https://github.com/paulloz/godot-ink/wiki).
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
The following instructions assume that you have a working Godot and .NET installation. If not, please refer to the
|
||||||
|
official [engine documentation](https://docs.godotengine.org/).
|
||||||
|
|
||||||
|
1. [Download](https://github.com/paulloz/godot-ink/releases/latest) and extract the code at the root of your project.
|
||||||
|
You should see a new `addons/GodotInk/` folder in your project's directory.
|
||||||
|
|
||||||
|
1. Add the following line in your `.csproj` file (before the closing `</Project>` tag).
|
||||||
|
```xml
|
||||||
|
<Import Project="addons\GodotInk\GodotInk.props" />
|
||||||
|
```
|
||||||
|
If you don't have a `.csproj` file, click the **Create C# Solution** button in the editor's
|
||||||
|
**Project/Tools** menu.
|
||||||
|
|
||||||
|
1. Build your project once.
|
||||||
|
|
||||||
|
1. Enable **GodotInk** in your project settings.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
*GodotInk* is released under MIT license (see the [LICENSE](/LICENSE) file for more information).
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace GodotInk;
|
||||||
|
|
||||||
|
public partial class InkChoice
|
||||||
|
{
|
||||||
|
#pragma warning disable IDE0022
|
||||||
|
/// <summary>
|
||||||
|
/// This method is here for GDScript compatibility. Use <see cref="CanContinue" /> instead.
|
||||||
|
/// </summary>
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public string GetText() => Text;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This method is here for GDScript compatibility. Use <see cref="CanContinue" /> instead.
|
||||||
|
/// </summary>
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public string GetPathStringOnChoice() => PathStringOnChoice;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This method is here for GDScript compatibility. Use <see cref="CanContinue" /> instead.
|
||||||
|
/// </summary>
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public string GetSourcePath() => SourcePath;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This method is here for GDScript compatibility. Use <see cref="CanContinue" /> instead.
|
||||||
|
/// </summary>
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public int GetIndex() => Index;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This method is here for GDScript compatibility. Use <see cref="CanContinue" /> instead.
|
||||||
|
/// </summary>
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public Godot.Collections.Array<string> GetTags() => new(Tags);
|
||||||
|
#pragma warning restore IDE0022
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
uid://dcwohy1x33mp8
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using Godot;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace GodotInk;
|
||||||
|
|
||||||
|
[Tool]
|
||||||
|
#if GODOT4_1_OR_GREATER
|
||||||
|
[GlobalClass]
|
||||||
|
#endif
|
||||||
|
public partial class InkChoice : RefCounted
|
||||||
|
{
|
||||||
|
public string Text => inner.text;
|
||||||
|
public string PathStringOnChoice => inner.pathStringOnChoice;
|
||||||
|
public string SourcePath => inner.sourcePath;
|
||||||
|
public int Index => inner.index;
|
||||||
|
public IReadOnlyList<string> Tags => inner.tags ?? (IReadOnlyList<string>)System.Array.Empty<string>();
|
||||||
|
|
||||||
|
private readonly Ink.Runtime.Choice inner;
|
||||||
|
|
||||||
|
private InkChoice()
|
||||||
|
{
|
||||||
|
inner = new Ink.Runtime.Choice();
|
||||||
|
}
|
||||||
|
|
||||||
|
public InkChoice(Ink.Runtime.Choice inner)
|
||||||
|
{
|
||||||
|
this.inner = inner;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
uid://jrgs3csjev4o
|
||||||
@ -0,0 +1,254 @@
|
|||||||
|
#if TOOLS
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using Godot;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace GodotInk;
|
||||||
|
|
||||||
|
[Tool]
|
||||||
|
public partial class InkDock : VBoxContainer
|
||||||
|
{
|
||||||
|
private static readonly StringName CHOICE_INDEX_META = "Index";
|
||||||
|
|
||||||
|
private Button loadButton = null!;
|
||||||
|
private Button startButton = null!;
|
||||||
|
private Button stopButton = null!;
|
||||||
|
private Button clearButton = null!;
|
||||||
|
|
||||||
|
private Label storyNameLabel = null!;
|
||||||
|
private VBoxContainer storyText = null!;
|
||||||
|
private VBoxContainer storyChoices = null!;
|
||||||
|
private ScrollContainer scroll = null!;
|
||||||
|
|
||||||
|
private EditorFileDialog fileDialog = null!;
|
||||||
|
|
||||||
|
private string storyPath = "";
|
||||||
|
private InkStory? story;
|
||||||
|
private bool storyStarted;
|
||||||
|
|
||||||
|
private string backupSave = "";
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
base._Ready();
|
||||||
|
|
||||||
|
CustomMinimumSize = new Vector2(0.0f, 228.0f);
|
||||||
|
|
||||||
|
// Initialize file dialog.
|
||||||
|
fileDialog = new()
|
||||||
|
{
|
||||||
|
FileMode = EditorFileDialog.FileModeEnum.OpenFile,
|
||||||
|
Access = EditorFileDialog.AccessEnum.Resources,
|
||||||
|
};
|
||||||
|
fileDialog.AddFilter("*.ink", "Ink Stories");
|
||||||
|
fileDialog.FileSelected += LoadStory;
|
||||||
|
|
||||||
|
AddChild(fileDialog);
|
||||||
|
|
||||||
|
// Initialize top.
|
||||||
|
loadButton = GetNode<Button>("Container/Left/Top/LoadButton");
|
||||||
|
storyNameLabel = GetNode<Label>("Container/Left/Top/Label");
|
||||||
|
startButton = GetNode<Button>("Container/Left/Top/StartButton");
|
||||||
|
stopButton = GetNode<Button>("Container/Left/Top/StopButton");
|
||||||
|
clearButton = GetNode<Button>("Container/Left/Top/ClearButton");
|
||||||
|
|
||||||
|
// Connect UI events.
|
||||||
|
loadButton.Pressed += () => fileDialog.PopupCenteredClamped(new Vector2I(1050, 700), 0.8f);
|
||||||
|
startButton.Pressed += StartStory;
|
||||||
|
stopButton.Pressed += StopStory;
|
||||||
|
clearButton.Pressed += () => ClearStory(false);
|
||||||
|
|
||||||
|
// Initialize bottom.
|
||||||
|
storyText = GetNode<VBoxContainer>("Container/Left/Scroll/Margin/StoryText");
|
||||||
|
storyChoices = GetNode<VBoxContainer>("Container/Right/StoryChoices");
|
||||||
|
scroll = GetNode<ScrollContainer>("Container/Left/Scroll");
|
||||||
|
|
||||||
|
// Set icons.
|
||||||
|
loadButton.Icon = GetThemeIcon("Load", "EditorIcons");
|
||||||
|
startButton.Icon = GetThemeIcon("Play", "EditorIcons");
|
||||||
|
stopButton.Icon = GetThemeIcon("Stop", "EditorIcons");
|
||||||
|
clearButton.Icon = GetThemeIcon("Clear", "EditorIcons");
|
||||||
|
|
||||||
|
// Update UI.
|
||||||
|
UpdateTop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateTop()
|
||||||
|
{
|
||||||
|
bool hasStory = story != null;
|
||||||
|
|
||||||
|
storyNameLabel.Text = hasStory ? storyPath : string.Empty;
|
||||||
|
|
||||||
|
startButton.Visible = hasStory && !storyStarted;
|
||||||
|
stopButton.Visible = hasStory && storyStarted;
|
||||||
|
clearButton.Visible = hasStory;
|
||||||
|
clearButton.Disabled = storyText.GetChildCount() <= 0;
|
||||||
|
|
||||||
|
storyChoices.GetParent<Control>().Visible = hasStory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadStory(string path)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
storyPath = path;
|
||||||
|
story = ResourceLoader.Load<InkStory>(path, null, ResourceLoader.CacheMode.Ignore);
|
||||||
|
|
||||||
|
UpdateTop();
|
||||||
|
}
|
||||||
|
catch (InvalidCastException)
|
||||||
|
{
|
||||||
|
GD.PrintErr($"{path} is not a valid ink story. "
|
||||||
|
+ "Please make sure it was imported with `is_main_file` set to `true`.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartStory()
|
||||||
|
{
|
||||||
|
if (story == null) return;
|
||||||
|
|
||||||
|
storyStarted = true;
|
||||||
|
ContinueStory();
|
||||||
|
|
||||||
|
UpdateTop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StopStory()
|
||||||
|
{
|
||||||
|
StopStory(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StopStory(bool setStoryToNull)
|
||||||
|
{
|
||||||
|
storyStarted = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
story?.ResetState();
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
story = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setStoryToNull)
|
||||||
|
story = null;
|
||||||
|
|
||||||
|
ClearStory(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearStory(bool clearChoices)
|
||||||
|
{
|
||||||
|
RemoveAllStoryContent();
|
||||||
|
if (clearChoices)
|
||||||
|
RemoveAllChoices();
|
||||||
|
|
||||||
|
UpdateTop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ContinueStory()
|
||||||
|
{
|
||||||
|
if (story == null) return;
|
||||||
|
if (!story.CanContinue) return;
|
||||||
|
|
||||||
|
string currentText = story.ContinueMaximally().Trim();
|
||||||
|
|
||||||
|
if (currentText.Length > 0)
|
||||||
|
{
|
||||||
|
Label newLine = new()
|
||||||
|
{
|
||||||
|
AutowrapMode = TextServer.AutowrapMode.WordSmart,
|
||||||
|
Text = currentText,
|
||||||
|
};
|
||||||
|
AddToStory(newLine);
|
||||||
|
|
||||||
|
if (story.CurrentTags.Count > 0)
|
||||||
|
{
|
||||||
|
newLine = new Label()
|
||||||
|
{
|
||||||
|
AutowrapMode = TextServer.AutowrapMode.WordSmart,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
Text = $"# {string.Join(", ", story.CurrentTags)}",
|
||||||
|
};
|
||||||
|
newLine.AddThemeColorOverride("font_color", GetThemeColor("font_color_disabled", "Button"));
|
||||||
|
AddToStory(newLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (InkChoice choice in story.CurrentChoices)
|
||||||
|
{
|
||||||
|
Button button = new() { Text = choice.Text };
|
||||||
|
button.SetMeta(CHOICE_INDEX_META, choice.Index);
|
||||||
|
|
||||||
|
button.Connect(Button.SignalName.Pressed, Callable.From(ClickChoice));
|
||||||
|
|
||||||
|
storyChoices.AddChild(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
backupSave = storyStarted ? story.SaveState() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClickChoice()
|
||||||
|
{
|
||||||
|
if (storyChoices.GetChildren().OfType<Button>().First(button => button.ButtonPressed) is not Button button) return;
|
||||||
|
if (!button.HasMeta(CHOICE_INDEX_META)) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ClickChoice(button.GetMeta(CHOICE_INDEX_META).As<int>());
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
story?.LoadState(backupSave);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ClickChoice(button.GetMeta(CHOICE_INDEX_META).As<int>());
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
StopStory(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClickChoice(int idx)
|
||||||
|
{
|
||||||
|
if (story == null) return;
|
||||||
|
|
||||||
|
story.ChooseChoiceIndex(idx);
|
||||||
|
|
||||||
|
RemoveAllChoices();
|
||||||
|
AddToStory(new HSeparator());
|
||||||
|
|
||||||
|
ContinueStory();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void AddToStory(CanvasItem item)
|
||||||
|
{
|
||||||
|
storyText.AddChild(item);
|
||||||
|
await ToSignal(GetTree(), "process_frame");
|
||||||
|
await ToSignal(GetTree(), "process_frame");
|
||||||
|
scroll.ScrollVertical = (int)scroll.GetVScrollBar().MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveAllStoryContent()
|
||||||
|
{
|
||||||
|
foreach (Node n in storyText.GetChildren())
|
||||||
|
n.QueueFree();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveAllChoices()
|
||||||
|
{
|
||||||
|
foreach (Node n in storyChoices.GetChildren().OfType<Button>())
|
||||||
|
n.QueueFree();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WhenInkResourceReimported()
|
||||||
|
{
|
||||||
|
StopStory(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -0,0 +1 @@
|
|||||||
|
uid://cqdq3u7vt365x
|
||||||
@ -0,0 +1,99 @@
|
|||||||
|
[gd_scene load_steps=4 format=3 uid="uid://bi0phc77n67u"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/GodotInk/Src/InkDock.cs" id="1_c6vi7"]
|
||||||
|
|
||||||
|
[sub_resource type="Image" id="Image_s0a34"]
|
||||||
|
data = {
|
||||||
|
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 93, 93, 55, 255, 97, 97, 58, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||||
|
"format": "RGBA8",
|
||||||
|
"height": 16,
|
||||||
|
"mipmaps": false,
|
||||||
|
"width": 16
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="ImageTexture" id="ImageTexture_a3xdy"]
|
||||||
|
image = SubResource("Image_s0a34")
|
||||||
|
|
||||||
|
[node name="InkDock" type="VBoxContainer"]
|
||||||
|
custom_minimum_size = Vector2(0, 228)
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
script = ExtResource("1_c6vi7")
|
||||||
|
|
||||||
|
[node name="Container" type="HSplitContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="Left" type="VBoxContainer" parent="Container"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="Top" type="HBoxContainer" parent="Container/Left"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="LoadButton" type="Button" parent="Container/Left/Top"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Load..."
|
||||||
|
icon = SubResource("ImageTexture_a3xdy")
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="Container/Left/Top"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="StartButton" type="Button" parent="Container/Left/Top"]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Start"
|
||||||
|
icon = SubResource("ImageTexture_a3xdy")
|
||||||
|
|
||||||
|
[node name="StopButton" type="Button" parent="Container/Left/Top"]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Stop"
|
||||||
|
icon = SubResource("ImageTexture_a3xdy")
|
||||||
|
|
||||||
|
[node name="ClearButton" type="Button" parent="Container/Left/Top"]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
disabled = true
|
||||||
|
text = "Clear"
|
||||||
|
icon = SubResource("ImageTexture_a3xdy")
|
||||||
|
|
||||||
|
[node name="Scroll" type="ScrollContainer" parent="Container/Left"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 11
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="Margin" type="MarginContainer" parent="Container/Left/Scroll"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="StoryText" type="VBoxContainer" parent="Container/Left/Scroll/Margin"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 11
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="Right" type="VBoxContainer" parent="Container"]
|
||||||
|
visible = false
|
||||||
|
custom_minimum_size = Vector2(228, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="Top" type="HBoxContainer" parent="Container/Right"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="Container/Right/Top"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
text = "Choices"
|
||||||
|
|
||||||
|
[node name="StoryChoices" type="VBoxContainer" parent="Container/Right"]
|
||||||
|
layout_mode = 2
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace GodotInk;
|
||||||
|
|
||||||
|
#if GODOT4_1_OR_GREATER
|
||||||
|
[GlobalClass]
|
||||||
|
#endif
|
||||||
|
public partial class InkList : RefCounted
|
||||||
|
{
|
||||||
|
private readonly Ink.Runtime.InkList inner;
|
||||||
|
|
||||||
|
public InkList(Ink.Runtime.InkList inner)
|
||||||
|
{
|
||||||
|
this.inner = inner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InkList(InkList otherList)
|
||||||
|
: this(new Ink.Runtime.InkList(otherList.inner))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning disable IDE0022
|
||||||
|
public InkList Inverse => new(inner.inverse);
|
||||||
|
public InkList All => new(inner.all);
|
||||||
|
|
||||||
|
public void AddItem(string itemName) => inner.AddItem(itemName);
|
||||||
|
|
||||||
|
public bool Contains(string listItemName) => inner.Contains(listItemName);
|
||||||
|
public bool Contains(InkList otherList) => inner.Contains(otherList.inner);
|
||||||
|
public bool ContainsItemNamed(string itemName) => inner.ContainsItemNamed(itemName);
|
||||||
|
|
||||||
|
public override bool Equals(object? other) => inner.Equals(other);
|
||||||
|
public override int GetHashCode() => inner.GetHashCode();
|
||||||
|
public override string ToString() => inner.ToString();
|
||||||
|
|
||||||
|
public bool GreaterThan(InkList otherList) => inner.GreaterThan(otherList.inner);
|
||||||
|
public bool GreaterThanOrEquals(InkList otherList) => inner.GreaterThanOrEquals(otherList.inner);
|
||||||
|
public bool LessThan(InkList otherList) => inner.LessThan(otherList.inner);
|
||||||
|
public bool LessThanOrEquals(InkList otherList) => inner.LessThanOrEquals(otherList.inner);
|
||||||
|
|
||||||
|
public bool HashIntersection(InkList otherList) => inner.HasIntersection(otherList.inner);
|
||||||
|
public InkList Intersect(InkList otherList) => new(inner.Intersect(otherList.inner));
|
||||||
|
public InkList Union(InkList otherList) => new(inner.Union(otherList.inner));
|
||||||
|
public InkList Without(InkList listToRemove) => new(inner.Without(listToRemove.inner));
|
||||||
|
|
||||||
|
public InkList MaxAsList() => new(inner.MaxAsList());
|
||||||
|
public InkList MinAsList() => new(inner.MinAsList());
|
||||||
|
|
||||||
|
public void SetInitialOriginName(string initialOriginName) => inner.SetInitialOriginName(initialOriginName);
|
||||||
|
public void SetInitialOriginNames(string[] initialOriginNames) => inner.SetInitialOriginNames(new(initialOriginNames));
|
||||||
|
#pragma warning restore IDE0022
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
uid://dopg4y7h1e8vj
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using Godot;
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using VariantGArray = Godot.Collections.Array<Godot.Variant>;
|
||||||
|
|
||||||
|
namespace GodotInk;
|
||||||
|
|
||||||
|
public partial class InkStory
|
||||||
|
{
|
||||||
|
#pragma warning disable IDE0022
|
||||||
|
/// <summary>
|
||||||
|
/// This method is here for GDScript compatibility. Use <see cref="CanContinue" /> instead.
|
||||||
|
/// </summary>
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public bool GetCanContinue() => CanContinue;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This method is here for GDScript compatibility. Use <see cref="CurrentChoices" /> instead.
|
||||||
|
/// </summary>
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public Godot.Collections.Array<InkChoice> GetCurrentChoices() => new(CurrentChoices);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This method is here for GDScript compatibility. Use <see cref="CurrentTags" /> instead.
|
||||||
|
/// </summary>
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public Godot.Collections.Array<string> GetCurrentTags() => new(CurrentTags);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This method is here for GDScript compatibility. Use <see cref="CurrentText" /> instead.
|
||||||
|
/// </summary>
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public string GetCurrentText() => CurrentText;
|
||||||
|
#pragma warning restore IDE0022
|
||||||
|
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public void ChoosePathString(string path)
|
||||||
|
{
|
||||||
|
ChoosePathString(path, true, Array.Empty<Variant>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public void ChoosePathString(string path, bool resetCallstack)
|
||||||
|
{
|
||||||
|
ChoosePathString(path, resetCallstack, Array.Empty<Variant>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public void ChoosePathString(string path, VariantGArray arguments)
|
||||||
|
{
|
||||||
|
ChoosePathString(path, true, arguments.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public void ChoosePathString(string path, bool resetCallstack, VariantGArray arguments)
|
||||||
|
{
|
||||||
|
ChoosePathString(path, resetCallstack, arguments.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evaluates a function defined in ink.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="functionName">The name of the function as declared in ink.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The return value as returned from the ink function with `~ return myValue`, or a nil
|
||||||
|
/// variant if nothing is returned.
|
||||||
|
/// </returns>
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public Variant EvaluateFunction(string functionName)
|
||||||
|
{
|
||||||
|
return EvaluateFunction(functionName, Array.Empty<Variant>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public void BindExternalFunction(string funcName, Callable callable)
|
||||||
|
{
|
||||||
|
BindExternalFunction(funcName, callable, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
uid://dqr3nfjf03ov4
|
||||||
@ -0,0 +1,698 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using Godot;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using static GodotInk.MarshalUtils;
|
||||||
|
|
||||||
|
using PropertyList = Godot.Collections.Array<Godot.Collections.Dictionary>;
|
||||||
|
|
||||||
|
namespace GodotInk;
|
||||||
|
|
||||||
|
[Tool]
|
||||||
|
#if GODOT4_1_OR_GREATER
|
||||||
|
[GlobalClass, Icon("../GodotInk.svg")]
|
||||||
|
#endif
|
||||||
|
public partial class InkStory : Resource
|
||||||
|
{
|
||||||
|
[Signal]
|
||||||
|
public delegate void ContinuedEventHandler();
|
||||||
|
|
||||||
|
[Signal]
|
||||||
|
public delegate void MadeChoiceEventHandler(InkChoice choice);
|
||||||
|
|
||||||
|
protected virtual string RawStory
|
||||||
|
{
|
||||||
|
get => rawStory;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
rawStory = value;
|
||||||
|
#if TOOLS
|
||||||
|
// There's really no need to instantiate Ink.Runtime in the editor itself.
|
||||||
|
// if (Engine.IsEditorHint()) return; <- Commenting for now, because it prevents
|
||||||
|
// the InkDock from running stories.
|
||||||
|
#endif
|
||||||
|
InitializeRuntimeStory();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string rawStory = string.Empty;
|
||||||
|
private Ink.Runtime.Story runtimeStory = null!;
|
||||||
|
|
||||||
|
private readonly Dictionary<string, HashSet<Callable>> observers = new();
|
||||||
|
private readonly Dictionary<string, Ink.Runtime.Story.VariableObserver> internalObservers = new();
|
||||||
|
|
||||||
|
public static InkStory Create(string rawStory)
|
||||||
|
{
|
||||||
|
return new InkStory()
|
||||||
|
{
|
||||||
|
rawStory = rawStory
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeRuntimeStory()
|
||||||
|
{
|
||||||
|
if (runtimeStory != null)
|
||||||
|
{
|
||||||
|
runtimeStory.onDidContinue -= OnContinued;
|
||||||
|
runtimeStory.onMakeChoice -= OnMadeChoice;
|
||||||
|
}
|
||||||
|
|
||||||
|
runtimeStory = new Ink.Runtime.Story(rawStory);
|
||||||
|
|
||||||
|
runtimeStory.onDidContinue += OnContinued;
|
||||||
|
runtimeStory.onMakeChoice += OnMadeChoice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string CurrentText => runtimeStory.currentText;
|
||||||
|
|
||||||
|
public IReadOnlyList<InkChoice> CurrentChoices => ToVariants(runtimeStory.currentChoices);
|
||||||
|
|
||||||
|
public IReadOnlyList<string> CurrentTags => runtimeStory.currentTags;
|
||||||
|
|
||||||
|
public bool HasWarning => runtimeStory.hasWarning;
|
||||||
|
|
||||||
|
public IReadOnlyList<string> CurrentWarnings => runtimeStory.currentWarnings;
|
||||||
|
|
||||||
|
public bool HasError => runtimeStory.hasError;
|
||||||
|
|
||||||
|
public IReadOnlyList<string> CurrentErrors => runtimeStory.currentErrors;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check whether more content is available if you were to call <c>Continue()</c> - i.e.
|
||||||
|
/// are we mid story rather than at a choice point or at the end.
|
||||||
|
/// </summary>
|
||||||
|
public bool CanContinue => runtimeStory.canContinue;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Continue the story for one line of content, if possible.
|
||||||
|
/// If you're not sure if there's more content available, for example if you
|
||||||
|
/// want to check whether you're at a choice point or at the end of the story,
|
||||||
|
/// you should call <c>canContinue</c> before calling this function.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The line of text content.</returns>
|
||||||
|
public string Continue()
|
||||||
|
{
|
||||||
|
return runtimeStory.Continue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Continue the story until the next choice point or until it runs out of content.
|
||||||
|
/// This is as opposed to the Continue() method which only evaluates one line of
|
||||||
|
/// output at a time.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The resulting text evaluated by the ink engine, concatenated together.</returns>
|
||||||
|
public string ContinueMaximally()
|
||||||
|
{
|
||||||
|
return runtimeStory.ContinueMaximally();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Chooses the Choice from the currentChoices list with the given
|
||||||
|
/// index. Internally, this sets the current content path to that
|
||||||
|
/// pointed to by the Choice, ready to continue story evaluation.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="choiceIdx">The index of the choice to choose.</param>
|
||||||
|
public void ChooseChoiceIndex(int choiceIdx)
|
||||||
|
{
|
||||||
|
runtimeStory.ChooseChoiceIndex(choiceIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ChoosePathString(string path, bool resetCallstack = true, params Variant[] arguments)
|
||||||
|
{
|
||||||
|
runtimeStory.ChoosePathString(path, resetCallstack, FromVariants(arguments));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unwinds the callstack. Useful to reset the Story's evaluation
|
||||||
|
/// without actually changing any meaningful state, for example if
|
||||||
|
/// you want to exit a section of story prematurely and tell it to
|
||||||
|
/// go elsewhere with a call to ChoosePathString(...).
|
||||||
|
/// Doing so without calling ResetCallstack() could cause unexpected
|
||||||
|
/// issues if, for example, the Story was in a tunnel already.
|
||||||
|
/// </summary>
|
||||||
|
public void ResetCallstack()
|
||||||
|
{
|
||||||
|
runtimeStory.ResetCallstack();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reset the Story back to its initial state as it was when it was
|
||||||
|
/// first constructed.
|
||||||
|
/// </summary>
|
||||||
|
public void ResetState()
|
||||||
|
{
|
||||||
|
runtimeStory.ResetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get any global tags associated with the story. These are defined as
|
||||||
|
/// hash tags defined at the very top of the story.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<string> GlobalTags => runtimeStory.globalTags;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets any tags associated with a particular knot or knot.stitch.
|
||||||
|
/// These are defined as hash tags defined at the very top of a knot or stitch.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path of the knot or stitch, in the form "knot" or "knot.stitch".</param>
|
||||||
|
/// <returns>The list of tags.</returns>
|
||||||
|
public IReadOnlyList<string> TagsForContentAtPath(string path)
|
||||||
|
{
|
||||||
|
return runtimeStory.TagsForContentAtPath(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string CurrentFlowName => runtimeStory.currentFlowName;
|
||||||
|
|
||||||
|
public bool CurrentFlowIsDefaultFlow => runtimeStory.currentFlowIsDefaultFlow;
|
||||||
|
|
||||||
|
public IReadOnlyList<string> AliveFlowNames => runtimeStory.aliveFlowNames;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="flowName"></param>
|
||||||
|
public void RemoveFlow(string flowName)
|
||||||
|
{
|
||||||
|
runtimeStory.RemoveFlow(flowName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="flowName"></param>
|
||||||
|
public void SwitchFlow(string flowName)
|
||||||
|
{
|
||||||
|
runtimeStory.SwitchFlow(flowName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public void SwitchToDefaultFlow()
|
||||||
|
{
|
||||||
|
runtimeStory.SwitchToDefaultFlow();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int VisitCountAtPathString(string pathString)
|
||||||
|
{
|
||||||
|
return runtimeStory.state.VisitCountAtPathString(pathString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Variant FetchVariable(string variableName)
|
||||||
|
{
|
||||||
|
return ToVariant(runtimeStory.variablesState[variableName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T FetchVariable<[MustBeVariant] T>(string variableName)
|
||||||
|
{
|
||||||
|
return FetchVariable(variableName).As<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StoreVariable(string variableName, Variant value)
|
||||||
|
{
|
||||||
|
runtimeStory.variablesState[variableName] = FromVariant(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StoreVariable<[MustBeVariant] T>(string variableName, T value)
|
||||||
|
{
|
||||||
|
StoreVariable(variableName, Variant.From(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="variableName"></param>
|
||||||
|
/// <param name="observer"></param>
|
||||||
|
public void ObserveVariable(string variableName, Callable observer)
|
||||||
|
{
|
||||||
|
if (!internalObservers.ContainsKey(variableName))
|
||||||
|
{
|
||||||
|
Ink.Runtime.Story.VariableObserver internalObserver = BuildObserver();
|
||||||
|
runtimeStory.ObserveVariable(variableName, internalObserver);
|
||||||
|
internalObservers[variableName] = internalObserver;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (observers.ContainsKey(variableName))
|
||||||
|
_ = observers[variableName].Add(observer);
|
||||||
|
else
|
||||||
|
observers[variableName] = new() { observer };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="variableNames"></param>
|
||||||
|
/// <param name="observer"></param>
|
||||||
|
public void ObserveVariable(string[] variableNames, Callable observer)
|
||||||
|
{
|
||||||
|
foreach (string variableName in variableNames)
|
||||||
|
ObserveVariable(variableName, observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Ink.Runtime.Story.VariableObserver BuildObserver()
|
||||||
|
{
|
||||||
|
return delegate (string name, object? value)
|
||||||
|
{
|
||||||
|
if (!observers.TryGetValue(name, out var callables)) return;
|
||||||
|
|
||||||
|
Variant variant = ToVariant(value);
|
||||||
|
foreach (Callable callable in callables)
|
||||||
|
_ = callable.Call(name, variant);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="callable"></param>
|
||||||
|
public void RemoveVariableObserver(Callable callable)
|
||||||
|
{
|
||||||
|
foreach (string variableName in observers.Keys)
|
||||||
|
RemoveVariableObserver(callable, variableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="specificVariableName"></param>
|
||||||
|
public void RemoveVariableObserver(string specificVariableName)
|
||||||
|
{
|
||||||
|
runtimeStory.RemoveVariableObserver(null, specificVariableName);
|
||||||
|
_ = internalObservers.Remove(specificVariableName);
|
||||||
|
_ = observers.Remove(specificVariableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="callable"></param>
|
||||||
|
/// <param name="specificVariableName"></param>
|
||||||
|
public void RemoveVariableObserver(Callable callable, string specificVariableName)
|
||||||
|
{
|
||||||
|
var callables = observers[specificVariableName];
|
||||||
|
if (!callables.Contains(callable)) return;
|
||||||
|
|
||||||
|
_ = callables.Remove(callable);
|
||||||
|
if (callables.Count > 0) return;
|
||||||
|
|
||||||
|
runtimeStory.RemoveVariableObserver(null, specificVariableName);
|
||||||
|
_ = internalObservers.Remove(specificVariableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An ink file can provide a fallback functions for when when an EXTERNAL has been left
|
||||||
|
/// unbound by the client, and the fallback function will be called instead. Useful when
|
||||||
|
/// testing a story in play mode, when it's not possible to write a client-side C# external
|
||||||
|
/// function, but you don't want it to fail to run.
|
||||||
|
/// </summary>
|
||||||
|
public bool AllowExternalFunctionFallbacks => runtimeStory.allowExternalFunctionFallbacks;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a function exists.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="functionName">The name of the function as declared in ink.</param>
|
||||||
|
/// <returns>True if the function exists, else false.</returns>
|
||||||
|
public bool HasFunction(string functionName)
|
||||||
|
{
|
||||||
|
return runtimeStory.HasFunction(functionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evaluates a function defined in ink.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="functionName">The name of the function as declared in ink.</param>
|
||||||
|
/// <param name="arguments">
|
||||||
|
/// The arguments that the ink function takes, if any. Note that we don't (can't) do any
|
||||||
|
/// validation on the number of arguments right now, so make sure you get it right!
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The return value as returned from the ink function with `~ return myValue`, or a nil
|
||||||
|
/// variant if nothing is returned.
|
||||||
|
/// </returns>
|
||||||
|
public Variant EvaluateFunction(string functionName, params Variant[] arguments)
|
||||||
|
{
|
||||||
|
object? result = runtimeStory.EvaluateFunction(functionName, FromVariants(arguments));
|
||||||
|
return ToVariant(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evaluates a function defined in ink.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="functionName">The name of the function as declared in ink.</param>
|
||||||
|
/// <param name="arguments">
|
||||||
|
/// The arguments that the ink function takes, if any. Note that we don't (can't) do any
|
||||||
|
/// validation on the number of arguments right now, so make sure you get it right!
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The return value as returned from the ink function with `~ return myValue`, or a nil
|
||||||
|
/// variant if nothing is returned.
|
||||||
|
/// </returns>
|
||||||
|
public Variant EvaluateFunction(string functionName, Godot.Collections.Array<Variant> arguments)
|
||||||
|
{
|
||||||
|
object? result = runtimeStory.EvaluateFunction(functionName, FromVariants(arguments));
|
||||||
|
return ToVariant(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evaluates a function defined in ink, and gathers the possibly multi-line text as generated
|
||||||
|
/// by the function. This text output is any text written as normal content within the function,
|
||||||
|
/// as opposed to the return value, as returned with `~ return`.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="functionName">The name of the function as declared in ink.</param>
|
||||||
|
/// <param name="textOutput">The text content produced by the function via normal ink, if any.</param>
|
||||||
|
/// <param name="arguments">
|
||||||
|
/// The arguments that the ink function takes, if any. Note that we don't (can't) do any
|
||||||
|
/// validation on the number of arguments right now, so make sure you get it right!
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The return value as returned from the ink function with `~ return myValue`, or a nil
|
||||||
|
/// variant if nothing is returned.
|
||||||
|
/// </returns>
|
||||||
|
public Variant EvaluateFunction(string functionName, out string textOutput, params Variant[] arguments)
|
||||||
|
{
|
||||||
|
object? result = runtimeStory.EvaluateFunction(functionName, out textOutput, FromVariants(arguments));
|
||||||
|
return ToVariant(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bind a C# function to an ink EXTERNAL function declaration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="funcName">EXTERNAL ink function name to bind to.</param>
|
||||||
|
/// <param name="callable">The Godot Callable to bind.</param>
|
||||||
|
/// <param name="lookaheadSafe">The ink engine often evaluates further
|
||||||
|
/// than you might expect beyond the current line just in case it sees
|
||||||
|
/// glue that will cause the two lines to become one. In this case it's
|
||||||
|
/// possible that a function can appear to be called twice instead of
|
||||||
|
/// just once, and earlier than you expect. If it's safe for your
|
||||||
|
/// function to be called in this way (since the result and side effect
|
||||||
|
/// of the function will not change), then you can pass 'true'.
|
||||||
|
/// Usually, you want to pass 'false', especially if you want some action
|
||||||
|
/// to be performed in game code when this function is called.</param>
|
||||||
|
public void BindExternalFunction(string funcName, Callable callable, bool lookaheadSafe = false)
|
||||||
|
{
|
||||||
|
runtimeStory.BindExternalFunctionGeneral(funcName, trampoline, lookaheadSafe);
|
||||||
|
|
||||||
|
object? trampoline(object?[] arguments) => FromVariant(callable.Call(ToVariants(arguments)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bind a C# function to an ink EXTERNAL function declaration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="funcName">EXTERNAL ink function name to bind to.</param>
|
||||||
|
/// <param name="func">The C# function to bind.</param>
|
||||||
|
/// <param name="lookaheadSafe">The ink engine often evaluates further
|
||||||
|
/// than you might expect beyond the current line just in case it sees
|
||||||
|
/// glue that will cause the two lines to become one. In this case it's
|
||||||
|
/// possible that a function can appear to be called twice instead of
|
||||||
|
/// just once, and earlier than you expect. If it's safe for your
|
||||||
|
/// function to be called in this way (since the result and side effect
|
||||||
|
/// of the function will not change), then you can pass 'true'.
|
||||||
|
/// Usually, you want to pass 'false', especially if you want some action
|
||||||
|
/// to be performed in game code when this function is called.</param>
|
||||||
|
public void BindExternalFunction(string funcName, Func<Variant> func, bool lookaheadSafe = false)
|
||||||
|
{
|
||||||
|
runtimeStory.BindExternalFunction(funcName, trampoline, lookaheadSafe);
|
||||||
|
|
||||||
|
object? trampoline() => FromVariant(func.Invoke());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bind a C# function to an ink EXTERNAL function declaration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="funcName">EXTERNAL ink function name to bind to.</param>
|
||||||
|
/// <param name="func">The C# function to bind.</param>
|
||||||
|
/// <param name="lookaheadSafe">The ink engine often evaluates further
|
||||||
|
/// than you might expect beyond the current line just in case it sees
|
||||||
|
/// glue that will cause the two lines to become one. In this case it's
|
||||||
|
/// possible that a function can appear to be called twice instead of
|
||||||
|
/// just once, and earlier than you expect. If it's safe for your
|
||||||
|
/// function to be called in this way (since the result and side effect
|
||||||
|
/// of the function will not change), then you can pass 'true'.
|
||||||
|
/// Usually, you want to pass 'false', especially if you want some action
|
||||||
|
/// to be performed in game code when this function is called.</param>
|
||||||
|
public void BindExternalFunction<T>(string funcName, Func<T, Variant> func, bool lookaheadSafe = false)
|
||||||
|
{
|
||||||
|
runtimeStory.BindExternalFunction(funcName, (Func<T, object?>)trampoline, lookaheadSafe);
|
||||||
|
|
||||||
|
object? trampoline(T a) => FromVariant(func.Invoke(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bind a C# function to an ink EXTERNAL function declaration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="funcName">EXTERNAL ink function name to bind to.</param>
|
||||||
|
/// <param name="func">The C# function to bind.</param>
|
||||||
|
/// <param name="lookaheadSafe">The ink engine often evaluates further
|
||||||
|
/// than you might expect beyond the current line just in case it sees
|
||||||
|
/// glue that will cause the two lines to become one. In this case it's
|
||||||
|
/// possible that a function can appear to be called twice instead of
|
||||||
|
/// just once, and earlier than you expect. If it's safe for your
|
||||||
|
/// function to be called in this way (since the result and side effect
|
||||||
|
/// of the function will not change), then you can pass 'true'.
|
||||||
|
/// Usually, you want to pass 'false', especially if you want some action
|
||||||
|
/// to be performed in game code when this function is called.</param>
|
||||||
|
public void BindExternalFunction<T1, T2>(string funcName, Func<T1, T2, Variant> func, bool lookaheadSafe = false)
|
||||||
|
{
|
||||||
|
runtimeStory.BindExternalFunction(funcName, (Func<T1, T2, object?>)trampoline, lookaheadSafe);
|
||||||
|
|
||||||
|
object? trampoline(T1 a, T2 b) => FromVariant(func.Invoke(a, b));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bind a C# function to an ink EXTERNAL function declaration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="funcName">EXTERNAL ink function name to bind to.</param>
|
||||||
|
/// <param name="func">The C# function to bind.</param>
|
||||||
|
/// <param name="lookaheadSafe">The ink engine often evaluates further
|
||||||
|
/// than you might expect beyond the current line just in case it sees
|
||||||
|
/// glue that will cause the two lines to become one. In this case it's
|
||||||
|
/// possible that a function can appear to be called twice instead of
|
||||||
|
/// just once, and earlier than you expect. If it's safe for your
|
||||||
|
/// function to be called in this way (since the result and side effect
|
||||||
|
/// of the function will not change), then you can pass 'true'.
|
||||||
|
/// Usually, you want to pass 'false', especially if you want some action
|
||||||
|
/// to be performed in game code when this function is called.</param>
|
||||||
|
public void BindExternalFunction<T1, T2, T3>(string funcName, Func<T1, T2, T3, Variant> func, bool lookaheadSafe = false)
|
||||||
|
{
|
||||||
|
runtimeStory.BindExternalFunction(funcName, (Func<T1, T2, T3, object?>)trampoline, lookaheadSafe);
|
||||||
|
|
||||||
|
object? trampoline(T1 a, T2 b, T3 c) => FromVariant(func.Invoke(a, b, c));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bind a C# function to an ink EXTERNAL function declaration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="funcName">EXTERNAL ink function name to bind to.</param>
|
||||||
|
/// <param name="func">The C# function to bind.</param>
|
||||||
|
/// <param name="lookaheadSafe">The ink engine often evaluates further
|
||||||
|
/// than you might expect beyond the current line just in case it sees
|
||||||
|
/// glue that will cause the two lines to become one. In this case it's
|
||||||
|
/// possible that a function can appear to be called twice instead of
|
||||||
|
/// just once, and earlier than you expect. If it's safe for your
|
||||||
|
/// function to be called in this way (since the result and side effect
|
||||||
|
/// of the function will not change), then you can pass 'true'.
|
||||||
|
/// Usually, you want to pass 'false', especially if you want some action
|
||||||
|
/// to be performed in game code when this function is called.</param>
|
||||||
|
public void BindExternalFunction<T1, T2, T3, T4>(string funcName, Func<T1, T2, T3, T4, Variant> func, bool lookaheadSafe = false)
|
||||||
|
{
|
||||||
|
runtimeStory.BindExternalFunction(funcName, (Func<T1, T2, T3, T4, object?>)trampoline, lookaheadSafe);
|
||||||
|
|
||||||
|
object? trampoline(T1 a, T2 b, T3 c, T4 d) => FromVariant(func.Invoke(a, b, c, d));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bind a C# Action to an ink EXTERNAL function declaration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="funcName">EXTERNAL ink function name to bind to.</param>
|
||||||
|
/// <param name="action">The C# action to bind.</param>
|
||||||
|
/// <param name="lookaheadSafe">The ink engine often evaluates further
|
||||||
|
/// than you might expect beyond the current line just in case it sees
|
||||||
|
/// glue that will cause the two lines to become one. In this case it's
|
||||||
|
/// possible that a function can appear to be called twice instead of
|
||||||
|
/// just once, and earlier than you expect. If it's safe for your
|
||||||
|
/// function to be called in this way (since the result and side effect
|
||||||
|
/// of the function will not change), then you can pass 'true'.
|
||||||
|
/// Usually, you want to pass 'false', especially if you want some action
|
||||||
|
/// to be performed in game code when this function is called.</param>
|
||||||
|
public void BindExternalFunction(string funcName, Action action, bool lookaheadSafe = false)
|
||||||
|
{
|
||||||
|
runtimeStory.BindExternalFunction(funcName, action, lookaheadSafe);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bind a C# Action to an ink EXTERNAL function declaration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="funcName">EXTERNAL ink function name to bind to.</param>
|
||||||
|
/// <param name="action">The C# action to bind.</param>
|
||||||
|
/// <param name="lookaheadSafe">The ink engine often evaluates further
|
||||||
|
/// than you might expect beyond the current line just in case it sees
|
||||||
|
/// glue that will cause the two lines to become one. In this case it's
|
||||||
|
/// possible that a function can appear to be called twice instead of
|
||||||
|
/// just once, and earlier than you expect. If it's safe for your
|
||||||
|
/// function to be called in this way (since the result and side effect
|
||||||
|
/// of the function will not change), then you can pass 'true'.
|
||||||
|
/// Usually, you want to pass 'false', especially if you want some action
|
||||||
|
/// to be performed in game code when this function is called.</param>
|
||||||
|
public void BindExternalFunction<T>(string funcName, Action<T> action, bool lookaheadSafe = false)
|
||||||
|
{
|
||||||
|
runtimeStory.BindExternalFunction(funcName, action, lookaheadSafe);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bind a C# Action to an ink EXTERNAL function declaration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="funcName">EXTERNAL ink function name to bind to.</param>
|
||||||
|
/// <param name="action">The C# action to bind.</param>
|
||||||
|
/// <param name="lookaheadSafe">The ink engine often evaluates further
|
||||||
|
/// than you might expect beyond the current line just in case it sees
|
||||||
|
/// glue that will cause the two lines to become one. In this case it's
|
||||||
|
/// possible that a function can appear to be called twice instead of
|
||||||
|
/// just once, and earlier than you expect. If it's safe for your
|
||||||
|
/// function to be called in this way (since the result and side effect
|
||||||
|
/// of the function will not change), then you can pass 'true'.
|
||||||
|
/// Usually, you want to pass 'false', especially if you want some action
|
||||||
|
/// to be performed in game code when this function is called.</param>
|
||||||
|
public void BindExternalFunction<T1, T2>(string funcName, Action<T1, T2> action, bool lookaheadSafe = false)
|
||||||
|
{
|
||||||
|
runtimeStory.BindExternalFunction(funcName, action, lookaheadSafe);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bind a C# Action to an ink EXTERNAL function declaration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="funcName">EXTERNAL ink function name to bind to.</param>
|
||||||
|
/// <param name="action">The C# action to bind.</param>
|
||||||
|
/// <param name="lookaheadSafe">The ink engine often evaluates further
|
||||||
|
/// than you might expect beyond the current line just in case it sees
|
||||||
|
/// glue that will cause the two lines to become one. In this case it's
|
||||||
|
/// possible that a function can appear to be called twice instead of
|
||||||
|
/// just once, and earlier than you expect. If it's safe for your
|
||||||
|
/// function to be called in this way (since the result and side effect
|
||||||
|
/// of the function will not change), then you can pass 'true'.
|
||||||
|
/// Usually, you want to pass 'false', especially if you want some action
|
||||||
|
/// to be performed in game code when this function is called.</param>
|
||||||
|
public void BindExternalFunction<T1, T2, T3>(string funcName, Action<T1, T2, T3> action, bool lookaheadSafe = false)
|
||||||
|
{
|
||||||
|
runtimeStory.BindExternalFunction(funcName, action, lookaheadSafe);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bind a C# Action to an ink EXTERNAL function declaration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="funcName">EXTERNAL ink function name to bind to.</param>
|
||||||
|
/// <param name="action">The C# action to bind.</param>
|
||||||
|
/// <param name="lookaheadSafe">The ink engine often evaluates further
|
||||||
|
/// than you might expect beyond the current line just in case it sees
|
||||||
|
/// glue that will cause the two lines to become one. In this case it's
|
||||||
|
/// possible that a function can appear to be called twice instead of
|
||||||
|
/// just once, and earlier than you expect. If it's safe for your
|
||||||
|
/// function to be called in this way (since the result and side effect
|
||||||
|
/// of the function will not change), then you can pass 'true'.
|
||||||
|
/// Usually, you want to pass 'false', especially if you want some action
|
||||||
|
/// to be performed in game code when this function is called.</param>
|
||||||
|
public void BindExternalFunction<T1, T2, T3, T4>(string funcName, Action<T1, T2, T3, T4> action, bool lookaheadSafe = false)
|
||||||
|
{
|
||||||
|
runtimeStory.BindExternalFunction(funcName, action, lookaheadSafe);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove a binding for a named EXTERNAL ink function.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="funcName">The name of the EXTERNAL ink function to unbind.</param>
|
||||||
|
public void UnbindExternalFunction(string funcName)
|
||||||
|
{
|
||||||
|
runtimeStory.UnbindExternalFunction(funcName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message"></param>
|
||||||
|
public void Error(string message)
|
||||||
|
{
|
||||||
|
runtimeStory.Error(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message"></param>
|
||||||
|
/// <param name="useEndLineNumber"></param>
|
||||||
|
public void Error(string message, bool useEndLineNumber)
|
||||||
|
{
|
||||||
|
runtimeStory.Error(message, useEndLineNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message"></param>
|
||||||
|
public void Warning(string message)
|
||||||
|
{
|
||||||
|
runtimeStory.Warning(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Save the current story state a JSON string.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The current state serialized into a JSON string.</returns>
|
||||||
|
public string SaveState()
|
||||||
|
{
|
||||||
|
return runtimeStory.state.ToJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Save the current story state to a JSON file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath">The path to the file we will be writing to.</param>
|
||||||
|
public void SaveStateFile(string filePath)
|
||||||
|
{
|
||||||
|
using FileAccess file = FileAccess.Open(filePath, FileAccess.ModeFlags.Write);
|
||||||
|
file.StoreString(SaveState());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load a JSON string as the current story state.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jsonState">The JSON string to load.</param>
|
||||||
|
public void LoadState(string jsonState)
|
||||||
|
{
|
||||||
|
runtimeStory.state.LoadJson(jsonState);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load the content of a JSON file as the current story state.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath">The path to the file we will be reading from.</param>
|
||||||
|
public void LoadStateFile(string filePath)
|
||||||
|
{
|
||||||
|
using FileAccess file = FileAccess.Open(filePath, FileAccess.ModeFlags.Read);
|
||||||
|
LoadState(file.GetAsText());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnContinued()
|
||||||
|
{
|
||||||
|
_ = EmitSignal(SignalName.Continued);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMadeChoice(Ink.Runtime.Choice choice)
|
||||||
|
{
|
||||||
|
_ = EmitSignal(SignalName.MadeChoice, new InkChoice(choice));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override PropertyList _GetPropertyList()
|
||||||
|
{
|
||||||
|
PropertyList properties = base._GetPropertyList() ?? new PropertyList();
|
||||||
|
|
||||||
|
properties.Add(new Godot.Collections.Dictionary()
|
||||||
|
{
|
||||||
|
{ "name", PropertyName.RawStory },
|
||||||
|
{ "type", Variant.From(Variant.Type.Object) },
|
||||||
|
{ "usage", Variant.From(PropertyUsageFlags.NoEditor) },
|
||||||
|
});
|
||||||
|
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
uid://mj1x4xcobhpa
|
||||||
@ -0,0 +1,172 @@
|
|||||||
|
#if TOOLS
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using Godot;
|
||||||
|
using Ink;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
using DependenciesCache = System.Collections.Generic.Dictionary<string, string[]>;
|
||||||
|
using GArrayGictionary = Godot.Collections.Array<Godot.Collections.Dictionary>;
|
||||||
|
using GArrayString = Godot.Collections.Array<string>;
|
||||||
|
using Gictionary = Godot.Collections.Dictionary;
|
||||||
|
|
||||||
|
namespace GodotInk;
|
||||||
|
|
||||||
|
[Tool]
|
||||||
|
public partial class InkStoryImporter : EditorImportPlugin
|
||||||
|
{
|
||||||
|
private const string OPT_MAIN_FILE = "is_main_file";
|
||||||
|
private const string OPT_COMPRESS = "compress";
|
||||||
|
|
||||||
|
private static readonly Regex includeRegex = new(@"^\s*INCLUDE\s*(?<Path>.*)\s*$", RegexOptions.Multiline | RegexOptions.Compiled);
|
||||||
|
|
||||||
|
#pragma warning disable IDE0022
|
||||||
|
public override string _GetImporterName() => "ink";
|
||||||
|
|
||||||
|
public override string _GetVisibleName() => "Ink story";
|
||||||
|
|
||||||
|
public override string[] _GetRecognizedExtensions() => new string[] { "ink" };
|
||||||
|
|
||||||
|
public override string _GetResourceType() => nameof(Resource);
|
||||||
|
|
||||||
|
public override string _GetSaveExtension() => "res";
|
||||||
|
|
||||||
|
public override float _GetPriority() => 1.0f;
|
||||||
|
|
||||||
|
public override int _GetPresetCount() => 0;
|
||||||
|
|
||||||
|
public override int _GetImportOrder() => 0;
|
||||||
|
|
||||||
|
public override GArrayGictionary _GetImportOptions(string path, int presetIndex) => new()
|
||||||
|
{
|
||||||
|
new() { { "name", OPT_MAIN_FILE }, { "default_value", false } },
|
||||||
|
new() { { "name", OPT_COMPRESS }, { "default_value", true } }
|
||||||
|
};
|
||||||
|
|
||||||
|
public override bool _GetOptionVisibility(string path, StringName optionName, Gictionary options) => true;
|
||||||
|
#pragma warning restore IDE0022
|
||||||
|
|
||||||
|
public override Error _Import(string sourceFile, string savePath, Gictionary options, GArrayString _, GArrayString __)
|
||||||
|
{
|
||||||
|
UpdateCache(sourceFile, ExtractIncludes(sourceFile));
|
||||||
|
|
||||||
|
string destFile = $"{savePath}.{_GetSaveExtension()}";
|
||||||
|
Error returnValue = options[OPT_MAIN_FILE].AsBool()
|
||||||
|
? ImportFromInk(sourceFile, destFile, options[OPT_COMPRESS].AsBool())
|
||||||
|
: ResourceSaver.Save(new StubInkStory(), destFile);
|
||||||
|
|
||||||
|
string[] additionalFiles = GetCache().Where(kvp => kvp.Value.Contains(sourceFile)).Select(kvp => kvp.Key).ToArray();
|
||||||
|
foreach (var additionalFile in additionalFiles)
|
||||||
|
AppendImportExternalResource(additionalFile);
|
||||||
|
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Error ImportFromInk(string sourceFile, string destFile, bool shouldCompress)
|
||||||
|
{
|
||||||
|
using Godot.FileAccess file = Godot.FileAccess.Open(sourceFile, Godot.FileAccess.ModeFlags.Read);
|
||||||
|
|
||||||
|
if (file == null)
|
||||||
|
return Godot.FileAccess.GetOpenError();
|
||||||
|
|
||||||
|
Compiler compiler = new(file.GetAsText(), new Compiler.Options
|
||||||
|
{
|
||||||
|
sourceFilename = sourceFile,
|
||||||
|
errorHandler = InkCompilerErrorHandler,
|
||||||
|
fileHandler = new FileHandler(
|
||||||
|
Path.GetDirectoryName(file.GetPathAbsolute()) ?? ProjectSettings.GlobalizePath("res://")
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string storyContent = compiler.Compile().ToJson();
|
||||||
|
InkStory resource = InkStory.Create(storyContent);
|
||||||
|
ResourceSaver.SaverFlags flags = shouldCompress ? ResourceSaver.SaverFlags.Compress
|
||||||
|
: ResourceSaver.SaverFlags.None;
|
||||||
|
return ResourceSaver.Save(resource, destFile, flags);
|
||||||
|
}
|
||||||
|
catch (InvalidInkException)
|
||||||
|
{
|
||||||
|
return Error.CompilationFailed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<string> ExtractIncludes(string sourceFile)
|
||||||
|
{
|
||||||
|
using Godot.FileAccess file = Godot.FileAccess.Open(sourceFile, Godot.FileAccess.ModeFlags.Read);
|
||||||
|
return includeRegex.Matches(file.GetAsText())
|
||||||
|
.OfType<Match>()
|
||||||
|
.Select(match => sourceFile.GetBaseDir().PathJoin(match.Groups["Path"].Value.TrimEnd('\r')))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void InkCompilerErrorHandler(string message, ErrorType errorType)
|
||||||
|
{
|
||||||
|
switch (errorType)
|
||||||
|
{
|
||||||
|
case ErrorType.Warning:
|
||||||
|
GD.PushWarning(message);
|
||||||
|
break;
|
||||||
|
case ErrorType.Error:
|
||||||
|
GD.PushError(message);
|
||||||
|
throw new InvalidInkException();
|
||||||
|
case ErrorType.Author:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const string CACHE_FILE = "user://ink_cache.json";
|
||||||
|
|
||||||
|
public static void UpdateCache(string sourceFile, List<string> dependencies)
|
||||||
|
{
|
||||||
|
DependenciesCache cache = GetCache();
|
||||||
|
cache[sourceFile] = dependencies.ToArray();
|
||||||
|
cache = cache.Where(kvp => kvp.Value.Length > 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||||
|
|
||||||
|
using Godot.FileAccess file = Godot.FileAccess.Open(CACHE_FILE, Godot.FileAccess.ModeFlags.Write);
|
||||||
|
file?.StoreString(JsonSerializer.Serialize(cache));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DependenciesCache GetCache()
|
||||||
|
{
|
||||||
|
using Godot.FileAccess file = Godot.FileAccess.Open(CACHE_FILE, Godot.FileAccess.ModeFlags.Read);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return JsonSerializer.Deserialize<DependenciesCache>(file?.GetAsText() ?? "{}")
|
||||||
|
?? new DependenciesCache();
|
||||||
|
}
|
||||||
|
catch (JsonException)
|
||||||
|
{
|
||||||
|
return new DependenciesCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FileHandler : IFileHandler
|
||||||
|
{
|
||||||
|
private readonly string rootDir;
|
||||||
|
|
||||||
|
public FileHandler(string rootDir)
|
||||||
|
{
|
||||||
|
this.rootDir = rootDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ResolveInkFilename(string includeName)
|
||||||
|
{
|
||||||
|
return Path.Combine(rootDir, includeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string LoadInkFileContents(string fullFilename)
|
||||||
|
{
|
||||||
|
return File.ReadAllText(fullFilename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -0,0 +1 @@
|
|||||||
|
uid://dwgcrdfs75ju0
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace GodotInk;
|
||||||
|
|
||||||
|
public class InvalidInkException : System.Exception
|
||||||
|
{
|
||||||
|
public InvalidInkException() : base()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidInkException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
uid://b7c4yha1omm5r
|
||||||
@ -0,0 +1,105 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using Godot;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace GodotInk;
|
||||||
|
|
||||||
|
public static class MarshalUtils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Convert from an ink variable type to a Godot Variant.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The ink variable to convert.</param>
|
||||||
|
/// <returns>The converted Godot Variant.</returns>
|
||||||
|
/// <exception cref="ArgumentException"></exception>
|
||||||
|
public static Variant ToVariant(object? obj)
|
||||||
|
{
|
||||||
|
return obj switch
|
||||||
|
{
|
||||||
|
bool b => Variant.CreateFrom(b),
|
||||||
|
|
||||||
|
int n => Variant.CreateFrom(n),
|
||||||
|
float n => Variant.CreateFrom(n),
|
||||||
|
|
||||||
|
string str => Variant.CreateFrom(str),
|
||||||
|
|
||||||
|
Ink.Runtime.Choice choice => new InkChoice(choice),
|
||||||
|
Ink.Runtime.InkList list => new InkList(list),
|
||||||
|
|
||||||
|
null => new Variant(),
|
||||||
|
|
||||||
|
_ => throw new ArgumentException($"Argument of type {obj.GetType()} is not valid."),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert from a Godot Variant to an ink variable.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="variant">The Godot Variant to convert.</param>
|
||||||
|
/// <returns>The converted ink variable.</returns>
|
||||||
|
/// <exception cref="ArgumentException"></exception>
|
||||||
|
public static object? FromVariant(Variant variant)
|
||||||
|
{
|
||||||
|
return variant.VariantType switch
|
||||||
|
{
|
||||||
|
Variant.Type.Bool => variant.AsBool(),
|
||||||
|
|
||||||
|
Variant.Type.Int => variant.AsInt32(),
|
||||||
|
Variant.Type.Float => variant.AsSingle(),
|
||||||
|
|
||||||
|
Variant.Type.String => variant.AsString(),
|
||||||
|
|
||||||
|
Variant.Type.Nil => null,
|
||||||
|
|
||||||
|
_ => throw new ArgumentException($"Argument of type {variant.Obj?.GetType()} is not valid."),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert from a collection of ink variables to a collection of Godot Variants. The
|
||||||
|
/// collection doesn't need to be homogeneous in type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="objects">The collection of ink variables to convert.</param>
|
||||||
|
/// <returns>The collection of converted Godot Variants.</returns>
|
||||||
|
/// <exception cref="ArgumentException"></exception>
|
||||||
|
public static Variant[] ToVariants(IReadOnlyList<object?> objects)
|
||||||
|
{
|
||||||
|
if (objects.Count <= 0)
|
||||||
|
return Array.Empty<Variant>();
|
||||||
|
|
||||||
|
Variant[] variants = new Variant[objects.Count];
|
||||||
|
for (int i = 0; i < objects.Count; ++i)
|
||||||
|
variants[i] = ToVariant(objects[i]);
|
||||||
|
return variants;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert from a collection of Godot Variants to a collection of ink variables. The
|
||||||
|
/// collection doesn't need to be homogeneous in type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="variants">The collection of Godot Variants to convert.</param>
|
||||||
|
/// <returns>The collection of converted ink variables.</returns>
|
||||||
|
/// <exception cref="ArgumentException"></exception>
|
||||||
|
public static object?[] FromVariants(IReadOnlyList<Variant> variants)
|
||||||
|
{
|
||||||
|
if (variants.Count <= 0)
|
||||||
|
return Array.Empty<object?>();
|
||||||
|
|
||||||
|
object?[] objects = new object[variants.Count];
|
||||||
|
for (int i = 0; i < variants.Count; ++i)
|
||||||
|
objects[i] = FromVariant(variants[i]);
|
||||||
|
return objects;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specific case for List<Choice>, we want to stay true to the ink implementation and return
|
||||||
|
// a List<T> instead of a T[]. So we're converting to a List<InkChoice>.
|
||||||
|
public static List<InkChoice> ToVariants(IReadOnlyList<Ink.Runtime.Choice> choices)
|
||||||
|
{
|
||||||
|
List<InkChoice> inkChoices = new();
|
||||||
|
foreach (Ink.Runtime.Choice choice in choices)
|
||||||
|
inkChoices.Add((InkChoice)ToVariant(choice));
|
||||||
|
return inkChoices;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
uid://dv72cso206ijq
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace GodotInk;
|
||||||
|
|
||||||
|
public partial class StubInkStory : InkStory
|
||||||
|
{
|
||||||
|
protected override string RawStory
|
||||||
|
{
|
||||||
|
get => base.RawStory;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
#if TOOLS
|
||||||
|
if (Engine.IsEditorHint()) return;
|
||||||
|
#endif
|
||||||
|
throw new InvalidInkException(
|
||||||
|
"To load this story directly, please import it with 'is_main_file' set to true."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
uid://bhdpuurapp665
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
[plugin]
|
||||||
|
|
||||||
|
name="ink"
|
||||||
|
description="An integration for ink, a powerful narrative scripting language."
|
||||||
|
author="Paul Joannon"
|
||||||
|
version="1.1.2"
|
||||||
|
script="plugin.gd"
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
@tool
|
||||||
|
extends EditorPlugin
|
||||||
|
|
||||||
|
|
||||||
|
var _importer: EditorImportPlugin
|
||||||
|
var _dock: Control
|
||||||
|
|
||||||
|
|
||||||
|
func _enter_tree() -> void:
|
||||||
|
var src_path : String = get_script().get_path().get_base_dir().path_join("Src")
|
||||||
|
|
||||||
|
var importer_script : CSharpScript = load(src_path.path_join("InkStoryImporter.cs")) as CSharpScript
|
||||||
|
if not importer_script.can_instantiate():
|
||||||
|
return
|
||||||
|
_importer = importer_script.new() as EditorImportPlugin
|
||||||
|
add_import_plugin(_importer)
|
||||||
|
|
||||||
|
var dock_scene : PackedScene = load(src_path.path_join("InkDock.tscn")) as PackedScene
|
||||||
|
_dock = dock_scene.instantiate() as Control
|
||||||
|
add_control_to_bottom_panel(_dock, "Ink preview")
|
||||||
|
|
||||||
|
var fs : EditorFileSystem = get_editor_interface().get_resource_filesystem()
|
||||||
|
if not fs.resources_reimported.is_connected(_when_resources_reimported):
|
||||||
|
fs.resources_reimported.connect(_when_resources_reimported)
|
||||||
|
|
||||||
|
|
||||||
|
func _exit_tree() -> void:
|
||||||
|
var fs : EditorFileSystem = get_editor_interface().get_resource_filesystem()
|
||||||
|
if fs.resources_reimported.is_connected(_when_resources_reimported):
|
||||||
|
fs.resources_reimported.disconnect(_when_resources_reimported)
|
||||||
|
|
||||||
|
if _dock != null:
|
||||||
|
remove_control_from_bottom_panel(_dock)
|
||||||
|
_dock.free()
|
||||||
|
_dock = null
|
||||||
|
|
||||||
|
if _importer != null:
|
||||||
|
remove_import_plugin(_importer)
|
||||||
|
_importer = null
|
||||||
|
|
||||||
|
|
||||||
|
func _notification(what : int) -> void:
|
||||||
|
if what == NOTIFICATION_POST_ENTER_TREE:
|
||||||
|
if _importer != null:
|
||||||
|
return
|
||||||
|
get_editor_interface().set_plugin_enabled("GodotInk", false)
|
||||||
|
printerr(
|
||||||
|
"GodotInk could not be loaded.\n" +
|
||||||
|
"Make sure your .csproj file references GodotInk.props, and rebuild the solution.\n" +
|
||||||
|
"See the installation guide for more information: https://github.com/paulloz/godot-ink/wiki\n" +
|
||||||
|
"If you think this is not an issue on your end, please open a bug report: https://github.com/paulloz/godot-ink/issues/new/choose"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _when_resources_reimported(resources : PackedStringArray) -> void:
|
||||||
|
if _dock == null:
|
||||||
|
return
|
||||||
|
|
||||||
|
for resource in resources:
|
||||||
|
if resource.get_extension() == "ink":
|
||||||
|
_dock.call("WhenInkResourceReimported")
|
||||||
|
return
|
||||||
@ -0,0 +1 @@
|
|||||||
|
uid://4gcqopmrdxyx
|
||||||
Binary file not shown.
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2016-2023 The Godot Engine community
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,10 @@
|
|||||||
|
[configuration]
|
||||||
|
|
||||||
|
entry_symbol = "git_plugin_init"
|
||||||
|
compatibility_minimum = "4.2.0"
|
||||||
|
|
||||||
|
[libraries]
|
||||||
|
|
||||||
|
linux.editor.x86_64 = "linux/libgit_plugin.linux.editor.x86_64.so"
|
||||||
|
macos.editor = "macos/libgit_plugin.macos.editor.universal.dylib"
|
||||||
|
windows.editor.x86_64 = "windows/libgit_plugin.windows.editor.x86_64.dll"
|
||||||
@ -0,0 +1 @@
|
|||||||
|
uid://nd6xfab2f6e8
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,33 @@
|
|||||||
|
extends CharacterBody3D
|
||||||
|
|
||||||
|
# need to do:
|
||||||
|
# direction -> $CharacterBody3D.position.z
|
||||||
|
|
||||||
|
func _physics_process(delta: float) -> void:
|
||||||
|
var direction = Vector3.ZERO;
|
||||||
|
|
||||||
|
if Input.is_action_pressed("moveForward"):
|
||||||
|
direction.z -= 1
|
||||||
|
#print("forward")
|
||||||
|
velocity.z = direction.z;
|
||||||
|
move_and_slide();
|
||||||
|
|
||||||
|
if Input.is_action_pressed("moveBackward"):
|
||||||
|
direction.z += 1
|
||||||
|
velocity.z = direction.z;
|
||||||
|
move_and_slide();
|
||||||
|
|
||||||
|
if Input.is_action_pressed("moveLeft"):
|
||||||
|
direction.x -= 1;
|
||||||
|
velocity.x = direction.x;
|
||||||
|
move_and_slide();
|
||||||
|
|
||||||
|
if Input.is_action_pressed("moveRight"):
|
||||||
|
direction.x += 1;
|
||||||
|
velocity.x = direction.x;
|
||||||
|
move_and_slide();
|
||||||
|
|
||||||
|
if Input.is_action_pressed("moveJump"):
|
||||||
|
direction.y += 1;
|
||||||
|
velocity.y = velocity.y;
|
||||||
|
move_and_slide();
|
||||||
@ -0,0 +1 @@
|
|||||||
|
uid://bfvl27u51p0lf
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
extends Control
|
||||||
|
|
||||||
|
|
||||||
|
# Called when the node enters the scene tree for the first time.
|
||||||
|
func _ready() -> void:
|
||||||
|
pass # Replace with function body.
|
||||||
|
|
||||||
|
|
||||||
|
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||||
|
func _process(delta: float) -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
func _on_button_pressed() -> void:
|
||||||
|
get_tree().change_scene_to_file("res://000.tscn")
|
||||||
@ -0,0 +1 @@
|
|||||||
|
uid://4fjs8or62ab2
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
[gd_scene format=3 uid="uid://dti3yc6lnv2p4"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://4fjs8or62ab2" path="res://control.gd" id="1_0fbet"]
|
||||||
|
|
||||||
|
[node name="Control" type="Control" unique_id=1628240064]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1_0fbet")
|
||||||
|
|
||||||
|
[node name="ColorRect" type="ColorRect" parent="." unique_id=1373649046]
|
||||||
|
layout_mode = 0
|
||||||
|
offset_right = 1152.0
|
||||||
|
offset_bottom = 648.0
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="." unique_id=1263255604]
|
||||||
|
layout_mode = 0
|
||||||
|
offset_right = 40.0
|
||||||
|
offset_bottom = 23.0
|
||||||
|
theme_override_colors/font_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_colors/font_outline_color = Color(0.9705853, 0.6323348, 0.8903854, 1)
|
||||||
|
theme_override_font_sizes/font_size = 60
|
||||||
|
text = "The Weather in Me is Tibet"
|
||||||
|
|
||||||
|
[node name="Button" type="Button" parent="." unique_id=1930893329]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = -1
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_top = 0.51900005
|
||||||
|
anchor_right = 0.5
|
||||||
|
anchor_bottom = 0.51900005
|
||||||
|
offset_left = -139.0
|
||||||
|
offset_top = -26.312042
|
||||||
|
offset_right = 134.0
|
||||||
|
offset_bottom = 25.687958
|
||||||
|
text = "Long ago, if my memory serves..."
|
||||||
|
|
||||||
|
[connection signal="pressed" from="Button" to="." method="_on_button_pressed"]
|
||||||
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 995 B |
@ -0,0 +1,43 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://c0oqj527nnpbd"
|
||||||
|
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://icon.svg"
|
||||||
|
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
shader_type spatial;
|
||||||
|
|
||||||
|
uniform float width = 0.1;
|
||||||
|
uniform float height = 0.1;
|
||||||
|
uniform float gap_x = 0.02;
|
||||||
|
uniform float gap_y = 0.04;
|
||||||
|
uniform float sink = -0.5;
|
||||||
|
uniform sampler2D brick;
|
||||||
|
uniform sampler2D wall;
|
||||||
|
uniform sampler2D gap;
|
||||||
|
uniform float ratio = 0.5;
|
||||||
|
|
||||||
|
void fragment() {
|
||||||
|
ALBEDO = vec3(0.75);
|
||||||
|
ROUGHNESS = 0.5;
|
||||||
|
|
||||||
|
float s = 1.0/height;
|
||||||
|
float shift = float(int(UV.y*s)) / 2.0*width;
|
||||||
|
float x = mod(shift+UV.x, width)/width;
|
||||||
|
float y = mod(UV.y, height)/height;
|
||||||
|
|
||||||
|
ALBEDO = texture(gap, vec2(UV.x, UV.y)).rgb / 2.5;
|
||||||
|
if(x > gap_x && x < 1.0-gap_x && y > gap_y && y < 1.0-gap_y){
|
||||||
|
NORMAL = vec3(0.0, 0.0, 0.0);
|
||||||
|
ALBEDO = texture(brick, vec2(x, y)).rgb * ratio;
|
||||||
|
ALBEDO += texture(wall, vec2(UV.x, UV.y)).rgb * (1.0-ratio);
|
||||||
|
}else if(x<y && (1.0-y)>x){
|
||||||
|
NORMAL = vec3(x*-sink, 0.0, 0.0);
|
||||||
|
}else if(x>y && (1.0-y)<x){
|
||||||
|
NORMAL = vec3((1.0-x)*sink, 0.0, 0.0);
|
||||||
|
}else if(y<x){
|
||||||
|
NORMAL = vec3(0.0, y*-sink, 0.0);
|
||||||
|
}else if(y>x){
|
||||||
|
NORMAL = vec3(0.0, (1.0-y)*sink, 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
uid://cgfte1tvivngj
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
shader_type spatial;
|
||||||
|
|
||||||
|
uniform float width = 0.1;
|
||||||
|
uniform float height = 0.1;
|
||||||
|
uniform float gap_x = 0.02;
|
||||||
|
uniform float gap_y = 0.04;
|
||||||
|
uniform float sink = -0.5;
|
||||||
|
uniform sampler2D brick;
|
||||||
|
uniform sampler2D wall;
|
||||||
|
uniform sampler2D gap;
|
||||||
|
uniform float ratio = 0.5;
|
||||||
|
|
||||||
|
void fragment() {
|
||||||
|
ALBEDO = vec3(0.75);
|
||||||
|
ROUGHNESS = 0.5;
|
||||||
|
|
||||||
|
float s = 1.0/height;
|
||||||
|
float shift = float(int(UV.y*s)) / 2.0*width;
|
||||||
|
float x = mod(shift+UV.x, width)/width;
|
||||||
|
float y = mod(UV.y, height)/height;
|
||||||
|
|
||||||
|
ALBEDO = texture(gap, vec2(UV.x, UV.y)).rgb / 2.5;
|
||||||
|
if(x > gap_x && x < 1.0-gap_x && y > gap_y && y < 1.0-gap_y){
|
||||||
|
NORMAL = vec3(0.0, 0.0, 0.0);
|
||||||
|
ALBEDO = texture(brick, vec2(x, y)).rgb * ratio;
|
||||||
|
ALBEDO += texture(wall, vec2(UV.x, UV.y)).rgb * (1.0-ratio);
|
||||||
|
}else if(x<y && (1.0-y)>x){
|
||||||
|
NORMAL = vec3(x*-sink, 0.0, 0.0);
|
||||||
|
}else if(x>y && (1.0-y)<x){
|
||||||
|
NORMAL = vec3((1.0-x)*sink, 0.0, 0.0);
|
||||||
|
}else if(y<x){
|
||||||
|
NORMAL = vec3(0.0, y*-sink, 0.0);
|
||||||
|
}else if(y>x){
|
||||||
|
NORMAL = vec3(0.0, (1.0-y)*sink, 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
uid://dbb44crkih6xp
|
||||||
@ -0,0 +1,76 @@
|
|||||||
|
; Engine configuration file.
|
||||||
|
; It's best edited using the editor UI and not directly,
|
||||||
|
; since the parameters that go here are not all obvious.
|
||||||
|
;
|
||||||
|
; Format:
|
||||||
|
; [section] ; section goes between []
|
||||||
|
; param=value ; assign values to parameters
|
||||||
|
|
||||||
|
config_version=5
|
||||||
|
|
||||||
|
[application]
|
||||||
|
|
||||||
|
config/name="Tibet"
|
||||||
|
run/main_scene="uid://hac4l25a1g7v"
|
||||||
|
config/features=PackedStringArray("4.7", "Forward Plus")
|
||||||
|
config/icon="res://icon.svg"
|
||||||
|
|
||||||
|
[display]
|
||||||
|
|
||||||
|
window/stretch/mode="canvas_items"
|
||||||
|
window/stretch/aspect="expand"
|
||||||
|
|
||||||
|
[dotnet]
|
||||||
|
|
||||||
|
project/assembly_name="shadrerTest"
|
||||||
|
|
||||||
|
[editor]
|
||||||
|
|
||||||
|
version_control/plugin_name="GitPlugin"
|
||||||
|
version_control/autoload_on_startup=true
|
||||||
|
|
||||||
|
[input]
|
||||||
|
|
||||||
|
moveForward={
|
||||||
|
"deadzone": 0.2,
|
||||||
|
"events": [null, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
moveBackward={
|
||||||
|
"deadzone": 0.2,
|
||||||
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
moveRight={
|
||||||
|
"deadzone": 0.2,
|
||||||
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
moveLeft={
|
||||||
|
"deadzone": 0.2,
|
||||||
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
moveJump={
|
||||||
|
"deadzone": 0.2,
|
||||||
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
turnLeft={
|
||||||
|
"deadzone": 0.2,
|
||||||
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":81,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
turnRight={
|
||||||
|
"deadzone": 0.2,
|
||||||
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
[physics]
|
||||||
|
|
||||||
|
3d/physics_engine="Jolt Physics"
|
||||||
|
|
||||||
|
[rendering]
|
||||||
|
|
||||||
|
rendering_device/driver.windows="d3d12"
|
||||||
Loading…
Reference in New Issue