GameMaker Game Programming with GML

5 (3 reviews total)
By Matthew DeLucas
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Getting Started – An Introduction to GML

About this book

GameMaker: Studio is a popular game engine used to publish games to a variety of platforms. Although GameMaker: Studio's drag-and-drop functionality makes creating games simple and fast, utilizing scripts can really help organize and speed up GameMaker's workflow exponentially.

This hands-on guide will help you build a strong foundation in programming in GameMaker Language by taking you through several example games. Knowledge gained by the end of the book can be applied so you can build your own line of exciting games.

Publication date:
April 2014
Publisher
Packt
Pages
350
ISBN
9781783559442

 

Chapter 1. Getting Started – An Introduction to GML

GML or GameMaker Language is a great tool for expanding the already vast variety of tools provided by GameMaker: Studio. GML scripts allow users to write their own code, creating an organized codebase that is easier to modify and debug than GameMaker: Studio's built-in drag-and-drop functionality.

Before exploring GML's use in creating actual games, this chapter will go over the basics of the language, such as the following components:

  • Syntax and formatting

  • Variables

  • Functions

  • Statements

  • Arrays

In the second half of this chapter, many of the previously mentioned components will be used in the creation of a modular button.

 

Creating GML scripts


Before diving into any actual code, the various places in which scripts can appear in GameMaker as well as the reasoning behind placing scripts in one area versus another should be addressed.

Creating GML scripts within an event

Within an object, each event added can either contain a script or call one. This will be the only instance when dragging-and-dropping is required as the goal of scripting is to eliminate the need for it. To add a script to an event within an object, go to the control tab of the Object Properties menu of the object being edited. Under the Code label, the first two icons deal with scripts. Displayed in the following screenshot, the leftmost icon, which looks like a piece of paper, will create a script that is unique to that object type; the middle icon, which looks like a piece of paper with a green arrow, will allow for a script resource to be selected and then called during the respective event. Creating scripts within events is most useful when the scripts within those events perform actions that are very specific to the object instance triggering the event. The following screenshot shows these object instances:

Creating scripts as resources

Navigating to Resources | Create Script or using the keyboard shortcut Shift + Ctrl + C will create a script resource. Once created, a new script should appear under the Scripts folder on the left side of the project where resources are located. Creating a script as a resource is most useful in the following conditions:

  • When many different objects utilize this functionality

  • When a function requires multiple input values or arguments

  • When global actions such as saving and loading are utilized

  • When implementing complex logic and algorithms

Scripting a room's creation code

Room resources are specific resources where objects are placed and gameplay occurs. Room resources can be created by navigating to Resources | Create room or using Shift + Ctrl + R.

Rooms can also contain scripts. When editing a room, navigate to the settings tab within the Room Properties panel and you should see a button labeled Creation code as seen in the following screenshot. When clicked on, this will open a blank GML script. This script will be executed as soon as the player loads the specified room, before any objects trigger their own events. Using Creation code is essentially the same as having a script in the Create event of an object.

 

Understanding parts of GML scripts


GML scripts are made up of many different parts. The following section will go over these different parts and their syntax, formatting, and usage.

Programs

A program is a set of instructions that are followed in a specific order. One way to think of it is that every script written in GML is essentially a program. Programs in GML are usually enclosed within braces, { }, as shown in the following example:

{
    // Defines an instanced string variable.
   	str_text = "Hello Word";

    // Every frame, 10 units are added to x, a built-in variable.
    x += 10;

   	// If x is greater than 200 units, the string changes.
    if (x > 200)
    {
    str_text = "Hello Mars";
    }
}

The previous code example contains two assignment expressions followed by a conditional statement, followed by another program with an assignment expression.

Note

If the preceding script were an actual GML script, the initial set of braces enclosing the program would not be required.

Each instruction or line of code ends with a semicolon (;). This is not required as a line break or return is sufficient, but the semicolon is a common symbol used in many other programming languages to indicate the end of an instruction. Using it is a good habit to improve the overall readability of one's code.

Tip

Downloading the example code

You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

snake_case

Before continuing with this overview of GML, it's very important to observe that the formatting used in GML programs is snake case. Though it is not necessary to use this formatting, the built-in methods and constants of GML use it; so, for the sake of readability and consistency, it is recommended that you use snake casing, which has the following requirements:

  • No capital letters are used

  • All words are separated by underscores

Variables

Variables are the main working units within GML scripts, which are used to represent values. Variables are unique in GML in that, unlike some programming languages, they are not strictly typed, which means that the variable does not have to represent a specific data structure. Instead, variables can represent either of the following types:

  • A number also known as real, such as 100 or 2.0312. Integers can also correspond to the particular instance of an object, room, script, or another type of resource.

  • A string which represents a collection of alphanumeric characters commonly used to display text, encased in either single or double quotation marks, for example, "Hello World".

Variable prefixes

As previously mentioned, the same variable can be assigned to any of the mentioned variable types, which can cause a variety of problems. To combat this, the prefixes of variable names usually identify the type of data stored within the variable, such as str_player_name (which represents a string). The following are the common prefixes that will be used throughout this book:

  • str: String

  • spr: Sprites

  • snd: Sounds

  • bg: Backgrounds

  • pth: Paths

  • scr: Scripts

  • fnt: Fonts

  • tml: Timeline

  • obj: Object

  • rm: Room

  • ps: Particle System

  • pe: Particle Emitter

  • pt: Particle Type

  • ev: Event

