├── Dialogue ├── . ... ├── Dialogue.gd ├── DialogueNode.tscn ├── DialogueNodeTest.cfg └── Intro.json ├── LICENSE ├── README.md ├── TestScene.tscn ├── default_env.tres ├── icon.png └── project.godot /Dialogue/. ...: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Dialogue/Dialogue.gd: -------------------------------------------------------------------------------- 1 | tool 2 | 3 | extends Node2D 4 | 5 | # Stand-alone node for the display of dialogues based on json files made with Levrault's dialogue editor 6 | # Editor can be found here: https://github.com/Levrault/LE-dialogue-editor 7 | # Use it to make and save your json file in the "Dialogue" folder 8 | # Load .json by triggering load(path) from your game script, where path is the name without extension in a folder called Dialogue 9 | # Then start dialogue with start() from your game script(s) 10 | # To use a timed dialogue box (which closes automatically), use the signal "timer" with the seconds as value 11 | # Conditions based on variables work using variables defined in this script or as autoload.variable 12 | 13 | export (int) var width = 600 14 | export (int) var height = 300 15 | export (String) var dialoguetext = "This is standard text." 16 | export (String) var npc_name = "NAME" 17 | export (String) var dialog_key = "DIALOGUE_PLACEHOLDER" 18 | export (bool) var has_input := false 19 | 20 | var _portraits_res := {} 21 | var _dialogues := {} 22 | var _conditions := {} 23 | var _current_dialogue := {} 24 | 25 | enum States { pending, questionning } 26 | 27 | var _message : String = '' 28 | var _is_last_dialogue : bool= false 29 | var _has_next_button : bool = false 30 | var _has_timer : bool = false 31 | var _has_condition : bool = false 32 | var _state: int = States.pending 33 | 34 | # change the below to allocate the various panels (which you can easily customize) 35 | onready var _text = $Panel/VBox/Message # message 36 | onready var _name = $Panel/VBox/Namelabel # npc name 37 | onready var _portrait = $Panel/VBox/Photo # npc picture 38 | onready var _choices_panel = $Panel/VBox/Choices # panel for choices 39 | onready var _next = $Panel/VBox/MarginContainer/Next # next button 40 | onready var _end = $Panel/VBox/MarginContainer/End # end of dialogue button 41 | onready var _timer = $Timer # timer for timed messages 42 | 43 | # initial dialogue file is based on NPC name - can be changed easily 44 | onready var dialogue_json : Dictionary = get_json("Dialogue/"+npc_name+".json") 45 | 46 | signal dialogue_started 47 | signal dialogue_changed(name, portrait, message) 48 | signal dialogue_text_displayed 49 | signal dialogue_last_text_displayed 50 | signal dialogue_finished 51 | signal dialogue_last_dialogue_displayed 52 | signal dialogue_animation_skipped 53 | signal dialogue_choices_changed(choices) 54 | signal dialogue_choices_displayed 55 | signal dialogue_choices_finished(choices) 56 | signal dialogue_choices_pressed 57 | signal nextbutton_pressed 58 | signal dialogue_condition(condition) 59 | signal timer(seconds) 60 | signal codexec(codetoexecute) 61 | 62 | # -------------------------- 63 | 64 | func _ready(): 65 | # change this to adjust panel size 66 | $Panel.rect_size.x = width 67 | $Panel.rect_size.y = height 68 | # to do : position of control (self.position) can be set easily 69 | # to do : animate panel popping up 70 | 71 | _load_portrait_in_memory() 72 | 73 | connect("dialogue_started", self, "_on_Dialogue_started") 74 | connect("dialogue_changed", self, "_on_Dialogue_changed") 75 | connect("dialogue_finished", self, "_on_Dialogue_finished") 76 | connect("dialogue_last_dialogue_displayed", self, "_on_Last_dialogue") 77 | connect("dialogue_choices_changed", self, "_on_Choice_changed") 78 | connect("dialogue_choices_finished", self, "_on_Choices_finished") 79 | connect("dialogue_text_displayed", self,"_on_dialogue_displayed") 80 | connect("dialogue_condition",self,"_on_conditions_check") 81 | 82 | _next.visible=false 83 | _end.visible=false 84 | _choices_panel.visible=false 85 | 86 | #-------------------------------------------------------------------- 87 | # the two lines of code below are only for testing purposes 88 | # load and start functions should rather be triggered by game scripts 89 | 90 | loaddialogue("Intro") # required to load the correct json file 91 | start() # starts the dialogue 92 | #-------------------------------------------------------------------- 93 | 94 | # ------------------------------------------------------- 95 | # Output Dialogue functions 96 | # Change the below to change the way things are displayed 97 | # ------------------------------------------------------- 98 | 99 | # show dialogue box 100 | func _on_Dialogue_started() -> void: 101 | visible = true 102 | 103 | func next_action() -> void: 104 | 105 | # this is what happens when there is a choice to be made 106 | if _state == States.questionning: 107 | _choices_panel.visible = true 108 | _choices_panel.get_child(0).grab_focus() 109 | emit_signal("dialogue_choices_displayed") 110 | return 111 | 112 | # this is what happens when the last dialogue box is displayed 113 | if _is_last_dialogue and _state == States.pending: 114 | emit_signal("dialogue_last_text_displayed") 115 | return 116 | 117 | # this is what happens when there is a next dialogue but no choices 118 | emit_signal("dialogue_text_displayed") 119 | 120 | # this is to display the dialogue text 121 | func _on_Dialogue_changed(name: String, portrait: StreamTexture, message: String) -> void: 122 | _message = message 123 | _name.text = name 124 | _portrait.texture = portrait 125 | _text.parse_bbcode(message) 126 | 127 | # make letters appear progressively 128 | var textsize=message.length() 129 | $Tween.interpolate_property(_text,"visible_characters",0,textsize,textsize*0.02,Tween.TRANS_LINEAR,Tween.EASE_IN_OUT) 130 | $Tween.start() 131 | yield($Tween,"tween_completed") 132 | 133 | # add "next" or "end" button unless it is a timed dialogue box 134 | if not _has_timer : 135 | if _is_last_dialogue: _end.visible=true 136 | if _has_next_button: _next.visible=true 137 | 138 | # Add and display player's optional choices 139 | func _on_Choice_changed(choices: Array) -> void: 140 | _state = States.questionning 141 | _choices_panel.visible=true 142 | for choice in choices: 143 | # the below is based on creating buttons but other stuff is possible 144 | var button: Button = Button.new() 145 | button.text = choice["text"][TranslationServer.get_locale()] 146 | button.connect("pressed", self, "_on_Choice_pressed", [choice["next"]]) 147 | _choices_panel.add_child(button) 148 | 149 | func _on_Choice_pressed(next: String) -> void: 150 | for choice in _choices_panel.get_children(): 151 | choice.queue_free() 152 | _state = States.pending 153 | _choices_panel.hide() 154 | emit_signal("dialogue_choices_finished", next) 155 | emit_signal("dialogue_choices_pressed") 156 | 157 | # Reset dialogue when finished 158 | func _on_Dialogue_finished() -> void: 159 | hide() 160 | _timer.stop() 161 | _next.visible=false 162 | _end.visible=false 163 | _choices_panel.visible=false 164 | _is_last_dialogue = false 165 | _has_next_button = false 166 | _has_timer = false 167 | # you could also queue_free() the dialogue box, depending on your game system 168 | 169 | # Last dialogue box has been displayed 170 | func _on_Last_dialogue() -> void: 171 | _is_last_dialogue = true 172 | # you can add here whatever happens after a final dialogue box 173 | 174 | # end button is pressed 175 | func _on_End_pressed(): 176 | _on_Dialogue_finished() 177 | 178 | # next button pressed 179 | func _on_Next_pressed(): 180 | _has_next_button=false 181 | _next.visible=false 182 | if _has_condition: 183 | _has_condition=false 184 | emit_signal("nextbutton_pressed") 185 | else: 186 | next() 187 | 188 | # timed dialogue box (signal "dialogue_timer" and the waiting time value) 189 | func timer(seconds:int): 190 | _has_timer = true 191 | yield(get_tree().create_timer(seconds),"timeout") 192 | _on_Timeout() 193 | 194 | # Triggered by the timer node when time is out 195 | func _on_Timeout() -> void: 196 | _on_Dialogue_finished() 197 | 198 | # Execute some code 199 | func codexec(codetoexec:String): 200 | # TODO: accept only certain things? 201 | # execute the code requested 202 | pass 203 | 204 | # ---------------------------------------- 205 | # Back-office dialogue functions 206 | # These should run pretty much plug & play 207 | # ---------------------------------------- 208 | 209 | # Load dialogue 210 | func loaddialogue(path) -> void: # path is the name of the file without extension 211 | dialogue_json = get_json("Dialogue/"+path+".json") 212 | 213 | # starts the dialogue 214 | func start() -> void: 215 | emit_signal("dialogue_started") 216 | _current_dialogue = get_next(dialogue_json.root) 217 | change() 218 | 219 | # Send dialogue based on language 220 | func next() -> void: 221 | _current_dialogue = get_next(_current_dialogue) 222 | change() 223 | 224 | # show next interactions 225 | func change() -> void: 226 | var text: String = _current_dialogue["text"][TranslationServer.get_locale()] 227 | emit_signal( 228 | "dialogue_changed", 229 | _current_dialogue["name"], 230 | _portraits_res[_current_dialogue["portrait"]], 231 | text 232 | ) 233 | 234 | # dialog triggers a signal 235 | if _current_dialogue.has("signals"): 236 | _emit_dialogue_signal(_current_dialogue["signals"]) 237 | 238 | # player can make some choice 239 | if _current_dialogue.has("choices"): 240 | var conditions: Array = ( 241 | _current_dialogue.get("conditions") 242 | if _current_dialogue.has("conditions") 243 | else [] 244 | ) 245 | emit_signal("dialogue_choices_changed", get_choices(_current_dialogue.choices, conditions)) 246 | return 247 | 248 | # there are conditions linked to the next dialogue 249 | if _current_dialogue.has("conditions"): 250 | emit_signal("dialogue_condition",_current_dialogue.conditions) 251 | return 252 | 253 | # there is no linked dialogue and no choice 254 | if not _current_dialogue.has("conditions") and not _current_dialogue.get("next"): 255 | emit_signal("dialogue_last_dialogue_displayed") 256 | clear() 257 | 258 | # in other cases: display a next button if there is a next node 259 | if _current_dialogue.has("next"): 260 | _has_next_button=true 261 | 262 | # clear dialogue 263 | func clear() -> void: 264 | _dialogues = {} 265 | _current_dialogue = {} 266 | _conditions = {} 267 | 268 | # get next node depending on situation 269 | func get_next(node: Dictionary) -> Dictionary: 270 | var default_next := "05f2b40d-5f4c-4544-bcde-793df48faab6" 271 | 272 | if node.has("next"): 273 | return dialogue_json[node.next] 274 | 275 | var next := "" 276 | if node.has("conditions"): 277 | var conditions = node.conditions.duplicate(true) 278 | var matching_condition := 0 279 | 280 | for condition in conditions: 281 | var predicated_next: String = condition.next 282 | condition.erase("next") 283 | 284 | if condition.empty(): 285 | default_next = predicated_next 286 | 287 | # partial matching 288 | var current_matching_condition := 0 289 | for key in condition: 290 | if _conditions.has(key): 291 | # conditions will never match 292 | if _conditions.size() < condition.size(): 293 | continue 294 | 295 | if condition.empty(): 296 | default_next = predicated_next 297 | 298 | if _check_conditions(condition, key): 299 | current_matching_condition += 1 300 | 301 | if current_matching_condition > matching_condition: 302 | matching_condition = current_matching_condition 303 | next = predicated_next 304 | 305 | if not next.empty(): 306 | return dialogue_json[next] 307 | 308 | assert(default_next.empty() == false) 309 | return dialogue_json[default_next] 310 | 311 | # function to manage condition node 312 | func _on_conditions_check(conditions: Array): 313 | for condition in conditions: 314 | for key in condition.keys(): 315 | if key != "next": 316 | if checkiftrue(key, condition[key]): 317 | _has_condition=true 318 | _has_next_button=true 319 | yield(self,"nextbutton_pressed") # wair for next button to be pressed 320 | emit_signal("dialogue_choices_finished", condition["next"]) 321 | # NB : if none of the conditions are met it's a dead end ... 322 | 323 | # checks if a given condition is true and returns a boolean 324 | func checkiftrue(variable, condition) -> bool: 325 | var vartocheck=get(variable) 326 | if "autoload" in variable: # variable for conditions can be an autoload 327 | variable=variable.replace("autoload.","") 328 | vartocheck=autoload.get(variable) 329 | if condition ["type"] == "boolean": 330 | if vartocheck == condition["value"]: return true 331 | else: return false 332 | if condition ["type"] == "int": 333 | if condition ["operator"] == "equal": 334 | if vartocheck == condition["value"]: return true 335 | else: return false 336 | if condition ["operator"] == "greater": 337 | if vartocheck > condition["value"]: return true 338 | else: return false 339 | if condition ["operator"] == "lower": 340 | if vartocheck < condition["value"]: return true 341 | else: return false 342 | if condition ["operator"] == "different": 343 | if vartocheck != condition["value"]: return true 344 | else: return false 345 | return false 346 | 347 | func get_choices(choices: Array, conditions: Array = []) -> Array: 348 | if conditions.empty(): 349 | return choices 350 | 351 | var result := [] 352 | var conditional_choices := {} 353 | 354 | for choice in choices: 355 | if choice.has("uuid"): 356 | conditional_choices[choice.uuid] = choice 357 | else: 358 | result.append(choice) 359 | 360 | if conditional_choices.empty(): 361 | return choices 362 | 363 | var matching_condition := 0 364 | for condition in conditions: 365 | var current_matching_condition := 0 366 | for key in condition: 367 | var predicated_next: String = condition.next 368 | condition.erase("next") 369 | 370 | if _conditions.has(key): 371 | # conditions will never match 372 | if _conditions.size() < condition.size(): 373 | continue 374 | 375 | if condition.empty(): 376 | result.append(conditional_choices[predicated_next]) 377 | 378 | if _check_conditions(condition, key): 379 | current_matching_condition += 1 380 | 381 | if current_matching_condition > matching_condition: 382 | result.append(conditional_choices[predicated_next]) 383 | 384 | # dialogue json file was badly configuarated since it doesn't have a default choice 385 | assert(not result.empty()) 386 | return result 387 | 388 | 389 | # Valid conditions 390 | # (Not sure this works) 391 | # @returns {bool} 392 | func _check_conditions(condition: Dictionary, key: String) -> bool: 393 | match condition[key].operator: 394 | "lower": 395 | return condition[key].value > _conditions[key] 396 | "greater": 397 | return condition[key].value < _conditions[key] 398 | "different": 399 | return condition[key].value != _conditions[key] 400 | _: 401 | return condition[key].value == _conditions[key] 402 | 403 | 404 | # Check all dialogue portraits and set them in memory 405 | func _load_portrait_in_memory() -> void: 406 | for key in dialogue_json: 407 | var values: Dictionary = dialogue_json[key] 408 | if ( 409 | not values.has("portrait") 410 | or values.portrait.empty() 411 | or _portraits_res.has(values.portrait) 412 | ): 413 | continue # to do: replace with standard image if there is no ref in the json 414 | _portraits_res[values.portrait] = load(values.portrait) 415 | 416 | # Function used for signals 417 | func _convert_value_to_type(type: String, value): 418 | match type: 419 | "Vector2": 420 | return Vector2(value["x"], value["y"]) 421 | "Number": 422 | if "." in type: return float(value) 423 | else: return int(value) 424 | return value 425 | 426 | func _on_Choices_finished(key: String) -> void: 427 | disconnect("dialogue_choices_finished", self, "_on_Choices_finished") 428 | # get choice 429 | _current_dialogue = dialogue_json[key] 430 | change() 431 | 432 | # Emit signals - this requires that the signals are "declared" somewhere ("signal string(value)") 433 | # For example timed dialog works with a signal called "dialogue_timer" 434 | func _emit_dialogue_signal(signals: Dictionary) -> void: 435 | for key in signals: 436 | if not signals[key] is Dictionary: 437 | if signals[key] == null: 438 | connect(key, self, key) 439 | emit_signal(key) 440 | continue 441 | connect(key, self, key) 442 | emit_signal(key, signals[key]) 443 | continue 444 | 445 | var multi_values_signal: Dictionary = signals[key] 446 | for type in multi_values_signal: 447 | var value = _convert_value_to_type(type, multi_values_signal[type]) 448 | connect(key, self, key) 449 | emit_signal(key, value) 450 | 451 | 452 | # -------------------------- 453 | # Read and parse Json file 454 | # -------------------------- 455 | 456 | func get_json(file_path: String) -> Dictionary: 457 | var file := File.new() 458 | 459 | if file.open(file_path, file.READ) != OK: 460 | print("get_json: file cannot been read") 461 | return {} 462 | 463 | var text_content := file.get_as_text() 464 | file.close() 465 | var data_parse = JSON.parse(text_content) 466 | if data_parse.error != OK: 467 | print("get_json: error while parsing") 468 | return {} 469 | 470 | data_parse.result.erase("__editor") 471 | return data_parse.result 472 | -------------------------------------------------------------------------------- /Dialogue/DialogueNode.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://Dialogue/Dialogue.gd" type="Script" id=1] 4 | [ext_resource path="/Users/Legion/Desktop/Gamedev tools/DialogEditor/GodotDialogueNode/icon.png" type="Texture" id=2] 5 | 6 | [node name="Dialogue" type="Node2D"] 7 | script = ExtResource( 1 ) 8 | npc_name = "Intro" 9 | dialog_key = "" 10 | has_input = true 11 | 12 | [node name="Panel" type="PanelContainer" parent="."] 13 | margin_right = 600.0 14 | margin_bottom = 300.0 15 | __meta__ = { 16 | "_edit_use_anchors_": false 17 | } 18 | 19 | [node name="VBox" type="VBoxContainer" parent="Panel"] 20 | margin_left = 7.0 21 | margin_top = 7.0 22 | margin_right = 593.0 23 | margin_bottom = 267.0 24 | size_flags_horizontal = 3 25 | size_flags_vertical = 2 26 | 27 | [node name="Namelabel" type="Label" parent="Panel/VBox"] 28 | margin_right = 586.0 29 | margin_bottom = 14.0 30 | text = "Godot" 31 | 32 | [node name="Photo" type="TextureRect" parent="Panel/VBox"] 33 | margin_top = 18.0 34 | margin_right = 586.0 35 | margin_bottom = 82.0 36 | texture = ExtResource( 2 ) 37 | 38 | [node name="Message" type="RichTextLabel" parent="Panel/VBox"] 39 | margin_top = 86.0 40 | margin_right = 586.0 41 | margin_bottom = 236.0 42 | rect_min_size = Vector2( 0, 150 ) 43 | size_flags_horizontal = 3 44 | size_flags_vertical = 3 45 | visible_characters = 41 46 | text = "Salut, ceci est un dialogue sans options." 47 | 48 | [node name="Choices" type="VBoxContainer" parent="Panel/VBox"] 49 | visible = false 50 | margin_top = 240.0 51 | margin_right = 586.0 52 | margin_bottom = 284.0 53 | 54 | [node name="MarginContainer" type="MarginContainer" parent="Panel/VBox"] 55 | margin_top = 240.0 56 | margin_right = 42.0 57 | margin_bottom = 260.0 58 | size_flags_horizontal = 0 59 | size_flags_vertical = 0 60 | 61 | [node name="Next" type="Button" parent="Panel/VBox/MarginContainer"] 62 | margin_right = 42.0 63 | margin_bottom = 20.0 64 | size_flags_horizontal = 0 65 | size_flags_vertical = 0 66 | text = "Next" 67 | 68 | [node name="End" type="Button" parent="Panel/VBox/MarginContainer"] 69 | visible = false 70 | margin_right = 35.0 71 | margin_bottom = 20.0 72 | size_flags_horizontal = 0 73 | size_flags_vertical = 0 74 | text = "End" 75 | 76 | [node name="Timer" type="Timer" parent="."] 77 | wait_time = 0.001 78 | one_shot = true 79 | 80 | [node name="Tween" type="Tween" parent="."] 81 | 82 | [connection signal="pressed" from="Panel/VBox/MarginContainer/Next" to="." method="_on_Next_pressed"] 83 | [connection signal="pressed" from="Panel/VBox/MarginContainer/End" to="." method="_on_End_pressed"] 84 | [connection signal="timeout" from="Timer" to="." method="_on_Timeout"] 85 | -------------------------------------------------------------------------------- /Dialogue/DialogueNodeTest.cfg: -------------------------------------------------------------------------------- 1 | [path] 2 | 3 | file="" 4 | resource="" 5 | OSX="" 6 | Windows="C:/Users/Legion/Desktop/Gamedev tools/DialogEditor/GodotDialogueNode" 7 | UWP="" 8 | X11="" 9 | 10 | [locale] 11 | 12 | current="fr" 13 | selected=[ "en", "fr" ] 14 | custom=[ ] 15 | 16 | [variables] 17 | 18 | characters=[ { 19 | "name": "Godot", 20 | "portraits": [ { 21 | "name": "icon.png", 22 | "path": "/Users/Legion/Desktop/Gamedev tools/DialogEditor/GodotDialogueNode/icon.png", 23 | "uuid": "6cb84c4d-0006-485d-838a-68dad231b5ae" 24 | } ] 25 | } ] 26 | files=[ { 27 | "name": "Intro.json", 28 | "path": "/Users/Legion/Desktop/Gamedev tools/DialogEditor/GodotDialogueNode/Dialogue/Intro.json" 29 | } ] 30 | 31 | [cache] 32 | 33 | last_opened_file={ 34 | "name": "Intro.json", 35 | "path": "/Users/Legion/Desktop/Gamedev tools/DialogEditor/GodotDialogueNode/Dialogue/Intro.json" 36 | } 37 | 38 | [info] 39 | 40 | version="v1.0.3-beta" 41 | -------------------------------------------------------------------------------- /Dialogue/Intro.json: -------------------------------------------------------------------------------- 1 | {"74b98e5c-5312-412b-87a6-abcf858d908e":{"name":"Godot","portrait":"/Users/Legion/Desktop/Gamedev tools/DialogEditor/GodotDialogueNode/icon.png","text":{"en":"Now you have a choice. \nDo you want a final timed node ?","fr":"Maintenant tu as un choix. \nTu veux une node finale avec un timer ?"},"choices":[{"text":{"en":"not OK","fr":"pas OK"},"next":"11b330d3-15f4-4216-b7af-26fec446ce8c"},{"text":{"en":"OK","fr":"OK"},"next":"b5ae0f1d-c5b5-4225-931b-6373c0554fe2"}]},"9be26633-3ad6-46e8-b79b-eb94b21ec0d2":{"name":"Godot","portrait":"/Users/Legion/Desktop/Gamedev tools/DialogEditor/GodotDialogueNode/icon.png","text":{"en":"Hello, this is a test dialogue without options","fr":"Salut, ceci est un dialogue sans options."},"next":"74b98e5c-5312-412b-87a6-abcf858d908e"},"b5ae0f1d-c5b5-4225-931b-6373c0554fe2":{"name":"Godot","portrait":"/Users/Legion/Desktop/Gamedev tools/DialogEditor/GodotDialogueNode/icon.png","text":{"en":"This dialogue box will disappear in 5 seconds.","fr":"Cette boite de dialogue va se fermer dans 5 secondes."},"signals":{"dialogue_timer":{"Number":"5"}}},"11b330d3-15f4-4216-b7af-26fec446ce8c":{"name":"Godot","portrait":"/Users/Legion/Desktop/Gamedev tools/DialogEditor/GodotDialogueNode/icon.png","text":{"en":"OK then you will have to close this dialogue box yourself.","fr":"OK alors il faut que tu fermes cette boite de dialogue toi-même."}},"root":{"next":"9be26633-3ad6-46e8-b79b-eb94b21ec0d2"},"__editor":{"root":{"uuid":"root","offset":[-300,60]},"dialogues":[{"uuid":"74b98e5c-5312-412b-87a6-abcf858d908e","offset":[320,-60]},{"uuid":"9be26633-3ad6-46e8-b79b-eb94b21ec0d2","offset":[-120,-80],"parent":"root"},{"uuid":"b5ae0f1d-c5b5-4225-931b-6373c0554fe2","offset":[1160,100]},{"uuid":"11b330d3-15f4-4216-b7af-26fec446ce8c","offset":[1160,-260]}],"conditions":[],"signals":[{"uuid":"55622d11-5412-4c37-beb9-45da28529ffa","offset":[1600,120],"parent":"b5ae0f1d-c5b5-4225-931b-6373c0554fe2","data":{"dialogue_timer":{"Number":"5"}}}],"choices":[{"uuid":"2efe92e9-5455-456b-a251-6c4e1de044d1","offset":[760,-140],"parent":"74b98e5c-5312-412b-87a6-abcf858d908e"},{"uuid":"7d3ecfda-2b8f-46ff-8f5d-34b8bffcca5d","offset":[760,100],"parent":"74b98e5c-5312-412b-87a6-abcf858d908e"}]}} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Blue Rabbit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DialogueNode 2 | 3 | Godot Engine standalone node system for using jsons made with Levrault's LeDialogue editor. 4 | 5 | The LeDialogue editor creates json files for dialogs, with various possibilities, based on a visual node system. It is very easy to use and was made to work with all sorts of game engines. It supports localization, which is very useful. Thanks to Levrault for creating it. 6 | 7 | My idea was to provide a very simple standalone script to use the json files made with the editor in Godot Engine Games. Display is pretty basic but it can easily be customized. Just change the UI nodes and adapt the beginning of the script. The script is commented in order to make customization easier. 8 | 9 | The editor itself can be found here, with instructions for use : https://github.com/Levrault/LE-dialogue-editor 10 | -------------------------------------------------------------------------------- /TestScene.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://Dialogue/DialogueNode.tscn" type="PackedScene" id=1] 4 | 5 | [node name="TestScene" type="Node2D"] 6 | 7 | [node name="Dialogue" parent="." instance=ExtResource( 1 )] 8 | position = Vector2( 208.757, 146.132 ) 9 | -------------------------------------------------------------------------------- /default_env.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Environment" load_steps=2 format=2] 2 | 3 | [sub_resource type="ProceduralSky" id=1] 4 | 5 | [resource] 6 | background_mode = 2 7 | background_sky = SubResource( 1 ) 8 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlooRabbit/DialogueNode/7d41b6f6fe9278fb0630de4a7aaf0a2d5f3050a2/icon.png -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=4 10 | 11 | [application] 12 | 13 | config/name="Dialogues" 14 | run/main_scene="res://TestScene.tscn" 15 | config/icon="res://icon.png" 16 | 17 | [display] 18 | 19 | window/size/always_on_top=true 20 | 21 | [physics] 22 | 23 | common/enable_pause_aware_picking=true 24 | 25 | [rendering] 26 | 27 | environment/default_environment="res://default_env.tres" 28 | --------------------------------------------------------------------------------