Tutorials: Dialog Programming Tutorial


This is a tutorial for making interactive conversations using the AGAST programming language. In case you have no programming experience, you should find this tutorial useful because it explains the script language along the way. If you are already a programmer, this tutorial should make for some very light reading, because you just have to learn two very basic predefined-functions, called addChoice and dialog, that cover the vast majority of dialog programming.

If you've played any LucasArts adventure game, you are most certainly accustomed to dialog menus. A dialog menu is simply a list of choices that is presented to the user. Each choice in the menu represents something the user is able to say. When the user selects a choice, by highlighting it with the mouse and clicking on it, the game continues along some respective course of action. By using several dialog menus strategically, you can design a pretty fun and interesting conversation.


Design

Before coding anything though, you really have to think about how you would design an interactive conversation that hasn't even happened yet--one that could unfold in many different ways. In thinking about it, take the perspective of the person who will be playing the game. The player sees an interesting-looking character in the scene, so he selects "talk to" and then clicks on that character. Now what happens?

Take a moment to design a simple conversation, just with a pencil and some paper. Or, at least, take a couple minutes to think about how you would write up a conversation, so that someone else, like your best friend for example, could read and understand it. Use as many dialog menus as you want to make it interesting. If you need an idea for the scenario, just imagine you're walking through a forest and you happen upon a guy dressed up in a duck suit, locked up in a cage, hanging from a tree, and you start talking to him. That's the one I'll be using in my own example, later in this tutorial.

