GML Managing my dialogue system better?

Discussion in 'Programming' started by Dr_Nomz, Jan 11, 2019.

  1. Dr_Nomz

    Dr_Nomz Member

    Joined:
    Oct 31, 2016
    Posts:
    429
    I made an awesome dialogue system that works really well for me, and it's all contained in one object, but the thing is it's unmanageable, and I would need to alter each and every little variable to display the correct dialogue option, NPC line, and the coords for where this all appears on screen, along with the coords for the text within the dialogue boxes.

    The first thing I should point out is that the topmost box is the dialogue from the NPC, which is whatever they're saying in response to whatever option you chose previously. I don't think I need any help with that part, so just keep in mind that it's at the top of the screen and it doesn't move from there. (It also can't have any other dialogue options overlapping it, or underlapping)

    The second is that the screen is going to be 1000(W) x 800(H) and the dialogue boxes are all 500(W) x 100(H) and display in the center of the screen. (so 250px to the right of the screen if their origin is on the left side) This means that there can only be 7 options displaying at any given time, and some must be chosen before others are available.

    Finally, the text is drawn overtop of the dialogue boxes, which also have to have their own specific coords, about 10-15 pixels inside the box. (so it doesn't display on the box's border.)

    ===

    Now that you know all that, here's what I need:

    First, the dialogue options coords (except the NPC dialogue, since that's fixed in place) should always display at the bottom of the screen, and if there are more options, the extra ones should be displayed 100 pixels above the former. I would also like to determine which options have which place in the lineup, so the end dialogue option can always default to the bottom, and others can have a certain order as well.

    Second, I need a better way to sort it all. Using "variables" can get really confusing the bigger this gets, so I could use some advice on how to better sort it using a ds_map or list or something, so it won't be nearly as difficult to keep track of it all and how it works.

    If anyone can help me out with that, I would greatly appreciate it, this is really important to my game, and I hope someone can help me with this.

    Here's the code I used for reference (added some notes to give you an idea of what certain things do)
    Code:
    var color=c_black;
    var x1 = view_xview + 250;
    var y1 = view_yview + 700;
    var x2 = x1 + 500;
    var y2 = y1 + 100;
    
    if hide_self=1{
    //NPC Lines
    if lines=0{
    draw_sprite(spr_Dialogue_Blue,0,x1,y1-700);
    draw_set_font(fnt_3);
    draw_set_colour(c_navy)
    draw_text(x1+12,y1-688,'Hi?');
    }
    if lines=1{
    draw_sprite(spr_Dialogue_Blue,0,x1,y1-700);
    draw_set_font(fnt_3);
    draw_set_colour(c_navy)
    draw_text(x1+12,y1-688,"I'm trying to get in my house but I can't find the key...#So I'm stuck out here until I can find it.");
    }
    if lines=2{
    draw_sprite(spr_Dialogue_Blue,0,x1,y1-700);
    draw_set_font(fnt_3);
    draw_set_colour(c_navy)
    draw_text(x1+12,y1-688,"What the... Yeah that's my key! Where did you find it?#And how did you know it was... nevermind. Thanks.");
    }
    if lines=3{
    draw_sprite(spr_Dialogue_Blue,0,x1,y1-700);
    draw_set_font(fnt_3);
    draw_set_colour(c_navy)
    draw_text(x1+12,y1-688,"Yeah, that's it! Where'd you find it?#Anyway, thanks for finding it for me.");
    }
    
    
    //Dialogue Option:
    if hide1=0{ //Variable determines if the option is available, if it is, it's shown here, and can be clicked on.
    draw_sprite(spr_Dialogue_Box,0,x1,y1-200); //Draws the dialogue box
    if(point_in_rectangle(mouse_x,mouse_y,x1,y1-200,x2,y2-200)){
      draw_set_alpha(0.5)
      draw_set_color(c_white);
      draw_rectangle(x1+5,y1-195,x2-6,y2-206,0);
      draw_set_alpha(1)
     
      if(mouse_check_button_pressed(mb_left)){
        var KEY_blue=false; //Simple item check, ignore this part.
        for(i=0; i<maxItems; i++){
          if (global.inventory[i]==8){
            KEY_blue=true;
            }
          }
          if KEY_blue=true{
            hide1=1; //If item is found, option to give key changes slightly.
            hide2=1;
            lines=1;
          }else{ //This part is important; It sets a bunch of variables to show new dialogue options, or change an existing one.
    //It also changes what the NPC is saying. (In the LINES variable)
            hide1=1;
            hide2=2;
            lines=1;
            }
      }
    }
    
    //And THIS is the text drawn on top of the dialogue box. Lets you know what you're
    //going to say before you say it.
    draw_set_font(fnt_3);
    draw_set_colour(color)
    draw_text(x1+10,y1-190,'What are you doing?');
    }
     
  2. Tsa05

    Tsa05 Member

    Joined:
    Jun 21, 2016
    Posts:
    482
    http://stormingleech.com/SLIDGE_Demo/

    Demo of my dialogue engine. 1 Object. Alll that stuff; position relative to views, background images, characters with stacked layers, multiple textboxes per screen, paginated dialog, choice boxes...

    Answer: JSON.
    ds_map_create();
    ds_map_add();
    ds_map_add_map();
    ds_list_create();
    ds_list_add();
    ds_map_add_list();
    ds_list_mark_as_map();

    Gamemaker has native support for it!
     
  3. Dr_Nomz

    Dr_Nomz Member

    Joined:
    Oct 31, 2016
    Posts:
    429
    EDIT: Wrong thread my bad!

    I'll look at your solution soon though!
     
  4. Dr_Nomz

    Dr_Nomz Member

    Joined:
    Oct 31, 2016
    Posts:
    429
    I just looked at it a bit, but that didn't help at all. How am I supposed to do any of the stuff in my post with that?
     
  5. Dr_Nomz

    Dr_Nomz Member

    Joined:
    Oct 31, 2016
    Posts:
    429
    Does anyone have some ideas for me? I can't work with it as it is and need some help.
     
  6. The Video Gamester

    The Video Gamester Member

    Joined:
    Dec 2, 2016
    Posts:
    40
    Use an external file to save the dialogue. Its a little complicated to figure out at first but I can help you with it if you want. If you use a .csv file you can edit the dialogue using excel or google docs. You load it into a map and then can call the dialogue lines from it using exclusively the info in the file
     
  7. The Video Gamester

    The Video Gamester Member

    Joined:
    Dec 2, 2016
    Posts:
    40
    This system actually has some additional bonuses if your planning on publishing the game as well. It allows fan translations to be made.
     
  8. kupo15

    kupo15 Member

    Joined:
    Jun 20, 2016
    Posts:
    696
    Interesting. I just checked out this file type and it seems to separate everything by commas and comma/line breaks (hence the name I guess) so the formatting is nice. How would you parse this? Like if you have 100 different NPCs and one of them was called Post Office, how would you just grab that text and sort it by dialog branch?

    Code:
    Post Office
    [Branch0]
    Hi how are you?
    We are very busy today, would you like to mail a letter?
    [Branch1]
    Letter Mailed! Have a nice day!
    [Branch2]
    Very well, if you have a letter you want mailed come back anytime!
    
    Ini files seems much easier to with their format because of the parsing functions. In the above I would do something like this
    Code:
    [Post Office]
    00 = Hi how are you?
    01 = We are very busy today, would you like to mail a letter?
    10 = Letter Mailed! Have a nice day!
    20 = Very well, if you have a letter you want mailed come back anytime!
    
    In the above, my idea would be to separate the keys as the NPC name and the values to be named like a 2d array (00 is branch 0, dialog 0...)

    I read that ini files are limited to 64KB, how much space do you think a decent sized game's dialog would be? How would I replicate this ini parsing as a text or csv file?
     
  9. Tsa05

    Tsa05 Member

    Joined:
    Jun 21, 2016
    Posts:
    482
    Everything in your post can be done with the things in my post.

    I will assume that you have not thoroughly read the documentation for the functions I've listed. I cried for several minutes about this, but I'm better now. This dialog question gets asked several times per week and since the use of nested data structures for nested data is the correct method, I'll describe some particulars here for the benefit of anyone searching.

    Since you're exploring ini, you are looking into using an external file for your data, and then using GameMaker's functions to read that data into meaningful form. But you've discovered that grouping things is rather tricky in ini since there's only 1 level of hierarchy. You'll also come around to discovering that GameMaker's flavor of ini is very "special" when it comes to newlines, quotation marks, and so on. I propose the same method--external file, read the data in--but using the proper nested structure. My own engine, which I showed a demo of to give a sense of the extreme levels of complexity that are possible, was started off using ini; it took me several weeks to re-do the thing using maps, since there were already many things completed, but once I switched, I was able to triple the functionality in half the time thanks to the organized, flexible structure of maps.

    First. You have data related directly to your dialog. The box to draw, the text to overlay... You want to keep all of that information grouped together.
    Second, you have data related directly to your choices. Boxes to draw, text to draw... You want this grouped together, and in with the text information.
    Third, you have variable data relating to the above two. Order of things to draw, position of things relative to the game window, etc.
    Fourth, you have structural data. All of the above three types of information, grouped as a single moment of dialog on the screen--and you have many such moments in various order.

    You want all of this data to travel together, while being easy to find, easy to categorize, and easy to modify.

    Consider the following structure:
    Code:
    Scene 0:
         List of moments:
              Moment 0:
                   Textbox
                   List of Choices
              Moment 1:
                   Textbox
                   List of Choices
              Moment 2:
                   Textbox
                   List of Choices
    Scene 1:
         List of moments:
              Moment 0:
                   Textbox
                   List of Choices
              Moment 1:
                   Textbox
                   List of Choices
              Moment 2:
                   Textbox
                   List of Choices
    It's fun and easy to list out the data as though it's all nicely packed into a structure like that, but it has got to be doable in GameMaker or else this is useless.

    In GameMaker, any collection of variable names and corresponding values can be grouped into something called a Map. A map structure simply "maps" a set of values to a set of names. It's like an Object, but lighter--there's no events or actions, just data. You could make a "person" map containing "name", "age", "favorite ice cream flavor" and more--and then send that map around to other objects (and even to other players over the internet) and all of the data travels with the map.

    In GameMaker, an ordered set of data is called a List. Lists offer some unique properties, including the ability to add to the end, insert, delete, and shuffle contents. Primarily, lists offer the ability to supply implicit order--in other words, each piece of data in a list gains the additional property of "what order it's in" by virtue of just being in a list.

    GameMaker provides the functionality of including lists within maps and maps within lists; in this way, you can have sets of data that contain additional sets of data. To use the "Person" example again, you can have a person Map containing "name", "age", and "favorite foods"... but then "favorite foods" can be a list instead of a single piece of information.

    In this way, it's possible to store all of your information, variables, properties, order, and any special information into a single data structure that is easy to access and that keeps all of your information organized.

    To revisit the dialog tree, with all of the indented categories, we just look at the "type" of data we need in each case.
    Let's say your "textbox" consists of a picture, some text, and an x and y position. That's a set of data, so there's a Map.
    Code:
    textbox = ds_map_create();
    ds_map_add("image", sprite_box);
    ds_map_add("text", "Sample Text");
    ds_map_add("x", 100);
    ds_map_add("y", 150);
    Ignore the code. That's how you might do it, but that's a long way around. The idea is there, though--you have a map, it has data in it. To draw a textbox, you just read each property you need. If you eventually need to add properties to the box (maybe like width and height), it's easy because you just put more into the map and read the extra data when it's time to draw.

    You need a choice button, right? Maybe that's a map, too, with an image and some text, and the name of a room to go to. But you need a list of choices..... Ez.
    Code:
    listOfChoices = ds_list_create();
    for( var i=0; i<number_of_choices_you_needed; i++){
         var choiceMap = ds_map_create();
         // Add stuff to map as needed
         var size = ds_list_size(listOfChoices);
         ds_list_add(listOfChoices, choiceMap);
         ds_list_mark_as_map(listOfChoices, size);
    }
    A loop that adds each map to a list, marking it as a map. GameMaker is now aware that each element in the listOfChoices is a map. So, when you send the list somewhere else in your code, the choices (and all of their properties) travel with them.

    The grand secret here is that GameMaker translates between maps/lists and JSON fluidly. What's JSON? It's a way of writing down how you want maps and lists and data structured. You use {curly brackets} to indicate maps, [square brackets] to indicate lists, and now you know how to write JSON instead of INI. An example, using the tree above:
    Code:
    {
         "Scene 0": {
              "moments": [{
                   "textbox": {
                        "image": "sprite_box",
                        "text": "Sample Text",
                        "x": 100,
                        "y": 150
                   },
                   "listOfChoices": [{
                        "image": "spr_choiceBox",
                        "text": "Option 1",
                        "room": "rm_optionOne"
                   },
                   {
                        "image": "spr_choiceBox",
                        "text": "Option 2",
                        "room": "rm_optionTwo"
                   }]
              ]
         }
    }
    Ok, yes, there's a bit to break down there, but I literally went straight to the most complicated example, and it's really not too bad.
    You have a text file, and it has groups of data written between {curly} and [square] brackets to indicate what's a collection of data versus what's a list of data. You read this text into GameMaker as a string, and you use the json_decode function on it. Boom, it's a map full of maps full of lists full of maps, one line of code.

    Now you can get anything you need. You want "Scene 0?" You lookup "Scene 0" in your map. The result will be a map containing all of the scene 0 info.
    You want a list of moments within scene 0? look up "moments." You'll get a ds_list data structure filled with all the data for that scene.
    You want to get the data to draw onscreen for the first section of dialog in a scene? Look up the first moment in the list. It will return a map of all the data.
    You want a list of choices to put on screen? Query the moment map for "listOfChoices" and you'll get a list of choices.
    Ready to draw a textbox? Query the moment map for "textbox", then query that result for x, y, image, and text. Look up the sprite by name and draw it, add the text, done.

    It's a strange-looking format, but once you've got it in place, moving data and compartmentalizing it and keeping it organized, and popping in new bits of data to remember but keeping it all together is automatic. It's built right into the format. ¯\_(ツ)_/¯
     
    Last edited: Jan 15, 2019
    DaMuffin and kupo15 like this.
  10. kupo15

    kupo15 Member

    Joined:
    Jun 20, 2016
    Posts:
    696
    Excellent thanks for the breakdown, Tsa05!
     
    Tsa05 likes this.
  11. Danei

    Danei Member

    Joined:
    Mar 23, 2018
    Posts:
    117
    wouldn't you want ds_list_size(listOfChoices) - 1? Since list indices start at 0 but ds_list_size returns the number of entries. Just for clarification.
     
  12. Tsa05

    Tsa05 Member

    Joined:
    Jun 21, 2016
    Posts:
    482
    @Danei Good question! I think that quote isn't quite exact, though, right? I sneakily grabbed the size *before* adding to the list, so that size is actually one less that it'll be a single line later. So yes, we're marking the list size -1 as a map; I just rigged the ordering a little to save one -1 computation :D
     
  13. Danei

    Danei Member

    Joined:
    Mar 23, 2018
    Posts:
    117
    Oh yeah, good point, I'm dumb. The quote IS exact though because I literally just copy-pasted : P
     
  14. The Video Gamester

    The Video Gamester Member

    Joined:
    Dec 2, 2016
    Posts:
    40
    ini files have the advantage of built in functionality. I prefer to build my own functions for this kind of thing as it allows me to customize the format to how I want. However Dr has the advantage of having more general code that requires less explaining and if your game works within the limits of ini functionality I say go for it. Usually for my .csv files I build a small string to int converter that stores the keys into a map that I can search same as an ini file. You also have to get around the fact that commas are no longer usable in a csv file which can be a bit of a pain but I usually add in a short symbol combo that I wouldn't use in dialog and then run a short search/concatenation script. Since it only needs to run once I usually run it as part of my load screen. Usually when editing the external file I put a scene, a key number, and the line of dialog. You could easily expand that to x,y coordinates, a pointer to where an option leads, etc... You can also use additional columns to put in some additional info just for you kind of like comments.
     
    kupo15 likes this.

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice