By Mine Own Hand Dev Thread
by Eric Armstrong · in Torque Game Builder · 04/23/2009 (9:51 am) · 8 replies
I've decided I want to open up the development I'm doing on By Mine Own Hand (BMOH). BMOH is a game I've been working on for quite a while, but never in a very structured or meaningful manner. To help keep myself working more consistently I have decided to start a development thread. Within this thread I will be making posts on at least a weekly basis discussing what I've done on BMOH.
The reason I'm doing this as a thread and not as a blog is because I want to post as much of the code I'm working on as I can. For BMOH, I will be gutting and reworking some functions and such from the Adventure Kit, so those I won't be posting here, but if people are interested, I can maybe create a sister thread in the Adventure Kit forums for those people with the Adventure Kit and a desire to see what I'm doing with that code.
Essentially what I'll be doing here is giving away all the code for my game, as I develop it. Other TGB owners will be able to see the thought processes I go through as I develop. They will be able to see how the code changes and grows over time. Some people may ask why I would do that. Basically, that is my payment to you folks for coming into this thread, posting your thoughts and ideas about what and how I'm going about things, and keeping me working and motivated. The only thing I ask of the people who read this information, and who use the code within, is to continually ask for more. Keep me motivated, keep me working, until this game is done. Then of course, buy the game. :-)
The first thing I will be going over is the dialog system for BMOH. Expect that post no later than Sunday April 26th.
The reason I'm doing this as a thread and not as a blog is because I want to post as much of the code I'm working on as I can. For BMOH, I will be gutting and reworking some functions and such from the Adventure Kit, so those I won't be posting here, but if people are interested, I can maybe create a sister thread in the Adventure Kit forums for those people with the Adventure Kit and a desire to see what I'm doing with that code.
Essentially what I'll be doing here is giving away all the code for my game, as I develop it. Other TGB owners will be able to see the thought processes I go through as I develop. They will be able to see how the code changes and grows over time. Some people may ask why I would do that. Basically, that is my payment to you folks for coming into this thread, posting your thoughts and ideas about what and how I'm going about things, and keeping me working and motivated. The only thing I ask of the people who read this information, and who use the code within, is to continually ask for more. Keep me motivated, keep me working, until this game is done. Then of course, buy the game. :-)
The first thing I will be going over is the dialog system for BMOH. Expect that post no later than Sunday April 26th.
#2
04/24/2009 (11:19 am)
Very cool idea. I've subscribed to the thread. :)
#3
I am starting with the dialog system, because it plays a very central role in BMOH. In setting out to create the game, one of the things I wanted to do was to make every part of the game interesting for the player, and give them something to do in every part of the game. Traditionally, in many RPG type games, conversations really are not that interactive. You may have 3 to 5 responses to choose from, but more often than not, all those responses lead to the same place. It is a false sense of choice. The second driving factor for coming up with the dialog system I did was that I wanted the story to unfold to the player via their actions. I did not want the story told to the player, I wanted the player to discover the story via their actions and interactions.
In doing some research on RPG dialog systems, I came across this post dealing with just this topic, and used the information within it to develop my initial design. Here is a screen shot of the initial dialog GUI:

