March Progress
I hit a bit of a snag recently with some of the refactoring I had been meaning to do with my code. Basically, I was relying on a system of 2D arrays for most of my level creation code, which was fine, but it turned my code into a disorganized mess. I spent more time writing code to untangle arrays within arrays within arrays than actually making my game. I suppose this is probably a normal feeling but rather than trudge forward into something I was unhappy with, I spent most of the past few weeks rewriting my random dungeon creation code.
I had been reading and writing my dungeons in a linear, top-to-bottom fashion. For example, the object in the top left area of the top left corner of the dungeon (room) would be placed first, then the one to the right of that, etc. This was actually not required at all as there is nothing in GameMaker or GML that requires instantiated objects to be added to rooms in this way. For some reason I just thought this made sense at the time. It did make debugging easier because I could print the values out as text and verify that they were working, but the additional steps I took to reorganize all of my layout data in this method were no longer needed. Another example: the dungeon paths are stored as a list of grid values in the actual layout of the maze. It also didn’t make sense to put these values in “order” and create the system of entrances/exits that I had been using. I could just run through the actual maze from start to finish rather than always starting from the top left corner of the dungeon.
I knew for some time that I needed a better way to construct area templates. Areas in my game are essentially chunks of the entire level, or room, in GameMaker lingo. My original plan was to create area templates through code like I had with the first iteration of my random dungeon generator. A basic area looked like this:
[[0,0,0,0,u,u,0,0,0,0], [0,1,1,1,1,1,1,1,1,0], [0,1,1,1,1,1,1,1,1,0], [l,1,1,1,2,1,1,1,1,r], [l,1,1,1,1,1,1,1,1,r], [0,1,1,1,1,1,1,1,1,0], [0,1,1,1,1,1,1,1,1,0], [0,0,0,0,d,d,0,0,0,0]]
This was okay but editing was slow and cumbersome, and I made stupid syntax errors all the time by forgetting commas, brackets, etc. I then looked into designing templates through something like JSON. These areas looked like this:
{ "area_a" : { "name" : "Starting Area", "enemies" : 2, "exits" : ["up", "down"], "gold" : 0, "locked" : true, "size" : [10,8], "tileset" : "t_stone_tileset" } }
In my head this made sense but it still didn’t offer me an easy way to build new templates. I really needed an editor. There are a few third party tools that could work, but I wanted to keep things within the GameMaker IDE, if possible. I also didn’t want to spend more time creating complex code to encode/decode various abstract level data structures. It seemed like potentially another waste of time.
I am used to using software like Unity or Adobe Animate (Flash) where it is possible to create objects made up of several other objects. For example, prefabs in Unity or MovieClips in Animate. At this moment there unfortunately isn’t an out-of-the-box solution for something equivalent in GameMaker Studio 2. It does have, however, a room building tool which is more or less the same as the scene views in Unity or Animate. For those unfamiliar with GameMaker, rooms are like scenes in Unity or the stage in Animate. I thought that maybe I could create small rooms for my areas and then piece them together like Lego bricks during the random level generation loop. There was another setback — there didn’t seem to be a way of accessing room data from within another room. Essentially, in order to get the information I needed from a room asset, I needed to be inside of that room. Okay.
I then realized that all I needed to do was create a persistent room-reading object that would start outside of my area template rooms, cycle through them, and then store their contents before loading the actual game room. I started by creating a simple room that was equivalent to one of my old 2D array-based areas using GMS2’s room editor.
I then created some code that would locate all of the objects in this room and store them in a DS grid.
// Create a DS grid the size of the dungeon area in tiles room_grid = ds_grid_create(DUNGEON_AREA_WIDTH,DUNGEON_AREA_HEIGHT); ds_grid_set_region(room_grid,0,0,DUNGEON_AREA_WIDTH,DUNGEON_AREA_HEIGHT,noone); // Valid area objects array var _objects = [o_solid, o_platform, o_brick_crumble]; // Run through each valid area object type for (var _type = 0; _type < array_length_1d(_objects); _type++) { for (var _object_count = 0; _object_count < instance_number(_objects[_type]); _object_count++) { // Retrieve the objects of each type in the room var _object_id = instance_find(_objects[_type], _object_count); var _grid_x = _object_id.x/CELL_WIDTH; var _grid_y = _object_id.y/CELL_HEIGHT; // Store objects in the room_grid at the coordinates specified ds_grid_set(room_grid, _grid_x, _grid_y, _objects[_type]); } } room_goto(rm_random);
After creating this grid, I then move to (rm_random) and build the room with the area data I gathered from the template.
// Traverse the grid and instantiate the objects found within for (var _x = 0; _x < DUNGEON_AREA_WIDTH; _x++) { for (var _y = 0; _y < DUNGEON_AREA_HEIGHT; _y++) { var _object_x = _x * CELL_WIDTH; var _object_y = _y * CELL_HEIGHT; // If there is an object at the specified coordinates, instantiate it at the proper location. if (room_grid[# _x,_y] != noone) { instance_create_layer(_object_x, _object_y, "Instances", room_grid[# _x,_y]); } } }
Which results in this:
It is a fairly simple process compared to my previous attempts. The final version of this code will read all of the required area template rooms and store them in DS grids, which will then be destroyed as they are consumed by the random dungeon code. The benefit to this system is that it will be much easier for me to quickly add new area templates by leveraging GameMaker’s existing room editor.
Leave a Reply