This is a dialogue system for GameMaker Studio (1 & 2). It features:
Click here to view/download the asset listing in the GameMaker marketplace. You can use this to download the project contents into your own project, rather than simply downloading my demo project. (Note: only for GMS2. Unfortunately I don't have a license to upload the GMS1 version!)
Click here to view/download the asset from itch.io.
CREDITS
The character sprites and portraits in the project/previews were made by Buch. The emotes were by Tomcat94. Also, some of the text effects (wave, colour shift) as well as the code logic I'm using for the effects is from this tutorial by diestware.
You do NOT have to credit me to use this asset. I want this to be a free and open-source tool. Use it for whatever you like (though I'd love to see what you do with it: feel free to @FCosmonaut on twitter to show me)!
FOREWORD
This has been in the works for ages! Eventually I want to do a proper tutorial on how it works, but for now, I'll just walk you through what you need to know to get it working.
The demo project contains the following:
NON ESSENTIAL
ESSENTIAL
NON ESSENTIAL
ESSENTIAL
NON ESSENTIAL
ESSENTIAL
NON ESSENTIAL
ESSENTIAL
NON ESSENTIAL
ESSENTIAL
NON ESSENTIAL
ESSENTIAL
Open up the demo project, or import its contents into your own. Replace the sprites, sounds, and fonts with your own assets. Feel free to rename the sprites, but you'll need to update any reference to them.
As said above, you can use an animated sprite with multiple frames. For GMS2 users: the sprite will animate at the "Speed" the sprite itself is set to in the sprite editor (assumes the mode is frames per second). For GMS1 users: you will have to manually set the animation speed by altering the "finishede_spd" variable in the create event of obj_textbox.
The sprite draws at the co-ordinates (finishede_x, finishede_y), which I have positioned down the bottom right corner of the dialogue box (minus the x and y buffers). Feel free to change these if you want it drawn somewhere else.
NOTE: The origin of my sprite is at the bottom-right corner, thus it appears anchored at the dialogue boxes' bottom right corner. You may want to do this too.
NOTE2: I have added a "shift" to the x co-ordinate of the sprite's position in the Draw GUI event of obj_textbox (down the very bottom). This is to give it a slight "wave" appearance in the horizontal direction. Feel free to remove this.
As said above, this parent object sets up all the defaults and sets out the code for detecting the interaction with the player. You'll want to:
- Change the playerobject variable to be whatever you've called your player object.
- Change the interact_key to whatever button you want to press to talk to the NPC. It's set to the "E" key by default.
- Set the detection_radius to be however far away to check for the presence of the player. This is used as the "radius" of the rectangle the speaker object checks for the player.
The myVoice, myPortrait, myName and myFont variables should be set to some default values. (Note: these could be just -1 like the other defaults, if ALL speakers set their own. But if you want to provide a default for characters, you could do it here.)
Note: You do not have to use portraits at all if you don't want. You can just use text!
The myPortraitTalk, myPortraitIdle, and their corresponding (x,y) co-ordinate variables are set to -1 so that, by default, animated portraits are NOT used. See the section "Using Animated Portraits" if you want to use them.
If you want to make an object part of the dialogue system as a speaker object, you'll need to make it a child of par_speaker. Remember to put event_inherited() in the create and step events of this object. Otherwise, it won't inherit the necessary code from its parent, such as checking for the presence of the player to start the dialogue.
A character can have its own voice, font, name, and portraits sprite. These are set with the variables myVoice, myFont, myName, and myPortrait. You only need to set these once in the create event, and they will be used whenever the object is speaking. Next are the variables that will be dependent on the particular round of dialogue: myText, mySpeaker, myEffects, myTextSpeed, myTypes, myNextline, myScripts, myTextCol, myEmotion, and myEmote. These are all set to their defaults using the script reset_dialogue_defaults(). These are all the arguments used in the create_dialogue and create_textevent scripts. HOWEVER: some of these are optional arguments: the bare minimum to specify is the myText and mySpeaker variables (the rest can use the defaults). So, for simple NPC dialogues, you only need to set these. For example, this is all you need for a simple speaker object:
event_inherited();
myText[0] = "Hello!";
mySpeaker = -1;
It will inherit the rest of the defaults from its parent. Be sure to examine the obj_examplechar for how a speaker can work, and also check out the sections"Using Text Effects, Speeds, and Colours", as well as "How The Scripts Work".
In its create event, you'll want to take a look at the variables in the section "Customise (FOR USER)". Anything there, you can change.
Take careful note of the "scale" variable. This controls the scale of the entire dialogue system. It draws everything in the Draw GUI event so that we avoid positioning the dialogue relative to the view. Generally speaking, the GUI is at a much "larger" scale than the view, so you'll likely need a value above 1. By default, it's set to 3, since the view in the demo project is a third of the viewport.
The x_buffer and y_buffer variables are for placing the text in the dialogue box. Presumably, you don't want the text right up in the top left corner. These buffer values correspond to the distance the text will sit away from the edges of the box.
NOTE: These should be scaled, so while you can change the numbers, leave the " * scale " multiplier on both of them. The portrait_frame, dialogue_box, emote_sprite, choice_snd_effect, and select_snd_effect are how the textbox will refer to your assets in the code, so that you only have to set these to your own assets once in the create event.
The default_col variable is the colour the text will be drawn (when no effects or colours have been specified). The choice_col and select_col are the colours the text will appear when you are selecting a dialogue option.
The name_col variable is the colour that character names will be drawn, and similarly, name_font is the font it will use. The name_box_x and _y variables are the co-ordinates that the spr_namebox sprite will be drawn. The name_box_text_x and _y variables are where the name text itself will be drawn.
NOTE: The name is centre-justified by default. To change this, you will need to go to:
obj_textbox -> Draw GUI event -> region Draw name and namebox (press on the [+] if it isn't open) -> Draw name text
In here, you will see draw_set_halign(fa_center), and also draw_set_halign(fa_left) - this is the default - after the text is drawn. Remove or alter these if you like.
The priority_snd_effect variable is just for setting the priority when using audio_play_sound. It will use this variable as the priority whenever the "voices" are played. Feel free to change this or keep it as is.
The open_mouth_frame variable is used for animated sprites (see the section on Using Animated Sprites for more info). This should be set to the frame where character's mouths are "open".
Again, you'll want to jump into this object's create event and take a look at the "Customise (FOR USER)" section. This object is the one that is created for a once-off text event, when calling the script create_textevent. The script actually allows you to specify the speaker in this, in the same way as the create_dialogue script. However, if you only supply the minimum arguments (lines of text, and put the speaker as "-1") then it will use the defaults in this object, so you WILL need to set them.
You have the option of using animated portraits. There are two kinds of animations, idle and talking. These should be set to animated sprites (ie. sprites with multiple frames), in the objects' create events (just like the myPortrait variable):
myPortraitIdle = spr_whatever_you_called_the_sprite;
myPortraitTalk = spr_whatever_you_called_the_sprite;
Note: if you do not set these variables, animated portraits will not be used.
The idle animation is played when the character is NOT talking, and the talking animation will play while the character talks (ie. while letters are spelling out). Note that these sprites will be drawn on TOP of the character's portrait (myPortrait), so you only need to put the ANIMATED bits in this sprite (eg. a talking mouth).
If the size of your animated portrait is different than the portrait set in myPortrait variable, you will want to set an x and y position of where to draw your animation from. These are set using myPortraitTalk_x, myPortraitTalk_y, and myPortraitIdle_x, myPortraitIdle_y. The position (0,0) would be the top left of the character's original portrait (myPortrait). If you do NOT set these variables, it will assume the portraits are all the same size, and the animations will be drawn anchored at the top left of the original portrait (0,0). See the demo project for an example: the examplechar's idle animation is the same as its portrait, so no co-ordinates are set. But the talking animation IS smaller (it is just the mouth), so co-ordinates are set.
For GMS2 users: The speed of the animation will match whatever you set as its image_speed (frames per second) in the sprite editor.
For GMS1 users: You will have to set the speeds for your portrait animation manually by setting the variables myPortraitIdle_imgspd and myPortraitTalk_imgspd in the create event of your character objects, alongside all of the other portrait variables.
I've had a bit of fun with this and included two different "types" of talking. The following characteristic is common to both of them:
However, one style includes the "consideration of vowels", while the other one does not. The "Consider Vowels" method is on by default.
The "Consider Vowels" method is on by default. To change to "No Vowel Consideration", you will need to make a tweak to the code. Go to:
obj_textbox -> Draw GUI event -> region TYPE 0: NORMAL DIALOGUE -> region Check for Pause, Voice, Animated Sprite -> Animated Sprite -> To include consideration of vowels
You will see some paragraphs of code between some "//*/, /*/, //*/". On the first //*/, right beneath the commented heading, change this to /*/. You should see the code comment "toggle", and a different section of code should become highlighted. This is the "No Vowel Consideration" code.
You can modify how the text is displayed by changing the effects, speeds, and colours of the different letters. These correspond to the myEffects, myTextSpeed, and myTextCol variables in the speaker objects. The format for all of these is the same. You make an array of entries, where the entries are in pairs, eg. myEffects = [1, 1, 2, 2, 3, 3, 4, 4, ...]. So, every time you want to tweak the effect, speed, or colour, you would put in a two-part entry.
The first entry should be the number (character count) of the character that you want the effect (or colour or speed) to start. Note that the first letter has a character count of 1, not 0. The second entry is the effect (or colour or speed) you want to change it to.
For example, say we have these lines of text:
myText[0] = "Hello there friend!";
myText[1] = "How are you doing?";
And say we want to do the following:
Then we would put:
myTextSpeed[0] = [1,0.5, 7,1]; //on character 1, change the speed to 0.5 Then on character 7, change it to 1.
myTextCol[0] = [13, c_blue, 19, c_white]; //on character 13, change the colour to blue. Then on character 19, change to white.
myEffects[1] = [1, 2]; //on character 1, apply "effect 2". We never change back, so whole line is affected.
Note that when using the variables, you ONLY have to make entries for the pages that have those effects. (So you don't need to fill myEffects[0] = -1 if we are not using effects in that line, this will be taken care of automatically.)
You can view the "User Event 0" of obj_examplechar, and the "Create" Event of obj_camera for more examples.
In the Draw GUI event of obj_textbox, scroll down until you see a long switch(effect) statement. Currently, there are 8 effects (but you could add more):
0 = normal
1 = shakey
2 = wave
3 = colour shift
4 = wave and colour shift
5 = spin
6 = pulse
7 = flicker
This script takes the form:
create_dialogue(myText, mySpeaker, myEffects, myTextSpeed, myTypes, myNextLine, myScripts, myTextCol, myEmotion, myEmote);
Most of the arguments are optional. You technically only need to provide the first two arguments, myText and mySpeaker: it will assume the defaults for the rest.
What all the arguments do:
This should be the array of text you want in the dialogue. NOTE: if you want a line to be a dialogue choice, you'll need to put an array of the choices at that line. For example:
myText[0] = "Hey, what's your favourite colour?";
myText[1] = ["Red", "Blue", "Green"];
The myText[0] entry is a normal line of text, whereas the myText[1] entry stores three dialogue options. You'll need to also set myType[1] = 1 for dialogue options (see myTypes below).
This should be the array of objects who will say each line. Each entry in this array will correspond to the entry in the myText array. That is, mySpeaker[0] will say line myText[0], and mySpeaker[1] will say myText[1]. For example, we can have two speakers:
myText[0] = "Hey Susan, how's it going?"
mySpeaker[0] = obj_bob;
myText[1] = "Not too bad, not too bad.";
mySpeaker[1] = obj_susan;
If you put "-1" for this argument, the script will automatically make an array for you, where the instance calling this script will be set as the speaker for every line.
See "Using Text Effects, Speeds, and Colours".
See "Using Text Effects, Speeds, and Colours".
This should be the array of dialogue "types" for each line. There are two "types" of text events in this dialogue system. Type 0 is what you might call "normal" dialogue, whereas Type 1 will be a "dialogue choice", where the player is selecting a dialogue option. Note that even if you only have ONE dialogue choice, whereas the other 4 lines are normal, you'll still need to put eg.
myTypes = [0, 0, 0, 1, 0];
Where the "1" is the entry in the myText array that is the dialogue choice. If you put "-1" for this argument, the script will automatically make an array for you and make every line a Type 0 dialogue.
This only needs to be set for the entries you have set a dialogue choice (Type 1). At that entry, you need to put an array that stores where you want the next line of dialogue to go to depending on the dialogue choice. Eg, following on from our "favourite colour" example above, you could declare:
myText[0] = "Hey, what's your favourite colour?";
myText[1] = ["Red", "Blue", "Green"];
myNextline[1] = [2, 3, 4];
myText[2] = "Red's pretty hot.";
myText[3] = "Blue's pretty cool.";
myText[4] = "Green, like the grass.";
myNextline[2] = 5;
myNextline[3] = 5;
myNextline[4] = 5;
myText[5] = "Well, bye!";
Note that each dialogue choice sends the dialogue to a different line. And then afterwards, each returns back to line 5.
Note: You don't HAVE to have a dialogue choice send the character to a specific line. If you don't want the choice to matter, you don't have to change this variable. You can leave it blank.
This is for if you want a custom script (built in functions will not work) to run after a specific dialogue. You should be able to run any script with up to 16 arguments. For example, in the demo project, we run the change_variable and create_instance_layer scripts. We can do this by setting the myScripts variable for a line. You simply give it an array containing the script name in the first index, and any argument thereafter (if any).
myScripts[i] = [script, arg1, arg2, arg3...].
Note that for dialogue choices, you can set multiple entries so that the script that runs depends on player choice.
myText[0] = ["My name is Xena.", "My name is Antonio."];
myScripts[0] = [[change_variable, obj_player, "name", "Xena"], [change_variable, obj_player, "name", "Antonio"]];
See "Using Text Effects, Speeds, and Colours".
This should be an array of "emotions" that correspond to the frame of the portrait sprite for whatever speaker is saying the current dialogue line. Each entry in the array should match the entry in the myText array. You can also just leave this blank or put 0 (ie. if you don't have any "emotions" in your portrait sprites).
This should be an array of "emotes" that correspond to the frame of the emotes sprite. You can leave this blank or put "-1". If you only want an emote on some lines but not others, put "-1" for the entries you do NOT want an emote.
This script takes the form:
create_textevent(myText, mySpeaker, myEffects, myTextSpeed, myTypes, myNextLine, myScripts, myTextCol, myEmotion, myEmote);
The end result will be very similar to if you had declared create_dialogue, except that it also creates an obj_textevent who then creates the obj_textbox. The obj_textevent will destroy itself as soon as the dialogue is finished.
Again, you technically only need to provide the first two arguments, myText and mySpeaker: it will assume the defaults for the rest. The only difference is now, if you put "-1" for the mySpeaker argument, it will set the speaker to an obj_textevent instead of one of your speaker objects. So you can use this event if you don't want to set a particular speaker.
NOTE: you don't need to declare the argument variables (myText, mySpeaker, etc...) to create a dialogue. You can input arrays straight into either script. Here as an example:
var ob = obj_bluey;
var og = obj_greeny;
var op = obj_red;
create_textevent(
[ //myText
"Sorry Greeny, but I'm way cooler than you.",
"Wait, WHAT?",
"I'm Greeny. No-one's cooler than me.",
"What do you think, Red?",
["Bluey is cooler.", "Greeny is cooler."],
"Haha, see?",
"Tch...",
"HA! It's as I said.",
"Aw..."
],
[ob, og, og, ob, op, ob, og, og, ob], //mySpeakers
[ [23,4, 33,0], [7,1, 11,0], [21,1, 29,0], [-1], [-1], [1,5, 5,0], [-1], [1,6, 3,0], [-1] ], //myEffects
-1, //myTextSpeed
[0, 0, 0, 0, 1, 0, 0, 0, 0], //myTypes
[0, 0, 0, 0, [5, 7], 0, -1 ,0, -1], //myNextLine
-1, //myScripts
-1, //myTextCol
[0, 3, 2, 1, 0, 5, 3, 1, 2], //myEmotion
-1 //myEmote
);
NOTE2: If you're using GMS1, the [entry1, entry2, etc] format will NOT work. You will have to instead use the included script create_array(entry1, entry2, etc). More info below.
This script takes no arguments, it simply resets all of the dialogue variables to their defaults (as if you had created the object fresh).
You will want to call this in any speaker object if you have already declared a bunch of myText/mySpeaker/myEmotions etc for a different dialogue. These variables will be arrays filled with entries, and you will have to delete them if you want to "start fresh" (otherwise they will keep values from the old declaration!).
So, if you want to change those variables in your character at some point, be sure to call reset_dialogue_defaults() before proceeding to set them again.
As said above, this only comes in the GMS1 file - GMS2 already has simliar functionality. In GMS2, you can declare arrays like this:
new_array = [entry1, entry2, entry3...];
But you cannot do this in GMS1. So that you don't have to go through the hassle of declaring each entry (new_array[0] = entry1, new_array[1] = entry2, etc), I added this script. Now you can just call:
new_array = create_array(entry1, entry2, entry3).