As you can see, there is quite a few things going on in this GUI, so I will kind of break each section down and give a description as to its purpose.
Keywords
On the right hand side, you will notice a list of keywords. This is the basis for the dialog system. As the player talks to NPCs, or explores the world around them, they will gather certain keywords they can use to build conversations with the people of the world. They also have the ability to add keywords to the list, which is an important part of the game. Not everything is spelled out for the player, they must think about what is happening, and use the keyword system to help find out other pieces of information.
From a code standpoint, each keyword is a script object. For example:
display_text holds how the keyword is displayed in the list.
isPlural is a flag telling us if the keyword is a plural form, this is used when integrating a keyword into a question or statement, so we get the grammar correct.
internalName is a name for this keyword we use to look it up.
url_text is a formatted version of the keyword that allows it to be clickable
type is the type of keyword. You will notice from the screen shot, that the player has the ability to filter the list by type. This facilitates that functionality.
All keyword objects are stored in a SimGroup:
Dialog Builder
In the upper center of the GUI is where the player puts together their question/statement. In the screen shot, not all of the possible buttons are being displayed. The buttons are broken into three types, what you are asking, how you are asking it, and third (there are no buttons for this section in the picture) what kind of influence you put on it. For any given conversation, the player will build their questions and/or statements using these buttons. This allows the player control over how they interact with the NPC, while still allowing me to manage the wording of those statements.
Ok, it is time for some more code. Let us start with the initialization of the dialog GUI:
...Continued...
04/25/2009 (7:23 am)
One word of caution as we begin, all of the code you will see is first run dump out of my head. Nothing has been gone over more than to simply remove any obvious bugs, or things that did not work right. We will be revisiting all of these systems more than once and cleaning, tuning, and overall giving better design. With that said, I’m more than willing to answer questions, take criticism, and any and all suggestions for better more efficient ways of doing things. Let us get the party started.I am starting with the dialog system, because it plays a very central role in BMOH. In setting out to create the game, one of the things I wanted to do was to make every part of the game interesting for the player, and give them something to do in every part of the game. Traditionally, in many RPG type games, conversations really are not that interactive. You may have 3 to 5 responses to choose from, but more often than not, all those responses lead to the same place. It is a false sense of choice. The second driving factor for coming up with the dialog system I did was that I wanted the story to unfold to the player via their actions. I did not want the story told to the player, I wanted the player to discover the story via their actions and interactions.
In doing some research on RPG dialog systems, I came across this post dealing with just this topic, and used the information within it to develop my initial design. Here is a screen shot of the initial dialog GUI:

