You need four steps:
- Obviously, install the plugin and activate it. You need the
res://addons/madtalk
folder (which contains the editor plugin) and theres://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 whichControl
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, anddialog_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.
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.
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 aControl
aligned tofull rect
covering the viewport with mouse filter tostop
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 aPanel
,TextureRect
orNinePatchRect
(but doesn't have to be) - A
RichTextLabel
to hold the actual message text (it was designed to be aRichTextLabel
even if you don't use BB code, and compatibility with a commonLabel
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.
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
orNinePatchRect
(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 beHBoxContainer
, 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.
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.
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 editorName
is what you want to show up in the name labelAvatar
is a default avatar, shown when no variant was specified in the dialog. For many games this is the only one you needVariants
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.)
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.
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 aColorRect
set to black with some alpha transparency, so it makes the game scenario a darker background while the dialog is going onTransition 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.
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.
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.
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
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.
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
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.
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 (ID0
). 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 belowstart_dialog(sheet_id: String, sequence_id: int)
: same as above, but starts directly at the specified sequence ID instead of0
. Use only if you know what you're doing, as it's easy to lose track of thingsdialog_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 itemdialog_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 everdialog_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'tselect_menu_option(option_index)
: when using external menu, selects the menu option by indexget_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 variableget_variable(variable_name: String)
: returns the value for the dialog variableexport_game_data()
: returns aDictionary
with all the game data handled by MadTalk (dialog variables and in-game time), to be serialized for saving the game stateimport_game_data(data: Dictionary)
: the reverse of the above, sets the dialog variables and in-game time based on aDictionary
previously exported byexport_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()
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 Container
property 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 aDictionary
with the fields:year
month
day
weekday
(numeric)weekday_name
(String)wday_name
- abbreviated version ofweekday_name
(String)date
- day and month, format internationaldd/mm
(String)date_inv
- day and month inverted, format USmm/dd
(String)hour
minute
second
(MadTalk doesn't work with seconds, will only be different from00
if you modify by code)time
- hour and minute, format 24hHH: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
andtext_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)
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!
- 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
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):
Pretty much the same thing, just... ugly.