csanyk.com

video games, programming, the internet, and stuff

Tutorial: Building a Game Maker Extension

[Update 1/13/2014: See the official GameMaker documentation and this MSDN blog entry for how to build GM Extensions directly inside of GM:Studio. The GM Extension Builder tool that I explain how to use in this article will build GEX that can work with GM 7, 8, 8.1, and GM:Studio. As long as the GML used in the extension is compatible with the version of GameMaker that the extension is added to, the extension should work. ]

One of the nice things about Game Maker is that it is extensible. Developers can make their code more re-usable by converting their GML scripts to Game Maker Extensions. Once the .gml code is packaged in a .gex extension, you can import the extension into Game Maker and use the functions it provides in any project with ease. This means over time you can build up an entire library of re-usable functions that you can bring into your projects, saving you time and allowing you to focus on building new stuff instead of re-implementing the same basic things again and again.

Getting started

The first thing you need to do is learn to write good GML code. Practice writing scripts, and get a feel for how to write efficient, bug-free code. Once you are comfortable with writing gml functions, learn to make them flexible.

When you make a gml function with the intent of turning it into an Extension, you need to make sure that the script is written in a generic manner such that it will not be dependent on any objects, variables, or constants in your project. To do this, you need to pretend that variables that live inside of your objects are not accessible to your new function. Instead of directly accessing member variables of other objects, you need to pass them into your function as arguments.

This is a good practice to learn in any case. In most modern object oriented programming languages, there’s a concept of Encapsulation, which is a way of protecting the data that belongs to an object from being accessed (read or written) from the outside, providing only what access is necessary, and providing it through a properly defined interface. Game Maker lacks this concept (all variables are essentially public, and thus can be accessed at any time from anywhere in the application), so you have to employ self-discipline and write your code as though object variables were private (accessible only from within the object). If you’re new to programming this way, it may sound tricky, but it is not a hard concept to grasp.

Coding a GML function for an Extension

Consider this example function:

unencapsulated_function(){obj1.var = obj2.var + obj3.var;}

Here we’re directly modifying obj.var from within the function unencapsulated_function(). In order for this function to work, there must exist objects named obj1, obj2, and obj3, with a variable named var. If these do not exist in the game, it will throw an error.

Now consider this function:

encapsulated_function(value, value){return argument0 + argument1;}

What we’ve done here is parameterized the function, and eliminated its dependency on obj2 and obj3. Now you can pass any two values into the function. This makes the function much more flexible, allowing you to re-use it. It doesn’t automatically assign the value to obj1.var anymore, but that’s easy to handle when you call the function in your code:

obj1.var = encapsulated_function(obj2.var, obj3.var);

This line of code gives exactly the same results as our unencapsulated example, but allows you to use encapsulated_function() to perform as a general-purpose addition function, rather than as as specialized function that only works for game projects that have three objects named obj1, obj2, and obj3, each with member variable var. This function is much more flexible so you can use it for other purposes.

When writing code for extensions, you need to eliminate any dependencies on external objects or variables (other than the pre-defined variables that are built-into the base Game Maker framework), and pass any values needed for the function in as parameters. This ensures that the function will be useful an any Game Maker project, regardless of what its objects and variables might be named.

Now that you understand Encapsulation and how to write functions that do not depend on external objects or variables, you are ready to learn how to use the Game Maker Extension Builder.

We’ll build a simple extension which includes a single function. Functions that are generic enough to be useful in a variety of projects are good candidates for turning into an Extension. In this case, we’ll create a function called draw_health_halo, that draws a health bar above the sprite of the object that calls it.

Building an Extension: Creating Our Function

In Game Maker, health is a built-in global variable, meant to be used for the player’s health. But in many games, other objects also need a health variable, not just player. Imagine an RTS (real-time strategy) game with numerous units, each having its own health. Often, the health is displayed as a bar that hovers above the object. I call these “Health Halos.”

