In most programming languages, the code within your program is static during run-time. Dynamic actions are usually simulated by branching to different sections of your code using conditional statements.
However, Game Maker provides some powerful functions to temporarily alter, append to, or delete from your source code during run-time. Those functions are what we are going to explore in this tutorial.
Part OneThe execute_string() and execute_file() Functionsexecute_string(codestr) accepts a single string argument. The function will take that string, and execute it as if it were a piece of code. For example:
execute_string("foo += 1;");
That would increment the variable
foo by 1. However, in that example, we used a static string. The power of execute_string() can only be seen when we use it dynamically. Consider this next example:
code = "foo += 1;"; //Executed once at game initialization.
...
execute_string(code); //Executed some place else
This will do the same thing as the previous example. However, unlike the previous example, we're now using a variable. This is important to note, because the values of non-constant variables can be changed while the program is running. This means that by changing the value of
code during run-time, we can change the code that gets executed.
Another important thing to realize is that by using string concatenation, we can alter fragments of a code action that couldn't otherwise be edited. In the statement
foo += 1;, the only thing that could normally be changed by utilizing variables is the value "1". The arithmetic operator
+= could not be changed, and the variable being incremented,
foo, also could not be changed. No matter what trickery you used, those two things would normally have to be a static part of your code.
However, consider this next example:
operators[0] = "+=";
operators[1] = "-=";
operators[2] = "*=";
operators[3] = "/=";
usage = -1;
while (usage < 0 || usage > 3)
usage = get_integer("Add(0), Subtract(1), Multiply(2), or Divide(3)?",0);
execute_string("foo "+operators[usage]+" 1;");
As you can see, we were able to explicitly alter the operator that was being used, using nothing but a variable, and string concatenation. A bulky if/else structure was completely avoided.
For a more practical example of this, think of Game Maker's Script system. It is fairly primitive compared to the function building of most object oriented languages, and does not allow you to pass in an array as an argument. However, by passing the name of an array as a String, and using some string concatenation trickery, we can use execute_string() to bypass that restriction:
/*
SCRIPT scr_arrayAdd(arrayName,index,value);
Adds a specified value to an index of an array.
arrayName: The name of an array to add a value to.
index: The index of the array to add to.
value: The value to add.
*/
execute_string(argument0 + "[" + string(argument1) + "] += " + string(argument2) + ";");
scr_arrayAdd("foo",0,1); //foo[0] += 1;
scr_arrayAdd("foo",1,4); //foo[1] += 4;
execute_file(filename) is another function that works very similarly to execute_string(). The only difference is that you pass it a file name, rather than a String. It takes the text file you specify, reads it in, and then executes the contents of the file as code.
Unlike execute_string(), which will normally be used to execute a single code action at a time, execute_file() can be used to execute large batches of code. However, there's some stuff you need to watch out for.
Whenever you use execute_string(), or execute_file(), it does not append the data to your source code. Every time that function is reached, it takes the specified code, compiles it, executes it, then discards it. This is an intensive process, and with any large quantity of code being executed in this way, you will begin to experience slowdowns. Ironically, this problem is even worse with execute_file(), because it is adding another step onto the process. Not only must it compile, execute, and discard, it must also read from a file.
Luckily, there is a solution to this. Game Maker has some other functions which allow you to temporarily append code to your source code during run-time. Using those functions, the code is only compiled once, and from that point on, it's no more intensive than if the code had been hard-coded by yourself in the code editor. We shall talk about those methods in...
Part TwoThe Appending and Deletion FunctionsIn this section, we will explore the following functions:
- object_add()
- object_delete()
- object_event_add(ind,evtype,evnumb,codestr)
- object_event_clear(ind,evtype,evnumb)
object_add() takes no arguments, and allows you to add a new object to your game, during run-time. This object will exist until you delete it, or until the game is closed. The function returns the reference to the new object, so make sure to store it in a variable:
global.obj_new = object_add();
By convention, you should always make that variable global, because all objects in your game should be accessible from anywhere (This is not to be confused with instances). The new object will contain no events, and no code; it'll be exactly how a new object would be when you press the "Create an Object" button in the editor.
You can delete an object with
object_delete(ind), where
ind is the object to delete. Like object_add, this is not a permanent operation; everything will be reverted back to normal when the program is closed.
object_event_add(ind,evtype,evnumb,codestr) will append code to the end of an event in a specified object.
ind is the object to add the code to.
evtype and
evnumb specify which event the code should be added to (Refer to the event_perform() section of the GM Help File for details on what to put for these arguments). If the event does not exist, it'll be created.
codestr is a string containing the code to be added to the event.
While this will typically be used to add data to the new objects we create with object_add(), it can also be used to append data to already existent objects.
Now, track back to execute_file() from the previous section. If we wanted to use execute_file() to execute code every step (which would create too much lag to be practical), we could use these new functions to replicate that functionality instead:
code = ""; //Will hold the code from the file
//////////////////////////////
//READ FILE
//////////////////////////////
data = file_text_open_read("file.txt");
while(!file_text_eof())
{
code += file_text_read_string(data);
file_text_readln();
}
file_text_close(data);
//////////////////////////////
//CREATE NEW OBJECT WITH CODE
//////////////////////////////
global.obj_new = object_add(); //Adds the new object
object_event_add(global.obj_new,ev_step,ev_step_normal,code); //Adds the code to the step event
instance_create(0,0,global.obj_new); //Creates a new instance of the new object
Notice that in the above code, I added all of the code to a single string, and added that string to the object's event. It had to be done this way, because despite the fact that object_event_add() appends to an event, we cannot add the code line by line in this case.
The reason behind this is that the new code you add is complied immediately as you use the object_event_add() function. If the code statement added is syntactically incorrect, it will throw off an error. For example, lets assume we tried to add the following code to the object's event line by line:
if (foo > 0)
{
foo -= 1;
}
That would throw off three consecutive errors. It would tell you that "
if (foo > 0)" is invalid, because there is no action to execute if the if statement is true. It would tell you that "{" and "}" are invalid, because they're used alone, out of context. The only thing that wouldn't throw off an error is "
foo -= 1;", because that's a valid piece of code by itself.
For this reason, it's safest to read in the file, and add all of it to a single string, then add that string to the object. However, you must also be careful when doing this. Your entire code will essentially be represented on a single line. If you don't take this into account, some things can cause your code to error out strangely, and unexpectedly. For example, if you include a single line comment anywhere in your code, EVERYTHING else in the code after that comment will be commented out.
object_event_clear(ind,evtype,evnumb) will take an event in an object, and delete all of the code that is inside it. We can use this either when we no longer need the event to execute our code, or
when we want to rewrite the entire event from scratch. Ponder that last statement for a second, as this is the revelation we were working towards.
Lets imagine you were working on a piece of code. A terrible, tedious, trial-and-error based piece of code, which requires endless tweaking. Now, enlightened after reading this tutorial, you pull that code out of the editor, and throw it in a text file. You write a short script to read the code from the file, and add it to the correct event in your object.
Now, you add a code to be executed when you press a certain button. The code does something along the lines of this:
object_event_clear(...); //Clear the contents of the event you added
//Read the code from the file again
object_event_add(...); //Add the code back to the event you just cleared
Now, during run-time, you can edit the text file, press the button in-game, and the game will instantly update itself with the new code. No need to restart or recompile the game. All it takes is a single button press, and about 1/15th of a second worth of processing time.