Note

Variable names cannot be started with numbers and most other non-alphanumeric characters, so it is best to stick with using basic letters.

Variable scope

Within GML scripts, variables have different scopes. This means that the way in which the values of variables are accessed and set varies. The following are the different scopes:

  • Instance: These variables are unique to the instances or copies of each object. They can be accessed and set by themselves or by other game objects and are the most common variables in GML.

  • Local: Local variables are those that exist only within a function or script. They are declared using the var keyword and can be accessed only within the scripts in which they've been created.

  • Global: A variable that is global can be accessed by any object through scripting. It belongs to the game and not an individual object instance. There cannot be multiple global variables of the same name.

  • Constants: Constants are variables whose values can only be read and not altered. They can be instanced or global variables. Instanced constants are, for example, object_index or sprite_width. The true and false variables are examples of global constants. Additionally, any created resource can be thought of as a global constant representing its ID and unable to be assigned a new value.

The following example demonstrates the assignment of different variable types:

// Local variable assignment.
var a = 1;

// Global variable declaration and assignment.
globalvar b;
b = 2;

// Alternate global variable declaration and assignment.
global.c = 10;

// Instanced variable assignment through the use of "self".
self.x = 10;

/* Instanced variable assignment without the use of "self".  Works identically to using "self". */
y = 10;
Built-in variables

Some global variables and instanced variables are already provided by GameMaker: Studio for each game and object. Variables such as x, sprite_index, and image_speed are examples of built-in instanced variables. Meanwhile, some built-in variables are also global, such as health, score, and lives. The use of these in a game is really up to personal preference, but their appropriate names do make them easier to remember. When any type of built-in variable is used in scripting, it will appear in a different color, the default being a light, pinkish red. All built-in variables are documented in GameMaker: Studio's help contents, which can be accessed by navigating to Help | Contents... | Reference or by pressing F1.

Creating custom constants

Custom constants can be defined by going to Resources | Define Constants... or by pressing Shift + Ctrl + N. In this dialog, first a variable name and then a correlating value are set. By default, constants will appear in the same color as built-in variables when written in the GML code. The following screenshot shows this interface with some custom constants created:

Functions and accessing script resources

A function is a statement that executes a program; it is either built into GML or created as a script resource. Functions can either execute an action, such as changing the alignment of a font during a Draw event, return a value, or do both. Functions have to be followed by a set of parentheses—( )—to execute properly. Another important aspect of functions is arguments. These comma-separated sets of data—string, integers, objects, and so on—are accessible to functions when executed. If there are no arguments, the parentheses are left empty; however, when needed, arguments are placed in between them. The following are some examples of functions with and without arguments:

// Executes an action, in this case, drawing the instance.
draw_self();

/* Executes an action which requires arguments, in this case, drawing an arrow. */
draw_arrow(0,0,100,100,2);

// Obtains a random value between two provided values.
random_value = random(10, 23);

GameMaker: Studio provides a wide variety of functions. Many of these functions will be used throughout this book; however, to find information about all of GML's available functions, go to Help | Contents... | Reference or press F1.

Scripts created as resources can be accessed using two methods. Either the script's name can be referenced and used like a built-in GML function or the function script_execute can be used, as shown in the following code snippet:

// Executes the script directly like a built-in function.
scr_script_resource("argument", obj_button, 0.12, false);

/* Executes the same script as the previous line but through the use of "script_execute". */
script_execute(scr_script_resource, "argument", obj_button, 0.12, false);

The advantage of using script_execute is that it allows a script-assigned variable to be used as shown in the following code:

// Assigns an instanced variable with the script resource's index.
self.script = scr_script_resource;

// Executes the assigned script.
script_execute(self.script, "argument", obj_button, 0.12, false);

The script_execute function can only be used on scripts created as resources; additionally, variables cannot be assigned built-in functions. The following code demonstrates this problematic situation:

// Assigns an instanced variable with a script resource ID.
self.script = scr_script_resource;

// Calling the instanced variable will cause a compile error.
self.script("argument", obj_button, 0.12, false);

Arguments

As mentioned previously, some functions require arguments. When creating a script, these arguments can be accessed within the script using the keywords argument0 through argument15, allowing for up to 16 different values if necessary.

Expressions

Expressions represent values usually stored within variables or evaluated by a conditional statement, which will be explained later. They can be real numbers, such as 3.4; hexadecimal numbers starting with a $ sign, such as $00FFCC (usually used to represent a color); and strings, which are created by encasing them in single or double quotation marks, for example, 'hello' or "hello".

Expression symbols

Expressions can be manipulated and evaluated using different symbols. The equals sign or assignment operator = sets the value of a variable as shown in the following code:

// Assigning a variable with a value.
a = 10;

/* Assigning a different variable with an expression, in this case, the sum of the previously declared variable and 7.5. */
b = (a + 7.5);

Expressions can also be combined with basic mathematical operations, as shown in the following code:

// Addition and subtraction, + and -
val = a + 20 - b;

// Multiplication and division, * and /
val = a * 20 / b;

/* Expressions encased in parenthesis, ( and ), will be evaluated first. */
val = (a + 20) * (b - 40);

// + can also be used to concatenate, or link, strings together. 
str = "hello " + "world";

Mathematical operations can be combined with = to create a compound assignment operator and perform relative addition, subtraction, multiplication, or division, as shown in the following code:

// Relative addition, +=
x += y; // equivalent to x = x + y;

// Relative subtraction, -=
x -= y; // equivalent to x = x – y;

// Relative multiplication, *=
x *= y; // equivalent to x = x * y;

// Relative division, /=
x /= y; // equivalent to x = x / y;

The main advantage of this code is its extreme simplicity. In the previous examples, x is a simple variable to type out, but if a variable name is longer, the preceding code cuts down on having to unnecessarily retype that name on both sides of the assignment operator.

Variables can also be incremented by one value as shown in the following code:

var a, b, c, d, str_a, str_b, str_c, str_d;
a = 1;
b = 1;
c = 1;
d = 1;

// The return string will be "1" but a's value is now 2.
str_a = string(a++);

// The return string will be "2" and b's value is 2.
str_b = string(++b);

// The return string will be "1" but c's value is 0;
str_c = string(c--);

// The return string will be "0" and d's value is 0;
str_d = string(--d);

In summary, if ++ or -- is included after the variable, the variable's current value is returned and then is incremented. If ++ or -- is set before the variable, its current value is incremented and that new value is returned.

Boolean comparisons, as shown in the following code, compare expressions and return the values true or false, which are GML constants equal to 1 and 0 respectively:

// Less than, <
if (a < 20) { instance_create(a, 20, obj_ball); }

// Less than or equals to, <=
if (a <= 20) { instance_create(a, 20, obj_ball); }

// Equals to, ==
if (a == 20) { instance_create(a, 20, obj_ball); }

// Not equals to, !=
if (a != 20) { instance_create(a, 20, obj_ball); }

// Greater than, >
if (a > 20) { instance_create(a, 20, obj_ball); }

// Greater than or equals to, >=
if (a >= 20) { instance_create(a, 20, obj_ball); }

Booleans can also be combined for evaluation, as shown in the following code:

// And, &&, will return true if both booleans are also true.
if (a == 20 && b == 40) { val = "and"; }

/* Or, ||, will return true if at least one of the booleans is true. */
if (a == 20 || b == 40) { val = "or"; }

/* xor, ^^, will return true if one booleans is true and the other false. */
if (a == 20 ^^ b == 40) { val = "xor"; }

Conditional statements

Conditional statements utilize a Boolean expression with a corresponding program. If the value returned is true, the program following the conditional statement will be executed; otherwise, it will be skipped. There are several types of conditional statements in GML, each of which has its own uses.

if, else, and switch

The if statement is probably the most common conditional statement that will be used while making these games. The if statements were introduced when discussing Booleans previously; the following is another example illustrating a simple if statement:

/* If the value of a is greater than the value of b, a is returned; otherwise, b is returned. */
if (a > b)
{
  return a;
}
else
{
  return b;
}

In the previous example, assuming both a and b are real numbers, if the value of a is greater than that of b, a is returned; otherwise, b is returned. One thing that should be noted is that b will be returned if it is less than or equal to a, since this is the opposite of greater than.

Now what if a variable needs to be compared against many different conditions? The following could be done:

/* Assigns the temperature of an object based on a color with multiple if-else-statements. */
if (color == c_green || color == c_purple || color == c_blue)
{
    temperature = "cool";
}
else if (color == c_red || color == c_orange || c == c_yellow)
{
    temperature ="warm";
}
else if (color == c_black || color == c_gray || color == c_white)
{
    temperature ="neutral";
}
else
{
    temperature ="other";
}

When there are a lot of objects, each with its own complex program that needs to be executed, the execution of these objects could become rather tedious and confusing. Instead of using long chains of if-else statements, a switch statement can be used. A switch statement is created with the keywords switch, case, break, and default, as shown in the following code:

/* Assigns the temperature of an object based on a color with a switch statement. */
switch (color)
{
  case c_red:
  case c_orange:
  case c_yellow:
    temperature = "warm";
    break;
  case c_green:
  case c_blue:
  case c_purple:
    temperature ="cool";
    break;
  case c_black:
  case c_white:
  case c_gray:
    temperature = "neutral";
    break;
  default:
    temperature = "other";
    break;
}

The previous switch statement is identical to the layered if-else statements created earlier. Each case statement is essentially testing whether or not the value of the variable supplied after switch is equal to its own. If the values are equal, the program is executed. If a default case is supplied, this program will be executed if none of the other cases satisfy the conditional statement. The keyword break, which will be discussed again in a later section, is used to end the program running within a case statement.

repeat, while, do, and for

The repeat, while, do, and for statements are all examples of statements that execute a program multiple times and this is often referred to as a loop. The Repeat statement is used to execute the same program for a specified number of times:

// Creates 10 buttons at random positions between 0 and 100.
repeat (10)
{
  instance_create(random(100), random(100), obj_button)
}