Since the variable health is predefined in Game Maker as a global variable, we’ll need a object-scope variable that has a name that doesn’t conflict with the global health variable. So let’s use life.

First I’ll demonstrate the function using an un-encapsulated function. In order for this code we’re about to write to work, any object that calls it will need to have member variables called life and maxlife.

Next, we will rewrite this code so that life and maxlife values are passed in as parameters every time the function is called, which would make the code more flexible in that those variables could be named whatever you wanted and stored wherever you wanted (in the object, globally, in a data structure, etc.)

Draw Event

When you draw the sprite, the health halo is drawn above the position of the object, signifying its relative state of health. Basically this is what the code will look like:

draw_health_halo()
{

//draw the health part of the halo
draw_set_color(c_green);
draw_line_width(x1, y1, x2, y2, w);

//draw the hurt part of the halo
draw_set_color(c_red);
draw_line_width(x1, y1, x2, y2, w);

}

That’s basically it. All we really need to do is figure out what x1, y1, x2, and y2 need to be for the health and hurt lines, and we’ll have our function. Width w we could make a parameter, but probably it only makes sense to use a fairly narrow range of values for this, and 4 seems like a decent number, so let’s just make it 4.

First, let’s consider how long the health halo should be. We’ll define a var for this, total_line_length. This line should be proportionate to the size of the sprite, in order to look like it belongs with the sprite. Perhaps making it equal to sprite_width works? It’s a good starting point.

So our total_line_length defines how long the entire halo is; we need to draw the health part of this line green, and the damage part of it red. So we really need to know these two lengths in order to that. These are simple calculations:

life_line_length should be a representation of the fraction life/maxlife. But it also needs to be proportionate to the length of the line, so:

life_line_length = life/maxlife*total_line_length;

Does this need to be rounded to an integer value, since you can’t have fractional pixels? (If we’re not sure, we can test and see if it matters — it’s good to avoid calling any function if you don’t need to.) If so, round it off:

round(life/maxlife*total_line_length).

As it turns out, Game Maker’s draw_line functions work just fine with non-integer parameters, so we don’t need to bother with this.

The hurt_line_length is even easier:

hurt_line_length = total_line_length - life_line_length;

Knowing these lengths is helpful, but we really want to know where the lines begin and end. So what are our x and y coordinates for the endpoints of these lines? We need to know four points: health_line_begin, health_line_end, hurt_line_begin, hurt_line_end.

Y is easy. Since we’ll always be drawing a horizontal line, we know the y for all four points will always be the same. It needs to be a short distance from the top of the calling object’s sprite. Game Maker provides a built-in variable, bbox_top, to tell us the top of the bounding box for the sprite. We can subtract a few pixels from this and have all of our y’s:

halo_y = bbox_top - 8;

Why 8? Because we’ll be drawing a 4px-wide line for our health halo, and we’d like to have 4px of clearance between the top of the sprite and the health halo. But we could parameterize this, if we wanted to make the function more flexible.

Optimization tip: It will save computation to perform this calculation one time and store it in a variable. There will be four y-coordinates for the four endpoints of our two lines, so storing the calculation will save us from having to re-calculate the same value three additional times. It may not seem like much, but if you have a large number of objects with health halos in the game, it can add up.

  • health_line_begin should start to the left of the object.x (object.x – total_line_length/2).
    (If we do keep total_line_length = sprite_width, then a simpler way to call this is bbox_left)
  • health_line_end should be health_line_begin + health_line_length
  • hurt_line_begin should be health_line_end
  • hurt_line_end should end at the right of the object.x (object.x + total_line_length/2)
    (If we do keep total_line_length = sprite_width, then a simpler way to call this is bbox_right)

So, let’s write out that code:

Create a script and name it draw_health_halo, and give it the following code:

{

var health_line_length, halo_y;
health_line_length = health/maxhealth*sprite_width;
halo_y = bbox_top - 8;

//draw the health part of the halo
draw_set_color(c_green);
draw_line_width(bbox_left, halo_y, bbox_left + health_line_length, halo_y, 4);

//draw the hurt part of the halo
draw_set_color(c_red);
draw_line_width(bbox_left + health_line_length, halo_y, bbox_right, halo_y, 4);

}

Optimization tip: The calculated lengths of the health and hurt parts of the bar are calculated every time when draw_health_halo() is called, which normally means every step during the draw event. This is wasteful, considering that health only changes at specific, known times (such as during a collision).

We can improve performance by storing the line lengths once they are calculated, and re-calculating only when life or maxlife changes, instead of re-calculating size every step. Again, it’s such a tiny amount that it probably will not be noticeable unless there are many, many objects on the screen. But again, every little bit helps.

Performance testing:  To test whether it really matters or not, we can build a room and generate a large number of objects that use the health halo.

To set up a good test room, create an object which has a function that generates the test object in groups of 5 or 10 whenever you press a key, and in its Draw event, make it draw the room_speed and fps to the screen. Also add a Key Press event to increase and decrease room_speed by 5 or 10. Now you have a fully adjustable room that you can test objects with, with a dashboard that gives you a readout of the room’s performance.

Run the game, set the room_speed to a high number, and keep adding objects. See how many objects the room can handle before fps drops below room_speed. That is the maximum number of objects that the code can handle (for your hardware).

Now set the room_speed to a reasonable number for an actual game, 30 or 60. See how many objects the room can handle before fps starts to drop below room_speed. If the number of objects isn’t acceptable, you may need find some way to optimize.

Now that we have our function coded, let’s turn it into an Extension.

Using the GM Extension Package Builder

In Game Maker, export the script to a gml file

Right-click the Scripts in the resource tree, and select export, and save it as draw_health_halo.gml

Launch GM Extension Package Builder

If you don’t have this program installed already, you can get it from here. Once you have it installed and running, you’ll see a screen like this:

GM Extension Package Builder

Click the +Add GML button. Select the draw_health_halo.gml file that you just exported. Now the functions in this file are available to our extension.

Define the function

A Game Maker Extension is basically a wrapper for the functions you imported from the .gml file. After you import the GML file, click Add Function.

You create an external name for the function, which users of the extension will use, and map it to the name of the function in your imported gml.

More often than not, these should be named the same in order to make things as simple as possible — I really can’t see any reason why you would ever need to name them differently from each other.

You should also provide a help string, which gives the form of the function call, and will pop up in auto-complete when entering the function name in the code editor.

Also, I always set the number of arguments to match the number of arguments the function actually uses. Doing this allows Game Maker’s code editor to check syntax for the function call, and if the number of parameters in the code don’t match what the function expects, it will highlight the line so you know there is a problem with the code.

Add a help file and other description text.

To make the Extension easier to use, it’s a good idea to provide a help document. The format required is .chm or .hlp. There are a variety of applications that can create .chm files, so find one you like. In your help document, you should:

  1. Describe each function and what it does. When I write a script, I like to use the following template in a block comment at the top of the function:
    1. Name: The name of the function.
    2. Description: A brief, but clear description that completely explains what the function does.
    3. Parameters: A description of each argument in the parameters list. Explain what type of data is required, its meaning or purpose in the function, and any special requirements (ie, cannot be zero, must be non-negative, must be within a certain range, etc.)
    4. Returns: If the function returns a value, explain what it is that gets returned. Typically a function will return the result of a calculation, or it may return true or false, or the id of an instance in the game, or it might return a status code that you’ve devised. If the function returns nothing, say “void”.
    5. Side Effects: Any changes to the program that persist when the function finishes executing, other than a return value, is a side effect. For example, if the script updates the score, and then exits, updating the score is a side effect becaue the score remains updated after the script is finished. If the script performs internal calculations and then returns a point value (which can be assigned to the score using a gml statement like score += get_points();), this is not a side effect. Describe all these completely so that a developer who uses the function will know everything that the function does.
  2. Show example code calling the function.
  3. Any assumptions the function makes. Say your function takes an argument which must be non-zero. You should probably check this within the function and handle things somehow if the argument is zero. If performance suffers as a result of this checking and handling, or if there is no one correct way to always handle this condition, then it might be better to make an assumption in code that the value must be non-zero. But if your function makes an assumption like this, then you must inform any programmer who wants to use this function, so that they can write their code in such a way that it never violates your assumption.