Done already? Very good! Most likely, you listed some choices, and what would happen when (or if) each is actually chosen. You might have an outline or diagram, or even a paragraph of text. Mine is a sort of sequence of instructions in outline form. I went with the name Ego for my player's character (to stay consistent), and Bill for the other guy (just because it reminded me of a duck for some reason). And I defined two dialog menus, each separate from the actions they would trigger. Here's how mine looks (see if it's similar in form to yours):

  • When the user talks to Bill, present these choices to the user:
    1. "Having a bad day?"
    2. "So how does a guy get dressed as a duck, locked up in a cage, and hung from a tree anyhow?"
    3. "You're probably not going to believe this, but the exact same thing happened to me last week."
    4. "Is that a costume, or did you just swallow the other three-hundred fifty-seven parakeets who were locked up in there with you?"
  • If user chooses 1, do this:
    • Bill says "Yeah, you can't really do much worse than this!"
  • Or else, if user chooses 2, do this:
    • Ego says line
    • Bill says "It wasn't easy!"
    • Bill says "Do you really want to know?"
    • Present these choices to the user.
      1. "Nah, I was just cracking wise with ya."
      2. "Hey, if you don't want to tell me, that's fine with me."
      3. "Well yeah, I have to admit I'm more than a little curious."
    • If user chooses A or B, nothing special happens.
    • Or else, if the user chooses C, do this:
      • Bill says "OK, I'll tell you as soon as I'm free."
  • Or else, if the user chooses is 3, do this:
    • Bill says "You too, huh?"
  • Or else, if user's choice is A4, do this:
    • Bill says "Yeah, very funny."
  • Finally, do this:
    • Bill says "Now, can you help me get out of here please?"
    • Ego says "You mean the suit or the cage?"
    • Bill says "Both of them!"
    • Ego says "I'll try, but you'd better be wearing something underneath."
  • And the conversation is over, so return to the game.

So now, what do we have here? Why, it's a program! Not one for the computer, but one for us human beings. Your best friend, for instance, should be able to read it, and understand all the possible interactions that might take place. He can read through it, line by line, as if he were a computer. Doing just that, here is one possible interaction that might unfold:

  • User chooses 2, so Ego says "So how does a guy get dressed as a duck, locked up in a cage, and hung from a tree anyhow?"
  • Bill says "It wasn't easy!"
  • Bill says "Do you really want to know?"
  • User chooses C, so Ego says "Well yeah, I have to admit I'm more than a little curious."
  • Bill says "OK, I'll tell you as soon as I'm free."
  • Bill says "Now, can you help me get out of here please?"
  • Ego says "You mean the suit or the cage?"
  • Bill says "Both!"
  • Ego says "I'll try, but you'd better be wearing something underneath."


Implementation

That's all good, but your best friend doesn't have a keyboard, mouse, or modem attached (unless he has been in some horrific accident), so he isn't much use as a game interpreter. Instead, you have to explain to the computer how to do it. The computer is a little pickier than (most) people though, because it is just a dumb machine. So we must translate our program into a language the computer can understand. Computer languages are made for the purpose of explaining complex ideas to a simple machine. Luckily, or rather by design, it isn't that much different to explain it to a computer.

First of all, we'll assume there are two objects, Ego and Bill, and that they are all ready for action, with their animations configured and their colors and positions set all set to go. Understand that each dialog is a three-step process. You have to build the dialog menu, let the user choose what to say, and then take the appropriate action in response. To keep it simple and organized, I like to put the code for each dialog menu in it's own script. So, since there are two dialog menus above, I will write two scripts.

script myDialog1()
{
	addChoice(1, "Having a bad day?");
	addChoice(2, "So how does a guy get dressed as a duck, locked \.
                      up in a cage, and hung from a tree anyhow?");
	addChoice(3, "You're probably not going to believe this, but \.
                      the exact same thing happened to me last week.");
	addChoice(4, "Is that a costume, or did you just swallow the \.
                      other three-hundred fifty-seven parakeets who \.
                      were locked up in there with you?");

	switch dialog()
	{
	case 1:
			BILL: "Yeah, you can't really do much worse than this!"
	
	case 2:
			BILL: "It wasn't easy!"
			"Do you really want to know?"
			myDialog2();

	case 3:
			BILL: "You too, huh?"

	case 4: 
			BILL: "Yeah, very funny.
	}
}

script myDialog2()
{
	addChoice(1, "Nah, I was just cracking wise with ya.");	
	addChoice(2, "Hey, if you don't want to tell me, that's \.
			fine with me.");
	addChoice(3, "Well yeah, I have to admit I'm more than \.
			a little curious.");

	switch dialog()
 	{
	case 3:
			BILL: "OK, I'll tell you as soon as I'm free."
	}
}

First, let's examine the basic syntax. The text enclosed in double-quotes are strings. You can see that some of the strings are continued to the next line with backslashed-dots ("\."). And you see those curly braces in Step 3? Those are used to group together several statements into blocks of code. Each script is really just a block of code, which is why they use those curly braces! And then you might notice the funny-looking parenthesis with nothing in them. That just means the script or function has no parameters. They can just as easily be omitted, but I think they help make the code more readable.

AddChoice is a predefined function in the AGAST script language. (All the predefined functions are fully explained in the AGAST Guide, in case you ever want to look them up.) Each function requires a certain number of arguments, which are simply values that you pass to it. AddChoice expects two arguments: a code number that you make up, and a string of text that is to be displayed in the dialog menu. Each time you call the addChoice function, it will add a new choice to the interpreter's dialog menu. But it doesn't show it to the user quite yet.

The second and final function you need to know for dialogs is the dialog function itself. It takes no input arguments, but it does return a value as output: the code number of the choice the user selected to say. When you call the dialog function, the interpreter will show your dialog menu to the user, wait for a response, clear the dialog menu, and return the code number of the choice the user selected.

Above, the dialog function is evaluated as the expression of a switch-statement. The value it returns decides which case to execute, so you have to write the code which is supposed to be the outcome. Such code usually involved directing objects to say things, or play animations of body gestures or facial expressions.

If other scripts are running in the background, perhaps to control the animation of scenery or to play random sounds effects, like chirping birds for example, they will continue without being disturbed.

Nothing here is laid in stone either. The AGAST language is a very flexible and free-form programming language that lets you write the same thing in possibly hundreds of different ways, and find your own way of doing things that I have never imagined. You always have to use those two functions for dialogs (unless you decide to write your own dialog system, which is entirely possible), but how you use them is totally up to you.


Branching and Looping Dialogs

Above is an example of a branching dialog, because the user's choice causes a branch in game logic; the choice is selected by the user, and the program branches to the desired outcome. But then it's all over, and the user has click on the object to talk again, and restart the event. But most dialogs combine such branching with looping. A looping dialog will present the same menu over and over again, so that the user may choose more than one choice. Looping dialogs are not that much different really. All you really have to do is put the whole thing in a loop, and have some mechanism to end that loop (so it doesn't repeat forever).

I'll add some code to the script above to support looping. All it needs are some slight modifications for looping, and a new exit choice to let the user choose to stop talking. It's important to give the user an exit choice, otherwise he may get stuck in the conversation and become frustrated or lost. I always give the exit choice a code number of 0, just for the sake of convention.

script myDialog1
{
	loop
	{
		addChoice(1, "Having a bad day?");
		addChoice(2, "So how does a guy get dressed as a duck, locked \.
        	              up in a cage, and hung from a tree anyhow?");
		addChoice(3, "You're probably not going to believe this, but \.
	                      the exact same thing happened to me last week.");
		addChoice(4, "Is that a costume, or did you just swallow the \.
	                      other three-hundred fifty-seven parakeets who \.
	                      were locked up in there with you?");
		addChoice(0, "I'll catch you later, ok?");

		switch dialog()
		{
		case 1:
				BILL: "Yeah, you can't really do much worse than this!"
		
		case 2:
				BILL: "It wasn't easy!"
				"Do you really want to know?"
				myDialog2();

		case 3:
				BILL: "You too, huh?"

		case 4: 
				BILL: "Yeah, very funny.

		default:
				BILL: "All right, I'll just keep hanging out here I guess."
				"Just don't forget about me!"
				EGO: "Don't worry, I never forget anything.");
				"Not that I can remember, at least."
				BILL: "Oh, that's reassuring."
				return;
		}
	}
}

Notice that last default-clause? I don't bother testing if the choice is equal to 0, because, by process of elimination, that's the only value remaining. The default-clause will handle whatever is left over. The loop-construct causes the dialog menu to keeping repeating. The loop ends when the exit choice is taken, and, as a result, the return-statement gets executed. The effect of the return-statement is to stop the script, just as if it had reached its end.


Dynamic Conversations

Sometimes, you may want the game to remember when something has been said, to shape future conversations (or affect anything else that happens in the game). For instance, it is common for given choices to disappear after they are chosen once by the user, and they usually stay that way for the rest of the game, or just the rest of that particular conversation.

Dynamic conversations are accomplished by declaring and modifying variables. Once something has been said, a flag variable can be set, causing the state of the game to change. Then, a condition can be added to take special action for any given state. For example, if choice 3 is selected, then in the case-particular code for that choice, a variable, say Said3 can be set to true. The next time the dialog menu is build, this variable can be consulted, so that if it is true, the choice will not be added to the dialog menu.

Here is the example from the last section, modified so that the choice with code 3 can only be selected once. Then it disappears.

script myDialog1
{
	var Said3;
	loop
	{
		addChoice(1, "Having a bad day?");
		addChoice(2, "So how does a guy get dressed as a duck, locked \.
						up in a cage, and hung from a tree anyhow?");
		
		addChoice(3, "You're probably not going to believe this, but \.
						the exact same thing happened to me last week.")
						unless Said3;

		addChoice(4, "Is that a costume, or did you just swallow the \.
						other three-hundred fifty-seven parakeets who \.
						were locked up in there with you?");

		addChoice(0, "I'll catch you later, ok?");

		switch dialog()
		{
		case 1:
				BILL: "Yeah, you can't really do much worse than this!"		

		case 2:
				BILL: "It wasn't easy!"
				"Do you really want to know?"
				myDialog2();

		case 3:
				BILL: "You too, huh?"
				Said3 = true;

		case 4: 
				BILL: "Yeah, very funny.

		default:
				BILL: "All right, I'll just keep hanging out here I guess."
				"Just don't forget about me!"
				EGO: "Don't worry, I never forget anything.");
				"Not that I can remember, at least."
				BILL: "Oh, that's reassuring."
				return;
		}
	}
}

What I mean by "disappears" is that it doesn't come back until you end the conversation, and start a new one. That's because Said1 is a local variable (meaning local to this myDialog1 script).

If your design requires that the choice is gone forever (during the rest of the game), that's no problem either. Just prefix the declaration with the static-modifier.

	static var Said3 = false;
A static variable is stored throughout game's entire lifetime, and all instances of the myDialog1 script will share it. Unlike global variables (which are also static), local static variables can't be seen from within other scripts. That means you can use names like Said3 in different script, without overlapping them.

However, if you want to see the variable from within more than one script, such as myDialog1 and myDialog2, for example, you will have to use a global variable. Then the scripts can share it. But that's also dangerous, because it's harder to keep track of all the different values, and very easy to introduce a bug. So you should write a comment near that variable that tells which script use it, and how.

All variables, no matter their scope or lifetime, are initialized automatically to false (or 0) whenever it is created. Static variables are initialized whenever the game is started, and non-static variables are initialized whenever their script is called.


Starting the Conversation

Usually, although not always, a conversation will be started when the user clicks the Talkto item on some object, or if the user right-clicks an object, especially if that object is another character. To code this, you just have to call your main dialog script (the one that defines the first level of conversation), which is called myDialog1 in this tutorial.
event Talkto -> Bill
{
	myDialog1();
	resume();
}

event default click
{
	myDialog1();
	resume();
}

Remember to resume input at the end of every event script by calling the resume function, because input is suspended whenever an event is started by the interpreter. If you forget, the user will have no control of the game, and he will get stuck. (But in the testgame, you can always press 'R' to resume.)


Conclusion

By mixing these basic techniques for conversation, and applying them to some clever game designs, interesting conversations can be created. There is really no end to the number of unique conversations that can be designed, nor is there a limit to the number of ways such conversations can be programmed.

It might be a good idea to stick to the conventions above, such as writing one dialog per script. (In programming it's always wise to KISS, or Keep It Simple, Stupid!) Because then you'll be able to understand your code, and maintain it, as long as necessary, and you won't become frustrated and give up or be forced to start over. As long as rely upon consistent conventions, even large and feature-rich dialogs will not become too complex.

The great thing is, you don't have to be a brilliant conversationalist to predefine a brilliant conversation! So plan well, take your time, and have fun!