As you can see, there is quite a few things going on in this GUI, so I will kind of break each section down and give a description as to its purpose.
Keywords
On the right hand side, you will notice a list of keywords. This is the basis for the dialog system. As the player talks to NPCs, or explores the world around them, they will gather certain keywords they can use to build conversations with the people of the world. They also have the ability to add keywords to the list, which is an important part of the game. Not everything is spelled out for the player, they must think about what is happening, and use the keyword system to help find out other pieces of information.
From a code standpoint, each keyword is a script object. For example:
new ScriptObject(FourFalls)
{
display_text = "Four Falls";
isPlural = false;
internalName = "Four Falls";
url_text = "<a:Four Falls>Four Falls</a>";
type = $PLACE;
};display_text holds how the keyword is displayed in the list.
isPlural is a flag telling us if the keyword is a plural form, this is used when integrating a keyword into a question or statement, so we get the grammar correct.
internalName is a name for this keyword we use to look it up.
url_text is a formatted version of the keyword that allows it to be clickable
type is the type of keyword. You will notice from the screen shot, that the player has the ability to filter the list by type. This facilitates that functionality.
All keyword objects are stored in a SimGroup:
if(!isObject(GlobalKeywords))
{
new SimGroup(GlobalKeywords);
GlobalKeywords.add(Caravan);
GlobalKeywords.add(FourFalls);
GlobalKeywords.add(BlockedRoad);
GlobalKeywords.add(BrokenWall);
}Dialog Builder
In the upper center of the GUI is where the player puts together their question/statement. In the screen shot, not all of the possible buttons are being displayed. The buttons are broken into three types, what you are asking, how you are asking it, and third (there are no buttons for this section in the picture) what kind of influence you put on it. For any given conversation, the player will build their questions and/or statements using these buttons. This allows the player control over how they interact with the NPC, while still allowing me to manage the wording of those statements.
Ok, it is time for some more code. Let us start with the initialization of the dialog GUI:
...Continued...
#4
...Continued...
04/25/2009 (7:27 am)
function initDialogSystem(%conversation)
{
if(!isObject(%conversation))
{
error("Conversation is not an object!!");
return;
}
if(!isObject(DialogScreen))
return;
//clear all buttons
BuilderButton1.text = "";
BuilderButton1.visible = false;
BuilderButton2.text = "";
BuilderButton2.visible = false;
BuilderButton3.text = "";
BuilderButton3.visible = false;
BuilderButton4.text = "";
BuilderButton4.visible = false;
BuilderButton5.text = "";
BuilderButton5.visible = false;
BuilderButton6.text = "";
BuilderButton6.visible = false;
BuilderButton7.text = "";
BuilderButton7.visible = false;
BuilderButton8.text = "";
BuilderButton8.visible = false;
BuilderButton9.text = "";
BuilderButton9.visible = false;
BuilderButton10.text = "";
BuilderButton10.visible = false;
BuilderButton11.text = "";
BuilderButton11.visible = false;
BuilderButton12.text = "";
BuilderButton12.visible = false;
NewKeyword.makeFirstResponder(false);
NewKeyword.setActive(false);
AllFilter.setStateOn(true);
//clear KeywordList
KeywordList.setText("");
//Clear History
DialogHistoryList.setText("");
//Clear New Keyword
NewKeyword.setText("");
//Setup with passed dialog
setupButtons(%conversation);
//Setup initial dialog from NPC
if(%conversation.initial_text !$= "" && !%conversation._visited)
{
DialogHistoryList.addText(%conversation.initial_text NL "", true);
DialogHistoryList.setPosition(6, 2);
}
else
{
if(%conversation._visited && %conversation.visited_text !$= "")
{
DialogHistoryList.addText(%conversation.visited_text NL "", true);
DialogHistoryList.setPosition(6, 2);
}
else
{
if(%conversation._completed && %conversation.completed_text !$= "")
{
DialogHistoryList.addText(%conversation.completed_text NL "", true);
DialogHistoryList.setPosition(6, 2);
}
}
}
//Setup keyword list
if(isObject(Player._keywords))
{
%count = Player._keywords.getCount();
for(%x = 0; %x < %count; %x++)
{
%keyword = Player._keywords.getObject(%x);
KeywordList.addText(%keyword.url_text NL "", true);
}
}
//Set the fact we have talked to this person
%conversation._visited = true;
$current_conversation = %conversation;
Canvas.pushDialog(DialogScreen);
}...Continued...
#5
Next, we clear out all 12 of the dialog buttons. That code is in need of refactoring into its own method. Then we remove first responder on the NewKeyword input and set it to be inactive. This is simply a hack to get by something I’ll have to track down from the code side. It has been a while since I wrote this code, so I am not exactly sure what it was doing, but we will explore it when I actually implement the new keyword functionality. We finish our initial cleanup by making sure the filter will display all keywords, and clearing the keywords list and the dialog history section.
Next, we setup the buttons based on this conversation:
As you can see, this method simply takes the button text and command from the conversation object and sets up the appropriate button. We will be doing some refactoring on this method in the future also, as the buttons are meant to be dynamic based on the current player stats. This code was the first I wrote for BMOH, so there was not any player structure or anything around yet.
Next, we setup the initial text that will display to the player. This can change based on if the player has talked to the NPC before or not. Right now, the conversation system only supports a two-stage conversation tracker, you have talked to the person before, or you have not. Depending on exactly how the conversation sections of the game workout, I may look at developing a more robust method for tracking multiple conversation states, or perhaps being able to link conversation together.
Finally, we add all the known keywords for the player to the keyword list, mark this conversation as visited, and display the dialog.
...Stay tuned for part II where we cover the remainder of the dialog screen and system...
04/25/2009 (7:28 am)
The first thing you will notice is that the initialization function takes a parameter called %conversation. This conversation object will control the functionality of the dialog GUI. We will get into the structure of that object in a minute. First thing we do in the method is make sure we have a conversation object, and that the GUI object exists; simply some basic sanity checking.Next, we clear out all 12 of the dialog buttons. That code is in need of refactoring into its own method. Then we remove first responder on the NewKeyword input and set it to be inactive. This is simply a hack to get by something I’ll have to track down from the code side. It has been a while since I wrote this code, so I am not exactly sure what it was doing, but we will explore it when I actually implement the new keyword functionality. We finish our initial cleanup by making sure the filter will display all keywords, and clearing the keywords list and the dialog history section.
Next, we setup the buttons based on this conversation:
function setupButtons(%conversation)
{
if(%conversation.button_text_1 !$= "")
{
BuilderButton1.text = %conversation.button_text_1;
BuilderButton1.Visible = true;
BuilderButton1.Command = "onCommand(" @ %conversation.button_command_1 @ ");";
}
if(%conversation.button_text_2 !$= "")
{
BuilderButton2.text = %conversation.button_text_2;
BuilderButton2.Visible = true;
BuilderButton2.Command = "onCommand(" @ %conversation.button_command_2 @ ");";
}
if(%conversation.button_text_3 !$= "")
{
BuilderButton3.text = %conversation.button_text_3;
BuilderButton3.Visible = true;
BuilderButton3.Command = "onCommand(" @ %conversation.button_command_3 @ ");";
}
if(%conversation.button_text_4 !$= "")
{
BuilderButton4.text = %conversation.button_text_4;
BuilderButton4.Visible = true;
BuilderButton4.Command = "onCommand(" @ %conversation.button_command_4 @ ");";
}
if(%conversation.button_text_5 !$= "")
{
BuilderButton5.text = %conversation.button_text_5;
BuilderButton5.Visible = true;
BuilderButton5.setStateOn(true);
}
if(%conversation.button_text_6 !$= "")
{
BuilderButton6.text = %conversation.button_text_6;
BuilderButton6.Visible = true;
}
if(%conversation.button_text_7 !$= "")
{
BuilderButton7.text = %conversation.button_text_7;
BuilderButton7.Visible = true;
}
if(%conversation.button_text_8 !$= "")
{
BuilderButton8.text = %conversation.button_text_8;
BuilderButton8.Visible = true;
}
if(%conversation.button_text_9 !$= "")
{
BuilderButton9.text = %conversation.button_text_9;
BuilderButton9.Visible = true;
}
if(%conversation.button_text_10 !$= "")
{
BuilderButton10.text = %conversation.button_text_10;
BuilderButton10.Visible = true;
}
if(%conversation.button_text_11 !$= "")
{
BuilderButton11.text = %conversation.button_text_11;
BuilderButton11.Visible = true;
}
if(%conversation.button_text_12 !$= "")
{
BuilderButton12.text = %conversation.button_text_12;
BuilderButton12.Visible = true;
}
}As you can see, this method simply takes the button text and command from the conversation object and sets up the appropriate button. We will be doing some refactoring on this method in the future also, as the buttons are meant to be dynamic based on the current player stats. This code was the first I wrote for BMOH, so there was not any player structure or anything around yet.
Next, we setup the initial text that will display to the player. This can change based on if the player has talked to the NPC before or not. Right now, the conversation system only supports a two-stage conversation tracker, you have talked to the person before, or you have not. Depending on exactly how the conversation sections of the game workout, I may look at developing a more robust method for tracking multiple conversation states, or perhaps being able to link conversation together.
Finally, we add all the known keywords for the player to the keyword list, mark this conversation as visited, and display the dialog.
...Stay tuned for part II where we cover the remainder of the dialog screen and system...
#6
Dialog Builder Part II
Let us now look at the structure of a conversation object:
As you can see, a conversation is a ScriptObject with a number of fields:
initial_text – This text will be displayed to the player when first engaging in this conversation. You will notice a number of anchored text values. These are keyword links. They allow you to put links to keywords into a conversation allowing the player to add keywords to their list.
visited_text – This text will be displayed after the player has initially talked with the person, but the conversation has not been "resolved".
completed_text – This text will be displayed once this conversation has been "resolved".
button_text_# - This controls the text displayed on each of the 12 possible conversation builder buttons. The conversation builder buttons are grouped into three categories of four buttons each.
button_command_# - This allows you to add a "command" to any of the buttons. Has you may have noticed previously, as the screen is being created, commands are setup for each of the first four buttons. Each command ties into some sort of question or statement the player will be making.
...Continued...
04/28/2009 (9:36 am)
I'm initially writing all of these posts in a Word document and plan on formatting and putting together a final doc based on all of these posts. This should make some nice documentation for the game, and perhaps an easier read. If anyone is interested in getting a copy, let me know and I'll start compiling a list.Dialog Builder Part II
Let us now look at the structure of a conversation object:
if(!isObject(CaravanOwnerDialog))
{
new ScriptObject(CaravanOwnerDialog)
{
initial_text = "Oh! Thank goodness you have come. Someone or something has <a:Blocked Road>blocked</a> the road here. " @
"I'm trying to get my <a:caravan>caravan</a> to <a:Four Falls>Four Falls</a>. Can you help me?";
visited_text = "What have you discovered?";
completed_text = "Thank you so much for your assistance.";
button_text_1 = "Tell me more about...";
button_command_1 = "1";
button_text_2 = "Offer assistence...";
button_command_2 = "2";
button_text_3 = "";
button_text_4 = "";
button_text_5 = "Normal";
button_text_6 = "";
button_text_7 = "";
button_text_8 = "";
button_text_9 = "";
button_text_10 = "";
button_text_11 = "";
button_text_12 = "";
display_text = "Can you tell me about";
keyword_text = "Can you tell me about <#KEYWORD>.";
display_text_1_0 = "Do you have more information";
keyword_text_1_0 = "Do you have more information on <#KEYWORD>?";
display_text_2_0 = "I'll take a look around and see if I can find anything.";
keyword_text_2_0 = "";
_currentKeyword = "";
_visited = false;
_completed = false;
_currentCommand = "";
};
CaravanOwnerDialog._known_keywords = new SimSet();
CaravanOwnerDialog._known_keywords.add(Caravan);
CaravanOwnerDialog._known_keywords.add(BlockedRoad);
CaravanOwnerDialog._known_keywords.add(FourFalls);
}As you can see, a conversation is a ScriptObject with a number of fields:
initial_text – This text will be displayed to the player when first engaging in this conversation. You will notice a number of anchored text values. These are keyword links. They allow you to put links to keywords into a conversation allowing the player to add keywords to their list.
visited_text – This text will be displayed after the player has initially talked with the person, but the conversation has not been "resolved".
completed_text – This text will be displayed once this conversation has been "resolved".
button_text_# - This controls the text displayed on each of the 12 possible conversation builder buttons. The conversation builder buttons are grouped into three categories of four buttons each.
button_command_# - This allows you to add a "command" to any of the buttons. Has you may have noticed previously, as the screen is being created, commands are setup for each of the first four buttons. Each command ties into some sort of question or statement the player will be making.
...Continued...
#7
keyword_text – I don't remember for sure what these were used for. I'll update or remove once I track it down.
display_text_1_0 – Here is where we start building the basic text values for the different button combinations. In this example, 1 is the first question button "Tell me more about…". If the player clicks that button, and there is no active keyword, then this is the text that will display. The 0 is for the selection of how the player will say it. Since this conversation only has a normal tone, there is only one possible way to state this. If, for example, there was an annoyed tone button in addition to normal, you might have a display_text_1_1 that said something like "Look, I’m getting fed up here, tell me about it NOW!", or something similar. This shows how the conversation system allows the writers the ability to keep the writing in the game at a consistent level and style, while still allowing the player some freedom in how their characters interact with NPCs.
Finally, we have a set of internal variables that help us track the state of this conversation. You will see how you use those in the upcoming code snippets. After creating the conversation object, we end with a SimSet that contains the "known" keywords for this conversation. Known keywords are keywords that this conversation has specific information on. This will become clearer in the upcoming code.
So know that we know a little more about the objects we are using during a conversation, let us get into the actual methods that use them. The first four builder buttons are basic command style buttons:
Whenever one of those buttons is pressed, it calls the onCommand function we built when initializing the gui:
This is simply calling the onCommand function and passing the command parameter setup in the conversation object:
As you can see, this simply calls the processCommand function for the current conversation. This simple pass through method is there to allow us to add some additional code in the future that makes use of character stats or other information to process commands differently.
Here is the processCommand function for our conversation:
...Continued...
04/28/2009 (9:38 am)
display_text – I don't remember for sure what these were used for. I'll update or remove once I track it down.keyword_text – I don't remember for sure what these were used for. I'll update or remove once I track it down.
display_text_1_0 – Here is where we start building the basic text values for the different button combinations. In this example, 1 is the first question button "Tell me more about…". If the player clicks that button, and there is no active keyword, then this is the text that will display. The 0 is for the selection of how the player will say it. Since this conversation only has a normal tone, there is only one possible way to state this. If, for example, there was an annoyed tone button in addition to normal, you might have a display_text_1_1 that said something like "Look, I’m getting fed up here, tell me about it NOW!", or something similar. This shows how the conversation system allows the writers the ability to keep the writing in the game at a consistent level and style, while still allowing the player some freedom in how their characters interact with NPCs.
Finally, we have a set of internal variables that help us track the state of this conversation. You will see how you use those in the upcoming code snippets. After creating the conversation object, we end with a SimSet that contains the "known" keywords for this conversation. Known keywords are keywords that this conversation has specific information on. This will become clearer in the upcoming code.
So know that we know a little more about the objects we are using during a conversation, let us get into the actual methods that use them. The first four builder buttons are basic command style buttons:
new GuiButtonCtrl(BuilderButton1) {
canSaveDynamicFields = "0";
isContainer = "0";
Profile = "GuiButtonProfile";
HorizSizing = "relative";
VertSizing = "relative";
Position = "3 24";
Extent = "140 30";
MinExtent = "8 2";
canSave = "1";
Visible = "1";
Command = "onCommand();";
hovertime = "1000";
groupNum = "-1";
buttonType = "PushButton";
useMouseEvents = "0";
};Whenever one of those buttons is pressed, it calls the onCommand function we built when initializing the gui:
BuilderButton1.Command = "onCommand(" @ %conversation.button_command_1 @ ");";This is simply calling the onCommand function and passing the command parameter setup in the conversation object:
function onCommand(%param)
{
$current_conversation.processCommand(%param);
}As you can see, this simply calls the processCommand function for the current conversation. This simple pass through method is there to allow us to add some additional code in the future that makes use of character stats or other information to process commands differently.
Here is the processCommand function for our conversation:
function CaravanOwnerDialog::processCommand(%this, %command)
{
%this._currentCommand = %command;
%this.setCorrectDialog();
}...Continued...
#8
Here is where we start to actually do some meaningful stuff. We switch what our current command is, which came from the button that was selected on the GUI. Then we check if we currently have a keyword selected. If we do, then we do another switch on the keyword, and display the text we have written for that command and keyword combination. If no keyword is selected, then we simply use the display text for the selected command.
We have one more section for the dialog system, and then we can wrap that up. I should have that within the next couple of days. After we finish the dialog system, I'll be talking about the battle system. I'll be going over a C++ prototype of the very basics of the combat, and where I want to take it in BMOH.
Stay tuned for more.
04/28/2009 (9:42 am)
Another simple function, that sets the internal _currentCommand variable with the %command parameter, and then calls the setCorrectDialog function:function CaravanOwnerDialog::setCorrectDialog(%this)
{
switch(%this._currentCommand)
{
case 1 :
if(isObject(%this._currentKeyword))
{
switch$(%this._currentKeyword.getInternalName())
{
case Caravan.getInternalName() :
CurrentText.setText("Can you give me more information on your Caravan?");
case BlockedRoad.getInternalName() :
CurrentText.setText("What all do you know about the blocked road?");
case FourFalls.getInternalName() :
CurrentText.setText("Why are you going to Four Falls?");
default :
CurrentText.setText(replaceKeyword(%this.keyword_text_1_0, %this._currentKeyword));
}
}
else
{
CurrentText.setText(%this.display_text_1_0);
}
case 2 :
CurrentText.setText(%this.display_text_2_0);
}
}Here is where we start to actually do some meaningful stuff. We switch what our current command is, which came from the button that was selected on the GUI. Then we check if we currently have a keyword selected. If we do, then we do another switch on the keyword, and display the text we have written for that command and keyword combination. If no keyword is selected, then we simply use the display text for the selected command.
We have one more section for the dialog system, and then we can wrap that up. I should have that within the next couple of days. After we finish the dialog system, I'll be talking about the battle system. I'll be going over a C++ prototype of the very basics of the combat, and where I want to take it in BMOH.
Stay tuned for more.
Torque 3D Owner Ted Southard