Playing with Strings in Game Maker Pt 2

In the last tutorial, we covered the basics of playing with strings. You should, by now, have a good grasp of the differences between chr and ord, numbers and strings, and how they can be used for capturing user input. In this tutorial, we are going to create two different projects that focus on manipulating strings: a script that can export map data, and a script that can tear apart and reconstruct data to replicate movement.

Map Exporter

Game Maker is an amazing program that easily allows users to create games. However, sometimes you might want to port your game to other platforms that aren’t exactly compatible with Game Maker’s data. All the levels you spent hours tweaking might have to be rebuilt by hand, and just the thought of that might make you hesitate about continuing on. If only you could export all the object placement data out of each room. Luckily, this can be quite easy to do if you know how to play with strings (and some other important functions).

A very common method for building worlds is to pull the object placement data from a CSV or Comma Separated Value text file. Each object is represented by a number and is placed accordingly on a grid specified by world builder. In our example, we will be using a unit value of 32 x 32 and will allow for rooms of any size. Our code will output a CSV text file for each room upon creation and will do all the formatting for us.

The first step is to set up all your room objects correctly. In the supplied file, you can see that I have four objects: obj_Block_Parent (all other objects are parented to this), obj_Block, obj_Ramp_L, and obj_Ramp_R. The three children objects each have a script in their Create event that initializes a variable “myType”. Each one has a different value: obj_Block = 1, obj_Ramp_L = 2, obj_Ramp_R = 3. This is the variable we will be grabbing the value from for our map file. Finally, you need to call this script in the Creation Code of the room you want to export data from so that it runs as soon as the room is displayed without having another object in the room. Now that everything is set up, let’s take a look at the script file.

scr_Tool_MapData

theData = "";
for (j = 0; j < room_height; j += 32) {
    for (i = 0; i < room_width; i += 32) {
        if !instance_position(i,j,obj_Block_Parent) {
            theData += string("0,");
        } else {
            type = instance_position(i,j,obj_Block_Parent)
            theData += string(type.myType) + string(",");
        }
        if i == room_width-32 {
            theData = string_delete(theData, string_length(theData),1) + chr(13);
            myFile = room_get_name(room);
            mapFile = file_text_open_write(string(myFile) + string(".txt"));
            file_text_write_string(mapFile, theData);
            file_text_close(mapFile);
        }
    }
}

We start off the script by initializing a variable “theData” to ensure our script doesn’t crash. Next, we have two for loops, the first one is for the vertical rows of the map, the second is for the horizontal columns. You should notice that I am using the room_height / room_width constants to make this work for rooms of any size, and we increase the loop by the size of our grid in this case, 32 pixels.

Once we are in the for loops themselves, we first check to see if the space is empty of our desired map data (obj_Block_Parent). If it is empty, then we add  (“0,” ) to the string variable theData. If there is an instance of one of the blocks, we query what type it is by grabbing its myData variable, which we then add to the string theData, plus a comma for separation (ex. “1,”).

When we get to the end of a row ( i == room_width – 32) we need to change up some of the data. We need to remove the final comma and then have a line break. Removing a substring is achieved by using the string_delete function, which returns the supplied string minus the the chunk you want removed. We find the position in the string of the last comma, by using the string_length command, which gives us the end of the entire string. (Remember, we are adding a comma after every number). We then remove 1 character. Finally, we use chr(13) which is the line break in ASCII code, or what is properly called: a carriage return.

The next line creates a variable that grabs the current room name, which we will use for our file name. The next three lines of code are using Game Maker’s internal file writing functions: file_text_open_write, file_text_write_string, and file_text_close.  The first one opens the desired file, the second write the chosen data to the file, and the last one, most importantly, closes the file.

The final result is that when you run the game and enter the room, a file will be created with the entire map printed as comma separated numbers. If you notice, this was a more complex project than the last, but we used the same string functions as before. Nothing new, just a different implementation of them.

Download the completed file.

 

Replicating Movement

Hopefully you are now starting to feel pretty comfortable with using the string functions, so let’s look a a couple other functions. In this project, we are going to build a practical example for why you would want to break apart data: replicating movement. Normally, I would use this when creating a networked game, where I want all the players to know what is occurring in the world. Rather than send lots of little packets with each element on its own, it is more stable to send a single string with all the info. Here we are going to keep it simple. We are going to have a player character that moves around when you press the up arrow key in the direction of the location of the mouse. Every couple of seconds afterwards, a new ghost version will be created which will follow the exact same movement as the player had previously done.