The previous code will create 10 instances of obj_button at random positions. The while statement will execute a program until a condition is no longer met:

// Reduces x by 10 until it is no longer greater than 100.
while (x > 100)
{
  x -= 10;
}

In the previous code, the value of x will be reduced until it is less than 100. The do statements are very similar to the while statements, except they require an additional keyword—until:

/* Initially reduces x by 10 but continues until x is less than or equal to 100. */
do
{
  x -= 10;
}
until (x <= 100);

The difference between do and while is subtle. In a do statement, the program is executed first and then the condition is checked; in a while statement, the condition is checked first before executing the program. The for statements or for loops have built-in conditions as shown in the following code:

// Declares a local variable.
var i;

/* 10 buttons are created, evenly spaced apart horizontally by 100 units with an initial offset of 25. */
for (i = 0; i < 10; i++)
{
    instance_create(i * 100 + 25, 25, obj_button)
}

The first statement after the keyword for sets the local variable i to 0. Then, there is the condition that if the value of i is less than 10, the block of code within the loop is executed. The value of the variable i is then incremented and checked again within the conditional statement, running the program if the statement is still valid. Now the previous for statement is very similar to the repeat statement. The important difference is that i is incremented by the statement, making this a good way to loop through values in a one- or two-dimensional array, which will be covered shortly. The following sample code demonstrates this functionality with a two-dimensional array:

// Creates two local variables.
var i,j;

/* This is an example of a nested for loop in which a 10 by 10 grid with values ranging from 0 to 99 will be created. */
for (i = 0; i < 10; i++)
{
  for (j = 0; j < 10; j++)
  {
    grid[i,j] = i + j * 10;
  }
}

The following code shows the same functionality as the one performed using the repeat statements:

// Creates local variable and then assigns their values to 0.
var i, j;
i = 0;
j = 0;

/* Repeat functionality is performed 10 times and 10 more times within each repeat-statement. */
repeat (10)
{
  repeat(10)
  {
    // assign the grid value
    grid[i,j] = i + j * 10;

    // increment after each inner repeat.
    j++;
  }
  // increment after each outer repeat.
  i++;
}

As demonstrated, a for statement is much clearer than a repeat statement in situations like these.

Before moving on, it must be noted that the repeat, while, do, and for statements can create a situation known as the infinite loop. Infinite loops are very dangerous and cause a game to freeze up, ignoring input from the user and forcing them to shut down the game. The following are some examples of statements that can cause this; do not use these in actual code as they are just examples of what not to do:

/* Since the condition in the while-statement is always true, it will never terminate. */
while (true)
{
  x += 1;
}

// Declares a local variable.
var i;

/* This for-statement will never end since i will never be less than 0. */
for (i = 0; i >= 0; i++)
{
  x += i;
}

break, continue, and return

As previously mentioned, looping statements can sometimes cause a situation in which they do not terminate. There are two ways to resolve this issue. One is known as break, which was already demonstrated in the switch statements to break out of a case statement, but it can also be used to break out of a loop. The following is an example of a break statement:

// local variable used in the for loop
var i;

/* Iterates through 1000 enemies.  At the first undefined enemy, the loop is exited, otherwise, the enemy moves by a random value between 1 and 10 horizontally. */
for (i = 0; i < 1000; i++)
{
  if (enemies[i] == noone)
  {
    break;
  }
  enemies[i].x += random_range(1,10);
}

In the previous code sample, the for statement is broken out of and no longer continues running once the internal condition is met. Suppose break were to be replaced with continue, as shown in the following example:

// local variable for for loop
var i;

/* Iterates through 1000 enemies.  If an undefined enemy is encountered, the remaining enemies are still checked; otherwise, the enemy is moved by a random value between 1 and 10 horizontally. */
for (i = 0; i < 1000; i++)
{
  if (enemies[i] == noone)
  {
    continue;
  }
  enemies[i].x += random_range(1,10);
}

The for statement would proceed to checking the additional enemies, even if they are all undefined. The break and continue statements, however, are very useful because in both examples, an undefined enemy is to be encountered and the game will crash upon trying to add the value generated by random_range to its x value. The return statements are used most often in GML scripts that are created as resources. The return statement will not only end any script in its entirety, but also return whatever value follows it as demonstrated in the following code:

// This script will simply add and multiply two arguments
var result;
result = (argument0 + argument1) * (argument0 * argument1);
return result;

If you want to obtain a value using the previously shown function, it can be accessed through the following code, assuming that the function scr_add_multipy is a script resource:

// Execute the method and assign it to x.
x = scr_add_multiply(1, 2);

Arrays

Now the one additional feature that variables have is that they can contain multiple values through the use of arrays. An array is a fundamental and useful programming tool. Instead of having to create a unique variable name to store dozens of values, such as those for a high-score list or a role-playing game's inventory system, all of these values can simply be stored in an array. The following code demonstrates the creation and assignment of an array in code:

// A local array is declared and assigned four values.
var array;
array[0] = 10;
array[1] = false;
array[2] = 32.88;
array[3] = "a";

The following figure illustrates the data contained within each portion of the newly created variable:

By enclosing an integer within brackets—[ ]—after a variable name, a one-dimensional array is created. This integer is then called an index.

