Game Maker Studio 2 Level Design
Designing Levels in Notepad | GameMaker Tutorial
This is a revamp of the 2017 tutorial of a similar concept . The source code for this project is available at the end of the article.
When GameMaker Studio 2's Room Editor is too much
YoYoGames shipped GameMaker Studio 2 in 2017 with a new and improved room editor. Using a simple drag and drop interface that's been a staple to the GameMaker line of software, complex levels can be crafted in no time. Level design in the IDE is quick and easy. But, we can make the process even easier by thinking outside of the box.
This is a written tutorial that accompanies the Step Event video of the same name. If you'd prefer to listen or follow along in real-time instead of reading, watch the embedded YouTube video below. You'll walk away from both mediums with the same amount of game making knowledge, so the choice is yours!
Making levels using ASCII art
Our approach to designing levels won't involve dragging objects around in the GameMaker Studio room editor. Instead, we'll be writing levels in text processing software! This is similar to the concept of ASCII art, or using alphanumeric characters to make pictures.
Each character we write will be associated with a specific object in our game. We'll hard-code these symbolic relationships into our game logic. Then, when the game runs, GameMaker will read our text file character by character and create instances of objects in the room.
This tutorial won't cover real-time updating like this .gif shows off. But, seeing the characters correspond to objects in the GameMaker room should help you understand our approach to designing levels.
In the image above, all "X" characters represent wall objects, all "P" characters represent player objects, and all "C" characters represent coins. When GameMaker reads the text file, it will create the associated object at an (x, y) coordinate relative to the row and column of the respective character.
Writing your GameMaker levels in Notepad or TextEdit
If you're on a Windows machine, chances are you've used Notepad before. This Windows-specific software is arguably the epitome of "bare bones" and "simplistic". It's the perfect tool for mass-producing tiny levels. If you're developing a maze game or puzzle game, I implore you to consider this approach.
Mac users, your device comes bundled with TextEdit. It's similar to Notepad.
At the end of the day, all you need is a text editor. I recommend a code-oriented text editor like Sublime Text or Notepad++ over something like Microsoft Word. My reasoning is two-fold: monospace typefaces and easy .txt file extension saving.
A typeface being monospaced means that each of the font's characters are the same width. Consolas is Notepad's default font and it's monospace. You'll quickly come to realize that writing levels is less of a feat when rows and columns of symbols line up.
You'll know you're working with a monospace font when the characters line up and are "grid-like".
We also want to save files in .txt format. Notepad saves files in .txt by default. A .txt format is easiest for our game programmatically read. Other formats, like Microsoft Word's .docx, has encoded data and may produce unintended results when GameMaker parses through it.
Writing the GameMaker Language code
Creating sprites and objects
In order to create levels, you'll need some objects. As seen a few sections back, this example project will have three level-related objects: a wall, a player, and a coin. I named the objects obj_wall, obj_player, and obj_coin, respectively. The sprites I assigned the objects are courtesy of Kenney, a delightful resource creator for game developers.
A screenshot of the GameMaker project and the Resource Tree, containing the three sprites and three objects.
All of my sprites have dimensions of 64x64. You'll come to realize that one of the major limitations to designing levels this way is that you're restricted to uniform grid size. For mazes and puzzles, this isn't a problem. For more ambitious games, like large platformers, you might feel restricted.
For simplicity's sake, I won't assign any code to the three objects. It'll be up to you to add in player movement, wall collision, and coin collecting if you choose to turn this sample into a fully-fledged game.
Declaring variables
We'll need to create a fourth object that acts as a controller. Because this controller object will eventually read a text file and generate levels, it can be named obj_generator. It doesn't need a sprite assigned to it because it doesn't need to be seen; the generator will do the behind-the-scenes magic.
Check the "persistent" box on the object so it'll be ever-present throughout your game, regardless of what room the player travels to. Drag an instance of obj_generator into the first room of your game so it'll initialize itself as soon as possible. This will be the only time you'll use the vanilla room editor for this project!
In the Create Event of obj_generator, add the following code.
layer_create(0, "layer_level"); level_data = - 1; current_level = 0; level_separator = "END"; cell_width = 64; cell_height = 64; generator_import_levels();
First, let's create a new layer called layer_level. We'll create instances of obj_wall, obj_player, and obj_coin on this layer later. Creating instances on a particular layer allows us to destroy them later using a simple call to GameMaker function layer_destroy_instances. You'll read more about this in the upcoming level loading section.
We'll store the data of the levels in the aptly-named variable, level_data. It'll be an array soon, but we'll initialize it as -1.
To keep track of the current level that's being played, we can use variable current_level.
Level_separator is a string of text that's used to denote the end of a level's data in the text file. When our game reads through the text and comes across this string, it knows that the end of the level is reached and a new level follows. We should, in theory, be able to design a limitless number of levels in a single text file.
Variables cell_width and cell_height are the base width and base height of cells in our grid. In other words, each character we write in our text document will correlate to 64x64 pixels of in-game real estate. Objects will be placed in the room at intervals of 64 pixels. If the choice of 64 width and height seem arbitrary, refer to the fact that these are the dimensions of my sprites.
Finally, we'll end the Create Event with a call to script generator_import_levels. This script, which we have yet to write, will read the levels.txt file and populate the level_data array. We'll come back to this in a little bit. You can go ahead and create an empty generator_import_levels script for the time being. Otherwise, if you try running your game you'll get an error telling you the script doesn't exist.
Establishing the symbolic relationship
Before we jump in and start designing levels, we need to tell the game how we'll be designing levels. This is where symbolic relationships come into play. The game won't know we want the character "X" to represent obj_wall unless we explicitly tell it such.
There are several ways we can accomplish this character-to-object relationship. One way is using a ds_map. But, we'll keep things straightforward and use a switch statement.
Create a new script named generator_character_lookup. This script will accept one argument, a character, and return the object associated with it.
switch (argument0) { case ("X"): return obj_wall; case ("C"): return obj_coin; case ("P"): return obj_player; default: return undefined; }
The code block takes argument0, our character, and checks it against all the different cases. When there's a match, the script stops its execution and returns an object. Because we have three level-related objects, there are three cases. If you want to add a fourth object, like a door, you could add a fourth case, "D", and return an obj_door, should it exist in your game.
If a character doesn't match any of the cases, the default is executed and the script returns undefined. This prevents the game from bugging out if an invalid character is passed into the script. It also means we can design levels with open spaces using, well, spaces (or any other character that doesn't have a case).
Note that the cases are case-sensitive. So, our program won't create an obj_wall for a lowercase "x", but will create an obj_wall for an uppercase "X".
How to make a video game in Notepad
Now that we have the relationships between characters and objects established, we can finally start designing the levels! Open up an empty text document and start typing away. Create a level using "X", "P", and "C" characters.
Remember, our cell_width is 64 pixels and our cell_height is 64 pixels. Knowing that the default width of a GameMaker Studio 2 room view is 1024 pixels and the default height is 768 pixels, we can fit 16 objects horizontally and 12 objects vertically before going off-screen.
If you're feeling especially creative, go ahead and design more levels! When you're satisfied with a level, type "END", our level_separator variable, on a new line.
Once you're satisfied, save your file as "levels.txt" to a familiar location. We'll be importing this document into GameMaker in the next step.
Importing the Notepad level
In the GameMaker Studio Resource Tree, navigate to Included Files. Right-click Included Files and then "Insert Included File". Alternately, you can use the hotkey ALT+I. Navigate to where you just saved levels.txt and Open the file.
Now, you should see levels.txt in the Resource Tree's Included Files folder. If you want to modify the levels you made, or create new ones, Right-click levels.txt and then Open in Explorer. You'll now see the directory that GameMaker added the file to. To reiterate, you should now be editing this copied file and not the original in the previous step.
Reading the file
Earlier we made an empty script called generator_import_levels. It's time to actually write some code for it. This script will load the levels from the levels.txt file into memory. This is a crucial step to the level generation process. Without properly-formatted data, our game can't generate levels.
The code block below will be a lot to take in at first glance! Though, if you take the time to read it, there's not a whole lot actually going on.
var file_name = working_directory + "\\levels.txt"; if (file_exists(file_name)) { var file = file_text_open_read(file_name); var temp_level_array = - 1; var level_at = 0; var level_row = 0; while (! file_text_eof(file)) { var line = file_text_read_string(file); if (line != level_separator) { for(var i = string_length(line) - 1; i >= 0; i --) { temp_level_array[i, level_row] = string_char_at(line, i + 1); } level_row ++; } else { level_data[level_at] = temp_level_array; temp_level_array = - 1; level_at ++; level_row = 0; } file_text_readln(file); } file_text_close(file); } else { show_error("Cannot locate " + string(file_name) + "!", true); return false; } return true;
Temporary variable file_name is the location of the text file that contains the levels. GameMaker's Included Files, like our levels.txt, are stored in the working_directory sandbox.
Next, we check to see if the file actually exists. If it doesn't, then we throw an error and the script stops executing by returning false. If the file exists, as it should given your journey thus far in the tutorial, then we open it and read it using the file_text_open_read function.
Now that the level's open, we can create some variables to help us make sense of it. temp_level_array will be a temporary array that holds the data of the current level being read. Remember, our text file can contain the level for multiple levels. We need to be able to differentiate one from another. Variable level_at holds the number of the current level being read into memory. Lastly, level_row keeps track of what row of the current level the program is reading. For every line of a given level the program reads, level_row increases by one.
The while loop is where the text file parsing happens. Basically, we keep executing the code inside of the loop until the end of the file is reached. Inside the loop, we tell the program to read the current line of text.
If the current line isn't the string of text we declared should denote the end of a level, we read the line of text character by character and write it to the temporary array. You'll notice that the for loop iterates over the string of text backwards. YoYo Games have stated on their developer blog that it's more efficient to initialize arrays in reverse order. Though, unless you're creating hundreds of levels, you probably won't notice even a sliver of performance impact by initializing going forward.
Once the entire row is written into the array, move on to the next line in the text file using file_text_readln. Keep reading and writing level rows until we encounter the level_separator string. When the end of a level's been reached, we add the temporary level data into the "official" level_data array that we created earlier. Clear the temporary level array to make room for the upcoming level data, increase the level counter, and reset the level row.
Once all the levels have been saved into memory, close out of the text file and then return true from the script. Following the logic of this script, the entire contents of levels.txt is now sitting nice and tidy inside the game's memory in array level_data.
Whew! Glad to be done with that mammoth of a script!
Generating the levels
In a new script called generator_load_level, add the following code. Just like generator_import_levels, it might look like a lot to take in at first. But, we'll step through it line by line. It's actually quite straightforward.
layer_destroy_instances("layer_level"); var level_number = argument0; if (level_number >= array_length_1d(level_data)) return false; var level_array = level_data[level_number]; for (var i = 0; i < array_height_2d(level_array); i ++) { for (var j = 0; j < array_length_2d(level_array, i); j ++) { var character = level_array[i, j]; var object = generator_character_lookup(character); if (object == undefined) continue; instance_create_layer(i * cell_width, j * cell_height, "layer_level", object); } } return true;
First, we destroy all level-related instances using the layer_destroy_instances function. This won't destroy the generator object itself, however, because obj_generator was manually placed into the room.
To ensure that the level_number, argument0, actually exists, we perform a check to see if it's out of bounds. If the level is invalid, we return false and the script ends. If the level is valid, temporary variable level_array will fetch the specific level data for this level number. A level_number of 0 means that level_array will contain the first level we made. If level_number is 1, then level_array contains the second level we made.
The next two lines of the code cycle over the level data. Because each level is merely a two-dimensional array, the first for loop iterates over the rows and the second loop iterates over the columns. We can determine how many rows are in the array using the function array_height_2d. Similarly, array_length_2d will tell us how many columns (indices) are in a given two-dimensional array index.
At this scope, we have access to every individual cell in our level. We can access the specific character at these (i, j) indices and lookup the object associated with it using our script generator_character_lookup. If we stumble across a character that doesn't have a symbolic relationship formed, then we skip the instance-creating portion of the code and move on to the next character in the array.
If the character is valid, an instance of the associated object will be created at coordinates relative to the (i, j) indices multiplied by our cell_width and cell_height, respectively.
Return true, indicating that the level was successfully generated. Woohoo!
Tying everything together
We're almost done! We've established symbolic relationships, then we designed levels, then we read the levels, and most recently we wrote the code that generates the levels. Now, all that's left is to call the generator_load_level script to actually play the levels.
The easiest way to do this is in the Step Event of obj_generator. Add the following block of code. It will cycle through our levels every time the spacebar is pressed. The number of levels is just the length of the level_data array minus one, so when we reach that number, cycle back to zero.
if (keyboard_check_pressed(vk_space)) { generator_load_level(current_level); if (current_level < array_length_1d(level_data)- 1) { current_level ++; } else { current_level = 0; } }
In practice, this works fine. In an actual game, players shouldn't be able to skip ahead just by pressing space. Instead of space sending the player to the next level, you might want some type of win condition performing such a trigger.
Limitations of making game levels in Notepad
If you've given this method of designing levels much time, you'll realize it's limiting in nature. First, and foremost, you're constrained to uniform grid size. The GameMaker Studio room editor allows for objects to be placed at any given (x, y) coordinate. Our Notepad editing keeps all objects contained to intervals of our defined cell width and height.
Going off the previous point, objects can't be overlapped. Only one instance of one object can spawn in a given cell. That's not to say it's impossible to add, but the basic framework doesn't account for it.
The bigger the level, the more complex it may be to read. The GameMaker room editor shows the sprites assigned to objects. So, as you build the level, you get a good sense of how things may look in the live game. ASCII art is single-color and characters only explain an object so much. "P" may stand for "player", but it hardly does the player sprite justice.
Finally, security is a major concern. Anyone who has downloaded your game can navigate to the levels.txt file, open it, and make changes. You can use a checksum to overcome this problem, but that's outside the scope of this tutorial. A checksum, in this context, could be hashing the level string alongside the level in the text document and then comparing the level string in-game. If the two hashed strings match, the level hasn't been modified.
GameMaker project source code
Whether you watched the YouTube video or read the tutorial, you should now understand how a text editor can be used to design GameMaker Studio levels. If you found this tutorial useful and want to see more like it, tell me! Subscribing to The Step Event on YouTube and sharing the video is a great way to show support.
The source code for this project is available to download on itch.io. It's pay-what-you-want, so if you're feeling highly generous, consider a small tip. Donations are optional but greatly appreciated and allow me to continue to produce educational content. You can also purchase Step Event laptop stickers!
I hope you learned something new!
Game Maker Studio 2 Level Design
Source: https://www.zackbanack.com/blog/designing-gamemaker-studio-2-levels-in-notepad
Posted by: fishersaity1935.blogspot.com
0 Response to "Game Maker Studio 2 Level Design"
Post a Comment