You can use the supplied file or if you want, the setup for this is fairly simple. We will need three objects (obj_Player, obj_Ghost, obj_Spawner), one room with the player and spawner placed inside, and five scripts which will deal with all the data. Let’s start by getting the spawner out of the way. All it needs is an alarm[0] in its Create event set for 150 frames. When alarm goes off, it should create an instance of obj_Ghost and reset the alarm to 150.

The Player

The player is the object that is going to be writing all the data. We need to start all of this in its Create Event with the following code.
scr_Player_Create

mySpeed = 8;
myDirection = 0;
image_angle = myDirection;

perFrame = 0;
infoArray[perFrame] = string(x) + string("|") + string(y) + string("|") + string(image_angle) + string("|");

We start by setting up variables for the players speed and direction to ensure we have a starting position. We also create a variable “perFrame” which will be used as a counter. Finally, we create an array that will carry the string of data. As you can see, we ask for the x coordinate and then place a | line. This | line is what we will be using to keep the data separate from each other. We could have used other symbols, such as #, but I chose it simply because I never use it for anything else. The array now has the string “x|y|image_angle|” stored in it.

scr_Player_Step

myDirection = point_direction(x,y,mouse_x,mouse_y);
image_angle = myDirection;

if keyboard_check(vk_up) {
    xx = lengthdir_x(mySpeed, myDirection);
    yy = lengthdir_y(mySpeed, myDirection);
    x += xx;
    y += yy;
}

perFrame += 1;
infoArray[perFrame] = string(x) + string("|") + string(y) + string("|") + string(image_angle) + string("|");

The controls themselves take up the majority of the code here and are irrelevant to our dealing with strings. The only two important lines to us are the last two, where we increase the perFrame variable, which in turn, adds a new row to the array. Once again, we add the x, y, and image angle data to a string. Make sure you place this in the Step event of the obj_Player.

Sorting Information

Now that we have something passing us data, we need to be able to break it apart and turn it into something useful. For this we are going to create a reusable script, which means we will want to pass an argument for what data it will use.

scr_SortInfo

theInfo = argument0; 

//Break apart all the info into useful bits
for (i = 0; i < string_count("|", argument0); i += 1)
{
    pos = string_pos("|", theInfo);
    info[i] = string_copy(theInfo, 0, pos - 1);
    theInfo = string_delete(theInfo, 1, pos);
}

The first line places the original data into a variable that we can manipulate. We then enter a for loop that is going to see how many instances of our “|” can be found in the string. Our first line inside the loop uses the function string_pos, which grabs the position of the first “|” it can find in the string. Once we know where the data needs to be clipped, we use the function string_copy to make a duplicate of the substring that starts at the beginning and goes up to the space just before our “|”. This information is stored into an array that we can then use in our Ghost. Finally, we remove the substring, including the “|”, and run through the loop until it is complete. This script isn’t added to any object, but instead will be called from other scripts when needed.

The Ghost

We stated that want our ghost to follow the same path as the player. To achieve this, we saved out player data every frame and now we have to put that data to use.
scr_Ghost_Create

perFrame = 0;

scr_SortInfo(obj_Player.infoArray[perFrame]);

//The Sorted Information
x = real(info[0]);
y = real(info[1]);
image_angle = real(info[2]);

Once again, in the Create Event, we can use perFrame as a counter variable, which is set to 0 to start. Next, we run the scr_SortInfo script that we just created. Here we are directly passing the Player object’s infoArray string.

After the script has chopped up the string, we now have the info array which we can apply to the Ghost. It is important to notice that each of these pieces are being converted from strings into numbers using the real function. This is necessary as the coordinates and angle expect a number to be passed to them.

scr_Ghost_Step

perFrame += 1;

scr_SortInfo(obj_Player.infoArray[perFrame]);

//The Sorted Information
x = real(info[0]);
y = real(info[1]);
image_angle = real(info[2]);

The Step event for the obj_Ghost is almost identical to the scr_Ghost_Create script, except that we are increasing the perFrame variable by one, which will duplicate the players motion. At this point, you should be able to run the game, move your character around and see the ghosts repeat the movement!

Download the completed file.

 

Conclusion

You have added characters to strings and removed them. You can locate the position of a single character, or find out how many times a word appears in a sentence. You can even change where a substring is placed and use that data in any way you see fit. As you can see, being able to manipulate strings can be a very powerful tool.

I only covered the most common functions that Game Maker has and I encourage you to check the others out. Until next time, go make games!

Leave a Reply