Arrays are useful for storing data that is iterated, but they do have their limits. An array cannot contain more than 32,000 members as this will cause an error, and arrays with several thousand members can cause memory issues and their initialization can be rather slow, so it is best to avoid using them when possible.

Two-dimensional arrays

A two-dimensional array is created by adding two comma-separated integers within the brackets after a variable name. They are great to use when creating a grid system: the first index represents a column and the second represents a row, as shown in the following code:

/* A 2-dimensional array is declared and assigned four different values. */
var grid;
grid[0,0] = 10;
grid[0,1] = false;
grid[1,0] = "ab";
grid[1,1] = 95.5;

The following figure illustrates the data within the two-dimensional array created in the previous code block:

Commenting

Throughout the previous code samples, characters such as //, /*, or */ have been used. These create comments, which are lines and blocks of code that are not compiled or that will not show up in the results of the game. Comments are used mostly to document code so that when others are looking at that code, it is easier to understand it and appears much more professional. They can also help the original programmer by citing information that may not be obvious or when the code hasn't been looked at in quite some time. Comments can also be used to temporarily remove a section of code without having to delete it while debugging or testing new features.

Note

Up to this point, the comments in the provided code samples have been very verbose for demonstration purposes. In future sections, the comments will not be as verbose, but will still be provided so that the examples are clear and understandable.

The // characters will comment just one line of code. Any number of lines encased within /* and then */ will be part of a comment block. Finally, using /// in the first line of a script used in an event will make the comment show up in the editor as shown in the following screenshot:

Errors

No one is perfect and mistakes are often made when writing scripts of any kind. There are two types of errors that are encountered when working in GameMaker: Studio—compile errors and runtime errors.

A compile error is one that prevents GameMaker: Studio from building the project. If a compile error occurs, the game cannot even start. The common reasons for compile errors are as follows:

  • Omitting a closing parenthesis, single quotation mark, double quotation mark, or bracket for every opened one

  • Misspelled variables, functions, or script names

  • Accessing an argument variable that has not been provided in a function call

  • Omitting commas between arguments in a function

A runtime error is one that causes a game to crash while it is active. Such errors can happen for any number of reasons. The following are a few examples:

  • Incorrect data provided as an argument in a function

  • Attempting to access instanced variables of an undefined object

  • Accessing an undefined member of an array

  • Division by 0

If either of these types of errors occur while working on a game in GameMaker: Studio, a window will pop up describing the error and where it is occurring. Though the name of the window in GameMaker: Studio will be titled Compile Errors, runtime errors will be displayed in red text and compile errors will be navy blue by default, as shown in the following screenshot:

 

Pushing your buttons


Now that the basic definitions and the syntax of GML have been covered, a simple button will be created to demonstrate some of these concepts. Buttons are a crucial part of most games' user interfaces and have a lot of uses. This button can be re-used in future projects, which allows the creation of custom buttons with just a few lines of code.

Creating the project

Before starting, a new project must be created. Upon opening GameMaker: Studio, a dialog window will open with several tabs. Click on the New tab to create a new project. This project will be named ButtonExample, as shown in the following screenshot:

Gathering resources

In this section, many of the different assets—objects, sprites, rooms, and so on—that are needed and referenced in the scripts as well as their attributes will be created. These attributes—size, scale, and so on—do not have to be identical to the ones created here, but will help with maintaining consistency. It is suggested that before writing any of the scripts in this section, all of the required resources be created. After right-clicking on the folder of each resource type, the option to create a new resource type is available. The different types of resources can also be created in the following ways:

  • A sprite can be created by navigating to Resource | Create Sprite or by pressing Ctrl + Shift + S

  • The font can be created by navigating to Resource | Create Font or by pressing Ctrl + Shift + F

  • The timeline can be created by navigating to Resources | Create TimeLine or by pressing Ctrl + Shift + T

  • The background can be created by navigating to Resources | Create Background or by pressing Ctrl + Shift + B

  • The path can be created by navigating to Resources | Create Path or by pressing Ctrl + Shift + P

  • The script can be created by navigating to Resources | Create Script or by pressing Ctrl + Shift + C

  • The sound can be produced by navigating to Resources | Create Sound or by pressing Ctrl + Shift + U

Additional tips on creating and editing assets can be found in GameMaker: Studio's help menu, which can be accessed through Help | Contents... | Reference or by pressing F1.

Sprites – spr_button

Only one sprite is needed to create this button. There are three important aspects of sprites: subimages, origin, and bounds. First, the subimages or frames of what will actually be displayed should be defined. By clicking on the Edit Sprite button, the Sprite Editor window will open. The button will be made from three frames representing three different states: a default state, a rollover state, and a down state, all of which are shown in the following screenshot. These sprites can be added using the images inside the sprites folder for this chapter's code files.

Once the sprites are loaded, the origin can be defined. The origin of the spr_button sprite is set to its center. This origin determines how the button is offset from its position. An origin of (0, 0) would align the button to the upper-left of its current position. This setting is displayed in the following screenshot with a crosshair on the sprite:

Finally, the bounding box of spr_button can be defined. The bounding box defines an area that the player will be able to interact with. This sprite will use a rectangular bounding box with an alpha tolerance of 128. The alpha tolerance determines how much of the sprite's transparency will be used to determine the definition of the automatic bounding box. The lower the value, the more of the sprites' alpha will be used—0 means that the entire image will be used and 255 means that the entire bounding box invalid. These settings are displayed in the following screenshot:

Objects – obj_button

The obj_button object will represent the buttons that are going to be created. This object needs certain events that can be added by navigating to the following paths:

  • Add Event | Create

  • Add Event | Mouse | Left button

  • Add Event | Mouse | Left pressed

  • Add Event | Mouse | Mouse enter

  • Add Event | Mouse | Mouse leave

  • Add Event | Mouse | Global mouse | Global left released

  • Add Event | Draw | Draw GUI

These different events will be explained and scripted later when creating the actual button. The sprite of obj_button should be set to spr_button, as illustrated in the following screenshot:

Room – rm_main

All games made within GameMaker need at least one room to run. This room is set to be 640 pixels wide and 480 pixels tall, but its size is irrelevant for this example. Additionally, the creation code will be used in this room, so do not add any instances of obj_button to it as one will be created with the code. The following screenshot shows the Room Properties page:

The events of obj_button

As discussed earlier, obj_button contains many different events, but those events are all relatively simple. To add an event to an object, click on the Add Event button at the bottom of the Events panel in the object editor window. In this section, the code for each event and the reasoning behind it will be explained.

The Create event

The Create event (Add Event | Create) is the first program executed when a new instance of an object is created. To create the event of obj_button, an Execute Code icon should be dragged-and-dropped in from the control tab of the Object Properties: obj_button panel. In the following script, the instantiation of the button's main variables will be handled:

/// Initializes main button components.

// Sets the image speed of the button to 0 so it will not animate.
image_speed = 0;

// Sets the image index to the first frame of the sprite set.
image_index = 0;

// Is this button currently down?
is_down = false;

// Is the mouse currently over the button?
is_over = false;

// Assigns the displayed text.
str_text = "Hello World";

// Scripts called during the different events.  
// The indices in this array are built-in constants that represent // different values and used for clarity. */

scripts[ev_left_button] = noone;
scripts[ev_left_press] = noone;
scripts[ev_mouse_enter]  = noone;
scripts[ev_left_release] = noone;

Note

The constant noone is used to indicate that a variable is undefined, but noone is actually equal to -4 since the concept of null or undefined objects does not truly exist in GML.

The Left Button event

The Left Button event (Add Event | Mouse | Left button) will be executed for every frame for which the left button of the user's mouse is down and the position of the mouse is within the position of the object's sprite's bounds. The following code will be executed when the button triggers this event:

/* Executes the script when the button is being held down with the left mouse. */
if (scripts[ev_left_button] != noone)
{
    script_execute(scripts[ev_left_button]);
}

The previous code is an example of a conditional statement. The script at the index ev_left_button is then checked. If the index is assigned, the script will be executed.

The Left Pressed event

The code for the Left Pressed event (Add Event | Mouse | Left pressed) will have a bit more functionality than the Left Button event. Unlike the Left Button event, the Left Pressed event will be executed only once while the user's mouse is clicked on when the cursor is within the button's bounds. The following code is executed when the specified script is assigned:

// If the specified script is assigned, it is executed.
if (scripts[ev_left_press] != noone)
{
    script_execute(scripts[ev_left_press]);
}

// Indicates that the button is down.
is_down = true;

// Sets the image index to the down state image index.
image_index = 2;

The previous code is similar to that of the Left Button event except for two minor differences. Firstly, a different index is used when testing for the presence of a script in the initial condition. Secondly, the is_down Boolean is set to true indicating that the button has been pressed, and image_index is set to 2, using the third frame of the sprite's animation.

The Mouse Enter event

Most buttons have a rollover state, indicating that the button is in the range of being clicked. The populating of the Mouse Enter event (Add Event | Mouse | Mouse enter) will be scripted similarly to the other events used thus far, as shown in the following code:

// Indicates that the mouse is currently over the button.
is_over = true;

// If the specified script is assigned, it is executed.
if (scripts[ev_mouse_enter] != noone)
{
    script_execute(scripts[ev_mouse_enter]);
}

/* If the left-mouse button is not down, the image index is assigned to the rollover frame. */
if (!is_down)
{
    image_index = 1;
}

The is_over object is set to true since the user's cursor is now hovering over the button. Then, the script indexed at ev_mouse_enter is tested and performed if assigned. Finally, the value of is_down is also tested. If the value is true, the button should stay in the down frame of its animation and not go to the rollover stage.

The Mouse Leave event

When the user's mouse leaves the bounds of the button, the Mouse Leave event (Add Event | Mouse | Mouse leave) will be triggered. This needs to be done to indicate that the cursor is no longer hovering over this button. The button should go to the first frame of its animation; however, this is only if the user no longer has the left mouse button held down. The following code explains the syntax for this event:

// Indicates that the mouse is no longer over the button.
is_over = false;

/* If not being held down, the image index is assigned to the default state frame. */
if (!is_down)
{
    image_index = 0;
}

The Global left release event

The Global left release event (Add Event | Mouse | Global mouse | Global left released) will be triggered regardless of the user's position when the left mouse button is released. The releasing function, however, will only be executed if the cursor is currently hovering over the button. The button should always return to the first frame or the default state frame, which is why a Global left release event is used as opposed to a Left Release event. The following code gives the syntax for this event:

// Indicates that the button is no longer being pressed.
is_down = false;

/* If the mouse is over the button, the release script is executed and the image index is set to the over frame; otherwise, the image index is set to the default frame. */
if (is_over)
{
    image_index = 1;
    if (scripts[ev_left_release] != noone)
    {
        script_execute(scripts[ev_left_release]);
    }
}
else
{
    image_index = 0;
}

The Draw GUI event

The Draw GUI event (Add Event | Draw | Draw GUI) will draw both the assigned sprite and anything called using a draw method. In this event, the text will be aligned horizontally and vertically; then, the center button will be used to place the text using its current size and origin. The following is the code for this event:

// Set the horizontal and vertical alignment to the center.
draw_set_halign(fa_center);
draw_set_valign(fa_middle);

// Find the x and y middle of the button.
var mid_x, mid_y;
mid_x = x + image_xscale * (sprite_width * 0.5 - sprite_xoffset);
mid_y = y + image_yscale * (sprite_height * 0.5 - sprite_yoffset);

// Draw the button's text at this middle point.
draw_text(mid_x, mid_y, str_text);

The first two functions executed—draw_set_halign and draw_set_valign—tell the draw functionality how to align text horizontally and vertically; fa_center and fa_middle are two different constants used to set up the alignment respectively.

Two variables local to this event, mid_x and mid_y, are then defined. These are calculated by using the position of the button. Then, the size of the sprite is derived and divided in half, subtracting the offset from the obtained sizes. This is all multiplied by the respective scaling so that the origin shifts in proportion with the changes.

Finally, the text is drawn using mid_x, mid_y, and str_text, that is, the text initialized in the Create event of the button.

Note

Functions such as draw_text will only execute when run in Draw and Draw GUI events. More about this will be explained later in the chapter.

Scripts – scr_create_button

Now that the button's events have been coded, two scripts will be written. In the first script, a script resource named scr_create_button will be used to create buttons at runtime and provide simple information about them, such as their position and displayed text.

scr_random_position

The scr_random_position script will be used as an example action for the button. It will randomly place an instanced object within the bounds of the room it is in.

Creating buttons using scripts

If this newly scripted object resource is dragged-and-dropped into the room, the resource should change frames upon the mouse entering and pressing the object; however, no scripts have been assigned, which makes this button rather useless. Now, a script will be written that will create and place buttons as well as assign the different scripts that are tested for and called during the button's different events.

For this script, the previously created script resource scr_create_button will be used. Enter the following code in the script:

// Create a local variable which will represent the ID of the instanced button.
var obj_new_button;

// argument0 is the x position, argument 1 is the y position.
obj_new_button = instance_create(argument0, argument1, obj_button);

// argument2 will be the displayed text.
obj_new_button.str_text = argument2;

// argument3 through argument6 will be the four script IDs.
obj_new_button.scripts[ev_left_button] = argument3;
obj_new_button.scripts[ev_left_press] = argument4;
obj_new_button.scripts[ev_left_release] = argument5;
obj_new_button.scripts[ev_mouse_enter] = argument6;

// The newly created button instance is returned.
return obj_new_button;

In this script, a button was created at a specific location, assigned its displayed text, and assigned its four scripts. The following values are those described in that script:

  • The x value

  • The y value

  • The displayed text

  • The left_button script ID

  • The left_press script ID

  • The left_release script ID

  • The mouse_enter script ID

Scripting room creation code

Now that a script for instantiating buttons has been created, buttons can actually be created! Another object that executes scr_create_button during its Create event could be created, but to eliminate the need for a new object resource, a single line of code will be added to the room's creation code. Again, a room's creation code can be accessed by navigating to settings | creation code from the Room Properties window. In this script window, scr_create_button will be executed using the following code:

// Creates a button centered in the room that will move to a random position when clicked.
script_execute(scr_create_button, room_width * 0.5, room_height * 0.5, "1st Button", noone, noone, scr_random_position, noone);

Again, the previous function simply calls scr_create_button and supplies the seven required arguments. Though the third script ID argument is assigned to scr_random_position, the button is still pretty inactive since the script hasn't been created yet. This will be done next!

Creating scr_random_position

The scr_random_position script is mostly going to be used as an example, but it is still useful and can be applied to a variety of game types. As shown in the following code, this script will first determine the minimum and maximum positions that an object can be placed at while staying within the bounds of a room:

/* Declares the x and y ranges for places to stay within the bounds of the room. */
var x_min, y_min, x_max, y_max;

x_min = sprite_xoffset * image_xscale;
y_min = sprite_yoffset * image_yscale;

x_max = room_width - (image_xscale * (sprite_width - sprite_xoffset));
y_max = room_height - (image_yscale * (sprite_height - sprite_yoffset));

// Set the x and y to random position within the range.
x = random_range(x_min, x_max);
y = random_range(y_min, y_max);

/* If the object is within the bounds of the mouse, the Mouse Enter Event is triggered; otherwise, the Mouse Leave Event is triggered. */
if (position_meeting(mouse_x, mouse_y, self))
{
    event_perform(ev_mouse, ev_mouse_enter);
}
else
{
    event_perform(ev_mouse, ev_mouse_leave);
}

In the first portion of the code, four local variables were defined, representing the ranges of x and y without allowing the object to leave the bounds of the room. The minimum of the range is derived by using the x and y sprite offsets. Then, the maximum of the range is calculated by subtracting the difference of the sprite size and the sprite offset, which is then multiplied by its scale, from the size of the room, as shown in the following snippet:

var x_min, y_min, x_max, y_max;

x_min = sprite_xoffset * image_xscale;
y_min = sprite_yoffset * image_yscale;

x_max = room_width - (image_xscale * (sprite_width  - sprite_xoffset));
y_max = room_height - (image_yscale * (sprite_height – sprite_yoffset));

To determine a position, the supplied built-in function random_range is used, supplying the desired minimum and maximum values respectively, as shown in the following code snippet:

x = random_range(x_min, x_max);
y = random_range(y_min, y_max);

The final section, as shown in the following snippet, mostly deals with the button, but can be applied to any object. Once the object is placed, the built-in function position_meeting is used, which tests if the specified x and y coordinates are contained within the bounds of the supplied object. In this case, the keyword self is supplied, which represents the ID of the object instance executing this script. If the mouse positions meet, the object's Mouse Enter event will be executed; otherwise, the object's Mouse Leave event will be executed. The reason this script can be used by other objects is that even if they lack a Mouse Enter or Mouse Leave event, the script will still execute safely and not throw a runtime error. The following code shows the final section of the script:

if (position_meeting(mouse_x, mouse_y, self))
{
    event_perform(ev_mouse, ev_mouse_enter);
}
else
{
    event_perform(ev_mouse, ev_mouse_leave);
}

Now that the scripts have been properly set up, this game can be run. A button should be visible and centered toward the middle of the room. When the mouse hovers over it, it should go to the second frame and upon being pressed, go to the third. If the mouse is released while still hovering over the button, the button should move to a random spot and its bounds should remain in the room, signifying that scr_random_position has been called. If this works and looks similar to the next image, then the creation of this introductory button has been a success!

Exporting and importing the button

Now that the button has been created, this GameMaker project can be exported and then imported into a new one so that it can be re-used without having to copy and paste all of the assets while preserving the original. To do this, go to File | Export Project and save the compressed GameMaker file. The following screenshot shows the import of our game file:

After creating a new project, an entire project can be imported using File | Import Project or by pressing Ctrl + I to open a dialog window to import the previously created project. Be warned: this will overwrite all current assets in a project, so it is best used at the beginning of a project. If resources from multiple projects need to be added, this can be done by right-clicking on each asset type in the resource tree and selecting Add Existing.... Then, search for the appropriate .gmx files for the type of asset being imported, such as sprite.gmx for sprites, object.gmx for objects, and so on. Unlike importing an entire project, if an asset of the same name already exists, the imported asset will be renamed ending with _new.

 

Summary


In this chapter, the basic syntax and components of GameMaker Language—programs, functions, variables, and so on—were explained to help you start building a strong foundation. A basic button was also created that utilizes many of these concepts. Code was also written so that this modular, easily customized button can be instantiated with just one line of code.

In the next chapter, the first game described in this text will be started: a simple, match-three puzzle game. The foundation for GML scripting, which we started learning about in this chapter, will be built by utilizing and going more in depth with already introduced topics such as built-in variables, for example, image_xscale. We will not only focus on placing objects in an organized fashion by using arrays to set up the puzzle game's grid, but also randomization, so that the layout of pieces is seemingly random and unique with each play.

About the Author

  • Matthew DeLucas

    Matthew DeLucas has been a gameplay engineer with Schell Games in Pittsburgh, Pennsylvania for over five years. He has worked on a wide range of interactive projects for PC, Web, mobiles, and consoles. Matt has also released independent projects for PC and Xbox 360, such as Convextrix, a puzzle game, and Battle High, which is a fighting game series. Being a programmer and designer, Matthew has also participated in almost every official, 48-hour Global Game Jam, managing to help his team achieve success while experimenting with new ideas.

    Matthew began his programming career in GameMaker: Studio and has become proficient with additional game engines, such as Gamebryo and Unity3D, and scripting languages such as C#, Python, Lua, and MaxScript for 3DS Max. Often, he chronicles his experiences with game production on his blog at www.mattrifiedgames.blogspot.com.

    Matthew has had a desire to work in the game industry ever since he was young, and he enjoys all of the facets of game production—programming, design, and art. His favorite genres include platformer, puzzles, racing, and fighting games, all of which influence his designs.

    Browse publications by this author

Latest Reviews

(3 reviews total)
Well written and clear to understand. Seems to be exactly what I was looking for.
대한민국(Republic of Korea)에서 게임메이커 관련한 자료를 찾다가 이 사이트에서 찾게 되었습니다. 저렴하게 자료를 구할 수 있어서 좋았습니다. 감사합니다.
Excellent

Recommended For You

Book Title
Access this book, plus 7,500 other titles for FREE
Access now