ZeDuval
Member
GM-version: GMS
Target-Platform: All
Content:
Target-Platform: All
Content:
- Introduction
- Colours & Terms
- Utility-Scripts & Function-Wrapping
- An extension as a bundle, turning scripts into functions
- Last words
1.) Introduction
Welcome, fellow Gamemaker,
I keep a personal Utility-extension with general-purpose-functions. I use this extension as a way to bundle these functions, a bundle that can easily be extended and imported into new projects. I see scripts as something affiliated to a specific project. Utility-scripts should be polished and turned into functions, often replacing original built-in functions. In this tutorial, which is targeted at intermediates, I'll show you how to create Wrapper-Functions and a Utility-extension for yourself. You should, however, already have experience with GML and fundamental knowledge about script arguments, loops, conditionals, and multi-dimensional variables.
2.) Colours & Terms
I'll use the colour-coding I'm used to, since it's better to differentiate, for example, scripts and functions.
Colours: Normal Text, Keywords, Values, Comment, Constants, Built-in-Variables, Functions, Script Names, Resource Names
A Utility-Script is a script that has a general purpose, meaning that it is not affiliated to a specific project and can therefore be imported and used in every new project.
Function-wrapping means to create a script and to use an existing function and add or improve its functionality, in order to replace the original function. In the best case, the original functionality will be also preserved.
3.) Utility-Scripts & Function-Wrapping
3.1.)For a start, we're gonna add 4 scripts. If more are wanted, just ask! Let's begin right away, for a better understanding, with something simple:
Original Function: instance_create()
Arguments: x,y,obj
Returns: Real
Problem: Exactly 3 arguments must be provided.
Goal: Controller-objects are mostly invisible and therefore created at the x/y - coordinates 0,0. We want a function that can be used with or without the x and y arguments. If only the object is provided to the function, x and y will both default to 0.
In the resource-tree on the left, right-click on Scripts and choose Create Group from the dropdown-menu. Name it Utility-Functions. Now right-click on this group and...
Create Script and name it instance_new.
/// instance_new(x,y,obj)
if argument_count==1{
/// instance_new(x,y,obj)
if argument_count==1{
return instance_create(0,0,argument[0]);
}else if argument_count==3{return instance_create(argument[0],argument[1],argument[2]);
}If the amount of arguments provided to instance_new() is exactly 1, an instance of the object specified with this single argument is created at the position 0,0. If the amount of arguments is exactly 3, all arguments will be forwarded to the original function.
When wrapping a function like this, it is important to also forward the return-value of the original function, if there is one. In this example, the return-value of instance_create() is the index of the newly created instance and we have to forward it using the return statement, in order to get the index from our script, like this:
var my_controller = instance_new(obj_controller);
There are many different ways to code this little script, I'll show you a few variations:
The way I would do it, a little change to the if-clause:
/// instance_new(x,y,obj)
if argument_count>1{
/// instance_new(x,y,obj)
if argument_count>1{
return instance_create(argument[0],argument[1],argument[2]);
}else{return instance_create(0,0,argument[0]);
}Making use of a local var:
/// instance_new(x,y,obj)
var i;
if argument_count==1{
return i;
Using a switch instead of an if-clause:
/// instance_new(x,y,obj)
switch argument_count{
case 1:return instance_create(0,0,argument[0]);
case 3:return instance_create(argument[0],argument[1],argument[2]);
}
Note: In my opinion, with only 2 possibilities, neither the creation of a local var, nor the use of a switch-case, makes sense. Within more complex scripts however, they might become the better choice. For now, just stay with the initial, top-most code./// instance_new(x,y,obj)
var i;
if argument_count==1{
i = instance_create(0,0,argument[0]);
}else{i = instance_create(argument[0],argument[1],argument[2]);
}return i;
Using a switch instead of an if-clause:
/// instance_new(x,y,obj)
switch argument_count{
case 1:return instance_create(0,0,argument[0]);
case 3:return instance_create(argument[0],argument[1],argument[2]);
}
3.2.)
Before we continue with something more complex, we'll add one more simple script. This time, we'll improve a function without argument by adding the possibility to provide one:
Original Function: instance_destroy()
Arguments: -
Returns: N/A
Problem: It is expected to use this function always from within the instance you want to destroy.
Goal: Controller-objects are often used to handle many instances of other objects. We want the possibility to destroy specific instances or all instances of a specific object from within the controller-objects.
Create Script and name it destroy.
/// destroy(obj)
if argument_count==0{
/// destroy(obj)
if argument_count==0{
instance_destroy();
}else if argument_count==1{with(argument[0]){instance_destroy();}
}From within another object, we can now provide the instance-to-be-destroyed as an argument. If the argument is the index of an instance, this instance will be destroyed. If the argument is an object-index, all instances of this object will be destroyed! From within the instance-to-be-destroyed itself, we can use destroy() without argument just like the original function. I see room for improvement, but for now we leave it like this.
3.3.)
This time we're not gonna wrap a function in the traditional sense, simply because there isn't a function for what we wanna do. Also, we're gonna write a script that takes a variable amount of arguments:
Original Function: -
Arguments: -
Returns: -
Problem: When creating an array with the values "a","b","c","d","e", we need to write:
var a; a[0]="a"; a[1]="b"; a[2]="c"; a[3]="d"; a[4]="e";
Goal: A Utility-script to create an array by providing the array-elements as arguments.
Create Script and name it array.
/// array(val1,val2,...)
var a,i=argument_count;repeat i a[ i]=argument[--i];return a;
One line for something so useful! Now we can create arrays like this:/// array(val1,val2,...)
var a,i=argument_count;repeat i a[ i]=argument[--i];return a;
var a = array("a","b","c","d","e");
Note: The array gets created and filled from behind because that's faster than doing it the other way around, where the array, to be increased, gets actually created anew again and again(as far as I know).
3.4.)
For the last example, we're gonna write something super useful, and also step by step. Create Script and name it debug_console.
Original Function: show_debug_message()
Arguments: string
Returns: N/A
Problem: Exactly 1 argument must be provided, no less, no more. If I want to add an empty line, I need to provide an empty string, "", as argument. If multiple variables get concatenated using +, every non-string-value must be wrapped within string(). Also, the function-name show_debug_message() is long af.
Goal: We want a shorter name, the possibility to output an empty line and a variable amount of arguments.
/// debug_console(val1,val2,...)
var s='',i=0;repeat argument_count s+=' '+string(argument[i++]);
show_debug_message(s);
Usage & Debug-Console-Output:
debug_console("hello","world"); // =>
hello world
var s='',i=0;repeat argument_count s+=' '+string(argument[i++]);
show_debug_message(s);
Usage & Debug-Console-Output:
debug_console("hello","world"); // =>
hello world
Right now, we can provide a variable amount of values. They will all be concatenated, seperated by spaces. Real values will turned into string values inside the script. If no argument is provided, the output will be an empty line. Every good console-output needs a time-stamp! Here we go:
/// debug_console(val1,val2,...)
var s='',i=0;repeat argument_count s+=' '+string(argument[i++]);
show_debug_message(date_time_string(date_current_datetime())+' |'+s);
Usage & Debug-Console-Output:
debug_console("hello","world"); // =>
03:42:56 | hello world
var s='',i=0;repeat argument_count s+=' '+string(argument[i++]);
show_debug_message(date_time_string(date_current_datetime())+' |'+s);
Usage & Debug-Console-Output:
debug_console("hello","world"); // =>
03:42:56 | hello world
Cool, cool. But that's not even its final form! Now, let's add the super useful additional info from within which object and event the script is executed by adding 2 local vars and 1 switch-case:
/// debug_console(val1,val2,...)
var o=object_get_name(object_index),e='',s='',i=0;repeat argument_count s+=' '+string(argument[i++]);
switch event_type{
case 0:e='Create';break;
case 1:e='Destroy';break;
case 2:e='Alarm';break;
case 3:e='Step';break;
case 4:e='Keyboard';break;
case 5:e='Mouse';break;
case 6:e='Collision';break;
case 7:e='Other';break;
case 8:e='Draw';break;
case 9:e='KeyRelease';break;
default:e='Room';o='noone';}
show_debug_message(date_time_string(date_current_datetime())+' | '+o+' | '+e+' |'+s);
Usage & Debug-Console-Output:
debug_console("hello","world"); // =>
04:02:03 | obj_init | Create | hello world
var o=object_get_name(object_index),e='',s='',i=0;repeat argument_count s+=' '+string(argument[i++]);
switch event_type{
case 0:e='Create';break;
case 1:e='Destroy';break;
case 2:e='Alarm';break;
case 3:e='Step';break;
case 4:e='Keyboard';break;
case 5:e='Mouse';break;
case 6:e='Collision';break;
case 7:e='Other';break;
case 8:e='Draw';break;
case 9:e='KeyRelease';break;
default:e='Room';o='noone';}
show_debug_message(date_time_string(date_current_datetime())+' | '+o+' | '+e+' |'+s);
Usage & Debug-Console-Output:
debug_console("hello","world"); // =>
04:02:03 | obj_init | Create | hello world
4.) An extension as a bundle, turning scripts into functions
Up until now, we created some scripts, that are bundled by putting them into one script-folder. Now we're gonna create an extension to bundle our scripts and turn these scripts into functions.
In the resource-tree on the left, right-click your Utility-Functions folder under Scripts and choose Export Group of Scripts... Name the file utility_functions and save it at some place you'll find again. Delete the Utility-Functions folder under Scripts.
Then, in the resource-tree, right-click on Extensions and choose Create Extension from the dropdown-menu. Name it Utility. Now right-click on this extension and choose Add File, browse for the utility_functions.gml and choose it. If you now double-click on utility_functions.gml inside your extension, the 4 scripts should open in an tabbed script-window.
Right-click on utility_functions.gml and choose Add Function. Double-click this new function. On the right (External Name), insert instance_new, same on the left(Name). In the Help field, insert instance_new(x,y,obj) Returns: Real. Don't change anything below and press OK. Repeat this procedure for destroy and array.
Now for debug_console, add a function, insert debug_console as External Name. In the Name field, insert dc and in the Help field, insert dc(val1,val2,...) Returns: N/A. This way, you can create short function names for very often used functions.
5.) Last words
If you have any questions: Feel free to ask. Suggestions, critique, whatever: Please post!
Last edited: