Managing dialogue with grids/arrays

BQubed

Member
I'm currently trying to setup a dialogue system in my game. I've been through the manual and every single dialogue tutorial on YouTube and I've learned an important lesson: You can't use other people's dialogue tutorials. So I've been working on creating my own but I'm having an issue with logic in terms of storing the dialogue. I've seen several options (ds_grid, ds_map, external .csv, external JSON) and since I have no clue how to setup the last two, I've opted for trying to use arrays.

At the core, I understand what an array is and I've used them before in another engine, but I'm not entirely sure how to implement one into my game in a manner that could make for an effective/efficient dialogue system. I understand that there are many ways to approach this, but I'd like to learn from the experience of more seasoned indie veterans.

I'll attempt to organize my confusion into something readable:

  • I've read that when you use functions such as ds_grid_create, you should always end it with ds_grid_destroy so as to avoid memory leaks. However, if I store all my dialogue in a grid that I'm required to destroy, does that mean every time I activate a dialogue it'll re-create the entire grid each time and destroy it each time? If so, doesn't that get cumbersome with dialogue intensive games?
  • Is it wiser to store all dialogue inside of a dialogue object (ie. obj_dialogue) or would it be more effective to have associated dialogue housed within each individual object?
    • If storing within a singular dialogue object, does it not become extremely difficult to navigate thousands of lines of dialogue with only #regions to organize it, or is there some other method I'm unaware of?
Really, the confusion lies in the implementation of data structures. I've watched a lot of tutorials on them and I get what they are and how they could be beneficial, but I'm having difficulty mentally bridging data structures with usage in game development.

Any advice would be greatly appreciated.
 
1. No, you don't have to destroy a data structure as soon as you stop accessing it. The point is that you need to destroy it at SOME point. So, for example, let's say you have a procedurally generated level. You store all of the procedurally generated enemies into a data structure when the level loads. That data structure you would keep until the player has beaten the level. So if they went back to the menu and generated a new level, that is the point where you no longer need the data for the old level and you would either clear the data structure and refill it with the new stuff, or destroy it and then create a new one for the new level. The point is that you keep the data structure while you need it and then destroy it when it's no longer needed.

2. This is kind of why people tend to gravitate towards CSV's or JSON when making dialogue trees. Having all the strings and stuff sitting nestled amongst codeblocks is a nightmare to look at and edit. Much better to have a spreadsheet organised in some way (perhaps by character or whatever) and then just load that spreadsheet into a data structure when the game runs. CSV and JSON are both actually pretty easy to wrap your head around, buffers might be a bit harder, but still no big deal. In fact, you can literally just call load_csv (https://docs2.yoyogames.com/source/_build/3_scripting/4_gml_reference/file handling/load_csv.html) to get all the data from your csv file stored into a ds_grid. And JSON already IS data structures (ds_maps and ds_lists), just parsed in a particular way. It's human readable if you care to open a JSON file. If I were you, I'd bite the bullet and spend time getting acquainted with data structures in general. They are pretty much a necessity for any project of even moderate size and I, personally, don't make a single prototype/game that doesn't use them in some way; in fact in most cases, the majority of my game programming is to do with data structure manipulation.
 

BQubed

Member
Thank you. This helped me make some pretty crucial decisions. Is there any particular place where I can take a look at examples of data structures in GMS games? Seeing examples always helps me wrap my mind around something.
 
Ok, I'll throw you two examples, one simplified and one a part of actual code from the game I'm working on right now:

Simplified Example (ds_grid and ds_list):
Code:
enum ground_stats {
   COLOUR = 0,
   OCCUPIED = 1,
}
ground_grid = ds_grid_create(room_width div 64,room_height div 64);
var gw = ds_grid_width(ground_grid);
var gh = ds_grid_height(ground_grid);
for (var xx=0;xx<gw;xx++) {
   for (var yy=0;yy<gh;yy++) {
      var ground_cell = ds_list_create();
      ground_cell[| ground_stats.COLOUR] = c_green; // The colour
      ground_cell[| ground_stats.OCCUPIED] = false;
      ground_grid[# xx,yy] = ground_cell;
   }
}
In the above code, I've defined two enums, ground_stats.COLOUR and ground_stats.OCCUPIED, this just gives me a visual indicator of a number, so ground_stats.COLOUR is exactly the same as typing 0. What I'm doing is creating a ds_grid (ground_grid) that represents the entire room width and height as cells that are 64x64 (so, basically a room width of 640 will mean the ds_grid is 10 cells wide, as 640/10 = 64). Then, I loop through each cell and create a ds_list (ground_cell), I then store the colour in slot 0 (or the enum ground_stats.COLOUR, which is equivalent) and whether it is occupied or not in slot 1 (or ground_stats.OCCUPIED). Then I store that list into the grid cell.

If I'm walking along later and I need to check what's going on in the grid cell I'm heading into I would do this:
Code:
var grid_x = (x+spd) div 64;
var grid_y = (y+spd) div 64;
var cell_list = ground_grid[# grid_x,grid_y];
if (cell_list[| ground_stats.OCCUPIED] == false) {
   x += spd;
   y += spd;
}
I grab whatever list is inside the grid cell at grid_x and grid_y, store it in a temp variable called cell_list, then I check what I need to check in cell_list. There's a million different ways of doing this, but that is one way.

Now here's some "in the trenches" code so to speak. This code is from my puzzle editor and it is called when I need to save everything that I have placed into a file (it's long, so I'll spoiler it):
Code:
var _galaxies = global.custom[? "galaxies"];
global.custom_galaxy = _galaxies[| global.current_galaxy_custom];
var _systems = global.custom_galaxy[? "systems"];
global.custom_system = _systems[| global.current_system_custom];

global.custom_galaxy[? "name"] = global.custom_galaxy_name;
global.custom_system[? "name"] = global.custom_system_name;
global.custom_system[? "room width"] = global.rm_width;
global.custom_system[? "room height"] = global.rm_height;
if (!ds_map_exists(global.custom_system,"object list")) {
    ds_map_add_list(global.custom_system,"object list",ds_list_create());
    log("System map does not contain key \"object list\", creating one.",log_type.INFO);
}
else {
    var _list = global.custom_system[? "object list"];
    var _list_size = ds_list_size(_list);
    for (var i=0;i<_list_size;i++) {
        var _map = _list[| i];
        ds_map_destroy(_map);
    }
    ds_list_clear(_list);
}
var _objects = global.custom_system[? "object list"];
with (obj_collision) {
    var _list_size = ds_list_size(_objects);
    var _object_map = ds_map_create();
    if (variable_instance_exists(id,"name")) {
        _object_map[? "name"] = name;
    }
    if (variable_instance_exists(id,"mass")) {
        _object_map[? "mass"] = mass;
    }
    if (variable_instance_exists(id,"max_mass")) {
        _object_map[? "max mass"] = max_mass;
    }
    if (variable_instance_exists(id,"min_mass")) {
        _object_map[? "min mass"] = min_mass;
    }
    if (variable_instance_exists(id,"orbit_speed")) {
        _object_map[? "orbit speed"] = orbit_speed;
    }
    if (variable_instance_exists(id,"orbit_radius")) {
        _object_map[? "orbit radius"] = orbit_radius;
    }
    if (variable_instance_exists(id,"rotation_speed")) {
        _object_map[? "rotation speed"] = rotation_speed;
    }
    if (variable_instance_exists(id,"collision_radius")) {
        _object_map[? "collision radius"] = collision_radius;
    }
    if (variable_instance_exists(id,"linked_body")) {
        if (linked_body != noone) {
            _object_map[? "linked body x"] = linked_body.x;
            _object_map[? "linked body y"] = linked_body.y;
            _object_map[? "linked body object type"] = object_get_name(linked_body.object_index);
        }
    }
    _object_map[? "start x"] = x;
    _object_map[? "start y"] = y;
    if (object_index != obj_ship_editor) {
        _object_map[? "object type"] = object_get_name(object_index);
    }
    else {
        _object_map[? "object type"] = object_get_name(obj_ship);
    }
    ds_list_add(_objects,_object_map);
    ds_list_mark_as_map(_objects,_list_size);
}

var file = file_text_open_write(global.custom_file_str);
file_text_write_string(file,json_encode(global.custom));
file_text_close(file);
It will be confusing to read through, but the basic gist is that I have a map called global.custom that I save ALL my data into. That map has a key called "galaxies" which holds a list of all the galaxies that have been created. Each position in the galaxies list holds a map, which has the galaxy name (under the key "name") and a list of the systems within the galaxy (under the key "systems"). Each position in the systems list has another map associated with it, which holds the system name (key: "name"), the width and height that you want the system room to be (keys: "room width" and "room height"), and then a list of instances (key: "object list"). Each position in the object list holds a map that has all the variables I need to save for each object, so basically, the object type, the name I've given the object, the max, min and actual mass, the orbit speed, orbit length, etc, etc. Just everything I need to know to recreate the instance as it appears in the map editor. Then I open the file and write the global.custom map to file, encoding it in JSON format. So that's an example of one way that I actually use data structures.
 
Last edited:

BQubed

Member
Thanks a ton. This is exactly the kind of thing I needed. See, I didn't even consider the thought of using ds_grid to create a ground grid. In my mind, it was always a method of storing written information for later retrieval. I'm fairly proficient at reading code, even without any explanation (so long as variables are named properly), but I'm sorely lacking in the conceptualization of it. This ends up with me holding a bunch of tools in my hands, knowing what they are and what they do, but not really knowing how to use them.
 

YanBG

Member
You started with simple GML videos and i get it why you didn't like them but there are some more advanced C# and Java tutorials on youtube from veteran devs. I've been checking them and you can get good ideas on the basics of game design, like pathfinding algorithm or filling and ordering game information in arrays/data_structures without the need for GMS type of objects.

I also want to improve the formatting and parsing of my dialogue system. I use external CSV(loaded to a ds_grid), in the first column you can specify which NPC uses that line, that way you'll assign it. Another column pointing to the ID of the next line in this dialogue tree and so on. It gets a bit long when you want to have checks on intelligence etc stats of the player, that's why i'll look into improving it.
 

BQubed

Member
Not a bad idea actually. I'd be happy to hear some recommendations.

On the subject of dialogue, I've actually managed to blunder my way into a dialogue system that follows my personal organizational patterns quite nicely. Now, all dialogue is called from within the creation code of the individual instance. I know that having an external dialogue file enables you to look at all dialogue in one place, compatmentalizing dialogue within each individual instance is something that works well with my style of workflow.
 

YanBG

Member
On the subject of dialogue, I've actually managed to blunder my way into a dialogue system that follows my personal organizational patterns quite nicely. Now, all dialogue is called from within the creation code of the individual instance. I know that having an external dialogue file enables you to look at all dialogue in one place, compatmentalizing dialogue within each individual instance is something that works well with my style of workflow.
Yeah it can be a matter of personal preference or style, if you can make a good game with whatever means it's still a win for you. In later projects you'd start changing to more modular and team oriented workflow, also if you get hired there are some industry standards to follow.
I downgraded my project a lot but still i keep it easy for players to mod, or make expansions.
Not a bad idea actually. I'd be happy to hear some recommendations.
I wanted to check more videos from this guy's channel. So far i only added his A* to my games, managed to understand the theory and code it(instead of just copy-pasting GML like i was doing before), C# has INFINITY which we lack but i just used 9999999, my path would never get that long anyway. The script is ~20 lines, very short, you have full control of it and i think it's faster than the built-in mp_grid


I took some rest from coding and forgot what i watched, also some more popular coding channels are either too technical or not really building games but here is this guy and info on binary space partitioning(BSP), haven't checked much of him and he is a bit "bad"(still will give you ideas and point you to articles to read)


Other techniques i learned:
-voronoi diagrams(split region into random sectors, good for provinces on a strategy map)
-perlin noise(procedural terrain generation)
-bresenham(line of sight)
 
Last edited:
Top