Build the extension

Once everything is ready, just select File>Build.

Test the extension

Make a duplicate copy of your test project. Open it up, and delete the script draw_health_halo from your project. Install the newly created extension. Now build the project and run it again. Everything should still work. If it does, congratulations, you have successfully built your first Game Maker extension.

Elaborate

Now that we have a basic function defined, how about some variations with other parameters?

draw_health_halo() //assumes the calling object has variables named life and maxlife

draw_health_halo_length(length) //define your own total_line_length

draw_health_halo_life(life, max) //pass in your own values for life and maxlife as parameters

draw_health_halo_color(health_color, hurt_color) //pass in your own color parameters

draw_health_halo_offset(offset) //define your own distance from the sprite to draw the halo

draw_health_halo_general(length, life, max, health_color, hurt_color, offset) //define all parameters with your own values

Add these to your .gml, import them into your GED project, and build a new extension.

Conclusion

Building extensions for Game Maker isn’t too hard, and makes it possible for you to make contributions that you can give back to the Game Maker community. The GM Extension Builder is not as well supported by YoYoGames as the main Game Maker product is, but with the knowledge contained in this tutorial, you should be able to create your own Game Maker extensions in no time.

8 Comments

Add a Comment
  1. I noticed yesterday while perusing the help documentation that there’s already a function in Game Maker called draw_healthbar() which does what my draw_health_halo() function does. Accordingly, I won’t bother developing the extension beyond what I did to produce this tutorial, but I hope the write-up helps someone. If you read it and found it useful, please drop me a comment!

      

  2. Hi, Chris. It didn’t really matter for me if the function is already there in GML. I just wanted to know how to make a game maker extension and it really really helped me. I appreciate that you took your time to elaborate the topic. Thanks a lot and now, I think that I’m going to make a basic physics engine. Again, lots of thanks.

      

    1. Thanks, Bending. That’s why I made the tutorial! I’m glad you found it useful. Good luck with the physics engine!

        

  3. Hi

    Would you please consider making an extension for ad provider such as : Chartboost or Flurry ? Yoyo Game is too slow to make a plugin for these ads, and the plugins from Marketplace are too lame. If you can make these I would love to buy it from you ( in this website if you can ),

    Yoyo makes the access to Marketplace a nuisance)

      

    1. Duy, I'm not sure what you mean by making access to the marketplace a nuisance. It generally works fine for me, although I'd agree that the interface to manage assets in the GM:S IDE is currently very poor.

      Personally, I don't like in-game advertising, from a design aesthetic standpoint. It can be a way to monetize a game, but it's not something I'm very interested in developing, myself. I'd rather stick to core gameplay. If I did release an extension for an ad provider, I'd definitely release it through the marketplace. I would not give away something for free that other people will use specifically to make money with.

        

  4. Hi csanyk.

    Yeah, I mean that if you can build a plugin for the ad provider, I’d love to buy it from you :D . Waiting for Yoyo Games to release such a plugin would take forever.

      

    1. Does this not meet your needs?

        

  5. Well, your post is a good starting point, however as far as I know, if I want to build an ad extension using SDK provided I must know how to code in Javascript, because I don’t know how to code in Javascript , so I have to buy it from someone who can :)

      

Leave a Reply

csanyk.com © 2016
%d bloggers like this: