Skip to content

Latest commit

 

History

History
567 lines (306 loc) · 38.1 KB

help.md

File metadata and controls

567 lines (306 loc) · 38.1 KB

MadTalk - A Feature-Rich Dialog System for Godot

Usage

You need four steps:

  • Obviously, install the plugin and activate it. You need the res://addons/madtalk folder (which contains the editor plugin) and the res://madtalk folder (which contains the runtime interpreter and dialog data, which are part of your exported game)
  • Create a set of Control nodes to show the dialog messages (examples further below)
  • Put a Node in the scene holding the runtime script, set the values you want and tell it which Control nodes to use (the ones you created above)
  • Add at least two function calls somewhere in your code called in the node mentioned above: start_dialog("sheet id") to fire a dialog sheet, and dialog_acknowledge() to progress the dialog (usually pressing space, enter, clicking mouse, etc)

After that, you can have fun doing actual dialog design in the editor, provided as an extra tab in the very top of the engine.

 

The MadTalk Node

Each scene which shows dialog must have a node with the MadTalk runtime. That is a node of the type Node, put anywhere in your scene, which will access the UI nodes, emit signals, and where you set options in the inspector. That is also where you set your character names and avatars (which is optional by the way).

Why is this node not a singleton? Because you might want to have different settings for different scenes. E.g. you might have a scene in your game which is The Beach Episode where the character avatars show sunglasses and swimsuits, UI nodes have a different layout, a separate set of animations, etc. The example project shows an extreme case of this, as the game has four scenes: a text game, a visual novel, a top down 2D RPG, and a 3D first person. All share the same dialog sheets, NPC IDs, etc, while being visually and mechanically completely different games. This is done on purpose to showcase the independence between the plugin and the game style.

Add a Node to your scene (in this readme it will be named MadTalk) and attach the res://madtalk/madtalk_runtime.gd script to it.

This will give you a lot of options. Don't worry, you can ignore most of that. For a minimal setup you need to worry about only three - the bare minimum control nodes.

 

Control Nodes

MadTalk uses Control-derived nodes as UI. If your game is based on a Node2D scene, you'll need a CanvasLayer to hold them (as the viewport coordinates in Controls and 2D camera are incompatible). But if your scene is based on Control nodes (e.g. point-and-click, visual novels, etc) or Spatial (all 3D games) you don't need this, just place the dialog UI nodes anywhere convenient in your tree and they'll work.

As a bare minimum, you need three nodes:

  • A top dialog node, to hold all dialog stuff inside. The rule is: hiding this node must make all dialog-related stuff disappear. It can be any Control-derived type. I like to use a Control aligned to full rect covering the viewport with mouse filter to stop so the player can't click on anything in the scene while the dialog is on screen - but that's a personal preference
  • A message box, similar concept as above, except this is only for messages (not including menus, standing pictures, etc - but does include name and avatar, if any). Hiding this should be enough to make all message-related things disappear. It can be any Control-derived node, but this node usually is the message rectangle (holding text, avatar, name labels, etc) so it's common for it to be a Panel, TextureRect or NinePatchRect (but doesn't have to be)
  • A RichTextLabel to hold the actual message text (it was designed to be a RichTextLabel even if you don't use BB code, and compatibility with a common Label is not garanteed)

You can have anything else you want inside those nodes, for decoration, UI, etc. MadTalk will only touch the nodes you tell it to. Also a node being inside another (e.g. the message label inside the message box) doesn't mean they have to be immediate children - you can have a very complex hierarchy there if you want. They just have to be inside of it somehow.

Drag and drop the nodes to the MadTalk node as below, and you're set.

For this bare minimum setup, the dialogs should not use menu options (as there are no nodes set for that).

Visually, this game will run very dry, while in a final game you want messages to show up and hide in a juicy way. In another section you'll see how to automatically call animations for that.

 

Menus

If you want to have menu options in your dialogs, you need two more nodes:

  • A menu box, similar to the message box (and sibling to it, that is, it's also inside the top dialog node), where hiding this node hides all the menu-related stuff. Like the message box, this would normally be a Panel, TextureRect or NinePatchRect (but doesn't have to be)
  • A container node, the type being one of the automatic layout containers (VBoxContainer being the most common, but could also be HBoxContainer, one of the flow containers, etc)

Naturally, drag them to the MadTalk node so it's aware of it.

That's it.

If this is all you do, your menus will already work and you're good to go. See next session to make your first dialog sheet.

Optional: You'll notice the button shown in your menu when you run your game is the default Godot Button, which might not be the aesthetic you want in your game. You can have a custom button instead: prepare a nice node to your liking (one of the Button-derived classes, like Button itself or TextureButton), save that button as a separate scene file, and drag and drop that scene file to MadTalk's Dialog Button Scene File property, and that one will be used for your menus instead of the default one. Again, entirely optional. Just for testing, leaving that field blank will use Godot's default button.

Advanced: if you want to use as menu option a scene which is not even a button (something complex, even), all you have to do is create an export variable in there (with setter function) to set the text, and a signal emitted when the option is selected, and then set MadTalk's properties Dialog Button Text Property and Dialog Button Signal Name to match those.

If instead of all of this above you prefer to manage your menus entirely yourself in code, you can do that with custom menus, explained further down somewhere in this readme.

Now all you have to do to test the system is making some dialogs.

 

Designing Dialog Sheets

Dialogs are made of sheets (really like sheets of paper) which are the diagrams. Sheets contain sequences, which are the node blocks you can drag around. Each sequence has a list of items inside, which can be a:

  • Message
  • Effect
  • Condition
  • Menu option

When the dialog sequence runs, the items will run from top to bottom (except, obviously, menu options).

When you first open the dialog editor, you'll probably see this:

The right side panel shows the current sheet, the list of existing sheets, and a button to create a new sheet (you can collapse this panel). The rest of the screen is the sheet which is currently open.

When you just create a new sheet, it comes with the first sequence. All sequences have a numeric ID (you can't change that). By default all dialogs start at the sheet's sequence ID 0, so this one has the START title to remind you. On the top bar of the sequence you can create new items and set the menu options.

Sequences don't need to have any menu options, but if they do, each sequence can have only one menu and is always in the end. This is because one sequence means one path without branching out, and menus necessarily branch because there is no "default" option, so they end the sequence.

In the plus icon, you can create messages, conditions and effects.

After you create any of them, you can right click the item to edit, reorder it in the sequence, or remove.

(Reordering by drag-n-drop is a planned feature.)

In the menu options icon, you can set the menu options for this sequence. If you don't have any, the menu will be replaced by a default "Continue" output, which you can connect to any other sequence to continue the dialog. If you set options, MadTalk will work the menu for you in the end of this sequence, and branch accordingly. The sequence will show one output for each of the options, for you to connect to other sequences in this sheet.

You can click the tiny condition icon in the left of a menu option to set a condition which is required to show that button. If the condition is not met, the menu will show as if that button didn't exist. (Menu option conditions are limited to variable comparisons.)

To create a new sequence, right click on any empty space. To connect them, just drag an output from a sequence to an input of another sequence. Connecting a sequence to itself is valid, but only do that if you know what you're doing.

 

Characters, Names and Avatars

You can put a Label inside your message box (not the top dialog node) to show the speaker name (the character who is saying the message), and drag that to Dialog Speaker Label. Similarly, you can place a TextureRect somewhere inside the message box to show the avatar, and drag it to Dialog Speaker Avatar. They don't have to be immediate children of the message box node, but have to be inside of it (same applies to the message label).

So far, we don't have any characters set. The dialogs will already work (even without the name label and avatar). They already work even with just the first three nodes. If you create a dialog sheet, put some messages, and fire it with $MadTalk.start_dialog("some sheet id") (and use input to confirm via $MadTalk.dialog_acknowledge()), the messages will show properly, and if you have name label and avatar nodes, the name label will show the speaker ID and the avatar will be empty. For very simple games without avatars this is fine, just use the speaker ID field as name (or not even that if you only have narrator, or another way to indicate the speaker, like dialog bubbles).

To show avatars we have to create a list of characters, in MadTalk's first property. Set the number of how many characters you'll have in this scene, and for each of them create a MTCharacterData resource. You'll see each one of them has ID, Name, Avatar, and Variants fields.

  • ID is the speaker ID you type when writing the messages in the dialog editor
  • Name is what you want to show up in the name label
  • Avatar is a default avatar, shown when no variant was specified in the dialog. For many games this is the only one you need
  • Variants are a list of different avatars for the same character. They can be emotions (happy face, sad face, etc), or could be an entire different interpretation, up to you (e.g. healthy, hurt, tired, different clothes, etc). This is a dictionary where the keys are Strings (the variant ID) and the values are the images. Using variants is entirely optional and you can ignore for now

When you edit a message item, there are the Speaker ID and Variant fields you use to specify who says that message (see below). They will be taken from the MadTalk node where the dialog was invoked (where you called start_dialog()), so you can have different character lists in different scenes. (You could even have multiple runtime nodes in the same scene, but I never did that and see no use for it anyway... but it would work.)

 

Voice Clips

Each text message can also specify a voice clip (see above), which is the String path for an audio file in your project. MadTalk will not play it automatically, it will instead emit a signal passing that as argument. All you have to do is connect that signal to a method of yours, where you load (consider preloading) that clip into an AudioStreamPlayer or similar and play it.

The reason why MadTalk doesn't handle audio itself internally is because you might want different ways of doing it, such as some audio bus routing. Also e.g. for a visual novel, it would be indeed an AudioStreamPlayer, but for a 3D game you might want to use the speaker ID to select the AudioStreamPlayer3D belonging to the NPC node in the 3D world, so the voice will come from that direction in the 3D sound.

 

Juicy Dialogs - Transition Animations

The MadTalk node has two property slots for AnimationPlayer nodes. One of them is used internally to make dialogs juicy, the other one holds animations you can call yourself from the dialog sheets via effects (will be explained later on).

If you want dialog animations, you should create an AnimationPlayer node (good practice to put it inside the top dialog node), possibly called DialogTransitions or something more to your liking, drag it to MadTalk's Dialog Animation Player property, and create a few animations in it, to handle the transitions.

There are four pairs of animations used, each pair handles showing (fade in) and hiding (fade out):

  • Transition Animation Name Dialog is the name of the animation pair to show and hide the top dialog node. This normally doesn't have to be fancy. In most cases, your visible nodes will be inside the message and menu boxes, it's actually rare for something else to be visible in the dialog top node, but it's possible (like vignetes, decoration, or a background made darker or blurry, etc). I sometimes use the top dialog node as a ColorRect set to black with some alpha transparency, so it makes the game scenario a darker background while the dialog is going on
  • Transition Animation Name Message Box is the name of the animation pair showing and hiding the message box, assuming the top dialog node is visible (don't control both in the same animation!). This is one of the animations the players will see the most (other one being the text)
  • Transition Animation Name Menu is similar to above, but showing and hiding the menu box instead (again assuming the top dialog node is visible)
  • Transition Animation Name Text is used to gradually show the text (it can be a typing effect, or fading in, or something else, there are no requirements). This is one of the animations the players will see the most (other being the message box). You don't need to do this manually, see below

The text animation name option is there in case you want a custom one, but you don't need to do that yourself. Instead you can use the automatic typing animation, which is handled internally with Tweens. All you have to do is enable the Animate Text property, and configure the speed in Animated Text Milisecond Per Character, and MadTalk will do all the magic for you. If you also want a typing sound playing during that animation handled automatically for you, you can put an AudioStreamPlayer node (maybe inside the top dialog node), set it with a typing sound effect (set to loop), and drag that node to MadTalk's Key Press Audio Stream Player property. And done, typing animation and typing sound done automagically for you.

 

Effect Animations

You can have a separate AnimationPlayer node (maybe called something like EffectAnimations), which you can easily invoke from the dialogs. Just drag it to the Effects Animation Player property. One of the dialog effects is called Play Animation and Wait. If you use this effect (specifying an animation name), that animation will be played from this animation player, and the dialog will pause until this animation is complete.

You can, of course, play any animation from anywhere (waiting for completion or not, up to you) manually, invoked from the dialog sheets using custom effects (shown later on). But using the Effects Animation Player property involves no coding at all.

 

Year of Reference

Finally, this last property you would only touch if you are using the in-game time features and you need to fine tune the calendar. In a nutshell, the only purpose of this is to align the weekdays and leap years to your liking. The in-game time will start at 01 January of the year 1 (you can change this, of course, via code). You don't have to show "year 1" to your players, you can manually add any value to this when showing the year in the interface (so e.g. you'd add 2467 if in your game's lore the year is 2468), but internally the game starts at year 1. Which weekday is in-game 01/01/0001? Which year will be the next leap year?

MadTalk will link your in-game Year 1 to one year of the real world, and that is the reference year. So if you leave the default 1970, means the first day of your in-game time will be a Thursday, two years before the next leap year. Because 01/01/1970 was a Thursday and the next leap year was 1972. Players will never see the reference year, the number 1970 will never be used for anything in your game whatsoever. The only purpose is to define the weekdays and leap years.

So, e.g. if you want your game's January 1st to be a Sunday, and the very first year of in-game time to be a leap year, you can check the real world calendar to find when it happened (in this case, 1984) and use that as reference year.

If you're not using the in-game time features, then don't even bother about this field.

 

Custom Menu

If having a sequential container with buttons (e.g. VBoxContainer with a list of TextureButton) is not how your game works, and you want to manually assemble and control your menus from code, you can use the external menu feature. It uses a signal and a method call to interface your game code with MadTalk. To use the external menu feature, simply don't assign anything to Dialog Buttons Container. If that field is empty, MadTalk will assume you're using custom menus.

If you're using that feature and the dialog reaches a menu, MadTalk will not handle the buttons, instead it will emit the external_menu_requested signal passing the list of options the menu should have (as Array of String). You do whatever you want with this information.

When your custom code decides which of the options should be selected, you call the select_menu_option(option_index) method, where option_index is the index of the option you selected, from the Array passed in the signal above.

As example, if you are using custom menu (Dialog Buttons Container is empty) and you run this sequence:

The MadTalk node will emit the signal external_menu_requested passing as argument the Array:

["Let's walk in the park", "Let's eat a pão de queijo", "Nevermind"]

And if you want to continue the dialog with the player selecting "Let's eat a pão de queijo", you call:

$MadTalk.select_menu_option(1)     # Array indices start at 0

 

Dialog Variables

For many reasons (such as scope, avoiding spaghetti code, etc), MadTalk doesn't read your game code variables directly. Instead it has an internal Dictionary of dialog variables, and those are used in the effects and conditions, as well as in the message text. You can read and write them at any time from anywhere in your game code, explained further below (but changing them from outside when a dialog is already on screen might lead to Unexpected Behaviour ™️ ).

Conditions always expect variables to contain numbers (int or float are both acceptable). For now there is no condition to compare a variable to a String.

You can use dialog variables inside text messages in two ways: substitution and conditional text.

Substitution accepts numeric or String variables, and uses the syntax <<variable_name>>, example:

Hello, <<player_name>>!

Conditional text treats variables as numbers and uses the syntax {{if variable operator value: text}}, where operator is numerical comparison (<, >, <=, >=, =, or !=), example:

Hello{{if money >= 100: , milady}}!

Notice the equal operator is just one = (unlike C). Also the spaces around the operator and after : must be respected.

Substitution and conditional text can appear anywhere in the messages, in any quantity (just not nested). Conditional text can have substitutions. Unfortunately for now there is no else (it's planned, though).

You can use this for larger blocks of text as well, just be careful with spaces and new lines to avoid unnecessary blank spaces. Example:

Hello!
{{if money >= 100: Nice to meet, <<player_name>>.
It's great to see such a fine lady.
When I was young, my dream was to become someone as elegant as yourself,
but alas, here I am.
}}{{if money < 100: I haven't seen you before.
Are you sure your name is on the list?
You can't enter if it isn't.
}}

I consider improving this using GDScript evaluation for the conditions (or something similar), but it's low priority now. It's easier and more debug friendly to make separate sequences for those messages and use branching.

There are also some special variables used in substitution: $time, $date, $date_inv, $weekday and wday. You don't use the <<>> sintax for them, you just type them normally. They correspond to the fields from the in-game time dictionary, and meaning for them is explained further below in the section about signals.

 

Globals

MadTalk does have a singleton, called MadTalkGlobals. It handles dialog variables and in-game time, as those are shared across all scenes. Most methods and properties are in the MadTalk node in your scene, but some are in the singleton.

The following properties are in the MadTalkGlobals singleton:

  • is_during_dialog:

Exactly what it says on the tin. I use this one all the time to keep NPC and enemy AI in paused state during dialogs by simply adding an if at the start of their _physics_process:

func _physics_process(delta):
    if MadTalkGlobals.is_during_dialog:
        return
    
    # Rest of the code starts here
    # ...

Just be careful to handle animations which might be ongoing for them.

 

  • is_during_cinematic:

Name might be a bit misleading now, I swear it made more sense in earlier versions. This is true during the juicy animations from the Dialog Animation Player transition animations, as well as the effect animations played via the Play Animation and Wait effect.

  • time: stores the in-game time in seconds since the start of the calendar (midnight January 1st year 0001). Modifications require calling a method, explained further below

 

Export Vars for Dialog Sheets

You might want to have nodes in your scene with export variables for dialog sheets, so you can give them a sheet ID in the inspector.

E.g. you have a bunch of generic NPCs, and when you approach them and press E to interact, they fire a dialog. The script is the same, but different NPCs may or may not have separate dialog sheets.

You can put an export var named madtalk_sheet_id (or any name starting with madtalk_sheet_id) and MadTalk will do some inspector magic for you, showing a ... button helping you select the sheet among the list of sheets you have.

 

Methods

For simple dialog games, you would use only two methods from the list below (to start a dialog sheet, and to acknowledge messages). For average complexity dialogs, you might also use the get and set methods for dialog variables. The remaining methods are very rarely used, if ever.

Called in the MadTalk node:

  • start_dialog(sheet_id: String): starts a new dialog, from the start sequence (ID 0). If a dialog is already in progress, this one will be queued and start immediately when the previous one completes. If you want to start at another sequence, use the syntax below
  • start_dialog(sheet_id: String, sequence_id: int): same as above, but starts directly at the specified sequence ID instead of 0. Use only if you know what you're doing, as it's easy to lose track of things
  • dialog_acknowledge(): if a message is still in the middle of showing text, causes the text to show entirely, immediately. Otherwise, progress the dialog to the next item
  • dialog_abort(): interrupts the dialog, ignoring everything that would come after the current item. When I made this second version of the plugin back in mid 2020 I expected this to be useful, but 3 years and 26 published games and drafts later, and I never aborted a dialog ever
  • dialog_skip(): when a dialog is skipped messages are not shown anymore, but unlike abort, all the dialog chain is still traversed, all conditions are checked and branched, animations are played and effects take place. This is important since game logic can be critically based on those effects, like setting variables, and also events e.g. if an effect in the end of a conversation spawns a boss, skipping the dialog still spanws the boss, while aborting doesn't
  • select_menu_option(option_index): when using external menu, selects the menu option by index
  • get_sheet_names(): returns an Array of String with the IDs of all the sheets in the database

 

Called in the MadTalkGlobals singleton:

  • set_variable(variable_name: String, value): sets the value for the dialog variable
  • get_variable(variable_name: String): returns the value for the dialog variable
  • export_game_data(): returns a Dictionary with all the game data handled by MadTalk (dialog variables and in-game time), to be serialized for saving the game state
  • import_game_data(data: Dictionary): the reverse of the above, sets the dialog variables and in-game time based on a Dictionary previously exported by export_game_data(). If the data was exported during a dialog, importing it will not suddenly show the dialog message where it was. There is no saved track of ongoing dialog. Avoid allowing the player to save the game in the middle of a dialog

Methods to control in-game time via code are being reviewed, as I want something very user friendly (which currently it is not). But for now, if you want to change the in-game time, you can (carefully!!!) modify the time property in the MadTalkGlobals, which contains the number of seconds of in-game time elapsed since the start of calendar (midnight of January 1st of year 0001). Just remember to call the update_gametime_dict() method afterwards, example:

# Moves the in-game time 45 days to the future
MadTalkGlobals.time += 45*(24*60*60) # 45 days * 24h * 60 min * 60 secs = 45 days in seconds
MadTalkGlobals.update_gametime_dict()

 

Signals

You don't need any signals at all to use MadTalk.

 

All signals are optional.

 

That said... they are a lot 😅

The ones you'd use the most are the five below. Please notice the first two are not real signals. I mean, the signals exist in the inspector and you can connect things there, but MadTalk will not really emit the signals. It will, instead, read which method you connected, and then call the method in the conventional way. This is so it can wait for yields and get the return value, which signals can't. If you connect more than one method in them, only the first one of each will be used.

 

  • activate_custom_effect(custom_id: String, custom_data: Array of String):

When using the effect called custom effect, it will call the method connected to this signal, passing as argument the data you entered in the corresponding edit dialog (e.g. custom_id="teleport_player" and custom_data=["city1"]).

custom_data can have multiple items (one per line you entered in the dialog editor).

The dialog will wait for the method to finish, so if you have a yield in the method code, the dialog will wait for that yield (this is not a real signal).

 

  • evaluate_custom_condition(custom_id: String, custom_data: Array of String) -> bool:

One of the conditions is called custom condition, and it will call the method connected here. If the method returns true, the sequence continues, while if the method returns false, the dialog will branch out. The arguments work the same way as activate_custom_effect.

The dialog will pause and wait for the return value, so if you have a yield in the method code, the dialog will wait for that yield (this is not a real signal).

Example, a custom condition in a sequence, and the method connected to the evaluate_custom_condition signal:

 

  • speaker_changed(previous_speaker_id: String, previous_speaker_variant: String, new_speaker_id: String, new_speaker_variant: String):

This is probably the most powerful of MadTalk's signals, emitted when the speaker ID or variant changed between messages. The arguments are the ID and variant for the last message as well as for the current message about to be shown.

This can be used to modify the interface to match changes in speaker, e.g. to position the dialog bubble over the head of the character, or to show and hide (or highlight or blur) standing pictures, etc.

Example 1 - highlighting standing pictures:

 

Example 2 - using variants to control message font:

 

Example 3 - using speaker ID to point the camera to character:

 

This signal is only emitted if one of the fields has actually changed (either different speakers, or different variants for same speaker).

Example, if previous message was from speaker ID alice, no variant, and next message has speaker ID bob with variant happy, the signal emitted will be:

speaker_changed("alice", "", "bob", "happy")

 

  • voice_clip_requested(speaker_id: String, clip_path: String):

As explained in the Voice Clip section earlier in this readme, this signal is emitted whenever showing a message with a voice clip set. The argument speaker_id is the speaker ID for the message, and clip_path is a String containing the path to the audio file. The method connected to this signal is responsible for playing the audio.

 

  • external_menu_requested(menu_options):

As explained in the Custom menu section, this signal is emitted when a menu is required but there is no node set in the Dialog Buttons Containerproperty in the MadTalk node. Argument is an Array of String containing the menu options.

 

If the in-game time is relevant to your game, the signal below might be useful (e.g. showing clock on the UI):

  • time_updated(datetime_dict): emitted whenever the in-game time changes via dialog (not via code). Argument is a Dictionary with the fields:
    • year
    • month
    • day
    • weekday (numeric)
    • weekday_name (String)
    • wday_name - abbreviated version of weekday_name (String)
    • date - day and month, format international dd/mm (String)
    • date_inv - day and month inverted, format US mm/dd (String)
    • hour
    • minute
    • second (MadTalk doesn't work with seconds, will only be different from 00 if you modify by code)
    • time - hour and minute, format 24h HH:MM (String)

 

Signals below are used very rarely, so if you're still getting started you can forget they exist.

  • message_text_shown(speaker_id, speaker_variant, message_text, force_hiding):

Emitted when a message is about to be shown (but not yet), and the arguments are the values set in the message item. This can be used e.g. to keep a message log for the player to review past dialogs:

You can also use this signal to bypass MadTalk's default message showing system and implement your own without having to change the plugin source.

 

  • dialog_started(sheet_name, sequence_id): emitted when a dialog starts. Arguments are the sheet ID and the sequence ID where it started

  • dialog_finished(sheet_name, sequence_id): emitted when a dialog ends. Arguments are the last sheet ID and the sequence ID where it ended. NOT emitted when dialog is aborted (perhaps it should? Feedback appreciated)

  • dialog_aborted: this one is emitted instead if dialog is aborted

 

  • dialog_acknowledged and text_display_completed:

When dialog_acknowledge() is called, its behaviour depends on if the text is already fully shown or not. If the text is still being shown, dialog_acknowledge() will cause it to be entirely shown instantly, and in that case text_display_completed is emitted. Otherwise, it will cause the dialog to proceed to next item, and in that case dialog_acknowledged is emitted.

If the player doesn't interact and the text is shown to the end naturally via animation or Tween, text_display_completed is also emitted

 

  • dialog_sequence_processed(sheet_name, sequence_id): emitted when a sequence is completed. The order of the sequences is not respected (as they use recursive calls) and this signal will probably be revised

  • dialog_item_processed(sheet_name, sequence_id, item_index): emitted when an item inside a sequence is completed. The order is not necessarily respected (as they use recursive calls) and this signal will probably be revised

  • menu_option_activated(option_id): emitted when a menu option is selected, either via internal automatic menu, or custom menu)

 


Important Tip

If you have long streams of messages without menus or branching, don't make them a single, long sequence. Break them down into several shorter sequences. This mainly for two reasons:

  • It's more performance-friendly. When sequences run the entire sequence is read, and traversing the items is an Array lookup. The shorter the Array, the better. Also the recursive call method will be replaced soon, which means previous already traversed sequences can be unloaded while the next one runs.

  • Most importantly, it will not drive you crazy. New items are added to the bottom of the stack. If you divide a 30 items sequence into 5 sequences of 6 items, and you decide to add an effect at the beginning, you add to the first one and move it up 6 times. If you have a single sequence, you will have to move it up 30 times!


 

Planned Features

  • Invoking lip animation via signals (WIP)
  • Change the recursive call system to something else to take it easier on the call stack
  • Locale for text messages
  • Moving sequence items via dragging

 


 

History and Fun Facts

I'm writing the below because this plugin has some emotional value to me.

  • Early 2020 - Started working on the dialog system as soon as pandemic started, initially as part of an adventure game (abandoned). Finished first version before mid year

  • Mid 2020 - Decision to decouple the system from the game (which allows the game to rest in peace) and make it as a publicly available framework - was still using JSON for dialog storage then, I didn't know how to write editor plugins so the dialog editor was a Godot application I had to run and save the dialog data to a file

  • Somewhere 2020 - Learned how to make dialog plugins and work with resources. Kissed the JSON system good bye and revamped everything to use resources and built-in dialog editor (the message/condition/effect system was kept per the original)

  • Nov 2020 - pulawskig made the game Lovely Story for the Godot Wild Jam #27 and we discussed the dialog tools used. I decided MadTalk deserved more visibility and crushed development as if there was no tomorrow to polish the editor plugin interface with nice images and buttons, to propose presenting it in a talk in the upcoming GodotCon Jan 2021

  • Dec 2020 - I have ADHD, and my hyperfocus suddenly decided to turn off and I could not work on it anymore. The only thing left to do at the time before proposing was one single bug, which in retrospect sounds infuriating

  • Entirety of 2021 and 2022 - Absolutely nothing. I missed GodotCon, I didn't have a public repo, and the longer it passed that way the worse I felt, and the worse I felt the more difficult it was to pick it up again. I mean, just to say it wasn't "nothing", somewhere in 2021 I did try to pick it up again, created a repo to put on github (but repo was empty), created the example project in Sept 2021, but then I lost my job, moved to a different city, my HDD died before I could put anything on github, taking it all down to the bottom of the ocean. The bitbucket repo I had was still before the revamp and coupled to the abandoned game and wasn't an option. The only reason why this still exists is because the good soul of JohnGabrielUK happened to have the most recent version of the plugin added to a game for which he gave me repo access, and I could recover it from there

  • Jan 2023 - The ADHD gods smiled at me again and I suddenly felt I could do this. Interestingly enough, it was January 1st, the default first day of in-game time, when I finally added the second, revamped version of MadTalk to github. Yeeyy. The only problem was I didn't write any readme, so people would find this repo and have no interest in it whatsoever

  • Fall 2023 (now as I type) - There is a new GodotCon around (Nov 2023). I considered proposing to present this, but I'm still sore I couldn't finish it in 2021. Then Unity made controversial announcements causing an influx of developers into Godot, who would be searching for nice stuff in Godot asset store. This fuelled me to finally make this readme (which is the longest I ever wrote) to finally put this plugin into the asset store. The only new feature added after all this time was the voice clip, which was added for the Warthunder Type H game, and the external menu, added for a WIP game. Everything else (including UI polish) is as of today pretty much the version made aiming the GodotCon Jan 2021. Hopefully it will be easier to work on it further on, as people can see it and give feedback

 

 

And in case you're curious to see the first version (the early 2020 one, coupled to the game and I needed to run the editor and save the JSON file):

Main editor

Sequence editor

Pretty much the same thing, just... ugly.