RPG-Style Branching Dialogue Systems
BlitzMax Forums/BlitzMax Programming/RPG-Style Branching Dialogue Systems
| ||
Has anyone ever tried to create a dialogue system that allows conversation branching, multiple responses to a question, the ability to change dialogue based on your health/inventory/characters spoken to/etc.? Kinda like the LucasArts SCUMM system? I'm trying to plan one out, and I'm at a loss as to where to begin... |
| ||
heres one I did for mothership: http://slenkar.herobo.com/ here is the info for one character 'zer' is just the name for the object instance 'talk' is an array of what the NPC says 'reply' is what the player says zer.talk[1]="Greetings "+player_name zer.reply1[1]="Hi" zer.reply1_leads[1]=2 zer.talk[2]="The scriptures say:,'construct' weapons before entering battle" zer.reply1[2]="I will" zer.reply2[2]="No" zer.reply1_leads[2]=4 zer.reply2_leads[2]=3 zer.talk[3]="With no ships you shall perish" zer.talk[4]="You should travel to sector 3, we have been recieving Federation signals from that area" zer.reply1[4]="I will travel there first Thank You" zer.reply1_leads[4]=5 zer.talk[5]="We also need you to get some more food for our miners" zer.reply1[5]="OK" zer.reply1_leads[5]=6 zer.talk[6]="Good "+player_name+". I give you this mission" zer.mission="Bring some food to your home planet" zer.mission_talk=6 zer.mission_ongoing_talk="Please hurry with your mission" zer.mission_completed_talk="Our miners found an ancient passcode" zer.enemies_present=yes zer.chance_of_enemy_attack=100 zer.infinite_resources[1]=5 zer.mission_object_needed=object_food zer.mission_reward_resources[1]=10 zer.battle_reward_resources[1]=50 zer.battle_reward_resources[2]=2 zer.mission_reward_exists=yes zer.mission_reward_object=object_passcode here is a little bit of code for when the player presses a button: If w.buttons[1].text<>"" If w.buttons[1].ispressed() If w.buttons[1].text="Bye" Or current_talk_number=m.mission_talk change_game_mode(map_mode) Else current_talk_number=m.reply1_leads[current_talk_number] EndIf EndIf EndIf If w.buttons[2].text<>"" If w.buttons[2].ispressed() If w.buttons[2].text="Bye" change_game_mode(map_mode) Else current_talk_number=m.reply2_leads[current_talk_number] EndIf EndIf EndIf current_talk_number is a global Last edited 2010 Last edited 2010 Last edited 2010 Last edited 2010 Last edited 2010 |
| ||
Try looking up finite state machine. It depends to some extent how important it is for you to understand the script. slenkar's example is functional, but it looks unwieldly to write or modify. A general-purpose solution will allow conversations to be coded in a more human-readable, run-time parsable script, and even opens possibilities of modding your game story. But, that is more work. And there is probably a library or module around already to do it for you. Try asking on a SCUMM forum? I know there are a few fan emulators knocking around. Last edited 2010 |
| ||
My dialogue system is written in C# so posting code might not be terribly helpful. However, it uses polymorphism to create a tree structure which is made up of nodes. Each node class extends a base node class, and has to implement begin, update and finish methods ( which are called when the node is executed, every frame while it's active and when it's done, respectively) and so each node inserts its own functionality while still adhering to the stucture of the base class. Each node can have one or more children and these are returned by the Finish method. The Finish method is called when the Update method returns True. Each node can implement its own system of storing children (in which case you have to cast the node to its actual type, of course) or it can simply use the standard one, which is a list of children, all of which are executed simultaneously after the current node is finished. Typically, each node only has one child. If you want to add, for example, a dialogue menu, that would require its own children. So you'd have an array (or list) of options and when one is selected (either through Update or via callbacks setup in Begin) and Finish returns the option which corresponds to the user's choice. You need to use function pointers or a good messaging system to make this work cleanly. It's fairly simple, but fairly flexible, and it's easy to add new types of nodes when you decide you want your dialogue system to have features you didn't think of before. How you actually store the data and/or design the dialogues is really independent of the code and design. I create mine in code, but you could read them from XML, or create a visual dialogue editor, but I'm using Unity, which already does 95% of the work for a visual editor so my code for that would be even more useless. Last edited 2010 |
| ||
yes my system is a bit unwieldy using XML is probly the best option Last edited 2010 |
| ||
Ah, this is a fun subject. You need to come up with a scripting system for dialogues. This scripting system should have the following features: -> Specify who is talking (so that you can show a name or a portrait). -> Allow you build options and branch your dialogue based on choices of phrases you give the player. -> Allow you to change game elements based on the choice of phrase, or store the player's choice so that later on you can read this value to do whatever you want. PS: I seriously, seriously suggest you play around with the RPGMaker software so you know how it does it - it's not hard at all. It's got an amazing internal engine for RPG's and it's from where I get all reference regarding dialogue scripting. It has an interesting Switch system so that different events can relate to eachother (like you can easily make a dialogue choice have an impact on a door being open, some character doing something, etc.) Here's a screen from a dialogue script. This is merely for viewing purposes; the process to build the script is much more intuitive, with GUI's and easy-to-read buttons. You should reproduce this functionality in your game\engine\editor. I suggest you go the visual editor way (building one in blitzMax, that is) because you work hard on it once, but then get to use it for all your dialogues - doing it all by code is going to be a major pain. Now that MaxGUI is free, you're halfway there. By the way, here is the result of that script. This is an in-game shot from an RPGMaker project. Last edited 2011 |
| ||
Thanks, guys. I like the idea of using XML - the parent/child system is inherent, and I've used Brucey's XML module in the past. My issue is really the data structure - how do you structure the branching, trigger events, and set variables? Surely there has to be a better way than messaging (which has its uses but isn't particularly easy debug)...? |
| ||
What I'm going to tell you I learned from playing with RPGMaker, so I stress again that you should try it out to gain the same knowledge. With whom are you going to be talking? characters, right? or if not characters, let's call them 'special objects' (because you could still have a sign on a road that tells you something, or a book that you read that opens a dialogue as well). So for each special object you have a script of actions to perform every time the player uses 'Action' on that object. If it's a person, the player will talk to that person (if that's what you put in that person's script of course). Take a look again at that event-script from RPGMaker. This list will run through every time the player 'Acts' upon a special object with which this script has been associated, starting from the top, going down. Therefore, in BlitzMax, you could store in your TScript (or whatever user-type you create for it) an array or TList containing such actions, in order. These actions could be in the form of functions or other objects (say, a TDialogue object with it's choices properly formatted, the portraits set up, and the script would call TDialogue.Run() for it to become active on screen, and when it ends, call the next action on the list). Not list, actually, but "Tree", because you need the capability of having a different set of actions be performed depending on a certain condition (say, a person say something else if the player is holding a certain item or has reached a certain level). You need to have enough actions available for you to change anything in the game's world with your script. This way you can make levers that open doors, make treasure chests that give you a certain item, etc. All with a very modular, extensible and clean framework. I'm sure someone with a better understanding of blitzMax's OOP capabilities can answer better in a more practical level. I can only stay on the theory. Last edited 2011 |
| ||
Nice explanation, Kryzon... suddenly I feel like continuing my abandoned rpg and equip it with such a system :-) As for the data structures, personally I would keep each conversation as a script (the easiest way, if you have a script engine running). If you want it more flexible, you could parse your own conversation files, and indeed XML would be a good way to store them. There are various ways to structure them, perhaps something like this: <conversation npc="guard"> <message required="player.money>10" text="How can I help you sir?"> <option required="player.holds(MAP)" text="Can you point the Inn on my map?"> <message text="For 10 gp"> <option text="I'll find it myself" action="close()" /> <option text="Ok, here you go" action="player.moneyDecrease(10)"> <message text="Thank you sir" action="player.markOnMap(INN); close()" /> </option> </message> </option> <option text="Nothing, bye" action="close()" /> </message> <message required="player.money<11" text="Get lost tramp" action="close()" /> </conversation> I just made that up, but it would probably look something like that Last edited 2010 |
| ||
In my Armand game, a Zelda-type game, I assigned a script to each NPC. The script would be processed sequentially every time you talked to them, but it would check a set of flags for a value. And if something happened in the conversation, or elsewhere in the game that I wanted to impact the conversation, I'd set a flag. So, for example, talking to the bartender might be flag #1. When you start the game, that flag is set to 0. When you talk to him for the first time, he introduces himself and then asks what you want. The script sets the flag to 1. If you talk to him again, the script engine sees that flag 1 is set, so he knows not to repeat his intro speech, and goes right to the "what can I get you" speech. It worked well enough for the simple interactions in my game, but it got unwieldy if there was a lot of branching involved since I didn't have a good way to jump to another part of the conversation. I'm working on a new system for my Manifest Galaxy game, a much deeper game, and plan to post some pseudo code soon in my dev blog. |
| ||
@conard Ah, therein lies my problem - for example, how do you translate: option required="player.holds(MAP)" ...into actual code? I know what the XML is trying to say, but how to I get the game to call that function with that parameter? (Desperately waiting for the moment when this 'clicks' in my head...!) |
| ||
Well, you should have a class that handles the conversation, meaning it loads and parses the conversation file. Say the player talks to the guard, the TConversation class will load the conversation with tag "guard". It should then look for a <message> node inside. If it finds one, it should get the "required" property from the node (in this case "player.holds(MAP)"). This stage, parsing the required tag, is pretty game specific. Tbh, I haven't played with BMax for a while, but I don't believe there is a way to call a function from a string. (func$ = "print('hello')"; call(func) doesn't exist, right?) Meaning, you'll have to slice the string and process it's syntax. An example with a more convenient syntax follows. Don't remember Max' functionality with Strings, but in psuedo: function requirementMet(required$ = "player.holds.map") target$ = SplitStringAtDot(required, rest$) ' splits a string at dot like: (target).(rest) function$ = SplitStringAtDot(rest, parameter$) select(target) case "player": if(function = "holds") if(ourHero.inventory.contains(parameter)) return True EndIf break; EndSelect EndFunction Just to give you an idea - as you see it's very game specific how you handle this. |
| ||
I think you should quantize Conditions into classes to be added to the scripts action list. Each time you parse a script, you build up a TList full of the corresponding actions that script has (meaning, you are converting it into code). The Condition class of action would be something like this: Type TCondition Extends TAction Field OperandA Var 'Comparisons are always carried out from A in relation to B. Field OperandB Var Field Kind 'The kind of comparison operation to be performed on the two operands. Field TrueList:TList 'List of actions to execute in case the condition is true. Field FalseList:TList 'List of actions to execute in case the condition is false. Method Run() 'Overloaded method. Select Kind Case 1 ''Bigger than' comparison. If OperandA > OperandB Then Execute(TrueList) Else Execute(FalseList) Endif Case 2 ''Smaller than' comparison. If OperandA < OperandB Then Execute(TrueList) Else Execute(FalseList) Endif Case 3 ''Equal to' comparison If OperandA = OperandB Then Execute(TrueList) Else Execute(FalseList) Endif '[...] 'Implement as many comparisons as possible, to give you 'the most freedom. Comparisons like "is it True, is it 'False, does it contain (useful for items), etc." for instance. End Select End Method Method Close() 'Overloaded method. End Method Method Execute(ActionList:TList) 'Execute the appropriate action list. This is the same function a Script should have. For action:TAction = EachIn ActionList action.Run() 'Execute each action in the list. They could be more Conditions, or ones that open a Dialogue box, Flash the screen, add an item to the player, take some gold from him etc. Next End Method End Type When you create an instance of this condition class you should populate OperandA and OperandB fields with the appropriate variables (say, you place the Player.Money on OperandA and place a value in OperandB, and select the 'Bigger Than' comparison). Also, place the appropriate operation kind in the Kind field. Then this Action, like any other else, will be stored in that script's action list to be later executed when the game demands (say, when the player uses 'Action' on a special object that has this script attached). Last edited 2010 |
| ||
Just had the 'aha!' moment, thank you guys so much. :) Essentially, there's no graceful way to parse the file other than extract each element and run them through various Select statements...? |
| ||
Nope, you can abstract as much as you want to give it a cleaner and more understandable look, but in the end you'll have to deal with plain code anyway. I share your feeling of wanting to do something as elegantly as possible. We need to keep this good-taste even though we deal with plain code. Don't be afraid to use IFs and SELECTs, as long as they don't go cluttering your code unnecessarily - avoid placing "residue" that you'll have to refactor later on. I think it may come to a balance of elegancy vs readability. Essentially, there's no graceful way to parse the file other than extract each element and run them through various Select statements...? That's where I recommend you parse all the current level's script files at load time and store them as action sequences (meaning, TLists) so you don't have to interpret each script in real-time.Good luck. Last edited 2010 |
| ||
@Kryzon - Thanks again, we seem to be on the same page. Care to put together an example script with me? |
| ||
Sure. I would make up my own format, instead of using XML. It would allow more freedom and I wouldn't have to add any more modules to my code except my own set of functions (which could be part of my huge "[something] game module" where I store all my functions and classes for my game engine). I would rely a lot on ReadLine. It makes things much easier to deal with. Doesn't matter which tags you use (just pick your favorite, like "<>" or "[]"). We won't be using XML but we can always base on it, somewhat. I think the most important thing is to prioritize readibility, writeability and the easiness to parse the scripts later through code. Because of this, we should keep the markup tags or characters down to a minimum, as well as very clear command names. You should be able to write these game scripts out of NotePad, but I would highly recommend having GUI applications for this, just like RPGMaker has; it makes the process much more pleasant and intuitive. You use a series of windows and buttons to place actions on the queue, and the application builds the script file in the end. Example of possible constructs within script files (this is very crude and there is always some improvement to be made over the markups): [messageBox:7] [text:This is an example of a portrait-less Message Box. As you can see, there is no character-face markup telling which portrait to use when showing this text, so none will be used and the text will occupy the whole horizontal space available in the messagebox's frame. This is the kind of text you should use for signs, warnings and not-yet-revealed-character's dialogue. I was thinking the "7" in "messageBox:7" is the specification of the amount of lines the messagebox would hold - this influences the messagebox's vertical size.] [/messageBox] [condition:switch:SWITCH_zuraSoldier] [case:true] [messageBox:7] [face:Soldier1] [text:May the emperor's grace be with you.] [/messageBox] [case:false] [messageBox:7] [face:Soldier1] [text:Hello stranger. We only allow entrance to allies of Zura. Do you have an official seal to prove your alliance to our great emperor?] [/messageBox] [condition:inventory:zuraSeal] # Establish a condition. The player needs to have item 'zuraSeal' in order for the guard to let him pass. [case:true] [messageBox:7] # This message-box contains a dialogue with alternating face portraits. [face:Soldier1] [text:Ah, very well! You may pass then.] [face:Hero] [text:Thank you. I'm in a great quest and need to enter these plains at once.] [changeSwitch:SWITCH_zuraSoldier:true] # Change the switch which branches the dialogue, so next time the player uses ACTION on this soldier, he will just answer a simple thing. [moveChar:zuraSoldier:5,32] # Move soldier away from the path so the player can get through. [/messageBox] [case:false] [messageBox:7] [face:Soldier1] [text:Only allies are allowed in. You cannot pass.] [/messageBox] [/condition] [/condition] [comment] Possible comment-like markup so you can write about the script. Works just like the Rem from BlitzMAX. A special tag could also be implemented to allow single-line remarks. Something like "#". [/comment] # Single line comment. # End of Script. The Horizontal Tab\indenting present in this script is just to make the reading more comfortable. You can have these characters (ASCII code 9) be ignored by your script-parsing-algorithm. Last edited 2011 |