mirror of
https://github.com/yeslayla/simple-dialogue.git
synced 2025-01-13 12:33:44 +01:00
Initial commit
This commit is contained in:
commit
cf136a2e25
7
LICENSE
Normal file
7
LICENSE
Normal file
@ -0,0 +1,7 @@
|
||||
Copyright 2022 Joseph Manley
|
||||
|
||||
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.
|
105
ReadMe.md
Normal file
105
ReadMe.md
Normal file
@ -0,0 +1,105 @@
|
||||
![Release Version](https://img.shields.io/github/v/release/josephbmanley/simple-dialogue)
|
||||
|
||||
# Simple Dialogue
|
||||
|
||||
Requirements: Godot 3.x and [godot-yaml](https://github.com/Beliaar/godot-yaml-asset)
|
||||
|
||||
Table of Contents:
|
||||
- [Features](#Features)
|
||||
- [Examples](#Examples)
|
||||
- [Object Reference](#Objects)
|
||||
|
||||
Simple Dialogue is yet another dialogue plugin for Godot that is a lightweight dialogue system that can easily be used at a lower level dialogue.
|
||||
|
||||
Currently, this requires all dialogue to be written in `YAML`.
|
||||
|
||||
## Features
|
||||
|
||||
- Timelines
|
||||
- Choices
|
||||
- Localization
|
||||
|
||||
## Examples
|
||||
|
||||
See the [samples](addons/simple_dialogue/samples) for more examples.
|
||||
|
||||
### Dialogue File
|
||||
|
||||
```yaml
|
||||
apiVersion: 1.0
|
||||
kind: timeline
|
||||
events:
|
||||
- name:
|
||||
en_US: Person
|
||||
message:
|
||||
en_US: Hello, I say witty dialogue!
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```gdscipt
|
||||
var timeline = STimeline.new("res://addons/simple_dialouge/samples/sample.yaml")
|
||||
var event : SEvent
|
||||
|
||||
func _process(_delta):
|
||||
if(Input.is_action_just_pressed("ui_accept")):
|
||||
|
||||
if timeline.is_choice():
|
||||
var autoDecide = 1
|
||||
print("You: ", timeline.get_choices()[autoDecide])
|
||||
timeline.make_choice(autoDecide)
|
||||
return
|
||||
|
||||
event = timeline.read()
|
||||
|
||||
if event:
|
||||
print(event.name,": ", event.message)
|
||||
```
|
||||
|
||||
## Objects
|
||||
|
||||
### SChoice
|
||||
|
||||
Properties:
|
||||
|
||||
|Name|Type|Description|
|
||||
|---|---|---|
|
||||
|events|`Array`(`SEvent`)|Array of all events this choice leads towards.|
|
||||
|choice|`String`|Text associated with choice.|
|
||||
|
||||
### SEvent
|
||||
|
||||
Properties:
|
||||
|
||||
|Name|Type|Description|
|
||||
|---|---|---|
|
||||
|name|`String`|Name of current event speaker.|
|
||||
|message|`String`|Text spoken by speaker.|
|
||||
|portrait|`Texture`|Texture associated with current speaker.|
|
||||
|choices|`Array`(`SChoice`)|Array of all choices associated with event.|
|
||||
|
||||
Methods:
|
||||
|
||||
|Name|Return Type|Description|
|
||||
|---|---|---|
|
||||
|get_locale(property)|`String`|Returns the value of a localized property.|
|
||||
|
||||
### STimeline
|
||||
|
||||
Properties:
|
||||
|
||||
|Name|Type|Description|
|
||||
|---|---|---|
|
||||
|file_path|`String`|File path associated with timeline.|
|
||||
|events|`Array`(`SEvent`)|Array of all events associated with timeline.|
|
||||
|
||||
Methods:
|
||||
|
||||
|Name|Return Type|Description|
|
||||
|---|---|---|
|
||||
|get_cursor()|`int`|Returns the current cursor position.|
|
||||
|seek(new_pos)|`void`|Moves current cursor position on the timeline.|
|
||||
|get_event(offset=`0`)|`SEvent`|Returns the event at the cursor position.|
|
||||
|read()|`SEvent`|Returns the current event and moves the cursor foward.|
|
||||
|get_choices()|`Array`(`String`)|Returns an array of current choices text value at cursor.|
|
||||
|make_choice(choice_int)|void|Moves cursor to new event stream depending on choice value.|
|
7
addons/simple_dialogue/plugin.cfg
Normal file
7
addons/simple_dialogue/plugin.cfg
Normal file
@ -0,0 +1,7 @@
|
||||
[plugin]
|
||||
|
||||
name="Simple Dialogue"
|
||||
description=""
|
||||
author="joseph@cloudsumu.com"
|
||||
version="1.0"
|
||||
script="plugin.gd"
|
10
addons/simple_dialogue/plugin.gd
Normal file
10
addons/simple_dialogue/plugin.gd
Normal file
@ -0,0 +1,10 @@
|
||||
tool
|
||||
extends EditorPlugin
|
||||
|
||||
|
||||
func _enter_tree():
|
||||
pass
|
||||
|
||||
|
||||
func _exit_tree():
|
||||
pass
|
38
addons/simple_dialogue/samples/minimal/minimal.gd
Normal file
38
addons/simple_dialogue/samples/minimal/minimal.gd
Normal file
@ -0,0 +1,38 @@
|
||||
extends Node
|
||||
|
||||
var timeline = STimeline.new("res://addons/simple_dialogue/samples/sample.yaml")
|
||||
var event : SEvent
|
||||
export var autoDecide : int = 1
|
||||
|
||||
func _process(_delta):
|
||||
if(Input.is_action_just_pressed("ui_accept")):
|
||||
|
||||
# Check for a choice before reading the timeline
|
||||
# using `STimeline.is_choice()`
|
||||
if timeline.is_choice():
|
||||
|
||||
# You can utilize `STimeline.get_choices()` to return
|
||||
# an array with all choices as strings in order
|
||||
|
||||
# This sample uses the `autoDecide` variable for every
|
||||
# choice
|
||||
print("You: ", timeline.get_choices()[autoDecide])
|
||||
|
||||
# Use `STimeline.make_choice(int)` to make a choice
|
||||
# and load that choice's events
|
||||
timeline.make_choice(autoDecide)
|
||||
return
|
||||
|
||||
# Use `STimeline.read()` to get the next SEvent
|
||||
# in a dialogue timeline
|
||||
event = timeline.read()
|
||||
|
||||
# The primary properties of a SEvent is
|
||||
# `name`, `message`, and `portrait`
|
||||
if event:
|
||||
print(event.name,": ", event.message)
|
||||
|
||||
# When STimeline.read() returns null, the timeline has
|
||||
# completed and you can exit the dialogue
|
||||
if event == null:
|
||||
get_tree().quit()
|
6
addons/simple_dialogue/samples/minimal/minimal.tscn
Normal file
6
addons/simple_dialogue/samples/minimal/minimal.tscn
Normal file
@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://addons/simple_dialouge/samples/minimal/minimal.gd" type="Script" id=1]
|
||||
|
||||
[node name="Simple Dialogue Minimal Example" type="Node"]
|
||||
script = ExtResource( 1 )
|
53
addons/simple_dialogue/samples/sample.yaml
Normal file
53
addons/simple_dialogue/samples/sample.yaml
Normal file
@ -0,0 +1,53 @@
|
||||
apiVersion: 1.0 # `apiVersion` is used just in case of future updates to the schema
|
||||
kind: timeline # `kind` should be set to timeline for all timeline files
|
||||
events: # `events` are a list of event objects
|
||||
|
||||
# Sample Event Object
|
||||
- # Two string properties are provided for each locale
|
||||
# `name` should be the name of the speaker
|
||||
# `message` should be the words said by the speaker
|
||||
name:
|
||||
en_US: Person
|
||||
message:
|
||||
en_US: Hello, I say witty dialogue!
|
||||
|
||||
# Sample choice
|
||||
- name:
|
||||
en_US: Question Asker
|
||||
message:
|
||||
en_US: Pick a number between 1-3
|
||||
|
||||
# A `choices` block of `choice` objects can be added
|
||||
# to work as a choice
|
||||
choices:
|
||||
- # A choice object contains two properties
|
||||
# `choice` is a string that works as the display text for the choice
|
||||
# `events` is a list of all events triggered by this choice
|
||||
choice:
|
||||
en_US: 1
|
||||
events:
|
||||
- name:
|
||||
en_US: Question Asker
|
||||
message:
|
||||
en_US: You picked '1'
|
||||
- choice:
|
||||
en_US: 2
|
||||
events:
|
||||
- name:
|
||||
en_US: Question Asker
|
||||
message:
|
||||
en_US: You picked '2'
|
||||
- choice:
|
||||
en_US: 3
|
||||
events:
|
||||
- name:
|
||||
en_US: Question Asker
|
||||
message:
|
||||
en_US: You picked '3'
|
||||
|
||||
# After choice events finish, the timeline returns
|
||||
# the timeline
|
||||
- name:
|
||||
en_US: Person
|
||||
message:
|
||||
en_US: Wow! That is a very cool choice!
|
31
addons/simple_dialogue/scripts/schoice.gd
Normal file
31
addons/simple_dialogue/scripts/schoice.gd
Normal file
@ -0,0 +1,31 @@
|
||||
class_name SChoice
|
||||
|
||||
export(Dictionary) var raw_data = {}
|
||||
export(Array) var event_data = []
|
||||
|
||||
var SEVENT = load("res://addons/simple_dialogue/scripts/sevent.gd")
|
||||
|
||||
var events : Array setget ,_get_events
|
||||
var choice : String setget ,_get_choice
|
||||
|
||||
func _init(data : Dictionary):
|
||||
raw_data = data
|
||||
|
||||
func _get_events() -> Array:
|
||||
var event_array = []
|
||||
if "events" in raw_data:
|
||||
for event in raw_data["events"]:
|
||||
var dialogue = SEVENT.new(event)
|
||||
event_array.append(dialogue)
|
||||
return event_array
|
||||
|
||||
func _get_choice() -> String:
|
||||
var locale = TranslationServer.get_locale()
|
||||
if locale in raw_data["choice"]:
|
||||
return raw_data["choice"][locale]
|
||||
elif SEVENT.DEFAULT_LOCALE in raw_data["choice"]:
|
||||
return raw_data["choice"][SEVENT.DEFAULT_LOCALE]
|
||||
|
||||
push_error("Choice property does not exist with translation %s and %s"
|
||||
% [locale, SEVENT.DEFAULT_LOCALE])
|
||||
return "INVALID"
|
41
addons/simple_dialogue/scripts/sevent.gd
Normal file
41
addons/simple_dialogue/scripts/sevent.gd
Normal file
@ -0,0 +1,41 @@
|
||||
class_name SEvent
|
||||
|
||||
const DEFAULT_LOCALE = "en_US"
|
||||
|
||||
var raw_data = {}
|
||||
var name : String setget ,_get_name
|
||||
var message : String setget ,_get_message
|
||||
var portrait : Texture setget ,_get_portrait
|
||||
var choices : Array setget ,_get_choices
|
||||
|
||||
func _init(data : Dictionary):
|
||||
raw_data = data
|
||||
|
||||
func get_locale(property, locale = TranslationServer.get_locale()) -> String:
|
||||
if locale in raw_data[property]:
|
||||
return raw_data[property][locale]
|
||||
elif DEFAULT_LOCALE in raw_data[property]:
|
||||
return raw_data[property][DEFAULT_LOCALE]
|
||||
|
||||
push_error("%s property does not exist with translations %s and %s"
|
||||
% [property, locale, DEFAULT_LOCALE])
|
||||
return "INVALID"
|
||||
|
||||
func _get_choices() -> Array:
|
||||
var data = []
|
||||
if "choices" in raw_data:
|
||||
for choice in raw_data["choices"]:
|
||||
var choice_obj = SChoice.new(choice)
|
||||
data.append(choice_obj)
|
||||
return data
|
||||
|
||||
func _get_name() -> String:
|
||||
return get_locale("name")
|
||||
|
||||
func _get_message() -> String:
|
||||
return get_locale("message")
|
||||
|
||||
func _get_portrait() -> Texture:
|
||||
if raw_data.get("portrait","") != "":
|
||||
return load(raw_data["portrait"]) as Texture
|
||||
return null
|
83
addons/simple_dialogue/scripts/stimeline.gd
Normal file
83
addons/simple_dialogue/scripts/stimeline.gd
Normal file
@ -0,0 +1,83 @@
|
||||
class_name STimeline
|
||||
|
||||
export(String) var file_path = ""
|
||||
export(Array) var events = []
|
||||
|
||||
var _event_stream = []
|
||||
var _pos_lifo = []
|
||||
var _event_lifo = []
|
||||
var _pos : int = 0
|
||||
var _yaml = preload("res://addons/godot-yaml/gdyaml.gdns").new()
|
||||
|
||||
func _init(path : String):
|
||||
var file : File = File.new()
|
||||
if !file.file_exists(path):
|
||||
push_error("Could not load timeline at path: %s" % path)
|
||||
return
|
||||
file_path = path
|
||||
file.open(path, File.READ)
|
||||
|
||||
var raw_data = _yaml.parse(file.get_as_text())
|
||||
file.close()
|
||||
|
||||
if not "apiVersion" in raw_data or raw_data["apiVersion"] != 1.0:
|
||||
push_error("'%s' is using an outdated timeline!" % file_path)
|
||||
return
|
||||
if not "kind" in raw_data or raw_data["kind"] != "timeline":
|
||||
push_error("'%s' is not a timeline!" % file_path)
|
||||
return
|
||||
if "events" in raw_data:
|
||||
for event in raw_data["events"]:
|
||||
var dialogue = SEvent.new(event)
|
||||
events.push_back(dialogue)
|
||||
else:
|
||||
push_warning("'%s' timeline does not have any events!" % file_path)
|
||||
|
||||
_event_stream = events
|
||||
_pos = 0
|
||||
|
||||
func get_cursor() -> int:
|
||||
return _pos
|
||||
|
||||
func seek(new_pos : int) -> void:
|
||||
_pos = new_pos
|
||||
|
||||
func get_event(offset : int = 0) -> SEvent:
|
||||
if _pos + offset < len(_event_stream):
|
||||
return _event_stream[_pos + offset]
|
||||
return null
|
||||
|
||||
func read() -> SEvent:
|
||||
var event = get_event()
|
||||
if event == null and len(_event_lifo) > 0:
|
||||
_event_stream = _event_lifo.pop_back()
|
||||
_pos = _pos_lifo.pop_back()
|
||||
event = get_event()
|
||||
_pos += 1
|
||||
return event
|
||||
|
||||
func get_choices() -> Array:
|
||||
var choices = []
|
||||
var event = get_event(-1)
|
||||
if event:
|
||||
for choice in event.choices : choices.append(choice.choice)
|
||||
return choices
|
||||
|
||||
func is_choice() -> bool:
|
||||
return len(get_choices()) > 0
|
||||
|
||||
func make_choice(choice : int) -> void:
|
||||
var event = get_event(-1)
|
||||
if not event:
|
||||
push_error("Attempted to find choice at invalid event! Pos '%s' Event Stream: %s"
|
||||
% [_pos, _event_stream])
|
||||
return
|
||||
if choice > len(event.choices):
|
||||
push_error("Attempted to make an out of bounds choice! Timeline '%s': choice '%s' while expected size is '%s'"
|
||||
% [self.file_path, choice, len(event.choices)])
|
||||
return
|
||||
var choice_obj : SChoice = event.choices[choice]
|
||||
_event_lifo.push_back(_event_stream)
|
||||
_event_stream = choice_obj.events
|
||||
_pos_lifo.push_back(_pos)
|
||||
_pos = 0
|
Loading…
Reference in New Issue
Block a user