Transcript
GameMaker Language: An InDepth Guide
This book is dedicated to my beautiful wife, my daughter, and every aspiring game developer.
1
Table of Contents Preface: Introduction Book: Contents Chapter 1: Lexical Structure Chapter 2: Data Types and Values Chapter 3: Variables Chapter 4: Arrays and Data Structures Chapter 5: Expressions and Operators Chapter 6: Statements Chapter 7: Scripts and Audio Chapter 8: Objects and Sprites Chapter 9: Events Chapter 10: Game Audio Chapter 11: Development Patterns and Tricks Chapter 12: Drawing on the GUI Layer Chapter 13: Particles and Surfaces Chapter 14: Physics Chapter 15: Online Multiplayer Chapter 16: Artificial Intelligence Contact and Kickstarter
2
Preface
Introduction The Author Hey, guys! Good morning, afternoon, or evening, wherever and whenever you are! My name is Benjamin Anderson. You may know me from my YouTube channel, “HeartBeast,” where I upload free GameMaker tutorial videos. Teaching GameMaker is my passion. Let me tell you a little more about myself. I was raised in Vernal, Utah (where there are more cows than people). I grew up on a farm, but spent most of my days building small computer games in GameMaker. I finished my first recognized game, Deep Magic , at the age of 15 and published it for free on the YoYo Games sandbox website. There, it received nearly 18,000 downloads. Soon after finishing Deep Magic , I finished my second popular game, Ancient War , which was also published for free on the YoYo Games website and received over 27,000 downloads. You can find both of these games on GameJolt, but remember, I made them almost ten years ago, so don’t expect anything too fancy. During the next part of my life, I left for Rio de Janeiro, Brazil, where I lived for a couple of years to learn Portuguese, make new friends, and have an adventure. After returning from Brazil, I decided to pursue a career in computer science. My favorite thing to do is program games and teach other people to do the same. I started a successful YouTube channel in January of 2014. I plan to continue learning more about GameMaker and sharing my knowledge with anyone who is willing to listen.
3
Outside of the computer world, I enjoy wakeboarding, playing chess, reading, writing, doing math, playing guitar, playing piano, singing, playing Pokémon, learning languages, and watching Psych or Castle with my wife. “Welcome, Game Designer.” There is something magical about game development that can’t be found in any other creative medium. Everyone has their own reasons for enjoying this activity. For me, it’s the godlike feeling I get when I create my own world and the satisfaction that comes from knowing my created world will follow the rules I’ve set. It’s the sense of accomplishment I feel after spending hours on a frustrating problem and finally getting it to work and it’s the joy that fills me when I see a smile on the face of someone exploring my creation. These are the reasons I spend most of my free time learning how to program and teaching what I’ve learned to anyone willing to learn. Game creation in a digital form is a relatively unexplored medium. Whereas art and music have been around for as long as humans have, people of the current generation are the pioneers of this amazing new medium. It is easier to get started now than ever before. Our time is now.
4
GameMaker Components Welcome to square one. If you haven’t ever used GameMaker before, this will be the most important section of this book. If you have used GameMaker, then you may want to skip this section, as much of it will be review. Below, I will briefly explain the basic resources that you will use while building a game. Sprites A sprite in GameMaker refers to an image that represent an object in your game. Without sprites, all of the objects in the game would be invisible.
Origin Every sprite has an origin . The origin of a sprite is a reference point that determines the location at which the sprite is drawn, relative to the object it is assigned to. Quite often, the origin of a sprite will be set to “centered,” resulting in a symmetrical flip if the sprite is mirrored.
5
Mask A mask is used for collision detection by the object associated with the sprite. I most often use simple rectangular collision masks because they are the easiest to control and generally result in goodlooking collisions. If you are a beginner, then I suggest not clicking the “precise collision checking” checkbox until you fully understand how it works; it can cause glitches in your game’s collision checking and can also slow your game down because it is harder to compute.
Subimages Sprites can be made up of multiple images, each of these images is called a subimage . When running a game, the sprite will cycle through these subimages to create an animation. The default speed of the sprite is set to 1 frame per step, but you can also adjust the speed of this animation. The speed is dependent on the speed of the game, that is, the room speed, which can also be changed.
6
Objects Objects are central to the programming architecture of a GameMaker game. Objects in GameMaker interpret all of the events and run the majority of the code. Most objects have a sprite assigned to them. When you place an object in your level, the thing you will see displayed in the game will be the (first subimage of the) sprite associated with that object.
7
Events Events control the behavior of each object. An event is a condition that is met during the game; each event can trigger an action (more on actions below). An example of an event would be the Game Start Event . This event is triggered at the very moment the game starts running. It can run code in an action that is relevant to the start of the game, such as showing a start screen.
Actions Actions are what happen when an event occurs. GameMaker has a large variety of draganddrop actions that can be used. In this book, I will not be talking about draganddrop actions very much; there are already some excellent books available that teach you how to use them. There is one draganddrop action that you will be using quite often: the Execute Code Action . This is the action that runs GameMaker’s scripting language, GML (short for GameMaker Language). All of the code examples in this book will either be run in an Execute Code Action or inside a script (more on scripts later). The Execute Code Action is located in the “Control” tab under “Code” in an object’s properties.
8
Timelines A timeline is a list of moments (points in time) in a game. At each moment, you can set an action to be executed. Timelines are useful for controlling sequences of actions. I like to use them for artificial intelligence sequences. For example, you have an enemy that runs up to you, jumps forward to attack, and jumps back. This short sequence could be defined using a timeline.
Fonts You can use this resource to create different fonts for your game, changing the way that text appears. You can set the size and style for your fonts. Fonts can be accessed in your game using the font resource’s name.
9
Sounds Sounds , just like sprites, can add very important feedback to your game. Sounds can be played on a loop or played only a single time. Adding high quality sounds will make just as much of a difference to the feel of your game as adding high quality sprites.
10
Rooms Rooms contain the levels of your game. Each room can be filled with objects at different locations. You can also use rooms to create menus, stat screens, and inventory screens. Rooms have a height and width and can also employ backgrounds and views (more on these below).
Backgrounds Backgrounds are similar to sprites; however, a background is associated with a room in the game, not any specific object. You can add a background to a room in the room properties.
11
12
Views A view is a section of a room that can be displayed on the screen. There are many times in games when you don’t want the player to see the entire level; you can create a view inside the room that restricts what the player can see. Views have a height and width that set how much of the room is visible. Views also have a port height and port width that define the size of the game window on your screen.
13
Book
Contents What is in this book? This book as a mixture of prose, simple code examples, images, and actual game examples. At the start of the book, I use mainly images, prose, and simple code examples to get you started on learning how GameMaker Language works. Later, I start to give more complicated examples that involve building minigames or basic game engines. Chapter 1: Lexical Structure In Chapter 1, you will learn to understand comments, literals, identifiers, and some of the rules that must be followed while writing code in GameMaker Language. Chapter 2: Data Types Chapter 2 covers the different data types and values that you can use in GameMaker Language. This information will become more important as you learn both the different functions available in GameMaker and the types of data you should pass into each function. Chapter 3: Variables and Scope Chapter 3 will give you a solid understanding of variables, including type and scope. You will learn that GameMaker is a weakly typed language and how that influences the way you program. Chapter 4: Data Structures and Arrays In Chapter 4, your brain will probably explode as you attempt to absorb all the information about data structures and arrays. Chapter 4 is a tricky chapter, but it is also very rewarding because it will prepare you for networking in GameMaker Language. Chapter 5: Expressions and Operators Chapter 5 will teach you the different expressions and operators that you have at your disposal when writing in GML. Many of these operators are common to other programming languages, and a solid understanding of how they work is essential to writing powerful code in GameMaker Language. 14
Chapter 6: Statements When you reach Chapter 6, you will be ready to use all that you have learned up to that point to create different statements in GameMaker Language. These statements will help you define the logic of your game. Statements, like operators, are necessary for any large GameMaker project. Chapter 7: Scripts In Chapter 7, you will quickly learn of the power of GameMaker scripts. Scripts execute blocks of code and are used to cut out redundancies that may make your code bloated and hard to maintain. Writing the same type of code in all of your separate objects can become difficult to maintain and waste your time. Scripts will give you the power to write code in one place and then call that code in multiple locations (as needed). Chapter 8: Objects and Sprites As you read through Chapter 8, you will learn all about objects in GameMaker and their relationships with sprites. Objects and sprites are two of the main building blocks of any game; both have many different builtin variables and properties that you will want to become familiar with. Chapter 9: Events Chapter 9 will cover the basics of GameMaker events. You will learn how events control the execution of code and which events are used most often. You will also learn how to create and run customized events. Chapter 10: Game Audio Chapter 10 gets you started with playing sounds in your game. I’ll teach you the basic functions you need for audio and a few other functions you will use to create audio emitters that can modify your sounds while your game is running. Chapter 11: Patterns, Tricks, and Tips Chapter 10 is full of basic patterns, functions, tips, and tricks, including tricks for getting input from the player, how to control zoomed views, how to make an object follow the mouse, and how to make an object point towards the mouse (among many others). Chapter 12: Surfaces and Particles In Chapter 12, you will learn how to create amazing graphic effects in your game using the power of surfaces and particles. I’ll show you how to create and manipulate surfaces. In the section on particles, I’ll teach you how to create your own particle systems, particle types, and emitters (and we’ll figure out what these terms even mean). 15
Chapter 13: HUD and GUI In this chapter, you will learn how to draw information such as health, lives, and score on the screen. You will learn more specifically about the Draw GUI Event inside GameMaker and how it works differently from all other draw events. Chapter 14: Physics This chapter is all about GameMaker’s builtin physics engine and how you can use it to start building some very realistic games. I’ll show you how to build a simple boxesundergravity example and then move on to a more complicated truckandrandomlygeneratedterrain example. This chapter was loads of fun to write and I’m sure you will enjoy reading it. Chapter 15: Online Multiplayer In this chapter, I start by explaining some common terms that you will need to understand in order to build your first online multiplayer game. Next, I’ll walk you through a couple of examples. The first example is simple and can’t really be classified as a complete game, but it is great for learning the basics of network communication. After that, I’ll show you how to build a simple, turnbased board game. Chapter 16: Artificial Intelligence This chapter is dedicated to all the different types of artificial intelligence. I’ll cover basic topdown artificial intelligence, some options for platform artificial intelligence, some basic pathfinding, and a few other considerations when you are programming those baddies.
16
Chapter 1
Lexical Structure You might think that “lexical” is a big word. Well, I guess it isn’t really that big, but it definitely doesn’t come up often in everyday conversation. Lexical refers to the vocabulary of a language. In this chapter, I will teach you the basic vocabulary of GameMaker Language. Comments A comment in GameMaker Language is a section of text that is completely ignored by the compiler (the part of GameMaker that turns your code into a game); in other words, comments don’t do anything for your game. Comments are useful to programmers because there are times when it is beneficial to describe a piece of code. Adding Comments to Code There are three ways to declare a comment in GameMaker Language. The first is to place two forward slashes, // , before the line of code. This will comment out (we say “comment out” to remind ourselves that the comment is out of the consideration of the compiler) any characters on the line after the two slashes. // This is a comment on its own line vari = 0 ; // This is a comment after some code The second way to comment allows you to comment out multiple lines of the code. This kind of comment starts with a forward slash that is followed by an asterisk, /* , and finishes with an asterisk that is followed by a forward slash, */ . /* Here is a comment that spans a few lines */ The third way to add a comment is similar to the first, but it has some functionality that is specific to GameMaker Language. This type of comment is declared by placing three
17
forward slashes, /// , before the line of code. It works similar to the first type, but when this comment is placed on the the first line of a code, it has additional benefits . /// This is a comment with added benefits. The added benefits you get depend on whether the comment is on the first line of an Execute Code Action or on the first line of a script. If the comment is on the first line of an Execute Code Action , it will be shown as the description text of that action. You can use this kind of comment to label code actions and make it easier to navigate and maintain your code. Let me give you a screenshot example. I have this comment on the first line of my code.
/// Initialize all the data This is what the description of the Code Action looks like because of that comment:
If the comment is on the first line of a script, the comment will be shown as helper text (a popup that tells you which elements are needed) when calling the script elsewhere in your game. Here is an example of what the comment might look like inside the script itself: /// scr_add(number1,number2) Here is what you will see while calling the script in GameMaker because of the comment at the top of the script; you get descriptive code completion for the script, like this: 18
And, on top of that, you get argument helper text down at the bottom of the code editor while you are typing in the arguments (just like for all other GameMaker functions).
If that doesn’t make sense, you are probably just new to scripts. That’s okay! Just come back and reread this section after you have finished the chapter on scripts (Chapter 7). Best Practices for Comments In the world of commenting code, there are two extremes. On the one extreme, there are programmers who comment every line of code to describe exactly what that line of code does. For example: // Set health to 100 health = 100 ; Because the code is already pretty clear, these types of comments are, at best, redundant and, at worst, a waste of effort. On the other extreme, there are programmers 19
who never use comments. This is very common among new programmers because at the time of writing their code, it all makes perfect sense. The problem is, later on, uncommented code is more difficult to maintain. The best advice I that can give is to write your comments as if there were another programmer looking through your code (and maybe there will be). Keep the code clean, but try to explain the why behind the more complicated sections and not just the how . Hopefully, these few pages have convinced you of the benefits of using comments, while also persuading you to take advantage of those benefits. Literals A literal is a value that appears directly in your code. Here are some examples of what literals look like inside GameMaker Language:
true false 3 3.5 "Game Maker!" 'Language'
// The boolean value true // The boolean value false // A real number // A decimal number // A double-quote string // A single-quote string
Identifiers Identifiers are names given to variables. Here is an example of how to use an identifier: age = 25; Do you recognize what part of this code is the identifier? It’s the word “age”. The number 25 represents the literal value of the variable and the word “age” is the name or identifier . Identifiers are a fairly simple concept and, for the most part, you can choose whatever descriptive word you want for the identifier. However, there are some restrictions. I also have a few words of advice for choosing identifiers. 20
Identifier Restrictions There are five main restrictions on what you can use as an identifier. Don’t worry about memorizing them. As you start creating more and more variables, you will start to get a feel for what options you have. Here is the list: 1. Identifiers cannot exceed 64 characters 2. Identifiers cannot contain special characters 3. Identifiers cannot contain spaces 4. Identifiers cannot begin with a number (They can contain them, though.) 5. Identifiers cannot share the same name as another resource in GameMaker (e.g., obj_player ) Identifier Tips GameMaker Studio uses a naming practice called snake case for most of its builtin variables and functions. The pattern for naming a variable in snake case has easy rules. Here they are: 1. Every letter be lowercase 2. Replace every space be replaced with an underscore Here is an example of an identifier in snake case: my_name =" Ben "; Simple, right? I often use snake case when naming my variables and resources because I want to be consistent with the patterns used in the native GameMaker variables and functions. This does not mean that this is the only way to name things. In fact, many people only use other naming conventions so that they can more easily separate their variables and functions from the builtin ones. Another common naming convention is called camel case . There are only three rules for camel case and they might already be familiar to you. 1. Remove all spaces 2. Make sure the first word begins with a lowercase letter 3. Make sure all other words begin with an uppercase letter 21
Here is an example: myName =" Ben "; Regardless of what style you choose, the most important thing is that you are consistent ! I’m serious. If you are not consistent, you will get messed up later when you are trying to remember what you named certain variables. Case Sensitivity GameMaker Language is a casesensitive language. Two identifiers with different cases are different identifiers. You may have a variable called hp and a different variable called HP . hp = 100 ; HP = 200 ; These two variables are different . If you try to use the identifier HP to access hp , you will either get the wrong value (if HP has been defined) or get an error (if HP has not been defined). Reserved Words In GameMaker, there are some words that can’t be used as identifiers because they are reserved for builtin variables. Some of the common reserved words are: gravity x y health exp direction speed
22
The variables affect the player object in a way that you may not want in certain cases. Because of this, many programmers use modified versions to get around the reserved words. grav player_x player_y hp expr dir spd Optional Semicolons In GameMaker Language, placing a semicolon at the end of your statement is optional. In most programming languages, however, this is not the case. In GameMaker Language, these two statements are equivalent: x = 0 ; // A statement with a semicolon x = 0 // A statement without a semicolon I would highly recommend getting into the habit of placing semicolons after every statement because many other major programming languages require it.
23
Chapter 2
Data Types and Values Number Literals A number literal is a number inside your code. Here are some examples of number literals: 35 3.75555 -3 Working with Numbers Working with numbers in GameMaker Language is similar to working with numbers on a calculator. You can perform all of the standard operations such as addition, subtraction, multiplication, and division. There are also some other operations and functions that you can perform on numbers. Here are a few examples of some common operations and functions: 18 + 7 // Adds 7 to 18 30 - 5 // Subtracts 5 from 30 5 * 5 // Multiplies 5 by 5 100 / 4 // Divides 100 by 4 20 % 3 // Returns the remainder of 20 divided by 3, which is 2 round ( 5.6 ); round ( 5.3 ); floor ( 5.6 ); ceil ( 5.3 ); abs (- 4 );
/ Rounds 5.6 up to 6 / // Rounds 5.3 down to 5 // Rounds 5.6 down to 5 // Rounds 5.3 up to 6 // Returns the absolute value of -4
sign (- 4 );
/ Returns a -1 for a negative number and a / // +1 for a positive number
random ( 10 );
// Returns a random number between 0 and 10 24
irandom ( 10 ); // Returns a random integer from 0 to 10 random_range ( 5 , 10 ); // Returns a random number from 5 to 10 irandom_range ( 5 , 10 ); // Returns a random integer from 5 to 10 String Literals A string literal is a list of zero or more characters surrounded by single or double quotation marks. Here are some examples of string literals:
" Ben " " 3 " ' GameMaker ' ' ' Note that the string literal “3” is different from the number literal 3 . Later in this chapter, I will talk about the importance of knowing the difference between (and how to convert between) the two literal types. Working with Strings Knowing how to work with strings can be useful, but it can also be a little confusing at first. Unlike number values, strings behave differently than might be expected. Let’s look at some examples.
' Benjamin '+' Anderson '; // gives ‘BenjaminAnderson’ ' 4 ' + ' 8 ';// gives ‘48’, NOT 12 or ‘12’ Converting between Reals and Strings In GameMaker Language, it is critical to understand conversion between reals (numbers) and strings because it won’t happen automatically. This is a very common issue for new developers. Let’s look at how you can convert between the two so that you never have a problem with it. string ( 3 ); // Converts number 3 into the string '3' 25
real (' 3 '); // Converts the string ‘3’ it into the number 3 Why would it be important to use these functions? One of the most common reasons is to draw number values, such as the player’s stats, on the screen. hp = 100; // This is incorrect and will give an error draw_text ( x , y - 32 , ‘ HP: ’+ hp ); If you were to put this code in your game, it would give an error. The draw_text function needs a string value to be able to draw to the screen, but our variable hp currently holds a number value. To get this to work, you will need to convert the number to a string like this: hp = 100; // This is correct draw_text ( x , y - 32 , ‘ HP: ’+ string ( hp )); Boolean Values A boolean value has only two states: booleans are either true or false; they can’t be anything else. Here is an example of a boolean: in_air = false; moving = true; Simple, right? The name can often be intimidating, but there really isn’t much to them. Here is an ingame example of how you might use the in_air boolean:
26
if ( in_air == true ) { sprite_index = spr_player_jumping; } This code checks the boolean variable in_air to find out if the player is jumping. If the player is jumping, it changes the object’s sprite to a jumping sprite. There aren’t very many basic data types in GameMaker because the language is weakly typed, and so, there are implicit type conversions happening (almost magically) behind the scenes, where you don’t have to worry about them. This is nice, but a good understanding of the few basic types that GameMaker uses will help improve your ability to program error free in GameMaker Language.
27
Chapter 3
Variables Variables Variables in GameMaker Language (and in other programming languages) are used to store information in memory. Once a piece of information (or value) is stored, it can later be accessed and manipulated; this helps create code that is easier to understand and maintain. Skilled use of variables creates powerful code. Variable Typing Variable typing shows up in many programming languages. Generally, when you create a new variable, the compiler will want to know if that variable is going to be used to hold a string value, a number value, a boolean value, or any other type of value available in the language. This can be confusing for new programmers, so variables in GameMaker Language were designed to be weakly typed. This means that you don’t need to declare the type of a variable when you create it. It also means that a variable that was once used to hold a number value can (at any time in your code) be told to hold a string value. Even this basic understanding of variable typing will help you to avoid errors as you use GameMaker Language. Variable Declaration Declaring a variable in GameMaker is easy. There are a few different ways to declare them and I will talk about these different ways in the next section. For the most part, all you have to do is name the variable with an identifier; use the assignment operator, a single equals sign, = ; and then include a value. Here is an example of some of the ways that you can define a variable. name = "Benjamin" ; // String variable type age = 25 ; // Number variable type happy = true ; // Boolean variable type The next section will talk more about variable declaration and describe how to declare variables in different scopes. 28
Variable Scope The scope of a variable describes the locations in the code where you have access to that variable. In GameMaker Language, there are three main variable scopes. There is the global scope , the instance scope , and the local scope . Global Variables If a variable is created in the global scope, it can be accessed from anywhere in the code. Variables within the global scope are called global variables ( big surprise there). These variables are defined by placing the keyword global and a dot, . , before the variable identifier.
global . name = "Benjamin Anderson"; After the global variable has been created, you can access it from within any script or object. Instance Variables If a variable is created in the instance scope, it can only be accessed within the code of a single instance. Variables located in the instance scope are called instance variables . It is best to define these variables in the Create Event of the object in order to avoid errors caused by undefined variables. There is no secret to defining these variables; you simply name the variable with an identifier and then assign a value to it.
name = "Benjamin Anderson"; After declaring them in an object, you can access instance variables elsewhere inside the object. In older versions of GameMaker, instance variables had to be defined in the Create Event of an object or the compiler would throw an error. Local Variables If a variable is created in the local scope, it can only be accessed within the action or script in which it was defined. Variables located in the local scope are called local variables. Defining a local variable is just like defining an instance variable, only you place the var keyword before the identifier. 29
varname = "Benjamin Anderson"; Local variables didn’t exist in older versions of GameMaker. If you try to create a local variable in an older version, you will get an error. Macros (Formerly Constants) A macro is similar to a variable because it can contain a value that is accessible in the code. A macro is different from a variable because it can only have one value: once you set the value of a macro, it cannot be modified during the game. In many programming languages, the identifier for the macro is written in all caps. This helps the programmer to distinguish it from other variables, primarily as a reminder that it cannot be altered. To define a macro, you will need to click on the macro’s node in the resource tree. There are two nodes that you can choose from. I use the macro node labeled “Default” Once the macro window comes up, you can use the add button to create as many or few as you will need. After the macro has been defined, you can use it inside your code just like any other variable. draw_text ( x , y , COMPANY_NAME ); It should also be noted that macros fall under the global scope and can be used anywhere in the code. Enums Enums are a rather new (and very useful) part of GameMaker Language. The word enum is short for enumerator; an enum enumerates (or lists) a set of keyvalue pairs. Let’s look at how you can create one. enumbasestat { hp = 50, att = 20, def = 18,
30
}
spd = 24
Easy enough. After you have created the enum, you can access the values in it like this. varbasehp = basestat . hp; // Returns 50 It’s important to know that enums, like macros, are global in scope, so they can be accessed anywhere in your code . They are constants, meaning that they cannot hold values that will change. Enums also have default values, so you don’t have to assign a value to them. You could create an enum with default values like this: enummonths { January, February, March, April, May, June, July, August, September, October, November, December } The default values start at 1 and count up. In this example, month.January now has a value of 1 and month.December has a value of 12. In a later chapter, I will show you some ways to use enums in combination with arrays to give your code better structure and also make it easier to read. 31
Undefined Variables An undefined variable is one that has never had a value assigned to it. These types of variables will show up quite often when you are first learning to program because you will try to call a variable that has not yet been defined and the compiler will throw an error. If you read the error message carefully, it will tell you the object (and often, the exact line of code) where the error occurred.
32
Chapter 4
Arrays and Data Structures Arrays An array is a kind of variable that can hold more than one value. Easy, right? Let me see if I can give you an idea of what I’m talking about. Here is an example of some ordinary (that is, singlevalued) variables and then an example of an array: // Ordinary variables varname1 =' Ben '; varname2 =' Charly '; varname3 =' Dalin '; // Array varnames ; names [ 0 ]=' Ben '; names [ 1 ]=' Charly '; names [ 2 ]=' Dalin '; If you imagine that the array names refers to a row of small boxes in which each box has its own label (an index) and its own content (some data), then you are imagining what an array might look like in physical form. Here is an image that can help you to visualize it:
If you have never used an array before, then, at this point, you are probably wondering how an array is better than multiple variables. The main reason that arrays are better is 33
that you can loop through them. I will talk about loops more later, but for now, let me explain just the basics. What if you wanted to display these names on the screen? Here is how you would do it with the list of ordinary variables:
draw_text ( 32 , 32 , name1 ); draw_text ( 32 , 64 , name2 ); draw_text ( 32 , 96 , name3 ); Not too complicated right? In this case, it isn’t a problem because there are only three names, but what if there were 100 names? The coding would become pretty tedious. Now, let’s look at how you can display the names with an array and a loop. vari = 0; repeat ( 3 ){ draw_text ( 32 , 32 *( i + 1 ), names [ i ]); i ++; } I won’t go into great detail explaining the loop, but but know that this code does exactly what the code above does, only it uses an array. The benefit of this code is that, if there are 100 names, all you have to do is change the number in repeat() , like so: vari = 0; repeat ( 100 ){ draw_text ( 32 , 32 *( i + 1 ), names [ i ]); i ++; } Super cool, huh? This code is way better than typing 100 statements with only slight differences. 34
Arrays are extremely powerful. The value within the square brackets, [ ] , of the array contains what is called the array’s index . This number indicates what location in the array to read data from (or write data to). The index of the array starts at 0 and counts up from there. It is important to remember that the first index of an array will be at position “0”, not position “1”. ArrayRelated Functions In GameMaker Language, there are a few different functions that let you manipulate arrays. There aren’t very many, so I will cover them all here. The first function, is_array , allows you to check to see if a variable is actually an array. vara; a [ 0 ]= 0; a [ 1 ]= 0; if ( is_array ( a )){ show_message (' The variable is an array '); } else{ show_message (' The variable is NOT an array '); } This function returns a boolean (trueorfalse) value indicating whether or not the variable passed to it is an array. The other function (one that I, personally, use more often) is array_length_1d . vara; a [ 0 ]= 0; a [ 1 ]= 0; show_message (' The size of the array is: '+ string ( array_length_1d ( a )); This function returns the array’s size (as a number). You may wonder why the function has _1d at the end of it. The reason is that GameMaker supports both onedimensional 35
(1d) arrays and twodimensional (2d) arrays. So far, you have only seen 1d arrays; however, 2d arrays are at least as powerful as 1d arrays (and can sometimes be even more powerful). Let’s move on and learn a little about 2d arrays. TwoDimensional Arrays A 2d array is an array with two indexes. If a 1d array can be thought of as a row of data, with each index representing a position in the row, a 2d array is like a grid of data, where each indexindex pair represents a location in the grid. Different Data Structures in GameMaker GameMaker Studio has some amazing data structures available; here is a list of them: stacks queues lists maps priority queues grids Each of these data structures is a unique tool, and each can be applied to different data storage scenarios. I have used many of them in my own game projects. This section of the chapter is necessary to understand the chapter on multiplayer games. Make sure to pay close attention if you are interested in making multiplayer games. Stacks Stacks are data structures that can be found in many programming languages. A stack is a lastinfirstout (LIFO) data structure. If you remove one slice of data from the stack, it will be the mostrecentlyadded slice of data that gets removed. Imagine a deck of cards where you can only add or remove cards from the top. Here is an image that might help you to imagine how a stack works:
36
Last In, First Out (LIFO)
The code below shows you how to create a stack in GameMaker Language. This line of code will create a stack and then assign its id to the my_stack variable so that you can access it later through that variable. my_stack = ds_stack_create (); It is important to destroy data structures when you are done using them, because they can take up a lot of memory, even to the point of crashing your game. Here is how you can destroy the stack when you are finished with it: ds_stack_destroy ( my_stack ); There are the two methods used to add or remove data from a stack. When working with stacks, adding data is referred to as “pushing” and removing data is referred to as “popping.” Using our card analogy, pushing is like adding a card to the top of the deck, and popping is like removing a card from the top of the deck. Here is the function you use to push a value to the stack: ds_stack_push ( my_stack , 3 ); 37
I recommend making sure that the stack has data before attempting to call a pop method. Here is how you pop the top piece of data from the stack (if and only if it is not empty): if (! ds_stack_empty ( my_stack )){ varnumber = ds_stack_pop ( my_stack ); } Sometimes, you may want to check the value on the top of the stack before you pop it (calling the pop function will remove the data from the data structure, but this function will leave the data alone, a little like just peeking at the top card of the deck). varnumber = ds_stack_top ( my_stack ); To clear the stack of all of its values, simply call this function: ds_stack_clear ( my_stack ); You can copy stacks as well. my_newstack = ds_stack_create (); ds_stack_copy ( my_newstack ,my_stack ); Lastly, you can find out how many data elements (cards) there are in the stack. varstack_size = ds_stack_size ( my_stack ); Using a Stack for a Card Game Now that you’ve seen the basic functions used for manipulating a stack, I’m going to show you how we can use a stack to build a card game. I have simplified this example as much as I can so that we can focus on the stack data structure. 38
We will need to create a few sprites for this example. They can be as simple as different colored squares. I made my sprites rectangular to make them look like cards, but you can create them however you like. ● spr_card_red ● spr_card_green ● spr_card_blue ● spr_deck I made my deck an offwhite color, representing the back of a card. We can keep these really simple, but using different colors will help us visualize how stacks work in a game. Now that we have the sprites, it is time to create the objects. We will need three objects. Here is what I chose to name them: ● obj_deck ● obj_card ● obj_drag_controller Once we have created these three objects, we are ready to start programming. Open the deck object, add a Create Event to it, and drag over an Execute Code Action from the control tab on the right (this is where we will create our stack). obj_deck: Create Event /// Create the stack data structure deck = ds_stack_create (); This line of code creates a new stack and assigns it to the variable deck . Now, we are going to leave the deck object for a bit and open up the drag controller object. Once we have the drag controller object open, we can add a new Create Event to it and drag over an Execute Code Action . In this action, we are going to create a variable for controlling our draganddrop card movement.
39
obj_drag_controller: Create Event /// Create a global variable to keep track // of the card we are drawing global . card = noone; This global variable will hold the value of the card instance that we are dragging with the mouse. Once we have a reference to the instance, we can make it follow the mouse in a Step Event . obj_drag_controller: End Step Event /// Move the card if ( global . card != noone ){ with ( global . card ){ x = mouse_x; y = mouse_y; } } This code performs an important check before trying to access the global.card instance. It first checks to make sure that we actually have a card. If we do, then it sets the x and y positions of that card equal to the x and y positions of the mouse. This code is placed in the End Step Event for draw timing reasons. If we place it in the Step Event , the card will appear to lag behind the mouse because the position will be updated after drawing the sprite. Try it, if you want! Maybe you will like the effect. Our object drag controller is now finished, but we may notice that, currently, global.card will always hold the value noone . We need to add some code that allows us to pick up a card by assigning its id to the global.card variable. But first, we need to add some code that assigns a random card color to each card instance. Let’s handle that code inside of our card object. Open up the card object and add a new Create Event and an Execute Code Action . obj_card: Create Event /// Initialize the card object and choose a sprite
40
sprite_index = choose ( spr_card_red , spr_card_green, spr_card_blue ); This line of code chooses a random card sprite from the ones that we have created and assigns it to our card. With this event out of the way, we are ready to add the code that picks up the card. Add a new event. This time we are going to choose the Mouse, Left Pressed Event . obj_card: Left Pressed Event /// Pick up the card global . card = id; depth =- 1; Now we can pick up the cards! It is as easy as assigning the id of the card instance that we clicked on to the global variable that we created in the drag controller object. Once we have done that, the drag controller object that we already coded takes care of the rest and moves the card around for us. We are also setting the depth to 1 to bring the card in front of other cards that may be in the room. It’s time to write the code that will allow us to drop cards. Add a new mouse event with a Code Action . With this one, we will use the Mouse, Left Released Event . obj_card: Left Released Event /// Drop the card on the ground or on the deck varmx = mouse_x; varmy = mouse_y; if ( position_meeting ( mx , my , obj_deck )){ // Add the card to the deck stack with ( obj_deck ){ ds_stack_push ( deck , global . card . sprite_index ); } // Destroy the card instance with ( global . card ){ instance_destroy (); 41
} } global . card = noone; depth = 0; That is one of the most complicated sections of this example, so let me explain it. First, we get local references to the mouse_x and mouse_y positions. After that, we check to see if the mouse is hovering over the deck. If it is hovering over the deck, we push the global.card sprite index to the stack. For this example, we really only need to know what sprite the object had. After pushing to the stack, we destroy the card object. Once all of these steps have been executed, we set the global.card back to noone and set the depth back to zero. Of course, if the mouse isn’t hovering over the deck, then we will ONLY set global.card to noone and set the depth to zero. This will drop the card on the ground, where we can pick it up again later. Great job so far! There is only one more thing for us to add to get our game working. We need to allow the player to take a card off the top of the deck. Open up the deck object again and add a Mouse, Left Pressed Event to it. Drag a Code Action over into the event and write this code: obj_deck: Left Pressed Event /// Remove the card from the deck if ( ds_stack_size ( deck )> 0 ){ vartop_card = instance_create ( x , y , obj_card ); varcard_sprite = ds_stack_pop ( deck ); top_card . sprite_index = card_sprite; global . card = top_card; global . card . depth =- 1; } This last block of code is probably the second most complicated. The first thing we do is check to make sure that the stack isn’t empty. If the stack is empty and we try to remove information from it, we would get bad information. Once we know for sure that the stack has information, we can continue. The next thing we do is create a new card instance and store it in a local variable called top_card . After that, we pop the sprite information 42
that we stored in the stack and assign it to a local variable called card_sprite . Now, we have all of the information we need. We assign card_sprite to the new card instance that we created, make that new card instance global.card , and set its depth to 1 to simulate picking it up. Congratulations! You just built a simple card game using a stack data structure. As you play around with the game, pay close attention to the order in which cards are removed from the deck. If you are perceptive, you will notice that cards are removed in the reverse order that they were placed in the deck. This example shows the main principles of how a stack works. Queues A queue is a firstinfirstout (FIFO) data structure. The first data element that you add to it will be the first one that you can remove from it. The easiest way to understand a queue is just to think of a line at the supermarket. The first person in line will be the first person to be helped. Here is an image to help you imagine how a queue works:
43
First In, First Out (FIFO)
Let’s create a queue. It’s similar to how you would create a stack. my_queue = ds_queue_create (); Create the queue and assign its id to a variable so that you can access it later. You can destroy the queue like this: ds_queue_destroy ( my_queue ); To add an item to the queue, you use this function: ds_queue_enqueue ( my_queue , 3 ); To remove an item from the queue, you can use this function. It’s a good idea to make sure the queue is not empty first. if (! ds_queue_empty ( my_queue )){ 44
}
varnumber = ds_queue_dequeue ( my_queue );
Queues have two ends; they have what is called the head of the queue and the tail of the queue. The head is the next value to be removed when using the ds_queue_dequque function. The tail is the last value added using the ds_queue_enqueue function. You can peek at the head or the tail without removing them using these functions: varnumber = ds_queue_head ( my_queue ); varnumber = ds_queue_tail ( my_queue ); You can clear queues, copy queues, and find out how many values a queue contains, similar to how you did with the stack data structure. ds_queue_clear ( my_queue ); my_newqueue = ds_queue_create (); ds_queue_copy ( my_newqueue , my_queue ); varqueue_size = ds_queue_size ( my_queue ); Lists The list data structure in GameMaker has quite a few different functions that the other structures you have seen so far do not have. Creating a list is very similar to creating a 1d array. However, one of the differences between lists and arrays is that you do not need to know how long a list is going to be. Using the same analogy used with arrays, boxes of data are added to the list dynamically, and so, you don’t have to worry about the list’s size when you create it. my_list = ds_list_create (); 45
Just like the other data structures, a list should be destroyed when it is no longer being used. They are easy to destroy. Use this simple function: ds_list_destroy ( my_list ); Below is the function that you can use to add a value to the list. The value will be added to the end of the list. The first argument in this function indicates the list that you are adding to, and the second argument (in this case, the number 3) is the value being added to the list. ds_list_add ( my_list , 3 ); The function below will delete a value from the list. Unlike with adding a value, the second argument is the index of the value that you would like to delete, not the value itself. Just like arrays, when you are trying to access a value in a list, you will need to use the index of the value. ds_list_delete ( my_list , 0 ); // This function requires an index Remember how when you add a value to a list, the new box is dynamically added? Well, the same is true in reverse. When you delete an item from the list, the box is dynamically removed and any boxes after the one removed will shift to take up the empty space. For example, if you remove the 6th item, and there is a 7th item, the 7th item will become the new 6th item. Using the ds_list_find_index function, you provide a value and find its index (below, 3 is the value, not the index). If there are several items in the list that are the same, then this function will return the index of one of them, but you cannot know which one that it will return. If the value doesn’t exist, this function will return a value of 1. index= ds_list_find_index ( my_list , 3 );
46
The ds_list_find_value function will return the value for the given index. This function will not remove the value from the list (which the pop function will do for a stack). value=ds_list_find_value ( my_list , 0 ); One of the cool things about the list data structure is that you can insert a value into the list and the other values will move out of the way, instead of being replaced. Below is the function you can use for that. The second argument is the index and the third argument is the value you would like to insert. ds_list_insert ( my_list , 0 , 5 ); If replacing the value is your actual goal, you can use the function below. Once again, the second argument is the index and the third is the value. ds_list_replace ( my_list , 0 , 3 ); List data structures have an easytouse, builtin shuffle function. This function could be useful for a card game. ds_list_shuffle ( my_list ); You also have the ability to sort the values in a list. The second argument is a boolean value that determines whether the list should be sorted in ascending order. ds_list_sort ( my_list , true ); Here are the steps you would take to create a copy of a list that you have already created: 47
my_newlist = ds_list_create (); ds_list_copy ( my_newlist , my_list ); In the end, lists are very similar to 1d arrays, but they have some extra functions and, therefore, are better suited to certain programming situations. Maps Maps are a very powerful data structure. They are similar to an object that has no events or scripts. Maps contain keyvalue pairs, which means that you have a list of keys where each key has a value assigned to it. Here is a way you might visualize a key value pair: “key” : “value” Let’s take a quick look at some data that you might store in a ds_map . Note, that this is not code and you can’t add data to a ds_map like this. This is just an example to help you visualize how the data is organized. “class” : “wizard” “attack” : 25 “speed” : 18 “mana” : 37 Creating a map is easy; this kind of function should look familiar to you by now. my_map = ds_map_create (); It is easy to clear a map of all of its keyvalue pairs. Be aware that this function doesn’t actually destroy the map. It just clears the map of every keyvalue pair. ds_map_clear ( my_map ); 48
The code below shows how to add a new keyvalue pair to the map. Be sure to check out the easier way to do this just a little later in the chapter where I talk about accessors. ds_map_add ( my_map ,' hp ', 25 ); The function below shows how you can actually destroy the map that was created. This should be done once you are done using a map. ds_map_destroy ( my_map) Maps are a great way to store a large amount of data in an organized way. They are necessary when using the networking functions in GameMaker Language, so be sure to study them well. Grids Grids are the last data structure that I will talk about in this book. Grids are exactly what they sound like. Imagine a chessboard where each square can hold some piece of information. You can create grids of any size and manipulate specific areas or squares inside the grid. Grids are similar to 2d arrays, but they have a few unique functions. The function below shows how you can create a grid. The first argument is the number of columns, and the second argument is the number of rows. Let’s create a grid with only 9 total cells.
my_grid = ds_grid_create ( 3 , 3 ); And (really quickly) I’m going to show you how you can destroy a grid as well. Note that when you are done using the grid, you should call this function.
ds_grid_destroy ( my_grid ); 49
There are dozens of functions that you can use with grids. They are all super cool, and I would highly recommend learning about them. I’m going to cover a few of the main ones here, but I’m not going to talk about all of them, because the GameMaker Help file explains them all very well. After showing you the functions, we will build an example game using a grid so that you can learn about the practical applications that this data structure provides. Let’s start with the basics. After you have created the grid, you need to be able to add values to the cells. Below is the function you will use to set the values of individual cells. The first argument is the id of the grid, the second is the x position in the grid, the third is the y position, and the fourth is the value to be added.
ds_grid_set ( my_grid , 0 , 0 , 3 ); It is also possible to set cell values on multiple cells. Below is the function you can use to set the cell values for a rectangular region. The first argument is the grid id again, the second and third are the x and y position, respectively, for the left corner of the rectangle. The third and fourth arguments are the x and y position, respectively, for the bottom right corner. Finally, the fifth argument is the the value that will be set for each cell inside the region.
ds_grid_set_region ( my_grid , 0 , 0 , 2 , 2 , 3 ); It is also possible to set a region that has a circular shape (called a “disk” by GameMaker). You will have to check the help file for more information on that one though. Next, I’m going to show you how you can retrieve a value from the grid. Below is the function that you will use. The first argument is the id, the second is the x position of the cell, and the third is the y position of the cell.
varnum = ds_grid_get ( my_grid , 0 , 0 );
50
This piece of code will retrieve the value from the upperleft cell in the grid and store it in the local variable num . Now that you have a solid, basic understanding of this data structure, let’s create a real game example together to help you get more comfortable with these functions. TicTacToe with a Grid Data Structure Let’s step through a very simple example of how we can set up and use a grid to make a Tictactoe game in GameMaker. The first thing that we need to do for this example is create a new sprite and name it spr_char . This sprite will have a width of 160, a height of 160 (these two numbers will set the size of each cell in the grid), an x origin of zero, and a y origin of zero. It will also have two subimages. The first subimage will be a giant circle or “O” and the second subimage will be a giant cross or “X”. The second thing we will need is a new background called bg_tiles . We are going to use this background to create the 9 squares needed in every Tictactoe game. Give this background a width and a height of 160. Leave the center of the background transparent, but draw a white outline around all four edges. After creating the sprite and the background, create a new object and name it obj_game . This will be the only object in our example. It will contain our grid data structure and all of the code that is required to run the game. Add a new Create Event to the object. obj_game: Create Event /// Create the ds_grid and initialize the // game object grid = ds_grid_create ( 3 , 3 ); ds_grid_set_region ( grid , 0 , 0 , 2 , 2 ,- 1 ); This code creates a new grid and then sets every grid square equal to 1. The arguments for ds_grid_set_region should be explained. The first argument is the id of the grid. The next two arguments are the x and y position, respectively, for the upperleft corner of the region, followed by the two arguments for the x and y position, respectively, for the lowerright corner of the region. The last argument is the value that 51
should be set for each square or cell in the region. For our example, we will use 1 to represent an empty cell, 0 to represent an “O” cell, and 1 to represent an “X” cell. If you are extra perceptive, you will notice the “O” subimage in our sprite has an image_index of 0 and the “X” subimage in our sprite has an image_index of 1. We will be using this fact to our advantage. It’s time for the next phase in our Tictactoe example. Add a new Mouse > Global > Global Left Pressed Event and drag over a Code Action . Inside the Code Action , type this code: obj_game: Global Left Pressed /// Set an O vargridx = mouse_x div 160; vargridy = mouse_y div 160; ds_grid_set ( grid , gridx , gridy , 0 ); This small code will set an “O” at the clicked cell location in the grid. The ds_grid_set function takes a grid id as its first argument, the x value as its second argument, the y value as its third argument, and the value that the cell will be set to as its fourth argument. Here, we are setting the value to 0, which is our digital representation of “O”. Before we move on, it is important to have a small discussion about the math involved here. We have decided that our room should be divided up into 9 squares, each square being 160 pixels wide and 160 pixels tall. The upperleft of our square in the room could have x values and y values anywhere from 0 to 159, but in our grid, the x and y values of the upperleft square will both be 0. Our middle square in the room could have x and y values anywhere from 160 to 319, but in our grid, the x and y values will both be 1. We need some way to convert from the range of values in the room to the single value in the grid. One way to do this is to divide the x and y mouse positions from the room by the grid cell size (160) and then round them down (using the floor function). This works well, but there is an easier way. GameMaker has an operator called the div operator. This operator takes two operands, divides them, and returns a whole number answer (meaning that it doesn’t have the remainder). The mod operator and the div operator are very similar. The difference is that the mod operator returns only the remainder, without the whole number answer. Using this cool math trick, we can calculate the grid x
52
and y values of the mouse’s x and y positions in the room. Just as a side note, we can also use this method to make objects snap to a grid. Now that the math is out of the way, we need to add a way for our player to place an “X” in the room. The code will be similar to that for setting an “O”, but we will put it in a Mouse > Global Mouse > Global Right Pressed Event . obj_game: Global Right Pressed /// Set an X vargridx = mouse_x div 160; vargridy = mouse_y div 160; ds_grid_set ( grid , gridx , gridy , 1 ); We are setting the grid value to 1, because that is the digital representation that we chose for “X”. The last piece that we need in order to finish our small Tictactoe grid example is the code that will draw our game. This code can get a little tricky, because we need to have one for loop nested inside another for loop. I’ll do my best to explain it. obj_game: Draw Event /// Draw the grid vargridw = ds_grid_width ( grid ); vargridh = ds_grid_height ( grid ); for ( vari = 0 ;i < gridw ;i ++){ for ( varj = 0 ;j < gridh ;j ++){ if ( ds_grid_get ( grid , j , i )==- 1 ) { continue; } varsubi = ds_grid_get ( grid , j , i ); draw_sprite ( spr_char , subi , j * 160 , i * 160 ); } }
53
The two for loops in this code are used to cycle through the different cells in our grid. j represents the x values and i represents the y values. In each cell, we check its value. If the value is equal to 1, we continue (jump out of this current loop and start the next iteration). If it doesn’t equal 1, we will draw a sprite with an image_index of the value found in the grid. We will need to multiply the x and y positions in the grid by 160 to convert them from grid values to room x and y positions. Now, create a new room, give it a name, a width of 480, and a height of 480. Click the backgrounds tab, check “Visible when room starts”, choose bg_tiles as the background, and make sure “Tile Hor.” and “Tile Vert.” are checked. We should see the classic Tictactoe board in our room. Click the objects tab and place obj_game in the room. Now, run the game and test it. We should be able to place “O”s by left clicking and “X”s by right clicking. Now, grab a friend and enjoy a game of Tictactoe! Data Structure Accessors The set of different functions used to set and get values from data structures is rather large and, in my opinion, kind of a pain to use. Luckily for us, the newer versions of GameMaker have a neat little trick called accessors . Accessors allow you to manipulate the values of a data structure in a more intuitive way. Let’s look at some code. stats = ds_map_create (); ds_map_add ( stats ," health ", 100 ); ds_map_add ( stats ," mana ", 50 ); This bit of code creates a map with two keyvalue pairs: one for the health and one for the mana. It’s a giant mess of code for what little it actually does. Let me show you how to do the exact same thing using the accessor for map data structures. stats =ds_map_create (); stats [ ? " health "] = 100; stats [ ? " mana "] = 100; Of course, the other cool thing about accessor code is that it can be used to both set and get values from the data structure. 54
You may be thinking that the code above looks kind of like an array with a funny question mark at the start of it, right? That question mark tells the compiler what type of data structure it is dealing with. Each data structure has its own symbol to be used in the accessor. Here are the different symbols and their corresponding data structure types: list [ | ] map [ ? ] grid [ # ] Accessors are a great way to make your code easier both to understand and to write. As far as I know, there is no way to chain accessors in GameMaker Language right now (if you had a map inside another map and tried to chain the accessors to get a value two levels down), but hopefully this functionality will be added in a later version.
55
Chapter 5
Expressions and Operators Expressions An expression is a combination of operators and operands. If you haven’t learned a programming language before, that statement might sound alien to you. I’ll explain it in this chapter, and you should find that it is pretty easy to understand. Let me show you some examples of statements. You’ve actually seen them before. x = 10; Easy, right? I told you they would be familiar. Let me dissect this expression for you so that I can explain my earlier comments. The “ x ” and the “ 10 ” are the operands in this statement. They represent the data being operated on. The “ = ” is the operator . It is operating on the two operands. The single equals sign is called the assignment operator. This is because it assigns the right operand to the left operand. You will also notice there are two operands. That makes the assignment operator a binary operator. In most programming languages, there are unary , binary , and sometimes ternary operators. Equality vs Assignment This is not a difficult concept, but it is one of the most frequent mistakes new programmers make in GameMaker. (I’ve made it before…) Luckily, GameMaker Language is very forgiving. Best practice is to know the difference between these two operators and use them in the correct places; this is very important if you want to learn other major programming language later. The easy way to remember them is to know that the equality operator is the question and the assignment operator is the statement . Equality Operator The equality operator is the question, “Is this variable equal to this value?” This operator is represented by two equals signs, == , and is commonly combined with an if statement (for more information on if statements, see Chapter 6). Here is an example:
56
if ( health == 0 ){ instance_destroy (); } GameMaker is smart enough to know that if you (mistakenly) use a single equals sign (the assignment operator), the code will still work; however, this will get you into big trouble later if you ever move on to other programming languages. I would highly recommend getting in the habit of using this operator correctly. Assignment Operator The assignment operator is the statement, “This variable now has this value.” This operator is represented by a single equals sign and is commonly used when initializing a variable. Here is an example: health = 100; GameMaker will NOT allow you to use a double equals sign (the equality operator) here. You will get an error if you try this. Equality with other Operations The equality operator can be combined with other operators. The operators commonly combined with it are the greater than, less than, and not operators. Below are some examples of how these can be combined. The following example checks to see if the player’s health is less than or equal to zero. This can be very useful when the player takes damage that would put his health in the negative. if ( health <= 0 ){ // Do something } This example checks to see if the player’s health is greater than or equal to 100. This can be useful for making sure a variable has not passed some maximum value. 57
if ( health >= 100 ){ // Do something } This example checks to make sure the player’s health is not equal to 100. if ( health != 100 ){ // Do something } Add Operator The add operator has two main functions in GameMaker. The first is doing the math of adding numbers together. Here is a simple example:
/// Add two numbers using the add operator varresult = 5 + 3; Can you guess the value of result? Pretty easy, right? The second function of the add operator is to concatenate (concatenate just means to link) string values together. You’ve seen this before in a previous chapter, but I’m going to throw down a quick example here as well. /// Create a full name using the add operator varspace =" "; varfull_name =" Benjamin "+ space +" Anderson "; Can you imagine the result of this operation? The add operator one of the most common operators in GameMaker Language. It is simple to understand but very important. 58
Subtract Operator Unlike the add operator, the subtract operator cannot be used with strings; it will only be used with numbers. With numbers, it does the simple math that you would expect. /// Use the subtract operator to subtract two numbers varresult = 5 - 3 ; // Returns 2 This operator is straightforward. Just remember not to use it with strings. Multiply Operator The multiply operator does just what you would expect. To multiply, simply use the asterisk, * , symbol. Here is an example: /// Use the multiply operator to get the product of two numbers varresult = 5 * 3 ; // Returns 15 Just as easy as the subtract operator, but still very useful. Divide Operator The divide operator, just like the multiply operator, works just as you would expect. It uses a single slash, / , symbol. Here is a simple example: // Use the multiply operator to get the answer to 15/3 varresult = 15 / 3 ; // Returns 5 Soon, I will explain the division operator. Don’t get these two confused! The division operator works differently from the divide operator! Assignment with Operation The assignment operator can also be combined with the other operators in this way: hp += 10; 59
hp -= 10; hp *= 2; hp /= 2; Each of the previous statements are equivalent to the following corresponding statements. hp = hp + 10; hp = hp - 10; hp = hp * 2; hp = hp / 2; The simple operator combinations can be used as shortcuts. You will see them used more often than their writtenout counterparts. Increment and Decrement Operator The increment and decrement operators are also shortcuts. They are very commonly used in for loops, but they can be used outside for loops as well. level ++; level --; These two previous statements are (more or less) equivalent to these corresponding statements: level += 1; level -= 1;
60
You can actually change the order of the operation like this: ++ level; level ++ ; This last version gets a little more complicated, and I rarely use them like this. The difference is that if you put the operator before, then it will return the incremented value. If you put the operator after, it will return the value and then increment it. The Not Operator The not operator is like a switch in GameMaker Language. It works basically the same in many other programming languages. If you have a value that is equal to true and use you the not operator, then that value will become false. Here is an example: varfalling = false; falling =! falling; In this example, the falling variable starts out as false. The second line uses the not operator to flip it from false to true. You might ask why you can’t just set it to true, like this: falling = true; Well, the answer is that you can, but what if the value is already true? If the value is already true, then using the not operator will switch it to false. Basically, the above code allows you to toggle the variable’s value from false to true or from true to false, and you don’t have to know what it was originally. You just know that it is now the opposite of what it was before. You can also use the not operator in condition statements. You have seen this in a previous section that talked about combining the equality operator with other operators. Division Operator 61
The division operator is different from the divide operator because it returns a whole number (that is, with no remainder). This is convenient because you can use this anytime that you need to calculate snapping to a grid. End Step Event /// Make an object follow the mouse while snapping to a 32x32 grid x =( mouse_x div 32 )* 32; y =( mouse_y div 32 )* 32; In this code, you are using the division operator to get the wholenumber value of the mouse position divided by 32. If the mouse position is 64, then the answer will be 2. If the mouse position is 67, then answer will still be 2. After you get the value, you multiply your answer by 32. As you can see, both cases (values of 64 and 67) will return 64. The division operator is a little tricky at first, but once you figure it out, it can be very useful. Modulo Operator The modulo operator is the complement to the division operator: instead of returning the wholenumber answer, the modulo operator returns the remainder after performing the division. One common use for this is to discover whether a number is odd or even. /// Use the modulo operator to find if a number is odd or even varnum = irandom ( 10 ); varis_even =(( num mod 2 )== 0 ); As you can see, you use the modulo operator to divide num by 2. If the returned whole number remainder (the result of the operation) is equal to 0, then you know that the number is even. If not, the number must be odd. The modulo operator is even trickier than the division operator, but it is also well worth the few minutes that it will take you to learn how to use it.
62
Chapter 6
Statements Control Statements In programming, control statements are used to “branch” your code, meaning to run different sections of code based on a specific condition. The most basic form is an if statement . If The if control statement is very important to programming in any language. It makes it possible to execute code based on a specified condition. Here is an example of when you might use an if statement in GameMaker Language: if ( keyboard_check ( vk_right )){ x += 4 ; } This code will check to see if the keyboard’s right arrow key is being pressed; if it is, then the code will add 4 to the current x position of the object that is running the code. Adding 4 to the current x position will move the object to the right on the screen. Now that you have seen an ingame example of where you might use an if statement, let me show you a generic example. if ( condition ) { statement ; } If statements are fairly easy to understand, even for new programmers. The thing you need to check carefully is where you want your curly braces to end. Being consistent with your formatting style will help you to reduce bugs and make your code easier to read. Here are two common ways to format statements, starting with the one that I use most often. 63
First Example if ( condition ){ statement ; }
Second Example if ( condition ) { statement ; }
It really doesn’t matter which method you choose to use (the compiler treats these as equivalent), or if you choose to use one that I don’t have listed here. What really matters is that you choose one style and then stick with it. You will have way fewer bugs if you are consistent. Else The else control statement goes right along with the if control statement. The else is what happens if the condition inside the if evaluates to false.
if ( condition ){ // True? Do this statement } else{ // False? Do this statement } Else if The else if control statement is also combined with the if control statement. You can chain else if as many times as is necessary to build the logical structure that you want to use.
if ( condition1 ){ 64
// Condition1 is true? Do this // statement } else if ( condition2 ){ // Condition2 is true? Do this // statement } else if ( condition3 ){ // Condition3 is true? Do this // statement } else{ // None are true? Do this statement } Switch The switch control statement is an alternative to the if / else if / else combination. switch ( expression ){ case value1 : // Does expression evaluate to // value1? Do this break; case value2 : // Does expression evaluate to // value2? Do this break; default: // Do this if none are true break; } This one is a little tricky to understand with a generic example like this. An ingame use would better explain it.
switch ( direction ){ case 0: 65
// We are moving to the right sprite_index = spr_player_right; break; case 90: // We are moving up sprite_index = spr_player_up; break; case 180: // We are moving to the left sprite_index = spr_player_left; break; case 270: // We are moving down sprite_index = spr_player_down; break; default: // We are moving at an angle // Do nothing break; } This switch statement checks if an object is moving right, left, up, or down, and changes the sprite accordingly. The break statement at the end of each case prevents the switch statement from checking the other cases as well. With With is a powerful control statement inside GameMaker that allows you to run some code in the context of another object. An example will explain this best. // Code inside the enemy object with ( obj_player ){ hp -= 10; }
66
Even though this code is run from inside the enemy object, it is actually subtracting health from the player object and not from itself. You can also do this using the dot operator. // Code inside the enemy object obj_player . hp -= 10; This is basically the same thing but the with control statement is useful when you need to perform tons of changes on the other object, because you don’t have to keep referencing the object; you only reference it once. One other difference is that if you want to call a function inside the player object, you must use the with statement; you can’t use the dot operator. Loop Statements Loop statements in GameMaker Language allow you to run a line of code multiple times with slight differences. This is important for many kinds of games. There are four main loop statements that can be used in GameMaker, and there are different reasons for choosing each one. Repeat Repeat is the shortest and the simplest of all of the loops. It allows you to run the same section of code multiple times in a single Step Event (which happens basically instantaneously during runtime). varrand_x =random ( room_width ); varrand_y =random ( room_height ); repeat ( 10 ){ instance_create ( rand_x , rand_y , obj_enemy ); } Be careful with this code. There is actually only one line of code inside the repeat block, but it has to wrap because it is such a long line. This code will create ten enemies in random locations throughout the room. Of course, instead, you could type the instance_create() function out ten times, but that would be a pain. 67
While The while loop is a little more complicated than the repeat loop, but it’s not too bad either. The while loop takes a condition, and, if the condition is true, will repeat all the code within its curly braces. Once the condition is false, the while loop will exit. For The for loop is probably the hardest to understand out of all of the control statements, but it is powerful, and you need to know how it works. for ( vari = 0 ;i < 10 ;i ++){ draw_line ( 32 * i , 0 , 32 * i , room_height ); } Let me first tell you what this code does, and then I will explain how it does it. This code draws vertical lines stretching the entire height of the room. The first line is drawn at an x position of 32, and each line after that adds another 32 to the x position. The first part of the for loop you need to understand is the part wrapped in parenthesis. for ( vari = 0 ;i < 10 ;i ++) This part is made up of 3 sections. The first section is the initialization section, the second is the condition section, and the third is the iteration section. for ( initialize ; condition ; iterate ) The initialize section is only run the first time. It is generally used to create the variable that you will be using to iterate and check against in the condition. The condition section is checked every loop. If the condition evaluates to true, then the loop will run again. If the condition evaluates to false, then the loop will not run again. The iteration section is run every time the condition section evaluates to true, but after the code itself has been run. 68
I know this will be a little bit confusing if for loops are new to you. Let’s step through a short loop. for ( vari = 0 ;i < 2 ;i ++){ show_message (" Hi "); } This for loop will first create the variable i and set it to 0. After that, it will check to see if i is less than 2; because it is less than 2, the show_message code will be run. Then, the iteration section will be run and i++ will add 1 to i , making it now equal to 1. The condition section will make sure that i is still less than 2. Because i is equal to 1, the loop will evaluate as true and the show_message code will be run again. Now, the i++ code will run again, setting i to 2. The condition section will check if i is less than 2; since it is no longer less than 2, the show_message code will NOT be run again, and the program will exit the loop. Hopefully, that small example helped. Be sure to watch my video about for loops if you are still struggling with them. Expression Statements Here are a few common expression statements that you will find useful. Return Return statements are most useful in scripts. For example, you might create a script like this: ///scr_add(n1,n2); returnargument [ 0 ]+ argument [ 1 ]; You might not understand all of what is going on here, but know that this script takes two arguments (values) and then returns their sum. After creating this script, you could call it like this:
69
sum = scr_add ( 5 , 7 ); // sum now holds the value 12 As you can see, the script returns the number that has been added together inside the script. Many GameMaker functions return values, and it’s very important to understand what value types are returned from them. Break Break is commonly used in switch statements, but it can also be used in other loop statements to break out of the loop. for ( vari = 0 ;i < 100 ;i ++) { // Some code if ( i == 9 ) break; } That is a simple for loop. In a normal situation, it will run the code inside the loop 100 times; however, once i reaches 9, it will call the break statement and exit the loop, running only 10 times. Break statements elsewhere will also exit the loop once they are reached by the compiler. This means that every line of code before the break will be run inside that loop, and every line after it will not be run. Break statements will exit out of for loops, while loops, repeat loops, and with statements. Continue The continue statement works similarly to the break statement, but instead of exiting out of the loop completely, it simply skips one iteration. Basically, there may be times when you wish to skip one specific step in the loop but continue to execute the rest of the steps in the loop.
70
Exit Exit works like the break statement, but it applies to the entire section of code. If you place an exit in a script, it will exit the script completely. If you put one in a Code Action , it will exit that entire action (not the event, but the action).
71
Chapter 7
Scripts and Audio Creating Scripts Scripts are amazing! They are also easy to use! If you have ever programmed in a different language, a script is similar to a function, in that it allows you to write a block of code once and then run that block of code multiple times by executing the script it is contained in. One of the interesting things about scripts is that they are global in scope. When I say global, I mean that if you create a script, any object in your game could call that script. If you see lots of repeated code in your game, then it might be a good idea to create a script for that code and then just call the script whenever needed. Creating a script is pretty easy. You can use the shortcut keys shift+ctrl+c to add a new script to your game, or you can click the page icon with the green play symbol on it. After adding a new script, you will need to give the script its own identifier. Scripts, just like variables, have an identifier. Let’s create a script called scr_move_right . I’m going to put the name (identifier) of the script at the upper lefthand side of the code block so that you know it is a script and you know what it is called. scr_move_right /// scr_move_right() x += 4; As stated before, the scr_move_right on the upper lefthand side of the code block is the actual identifier for the script. In GameMaker, you will place the identifier for the script in this location. The triple slash comment has a special meaning at the top of a script file, as I described in Chapter 1. This comment provides the autocomplete functionality in GameMaker and can also be used to tell you the order of arguments that need to be passed to the script. The last line in the script simply moves the player four pixels to the right. This basic script isn’t super useful, but let me show you one that I use quite often in my projects.
72
scr_get_depth ///scr_get_depth() depth =- y ; This is a neat little script that will give objects that are closer to the bottom of the screen a lower depth (meaning that they will be drawn on top of objects that are closer to the top of the screen). This gives the game a sense of depth and, when used correctly, can create a cool visual effect. Script Arguments Just like functions, scripts can be passed arguments. Let’s upgrade the script we wrote in the last section from scr_move_right to scr_move and pass it some arguments. scr_move /// scr_move(xmov,ymov) x += argument [ 0 ]; y += argument [ 1 ]; There is an array in the local scope of each script called argument . It contains the values of the arguments that were passed to the script in the order in which they were passed. The first value passed in will be assigned to argument[0] , the second to argument[1] , and so on. I usually assign these values to local variables in order to help with the readability of the script. scr_move /// scr_move(xmov,ymov) varxmov = argument [ 0 ]; varymov = argument [ 1 ]; x += xmov; y += ymov; 73
It may seem a little redundant in this particular script (and maybe it is), but in a script where the argument is being used in multiple places, it is easier to understand a named variable than the generic argument[0] . Scripts Return Values Scripts can also return a value. This means that you can pass data to the script, do something with that data to get a result, and then have that result pop back out. For example, you might create a script like this. scr_add ///scr_add(n1,n2); returnargument [ 0 ]+ argument [ 1 ]; You might not understand all of what is going on here, but know that this script takes two arguments (values) and then returns the sum. After creating this script, you could call it like this: obj_add_button: Mouse Pressed Event sum = scr_add ( 5 , 7 ); // sum now holds the value 12 As you can see, this script will actually return the result of the two arguments added together by the script. Executing Scripts Let me show you how you can call a script inside your code. There are two ways to do so. The easiest way is to write the identifier for the script followed closely by two parenthesis. These two parenthesis are called the script call operator ; they will cause the script to be executed. scr_get_depth (); Let’s look at the second way you can call a script. This way involves a new function inside GameMaker Language called the execute_script function. Make sure that when you pass the script into this function you do not add the brackets to the end. 74
execute_script ( scr_get_depth ); Now, you might be wondering why on earth you would ever use this when it takes longer to type. The benefit of this method is that you can run a script that has been assigned to a variable. varmy_script =scr_get_depth ; execute_script ( my_script ); This is useful when you have a parent object that runs different scripts for each of its child objects (see chapter 8 for more information on the child/parent relationship in objects) depending on what you want them to do. Make sure that you don’t use the script call operator when assigning the script to a variable or executing it inside the execute_script function. Also, note that when you assign a script to a variable (as seen in the code above) you do not use the brackets on the end of the script. The brackets represents the script call operator or function call operator; therefore, they would cause GameMaker to execute the script and attempt to assign its returned value to the variable, instead of assigning the script’s id to the variable. This chapter on scripts has been short and sweet, but hopefully you learned something from it that you can apply in your future projects. I love using scripts to help organize my code, and I hope that you can find them useful as well.
75
Chapter 8
Objects and Sprites Creating Object Instances Something that I didn’t pick up on right away when I first started using GameMaker is the difference between instances and objects. An object in GameMaker is kind of like a cookie cutter, whereas the instance is sort of like the cookie. Objects are used as the molds for creating instances. There are two ways to create an instance of an object in GameMaker. The first way is to simply place the instance in a room. This creates a new instance of the object that you have selected. The other way to create a new instance of an object is by using the instance_create function. It is a simple function that takes only 3 arguments. instance_create ( 32 , 32 , obj_player ); This line of code will create an instance of the player object at x position 32, y position 32. Object Properties Every instance created from an object starts off with some basic properties. These properties are builtin variables, which have their own default values, and identifiers. There are quite a few of these properties, but let’s start off with the basics. Basic Instance Properties id An instance’s id is represented by a real value, in other words, a number. You can store this number in a variable that can later be used to refer to the instance. You will find an instance’s id can be your best friend. 76
solid Even though GameMaker has a builtin solid boolean property, I wouldn’t recommend using it. This variable affects the way GameMaker handles the Collision Event , and often, the effect is not a great one. The best way I’ve found to handle collisions is using a parent object. I’ll show you more about this later.
visible
The visible instance property is a simple boolean property that determines whether the instance is drawn on the screen or not.
persistent
The persistent property is another boolean property that determines whether the instance is carried over between rooms. If persistent is set to true, then the instance will not be destroyed when changing rooms; it will also retain the values of all of its other properties.
depth
The depth property is necessary. This property can be any real integer value. It controls which objects are drawn in front of other objects. Lower values are brought to the front and higher values are pushed to the back. If you have a tree object that you want to be in the background (behind the player), you might give it a depth value of 5 and your player a depth value of 10. This would draw the player object in front of the tree object because 10 is lower than 5. alarm
77
The alarm property is an array that is used in conjunction with alarm events. The property alarm[0] would return the value of the Alarm 0 event. object_index The object_index property returns a real value representing the id of that object (not the instance). This property can be used to compare instances to see if they were created from the same object.
x
The x property represents the current x position of the instance. y
The y property represents the current y position of the instance. xstart
The xstart property is the x position of the instance at the moment when it was created.
ystart
The ystart property is the y position of the instance at the moment when it was created.
xprevious
78
The xprevious property is the x position of the instance in the previous step. yprevious
The yprevious property is the y position of the instance in the previous step. direction
The direction property is the direction in which the instance will move if the speed property is set. It uses degrees, and it defaults to 0.
speed
The speed property is the speed at which the instance will move. If the direction property is not set, then the instance will move to the right.
friction
The friction property is a value that gets subtracted from the speed property at every step. If you have a speed of 10 and a friction of 1, then it will take 10 steps for the speed to fall to zero.
hspeed
The hspeed property represents the horizontal speed of the instance. A positive number will move the instance to the right , and a negative number will move the instance to the left . vspeed
79
The vspeed property represents the vertical speed of the instance. A positive number will move the instance dow n , and a negative number will move the instance up . Sprite Instance Properties The sprite instance properties are a set of properties that are used to control the sprite associated with a particular instance. sprite_index The sprite_index property can be used to get or set the sprite associated with an instance. It is generally used to change the sprite of an instance. image_index
The image_index represents the animation subimage index. If you have an animation with five subimages, and you want to set it to the first one, you would set image_index to 0.
image_speed
The image_speed property controls the speed at which the sprite animates, or more specifically, the speed at which it cycles through its subimages.
image_count The image_count property returns the number of subimages in the sprite associated with the instance. Note that this returns the number, not the value. The last subimage will actually be 1 less than image_count because the subimage index starts at 0. 80
Object Inheritance Object inheritance is one of my favorite features of GameMaker. A solid understanding of this section is very helpful if you ever want to build a largescale project like a roleplaying game because it allows you to reuse code in objects that will have similar behaviors. The first thing that you need to know is how to set up object inheritance and what the common terms are. Setting up Inheritance You can make one object inherit events and certain properties from another with GameMaker’s inheritance system. GameMaker uses the terms parent and child to describe the inheritance relationship between objects. The child object will inherit events and certain properties from its parent object. I’ll describe the specifics of how that works a little later, but for now, let’s look at how you can set up this relationship. When you open up one of your objects from the resource tree, you will see a property next to the mask property labelled “parent.” Click on that property and select the object that you want to become this object’s parent. You cannot select the same object, as this would create a loop. Once the parent/child relationship has been set, the child object will inherit events from the parent object. There are some important rules that you should be made aware of regarding how the inheritance actually works. 1. Child objects will inherit events from grandparents, great grandparents, and so on (see second rule for exceptions). 2. Child objects will not inherit events that they possess themselves. 3. It is possible for child events to have their own event and still run the parent’s event using the events_inherited() function. Let’s talk about rule three a little, because it is especially important. There will be cases when you want to create a base parent object and have children execute the base events of the parent, but also contain their own unique additions to the event. Generally, when I start creating a new game, I try to think about the main objects in my game and what actions they will be performing. After I figure these two things out, I start separating those actions into two categories, actions that will be the same between objects and actions that will be different . Let me give you a quick example. 81
You are building a platform game. You have two main types of objects. You have your player object, and you have your enemy objects. You want the player object and the enemy objects to be able to interact with the world in the normal platformer type way (falling, running, jumping, and such). You want your player object to be able to shoot arrows at the enemies, and you want the enemies to shoot fireballs at the player. Step one is to separate the actions that will be the same and the actions that will be different (there are many good ways to set this up, I’m only going to show you one of them). Actions that will be the same 1. Gravity 2. Jumping 3. Collisions Actions that will be different 1. Input (user controls player, AI controls enemy) 2. Attacking (enemy shoots fire, player shoots arrows) Once you have the actions separated, it is time to create your objects. You will create a parent object that controls the actions that are the same and then two child objects that will add to the parent’s actions and control the actions that are different . If you are new to programming, you might be asking yourself why you would do this. The answer is that it will make your life easier later. Let’s say that instead of using a parent object, you just coded the actions that will be the same twice, once for each child object. The code would work exactly the same, right? Also, you wouldn’t have to worry about parent object logic. You would be right to make these assumptions. But what if, instead of having two children, you have 20? Then, you would have to write the same code 20 times. What if you still decided to just push through and write the code 20 times, but then later, you wanted to change it? You would have to change your code in 20 different places. This could cause errors and discrepancies. If you use a parent system, then you need to write the code only once and then, if need be, change it in only one place. Getting used to the inheritance system in GameMaker can be tricky sometimes, but the effort is well worth the benefits. Once you get it down, it will become one of your favorite features of GameMaker Language. 82
Identifying Instances We’ve already talked about the differences between objects and instances. There are times in your programming where you will want to work with a specific instance. One way to do this is to create a reference to the instance at the time it’s created. A common place for this is when an enemy fires a bullet at the player. The instance_create function returns the id of the instance created. Because of this, we are able to create a reference to the instance like this:
// Creates a bullet instance varbullet = instance_create ( x , y , obj_bullet ); After we have created the instance and we have a reference to it, we can set its properties and have it call functions. // Set the direction of the bullet instance bullet . direction = dir; // Call a script inside the bullet instance. with ( bullet ){ scr_move (); } This is all made possible because we have a variable as a reference to the instance. If the instance has already been created, it is still possible to get a reference to it using some different functions. Let’s look at a few of these different methods. The Nearest Object Instance There is a useful little function in GameMaker that can be used to get the id of the nearest instance of an object. This function is called instance_nearest . Here is an example of how it works: 83
obj_player: Draw Event // Get a reference to the id of the nearest enemy object varnear_enemy = instance_nearest ( x , y , obj_enemy ); draw_sprite ( spr_target , 0 , near_enemy . x , near_enemy . y ); The instance_nearest function returns the id of the instance that is nearest to the x and y position passed into the function. You can use this to store the id in a variable and then access it later. As you can see in the code above, we use the variable that contains the id of the nearest enemy object to draw a target sprite on it. This is one of the many uses that this function has. The Furthest Object Instance There is also a function in GameMaker that is quite the opposite of instance_nearest . it is called instance_furthest . It takes the same arguments, but it will return the id of the instance that is farthest from the point given, instead of the nearest. obj_player: Draw Event // Get a reference to the id of the farthest enemy object varfar_enemy = instance_furthest ( x , y , obj_enemy ); draw_sprite ( spr_target , 0 , far_enemy . x , far_enemy . y ); This line of code will draw spr_target at the position of the enemy object that is the farthest from the player’s x and y position. Instance at a Position This is one of the most useful functions for getting a reference to an instance. You only need to know where that instance is, and you can use that information to get the id of the instance. Here is an example: obj_player: Step Event // Check to see if the left mouse button is being pressed if ( mouse_button_check ( mb_left )){ // Find the enemy object at the mouse's position 84
vari = instance_position ( mouse_x , mouse_y , obj_enemy ); // Destroy the found instance with ( i ){ instance_destroy (); } } This bit of code checks to see if the user is pressing the mouse button. If they are, then it checks to see if there is an instance of the enemy object at that location. If there is, then it stores the id of that instance in the local variable “ i ”. After that, it destroys the instance found. Instance at a Place There is one last function that I think is worth mentioning here. I use this one less often, but it is also useful. It is called the instance_place function. This function works almost exactly like the instance_position function. It takes the same arguments and will also return the id of the instance found. The one major difference is that this function uses the mask of the object calling and checks for a collision at the position passed , whereas instance_position only checks the onepixel point that is passed. This can cause drastic differences in results. Make sure that you understand this distinction when choosing between these two functions. I generally use instance_position more often. obj_player: Step Event // Check to see if the left mouse button is being pressed if ( mouse_button_check ( mb_left )){ // Find the enemy object that would collide with the // player object if it were at the mouse's position vari = instance_place ( mouse_x , mouse_y , obj_enemy ); // Destroy the found instance with ( i ){ instance_destroy (); }
85
} Here is the same code as above, but using instance_place instead. I’ve added extra comments to help you to understand the difference between the two. No Instance Found Now is a good time to mention what these functions will return if they can’t find an instance of the object you are looking for. There is a special constant in GameMaker called noone . This constant represents no object. It has an actual value of 4. The code below shows how you might use this constant. I like to use it because it makes the code easier to read and understand. vari = instance_position ( mouse_x , mouse_y , obj_enemy ); // Make sure we actually found an instance before we // attempt to destroy it. if ( i != noone ){ with ( i ){ instance_destroy (); } } Once you get the hang of using all of these great properties and functions with objects and instances, you will see how sweet they are. There is a lot to learn here, but it is fun when you start to use these to add features to your game. In the Events chapter, I will talk about one more way that you can get access to a specific instance in the room, but you have learned quite a bit already.
86
Chapter 9
Events Events In GameMaker Studio, each object has a list of events that can be fired (triggered) by instances of that object during the game. Create Event The Create Event is only run when an instance is first created and then is never used again during the life of that instance. It is an important event for setting up default properties and variables. Step Events The Step Event is run once at every frame of the game. If your game is set to run at 30 fps (frames per second), then your Step Event will be executed thirty times per second. This event is mainly used for controlling the states of your objects. Begin Step Event The Begin Step Event is almost exactly like the Step Event ; the only difference is that the Begin Step Event runs first. If you need to run a specific bit of code every game frame but want to run it before your Step Event , then this is the event for you. End Step Event The End Step Event is also almost exactly like the Step Event ; the difference here is that the End Step Event runs after the Step Event . This can be especially useful when having an object follow the mouse or another object. You will notice that if you make an object follow the mouse like this in the Step Event , there seems to be some lag. Step Event x = mouse_x; y = mouse_y; If you place the above code in the End Step Event , there will be no lag. This is due to the timing of the End Step Event coming after the Draw Event . 87
Alarm Events The Alarm Event works just like any alarm: you set a length of time for the alarm to wait and at the end of that time, something happens. Alarms are almost necessary in every game. Understanding of how alarms work should be obtained as quickly as possible. I am going to give you several examples of how alarms work and how you can use them in a game. Let’s start with the most basic example. Create Event /// Set an alarm alarm [ 0 ]= 120; Here, we have a Create Event , and we are setting the alarm to a value of 120. Alarms automatically subtract from their value at every step. If your game is running at 30 fps (frames per second), then the Alarm Event will fire after 4 seconds (120/30=4). You will notice that alarms are part of an alarm array. This gives you the option of using multiple alarms. alarm[0] is the first alarm. Currently, nothing will happen when this alarm reaches the end of its 120 steps; this is where the Alarm Event comes in. Inside the Alarm Event , we can define the actions that we want to happen when the alarm reaches zero. Alarm 0 Event /// Execute some action at the end of the alarm show_message (" Wakeup ,sleepy head ."); In the Alarm Event , we can drag over a Code Action and execute any kind of code that we want. As a simple joke, this code only shows a popup message telling the user to wake up. This alarm will only go off once. If you want the alarm to cycle and show this popup message every 120 steps, then we need to set the alarm again inside the Alarm Event like this. Alarm 0 Event /// Execute some action at the end of the alarm show_message (" Wakeup ,sleepy head ."); 88
alarm [ 0 ]= 120; As you can see, the only difference between this Alarm Event and the other one is that this one will reset its own timer. This will cause the alarm to run multiple times at a 120step interval. Collision Event There are several different ways to simulate a collision in games. The simplest way is to check the distance from the object that you are trying to collide with. One of the nice things about GameMaker is that it has a builtin event for collisions that handles all of the tricky collision code behind the scenes. Before I give you an example, you should know that if you check the “Precise collision checking” checkbox in your sprite, the collision checking for any object using that sprite will be quite a bit slower than if you were to leave that checkbox unchecked. The reason for this slowing is that “Precise collision checking” has to spend extra time to check each pixel of the sprite and see if it is colliding with the other object. You may think that this is what you want for your game, but 99% of the time, you will actually want to leave “Precise collision checking” turned off, as it can cause glitches in your game when your image cycles through its different subimages. For the majority of the games you build, using a square collision box is actually going to work better. One other thing that you should know is that the Collision Event has a builtin reference to the object being collided with. This reference has an identifier of other . Let's look at a quick example that illustrates what I mean. Collision Event /// Destroy both objects in the collision instance_destroy (); With ( other ){ instance_destroy (); } This code example will destroy both objects in the collision. First, we destroy the object calling the Collision Event , and then, we destroy the other object in the collision using the other reference. 89
Well, now you know basically all that you need to know in order to use collision events in your game. Keyboard Events A Keyboard Event is fired when a specific key is pressed and will continue to fire each step that the user holds that key down. There are quite a few different keys that you can choose from. Here is an example of how you might use keyboard events to create movement in a game. Keyboard Up Event /// Move the object up y -= 4; Keyboard Right Event /// Move the object to the right x += 4; Keyboard Down Event /// Move the object down y += 4; Keyboard Left Event /// Move the object to the left x -= 4; You could also write similar code in the Step Event using keyboard_check . Which method you choose to use is more a matter of style than anything else. Mostly, I use the Step Event with the keyboard_check function. 90
Key Press Event The Key Press Event works similarly to the Key Event , except that it only fires in the single step in which the keyboard key is pressed down. Key Release Event The Key Release Event also works similarly to the Key Event , but it will only fire in the single step in which the keyboard key is released. Mouse Events A Mouse Event is fired when the cursor is hovering over the collision box for the object calling the event and a specific mouse button is pressed, or, in other words, when the user clicks on the object containing the Mouse Event . The Mouse Event will continue to fire every step that the user holds down the mouse button that was pressed. Create Event /// Set the size of the object size = 1; Mouse Left Event /// Increase the size of the object image_xscale = size; image_yscale = size; size += 0.25; In this example, we have both a Create Event and a Mouse Left Event . First, we set the size of the object to 1 in the Create Event . Then, we use the Mouse Left Event to increase the size of the object. The size will only increase if the cursor is hovering over the object’s collision box and the left mouse button is being pressed. Mouse Pressed Events The Mouse Pressed Event is similar to the Mouse Event , except that it will only fire during the step in which the mouse button is pressed down. Here is a simple example:
91
Mouse Left Pressed Event /// Destroy the instance instance_destroy (); This bit of code destroys the instance that is clicked on. Mouse Released Events The Mouse Released Event is also similar to the Mouse Event , except that it will only fire during the step in which the mouse button is released. Here is an example similar to the one in the last section. You will notice that in this example, you can click on the instance, but it won’t be destroyed until you release the mouse button. Mouse Left Released Event /// Destroy the instance instance_destroy (); Once again, in this bit of code, we destroy the instance clicked on, but only when the mouse button is released. Global Mouse Events A Global Mouse Event is fired when a mouse button is pressed, regardless of where the cursor is located in the game. The event also continues to fire in each step as long as the user holds down the mouse button. Here is a slightly larger example that shows how to use the Alarm Event with the Global Mouse Left Pressed Event for a neat bullet firing system in a game. Alarms and Global Left Pressed for Firing Bullets Here is a combination of the Global Left Pressed Event and an Alarm Event to create a bullet firing system. A common use for alarms is to help with the timing of firing bullets in a game. In most games, we want the player to be able to hold the fire button down with fully automatic weapons, but we do not want the game to fire a bullet at every step, as this would be way too many bullets. We can limit the fire rate of our guns using alarms.
92
Create Event /// Initialize the alarm alarm [ 0 ]=- 1; fire_delay = 8; Alarm 0 Event /// We only have this here so that the compiler doesn't remove the // alarm event Global Left Pressed Event /// Fire the gun if ( alarm [ 0 ]==- 1 ){ instance_create ( x , y , obj_bullet ); alarm [ 0 ]= fire_delay; } I’m throwing all the code down at once, and I’ll explain it all here because it isn’t very much code to explain. First, we add a Create Event where we set the alarm to 1 (its ending value), create the fire_delay variable and set it to 8. Next, we create the Alarm 0 Event and add a comment in it to make sure that the compiler doesn’t remove the event. Finally, we add a Global Left Pressed Event where we check the value of the alarm, and then, create the bullet object if the value is equal to 1. After that, we make sure to reset the alarm to the fire_delay value so that the bullet will wait to be created until the alarm reaches a value of 1 again. Global Mouse Pressed Events The Global Mouse Pressed Event works similarly to the Global Mouse Event , except that it only fires in the single step in which the mouse button is pressed down. Global Mouse Released Events The Global Mouse Released Event also works similarly to the Global Mouse Event , except that it only fires in the single step in which the mouse button is released. 93
Other Events There are several different events that have been placed in the Other Event submenu. I will cover them here. Most will be easy for you to understand at a glance, and so, will have rather short explanations. Outside Room Event The Outside Room Event fires once an object leaves the boundaries of the room. It continues to fire every step as long as the object stays outside the room. Intersect Boundary Event The Intersect Boundary Event fires when an object intersects the boundary of the room. This means that if the object leaves the boundaries of the room, the event will fire and if the object returns to the boundaries of the room, the event will fire. In other words, this event fires once when it leaves and once when it returns. Outside View Event The Outside View Event fires when an object leaves the boundaries of the view and continues to fire each step in which the object remains outside the boundaries of the view. Boundary View Event The Boundary View Event fires when an object leaves the boundaries of a view and when the object returns to within the boundaries of a view. It works like the Intersect Boundary Event but with views instead of the room. Game Start Event The Game Start Event only fires when the game is launched and at no other time during the game. Game End Event The Game End Event only fires when the game ends and at no other time during the game. Room Start Event The Room Start Event will fire at the start of each room. Room End Event The Room End Event will fire at the end of each room. 94
No More Lives Event The No More Lives Event will fire if you are using GameMaker’s builtin, global lives variable and the variable reaches 0. No More Health Event The No More Health Event will fire if you are using GameMaker’s builtin, global health variable and the variable reaches 0. Animation End Event The Animation End Event fires when an object’s sprite animation loop reaches the last subimage. End of Path Event The End of Path Event fires if an object is moving along a path and it reaches the end of that path. UserDefined Event Userdefined events fire when the user fires them. They can be fired anywhere in your code using the event_user function. Here is an example of how you can do this. User Defined Event 0 /// Show a message show_message(“This is my own event”); Create Event /// Execute the user-defined event varevent_number = 0; event_user ( event_number ); We start by creating the User Defined Event . For the sake of this example, I’m just showing a message. After, we call the event using event_user . We chose to use event number 0, and so, we pass that into the event_user function so that GameMaker knows which user event to fire. There are 16 user events that can be used. 95
Draw Event The Draw Event fires each step of the game, just like the Step Event . It is a good idea to put as little code in the Draw Event as possible. You should only put code in the Draw Event that actually draws something. Another thing to note is that the Draw Event will fire for every instance in your room, unless that instance has the “Visible” object property unchecked. If you add your own Draw Event to an object, be aware that you will override the default Draw Event ; if you don’t place code in your Draw Event that actually draws the object’s sprite, then your instance will not show up. The easiest way to tell GameMaker to draw an object is to use the draw_self function like this: Draw Event /// We added our own draw event that will override the default // draw event so we have to make sure to draw the object // ourselves. draw_self (); The order in which you draw the different elements in your Draw Event will affect how they layer in your game. Items that you draw first will show up behind any items drawn afterwards. Draw Event /// Draw a textbox draw_rectangle ( x - 48 , y - 24 , x + 48 , y + 24 , false ); // Draw some text. This will be drawn on top of the rectangle varcol = c_white; draw_set_halign ( fa_center ); draw_set_valign ( fa_middle ); draw_text_colour ( x , y ," Hello GameMaker ", col , col , col , col , 1 ); We start by drawing a rectangle. Because this rectangle is drawn first, it will be below any items that we draw afterwards. The draw_rectangle function takes two x and y coordinate pairs. The first pair is the upperleft corner and the second pair is the lowerright corner. The last argument asks if the rectangle should be just an outline or if it should be filled. This rectangle will be drawn in black because that is the default color. 96
After drawing the rectangle, we create a temporary variable called col that holds the color for the text that we will draw. We set the horizontal alignment to a center alignment using draw_set_halign . Then, we set the vertical alignment to a middle alignment using draw_set_valign . Once our setup is done, we draw the text using the draw_text_color function. The first two arguments are the position of the text, the third is the text to draw, the next four are the colors to use (one for each corner of the text, as this function can be used to create a gradient in the text), and the final is the alpha value of the text. Draw GUI Event The Draw GUI Event exists on a separate plane from the Draw Event , but works similarly to it. Everything drawn in the Draw GUI Event will be drawn on top of the items drawn in the Draw Event . The Draw GUI Event may also have a different resolution than the Draw Event depending on how you setup your views. I won’t give any examples here on the Draw Gui Event because Chapter 11 has a more detailed explanation of the Draw GUI Event and some examples.
97
Chapter 10
Game Audio Sounds are easiest to explain by going through a simple example. In this example, we will create a button that the user can click on. When the button is pressed, it will play a sound. This example will also be divided up into two parts. In the first part of the example, the sound won’t have any effects; however, in the second part, we will use a sound emitter to alter the pitch (how high or low the sound is on the musical scale) and the gain (the loudness of the sound). The first thing that we need to do for this example is to add a new sprite. I’ve named my sprite spr_button . It is 32 x 32 pixels in size, its origin is centered, and it is a simple black box. ● spr_button Here is an image of my sprite:
Once the sprite has been created, we need to create its accompanying object. I’ve named my object obj_button . For now, we won’t add any events to this object. Let’s first create the sound we will be using. 98
● obj_button Add a new sound to the game. If you like, you can use bfxr.net to create a sound. I’ve named my sound snd_click . ● snd_click I also made the sound a “Stereo” sound using the dropdown option in the “Target Options” section of the sound properties. This gives the sound two audio channels. You could use this to play a sound only in the right speaker if the sound is coming from the right in the game or to play it only in the left speaker if the sound is coming from the left. Here is a screenshot of my sound’s properties:
The sound is added and ready to go! Open up the button object and add a new Mouse > Left Pressed Event to it. Inside this event, we will be executing the following code. obj_button: Left Pressed Event /// Play the sound audio_play_sound ( snd_click , 10 , false );
99
This short bit of code will use the audio_play_sound function to play our click sound when the user clicks on the button. This function takes three arguments. This first argument is the id of the sound to play. The second argument is the priority of the sound. The priority can be from 0 to 100; it helps GameMaker decided which sounds to play if there are too many playing at the same time. Higher numbers have a higher priority. The final argument tells GameMaker whether the sound should loop or not. We don’t want our sound to loop, so we pass a value of false . Create a simple room, add the button object to the room, run the game, and test the project. The sound should play whenever the button is clicked. So far, so good. Now that you know how to create a sound and play it when an event occurs, we have a problem: If you press the button multiple times in a row, the sound can get annoying. This is normal. No matter how good your sound is, if you hear it too many times in a row, it will become annoying. I’m going to teach you a trick that you can use to help mitigate this problem. Open up the button object again, and this time, add a new Create Event to it. obj_button: Create Event /// Create a sound emitter audio_em = audio_emitter_create (); In the code above, we are creating a new audio emitter and assigning its id to the audio_em variable. You probably remember from the introduction to this chapter that audio emitters can be used to alter the properties of a sound that is being played. The two most common properties that are altered are the pitch and the gain. We will be altering both of these properties of our sound; this will create a nice variance and prevent the sound from becoming annoying. First though, we need to make sure to destroy the emitter when the game ends. Add a new Game End Event to the button object.
100
obj_button: Game End Event /// Destroy the sound emitter audio_emitter_free ( audio_em ); In this code, we pass the audio_em variable into the audio_emitter_free function in order to destroy our audio emitter. Now, we need to jump back to our Mouse > Left Pressed Event and change the way that we play our sound. Make sure that you completely remove the code that was previously in the event before adding this new code. obj_button: Left Pressed Event /// Play the sound audio_emitter_pitch ( audio_em , random_range (. 5 , 1.5 )); audio_emitter_gain ( audio_em , random_range (. 1 , 1 )); audio_play_sound_on ( audio_em , snd_click , false , 10 ); We start by calling the audio_emitter_pitch function. We tell it what emitter we want to use and the pitch that we want our emitter to be set to. We pass in a random range from .5 to 1.5 . A normal pitch is set at 1 . Next, we use the audio_emitter_gain function. We also pass in the emitter and use a random range of .1 to 1 . Again, the normal gain for a sound is 1 . Finally, we play the sound using the audio_play_sound_on function. The first argument for this function is the emitter to be used, the second is the id of the sound, the third is whether or not the sound should loop, and the fourth is the priority of the sound. It is unfortunate that the last two arguments are swapped in this function from how they are ordered in the audio_play_sound function, but if you know that, it makes this function easier to manage. Run the game again and listen to the difference. I tried to use fairly drastic numbers in this example so that you will be able to hear the difference. If you still don’t notice a difference, you might try changing the random ranges until you do.
101
Great job! Now, you know how to use sounds in GameMaker, and you even know how to alter their pitch and gain during the game to create a better audio feedback experience.
102
Chapter 11
Development Patterns and Tricks States Setting up a state system in an object is smart if you want it to have different behaviors. Let’s say you want your player object to be able to run, swing a sword, roll out of the way of enemy attacks, and then continue running. These three behaviors could have their own states within the object. There are different ways of implementing this kind of system, but I can show you some of the ways that have been helpful to me in the past. When I was first learning to set up state systems, I tried creating a different object for each state and then I would use instance_change to change states. This worked, but not very well. instance_change is good for some things, but I wouldn’t recommend it for a state system. After trying that, I realized that I could just use a state variable combined with a switch statement in the Step Event . Let me show you. Create Event /// Create the state variable state =' idle '; Step Event /// Control the state switch ( state ){ case ' idle ' : scr_idle (); break; case ' move ': scr_move (); break;
103
case ' attack ': scr_attack (); break; case ' roll ': scr_roll (); break; } In order to help me to organize my code better, I chose to create a script for each state and then just run the scripts for each case. I’ve learned several different ways of doing this, and I discovered one that I like quite a bit. Of course, it isn’t perfect either and could be improved, but it’s pretty simple and easy to understand. This method uses a combination of macros and script_execute . Let me show you a simple example of how it works. One of the cool features of GameMaker is that you can assign an expression to a macro. You can also assign a script to a macro. For the sake of this example, I’ll use some fake code with the assignment operator but in reality, you will simply use the GameMaker user interface to create the macro. PLAYER_WALK = scr_player_walk Once you have this as your macro, you can set your state in the Create Event like this: state = PLAYER_WALK; You can use the same method to change the state of your object inside other events. After doing these first two steps, you still need to actually run the code associated with that state. To do so, you would place this in the Step Event of your object.
104
execute_script ( state ); It’s that simple! In the first step, you assign the script’s id to the macro/constant. In the second step, you assign that macro/constant (which now contains the script’s id) to the state variable. In the last step, you execute whatever script is assigned to the state. It’s a simple method, but it can work really well for separating the code of each state. Because enums are global in scope, you can use enums instead of macros. Either way works fine. Sometimes I find that macros are better, because you get syntax highlighting on macros, but you don’t on enums. Here is an example of how you might set up your states with an enum, instead of using macros:
enumstate { walk = scr_player_walk; } You can set the state in almost the same way.
state = state . walk; Executing the state will be done in exactly the same way. In the chapter on artificial intelligence, I go through a basic example of how you can use enums and states to create artificial intelligence for your enemies; if this section is a little confusing to you, be sure to check there for an ingame example. Creating PseudoObjects using Arrays and Enums While developing this book, I’ve been working on a method for creating arrays that are similar to objects. There are many situations in which you need to store some information in an organized way and access it later, but where creating an actual object in your game would be overkill, because the builtin objects in GameMaker come with a lot of extra baggage. Of course, it is possible to use ds_maps for this, but maps can become bloated and difficult to manage if you try to go three levels down (for example, if 105
you try to access a map data structure that is nested inside another map data structure). I can simulate the threelevel depth using a 2d array and a few enums. Let me show you what I mean: enum base{ name = 0, hp = 1, att = 2, def = 3, spd = 4 } enum class{ wizard = 0, knight = 1 , } You can use enums to make the code more readable and avoid “magic” numbers. A magic number is a number in your code that has little obvious meaning to the person reading/writing the code. Once you have set up the enums, you can start creating the 2d arrays. stats [ class . wizard , base . name ]=" Merlin "; stats [ class . wizard , base . hp ]= 25; stats [ class . wizard , base . att ]= 3; stats [ class . wizard , base . def ]= 1; stats [ class . wizard , base . spd ]= 3; stats [ class . knight , base . name ]=" Arthur "; stats [ class . knight , base . hp ]= 35; stats [ class . knight , base . att ]= 5; stats [ class . knight , base . def ]= 2; stats [ class . knight , base . spd ]= 1;
106
I would recommend creating a script (where the values being passed are the arguments) to set these up. The nice thing about this method is that later in your game, you can access this data in a way that is very readable. Let me show you. vardamage = stats [ class . wizard , base . att ]; enemy . hp -= damage; This example assumes that you have a reference to the “enemy” that you are attacking, but you get the idea. If you didn’t use enums, it would look something like this: stats [ 0 , 1 ]=" Merlin "; stats [ 0 , 2 ]= 25; stats [ 0 , 3 ]= 3; stats [ 0 , 4 ]= 1; stats [ 0 , 5 ]= 3; Do you see what I mean about magic numbers? Nobody would be able to understand this code. It would still be easy to access, but you would have to memorize the number, stat, and name associations. Make an Object Follow the Mouse It’s a pretty simple task to get an object to follow the mouse, but some readers might not know how to do this, so I’m going to put a quick example in. The trick with this functionality is making sure that you use the End Step Event . End Step Event x = mouse_x; y = mouse_y; This code sets the x position of the object to the current x position of the mouse at the end of every game step. If you use the normal Step Event , the timing of the move will be off, and the object will appear to lag behind the mouse. 107
Make a Gun Aim Towards the Mouse. Coding this function is also a simple task. The trick here is knowing how to use the builtin image_angle property and the point_direction function. vardir = point_direction ( x , y , mouse_x ,mouse_y ); image_angle = dir; direction = dir; You should also make sure that your sprite has its origin where you want the pivot point to be, and that the sprite starts out facing exactly to the right (or 0 degrees). Using Median or Clamp One neat trick that you can do is use the median function or the clamp function. Both of these functions can be used to limit a variable or property to a specific range. One common use for these functions is limiting the player’s movement to the room. obj_player: Step Event /// Limit the player's movement x = clamp ( x , 0 , room_width ); y = clamp ( y , 0 , room_height ); This is a simple piece of code that clamps the player’s x and y coordinates to the room boundaries. Alternatively, you could do this using median.
x = median ( 0 , x , room_width ); y = median ( 0 , y , room_height ); You can use either method, but I feel that clamp is a more specific word and that clamp makes the code more readable. Colors GameMaker has some builtin colors that you can use when drawing sprites or shapes. These colors are convenient, but I wouldn’t recommend using them in most cases because they are very bland. Some of them work okay for particles, but even in this 108
case, I generally avoid them. In the next segment, I’ll show you how you can create your own custom colors, but first, let me list the builtin colors. c_white c_ltgray c_gray c_dkgray c_black c_fuchsia c_purple c_yellow c_orange c_red c_maroon c_lime c_green c_teal c_agua c_blue c_navy c_silver c_olive Custom Colors Making your own draw colors is important for helping your game’s graphics match; you may want buttons or text to share a common color with a character or background. There are a few simple functions that you can use to more subtly adjust colors. my_green = make_colour_rgb ( 105 , 205 , 85 ); 109
The make_colour_rgb function used in the above code takes three arguments: a red value, a green value, and a blue value (or RGB). In most graphics programs, these RGB values are displayed when you create a custom color. my_red = make_colour_hsv ( 0 , 58 , 80 ); The make_colour_hsv function also takes three arguments, a hue, a saturation, and a value. These values will also typically show up while editing colors in any graphics program. I generally use paint.net (go to getpaint.net to download a copy). Paint.net is free and quite powerful for the price. my_orange = merge_colour ( c_red , c_yellow ,. 5 ); The merge_colour function takes two colors and blends them together to make a new color. The first two arguments are the colors to be used. The last argument is a number between 0 and 1 that represents the blend value for the color. A blend value of 0 will return the first color entered ( c_red ). A blend value of 1 will return the second color ( c_yellow ). A blend value of .5 will use 50% of each color. User Input In GameMaker Studio, there are many different ways that you can get input from the user. In this chapter, I will be covering the three most common methods: mouse input, keyboard input, and gamepad (controller) input. Mouse Input Keyboard input and mouse input are arguably the most common forms of input on computers. Mouse input in GameMaker Studio is simple and easy to learn. You can use the builtin object events, but I’m also going to show you how to check those events in code. Let’s begin by showing how to check, in code, if a mouse button is being pressed. if ( mouse_check_button ( mb_left )){ 110
// The left mouse button is being pressed } mb_left is a constant in GameMaker Language that refers to the left mouse button. There are constants for each of the other mouse buttons. mb_none mb_left mb_right mb_middle mb_any
// No mouse button // Left mouse button // Right mouse button // Middle mouse button // Any mouse button
mouse_check_button() is a function that returns true if the button is being pressed and returns false if it is not. At this point, I should clarify the difference between the case in which the mouse button is being pressed and the case in which the mouse button is (now) pressed. The first case implies that if the user is holding the button down, it would still return true. The second implies it the function will only return true in the step in which the mouse button is (initially) pressed down. After that, it will return false, even if the user continues to hold the button down. Let’s look at how you can check to see if the mouse is pressed.
if ( mouse_check_button_pressed ( mb_left )){ // The left mouse button was pressed. } Mouse Position In GameMaker Language, there are two variables that you can use to access the mouse position. mouse_x mouse_y 111
These two variables are global, and as such, can be accessed inside any object. If you wanted to make an object follow the mouse position, you could use this simple code. End Step Event x = mouse_x; y = mouse_y; As discussed above, because of event timing issues, you will most often want to make an object follow the mouse (or another object) in the End Step Event . Keyboard Input Keyboard input is pretty easy to learn how to use if you already know how to use mouse input, as the two are very similar. if ( keyboard_check ( vk_right )){ // The right arrow key is being pressed } vk_right is a constant that is similar to the mb_left constant that the mouse input uses. There are quite a few different vk_ prefixed constants that you can use. vk_right // Right arrow key vk_left // Left arrow key vk_up // Up arrow key vk_down // Down arrow key vk_nokey // No key vk_anykey // Any key vk_enter // Enter key vk_escape // Escape key vk_space // Space key vk_shift // Shift key vk_control// Control key vk_alt // Alt key 112
There are many more key constants than this. Most of them are rather obvious. If you want the full list, you can search for them in the GameMaker help file. The important thing to note is that you cannot use letter keys like this: if ( keyboard_check ( vk_a )) { // Causes an error } If you want to check whether the player has pressed any of the letter keys, you need to use a special function. if ( keyboard_check ( ord (' A ')){ // The "A" key is being pressed } Maybe you want to check for any key and then display the key pressed to the screen. You can do that like this: if ( keyboard_check ( vk_any )){ show_message ( keyboard_lastchar ); } keyboard_lastchar will return a string of the last keyboard key pressed. This will only work with letters and numbers though; it returns an empty string for any other keys. If you want to check for a key that isn’t a number or a letter you can use keyboard_key . This global variable returns the ASCII value of the key pressed. if ( keyboard_check ( vk_any )){ show_message ( string ( keyboard_key )); } 113
There will be times when you want to have two keys do the same exact thing. For example, many games allow the player to use either the arrow keys or the WASD letter keys to move the character. You can map keys to each other like this. // Map the ASDW keys to the arrow keys keyboard_set_map ( ord ( 'D' ),vk_right ); keyboard_set_map ( ord ( 'A' ),vk_left ); keyboard_set_map ( ord ( 'W' ),vk_up ); keyboard_set_map ( ord ( 'S' ),vk_down ); You only need to call these functions once, for example, in a character object’s Create Event . You would continue to program your game and character using the arrow keys, but the WASD keys would work as well, because they are mapped to the arrow keys. GamePad Input Using gamepad input is a little harder than keyboard input, but it is very similar. Most of the time, you will want your game to be compatible with several input devices. You may have it set up so that the player can use the keyboard (by default), but if they plug in a gamepad, then the keyboard will be disabled and the gamepad will be used for control. Let me show you a simple example that allows you to do this. vardevice = 0; if ( gamepad_is_connected ( device )){ // Get gamepad input } else{ // Get keyboard input } th This code checks to see whether the first (0 ) gamepad is connected. The device number is the assigned player number. If your controller’s light indicates that you are player number one, then the device number should be 0. If it shows that you are player number two, then the device number should be 1. The device number is important, because it is used in most functions for getting gamepad input; let’s look at some of them. Assume that the device number is still 0. 114
if ( gamepad_button_check ( device ,gp_face1 )) { // Do something cool } This function checks to see whether the user is pressing a button on the gamepad. The button being checked is gp_face1 ; on an Xbox controller, this is the “A” button. There is an entire list of button constants that can be used with these functions. You can check the GameMaker help file for the list of controller constants. Methods of Controlling Input The method that I generally use for controlling input is to separate the input from the actual movement with variables. This allows me to move a character or enemy with the variables created. I’m going to set up a simple example of this. First let’s create the variables. Create Event xaxis = 0; yaxis = 0; abtn = false; Now let’s set them in our player, based on the input. Step Event if ( gamepad_is_connected ( 0 )) { // Get gamepad input xaxis = gamepad_axis_value ( 0 , gp_axislh ); yaxis = gamepad_axis_value ( 0 , gp_axislv ); abtn = gamepad_button_check ( 0 , gp_face1 ); } else{ // Get keyboard input varright = keyboard_check ( vk_right ); varleft = keyboard_check ( vk_left ); 115
varup = keyboard_check ( vk_up ); vardown = keyboard_check ( vk_down );
}
xaxis = right - left; yaxis = down - up; abutton = keyboard_check ( ord (' A '));
Once you have created the variables and set them according to the input the player has given us, you can decide how to move the player. This code assumes that you are using obj_solid as your walls. Step Event // Set the speed varspd = 4; // Horizontal movement if (! place_meeting ( x + xaxis * spd , y , obj_solid )){ x += xaxis * spd ; } // Vertical movement if (! place_meeting ( x , y + yaxis * spd , obj_solid )){ y += yaxis * spd ; } if ( abtn ){ // Shoot a bullet instance_create ( x , y , obj_bullet ); }
As you can see, it is easy to separate the input from the control of the object, which allows us to pass any input type into this object and still have the same player behavior.
116
Chapter 12
Drawing on the GUI Layer Draw GUI Event GUI stands for graphical user interface. The Draw GUI Event is different from the Draw Event because the Draw GUI Event draws everything relative to the GUI layer and not relative to the actual game room. Often, the GUI layer seems to be relative to the view, and most of the time it is, but be aware that this isn’t always true. Often, when people start using the Draw GUI Event in conjunction with views, they run into scaling issues. There is a handly little function that you can use to resolve this. display_set_gui_size ( 640 , 360 ); display_set_gui_size takes a width and a height. You want to make sure that the width and the height of the GUI are the same as the width and height of your view so that the GUI surface will have the same resolution as the view. By default, the GUI layer seems to be the same resolution as the port on the screen. Here is a good way to set the GUI’s display size to the size of the view, no matter what the actual size of the view is. Place this code in the Create Event of your object: /// Set the GUI to the size of the view varview_width = view_wview [ view_current ]; varview_height = view_hview [ view_current ]; display_set_gui_size ( view_width , view_height ); Once you have the correct height and width, the Draw GUI Event is used just like the Draw Event . The output is different, but you can still use all the normal draw functions in this event. Let’s quickly draw some text using the Draw GUI Event . draw_text ( 32 , 32 ," Hello Draw GUI !"); 117
This simple code will draw the text “Hello Draw GUI” at position (32,32) of the view. It won’t matter if the player moves inside the room, the text will still be drawn relative to the view. As you can see, the draw_text function is used exactly like it would be in a normal Draw Event , but it works differently. You will also notice that the text is on top of the other objects in the room, regardless of the depth of the object that actually draws it. This is because the GUI layer is, by default, drawn on top of the rest of the room. As you are drawing on the GUI layer, you may need to get information about the GUI layer. Here are some functions that you can use to get this information:
vargui_width = display_get_gui_width (); vargui_height = display_get_gui_height (); draw_text ( gui_width - 32 , gui_height - 32 ," Hello Draw GUI !"); This code does the same thing as the code above it, but it draws the text at the lower righthand corner of the display. We get references to the width and height of the GUI layer in order to draw our text relative to those dimensions. Creating Buttons on the GUI Layer One of the most difficult things to deal with on the GUI layer is drawing buttons. This is because the GUI layer is independent of the application surface. The calculations for determining if the mouse is hovering over a button can be tricky. Let me show you how I have solved this problem with an example. Create a new sprite and name it spr_player . ● spr_player I’ve made my sprite a black 32 x 32 square. Now create two new objects. Name one of them obj_player and the other one obj_gui_button . ● obj_player ● obj_gui_button Create a new room and name it rm_test . Give the room a width of 640 and a height of 360. Click on the views tab, click “Enable the use of Views”, click “Visible when room 118
starts”, set the view width and height to 320 x 180, and set the port width and height to 640 x 360. Set the “Object following” to obj_player , the horizontal border (Hbor) to 160 and the vertical border (Vbor) to 90. This will force the view to follow our player object. Now, place both the player object and the GUI button in the room. Make sure that the GUI button falls inside the view. We are going to use its starting position to determine its position on the GUI Layer. Here is a screenshot of the room and its view properties.
You can see that I have also placed a basic green background in my room. It isn’t necessary, but you can add a background as well, if you want. Open up the player object and add a Step Event to it. This event will hold the code that moves the player in the room. We need the player object to move so that we can make sure that the GUI button object follows the view correctly. Here is the code that will go in the Step Event . obj_player: Step Event // Move the player varspd = 4; varup = keyboard_check ( vk_up ); 119
vardown = keyboard_check ( vk_down ); varleft = keyboard_check ( vk_left ); varright = keyboard_check ( vk_right ); x +=( right - left )* spd; y +=( down - up )* spd; This is some shortcut code that moves the player using the arrow keys. Now, open up the GUI button object and add a new Create Event . Drag over a Code Action and place this code block in it. obj_gui_button: Create Event /// Initialize the GUI button text =' Click Me ' ; width = 96; height = 28; hover = false ; scale = 2; // This is the button object's position relative to the GUI layer. display_x = xstart * scale ; display_y = ystart * scale ; We start by creating some instance variables that will define how our button looks and behaves. The text variable holds a string value of the text that will be shown on the button. After that, we have the width and height . Then we have a variable called hover . Our hover variable will be true if the mouse is over the button and false if it is not. We also create a variable called scale and set it to 2. Our scale is set to 2 because the GUI layer defaults to the size of the port on the screen and the port on the screen is twice the size of the view. After creating the initial instance variables, we create two new instance variables that will be used to position the button on the GUI layer. These two variables will grab the button object’s starting position in the room and multiply it by our scale . 120
Add a new End Step Event . In this event, we will do the math necessary to calculate whether the mouse is hovering over our button. obj_gui_button: End Step Event /// Check to see if the mouse is above the button // Find the edges varleftx = display_x - width / scale ; varrightx = display_x + width / scale ; vartopy = display_y - height / scale ; varbottomy = display_y + height / scale ; // Get the window position of the mouse varwmx = window_mouse_get_x (); varwmy = window_mouse_get_y (); // Check to see if the mouse is inside the edges hover = point_in_rectangle ( wmx , wmy , leftx , topy , rightx , bottomy ); First, we get the edges of the button. Next, we get the mouse’s window position. If the view has moved, the window position of the mouse will be different from the mouse’s position in the room. Lastly, we use the point_in_rectangle function to determine whether the mouse is hovering over the GUI button. The point_in_rectangle function takes six arguments. The first two are the point to check, the next two are the upperleft corner of the rectangle, and the final two are the lowerright corner of the rectangle. Add a Mouse > Global Mouse > Global Left Pressed Event to the GUI button object. Drag over a Code Action and type this code in it. obj_gui_button: Global Left Pressed Event /// Click the button if ( hover ){ show_message (" Youclicked the button ."); }
121
This code checks to make sure that we are over the button when the mouse button is pressed. If we are, it shows a message saying that the button was clicked. We can’t use the Left Pressed Event because this button object doesn’t have a collision box. Even if it did, we are working on the GUI layer, and the collision box would be in a different location from the drawn button. The last event that we need to add to our GUI button object is the event that will draw the button on the screen. We are using the Draw GUI Event . obj_gui_button: Draw GUI Event /// Draw the GUI button if ( hover ){ draw_set_alpha (. 2 ); } else{ draw_set_alpha (. 5 ); } varleftx = display_x - width / scale ; vartopy = display_y - height / scale ; varrightx = display_x + width / scale ; varbottomy = display_y + height / scale ; // Draw the button using a rectangle draw_rectangle ( leftx , topy , rightx , bottomy , false ); draw_set_alpha ( 1 ); // Set the horizontal and vertical alignments for text draw_set_halign ( fa_center ); draw_set_valign ( fa_middle ); // Draw the button text varcol = c_white; draw_text_colour ( display_x , display_y , text , col , col , col , col , 1 ); Using the hover variable that we created earlier, we set the draw alpha to either 0.2 or 0.5. This makes it easier to see the button if the mouse is hovering over it. We create some temporary variables to help us find the corners of the button and use the 122
draw_rectangle function to draw our button. After drawing the button, we make sure to set the draw alpha back to 1. We set the text alignment to center and middle, and then, we draw the text over the button. Testing the Game Run the game and make sure that, no matter where the player is in the room, the button responds to being hovered over and clicked on. Working with the GUI layer can be a little tricky sometimes, but often, it is well worth the work. Keep in mind, you don’t always need to use the GUI layer for HUDs, menus, and buttons. Sometimes you can make it work just by drawing relative to the view. Personally, I do whatever is going to take the least amount of code and still look good. For game start menus, I use normal objects, but for HUD items that don’t need any interaction (like health and lives), I will use the GUI layer because I don’t need to calculate the position using the view. Mess around with the previous examples and find out what works best for you.
123
Chapter 13
Particles and Surfaces Creating particle systems A particle system is a world that the particles live in. In GameMaker, it is impossible to create particles without first creating a particle system. Creating a particle system is actually easier than creating the particles themselves. Think of it this way. For objects, you must first create a room to put them in or the objects can’t be used or seen. Particle systems are like the room and the particles are like the objects. In most cases, you will only need to create one particle system to contain all of your particles. Because there is generally only one, I like to create an object for controlling the particles and the particle system. In this object, I create a global variable and assign the particle system to it like this. I would place this code in the Game Start Event of an object created specifically to manage the particles. global . ps = part_system_create (); After creating the particle system and assigning it to a global variable, you can reference it at any time. You are now ready to create your first particle type. Creating particle types The particle is going to be used by multiple objects, so I’m going to make it global, just like the particle system. This function just creates the particle type. Remember, though, this function does not actually create particles, it just defines the particle type! Later, you will use this particle type to actually create some particles inside of your particle system, which will display them in the room. global . pt_blood = part_type_create (); Now that you have created the particle type, you need to set the different properties. The properties that you set will determine the look and behavior of this particle type. There are lots of different properties to set. I will explain each one as it gets set.
124
I also like to create a local reference to the particle type for easier access while setting properties. varpt = global . pt_blood; The last line allows us to use pt as the id for the particle type, which is less typing. Let’s set the shape of the particle. part_type_shape ( pt , pt_shape_disk ); The first argument is the id of the particle type. I use pt , which is a reference to the particle type already created. The second argument is the particle shape. There are quite a few different builtin shapes. Here is a quick reference list of them: Particle Shape Types pt_shape_disk pt_shape_pixel pt_shape_line pt_shape_star pt_shape_sphere pt_shape_flare pt_shape_spark pt_shape_explosion pt_shape_snow pt_shape_smoke pt_shape_cloud pt_shape_square pt_shape_circle pt_shape_ring Try them all out to see what they look like, and find the ones that will best suit your style! You may have to adjust the sizes and colors to get a good idea of what they look like. I’ll show you how to do that now. 125
part_type_size ( pt , 0.1 , 0.2 , 0 , 0 ); This bit of code sets the size. There are five arguments: the first is the id, the second is the minimum size, the third is the maximum size, the fourth is the size increase, and the fifth is the size wiggle. I’m not going to explain exactly what each argument does. If you want to know more about that, you can check out the GameMaker help file or just mess around with the values. part_type_type_color2 ( pt , c_red , c_maroon ); This bit of code will change the particle from red to maroon along the span of its life. Let’s set the particle speed now.
part_type_speed ( pt , 2 , 5 , -0.1 , 0 ); The first argument is obviously the id again. After that, it is the minimum speed of the particle, the maximum speed, the amount to add to the speed at each step (you will notice that I am actually subtracting from the speed each step), and finally the amount to wiggle.
part_type_direction ( pt , 0 , 360 , 0 , 0 ); This function sets the direction in which the particle will travel. Once again, you notice that the first argument is the id. After the id, there is a minimum direction, a maximum direction, a direction increase, and a direction wiggle. You may be noticing a pattern with these functions. For the most part, they are all very similar.
part_type_gravity ( pt , 0.2 , 270 );
126
Now let’s set how gravity influences these particles. After the id argument at the start, there is a gravity amount argument followed by a gravity direction argument. As you probably know by now, 270 degrees is the downward direction. You are finished creating the particle type and setting all of the different attributes of your new particle type. Now it’s almost time to actually create some particles. Before you move on, you need to make sure to destroy the particle system and particle types in the Game End Event so that you don’t create a memory leak. It is pretty easy to do. Game End Event part_type_destroy ( global . pt_blood ); part_system_destroy ( global . ps ); And you’re done! Creating particles This part is where it gets fun. Let’s create some actual particles. First, I’m going to show you how to create particles without using an emitter. You might not know what a particle emitter is, and that’s okay. I will explain it after this. If you are just testing your particles, you can place this next bit of code in a Global Mouse, Left Pressed Event . Global Mouse, Left Pressed Event var mx=mouse_x; var my=mouse_y; part_particles_create ( global . ps , mx , my , global . pt_blood , 10 ); This code will create ten blood particles at the mouse’s position. The first argument is the system that you want to create the particles in; the second and third are the x and y positions of the particles, respectively; the fourth is the particle type that you want to use; and the last argument is the number of particles that you want to create. Creating particle emitters Using part_particles_create is a good way for beginners to get some practice using particles, but generally, you will want to use an emitter to create your particles. Emitters are special little objects that let you create particles in bursts, in streams, across large 127
areas of the room, and in different defined areas. The first thing you need to do in order to use an emitter is create one.
em = part_emitter_create ( global . ps ); The only argument for this function is the id of the particle system that you want this emitter to live in. I generally create emitters using an instance variable (instance scope), because only the object that creates the emitter is going to use it. Placing it in the global scope would generally be pointless. After creating the emitter, you need to set the region that the emitter will create particles in. This is a simple function that looks like this:
var mx=mouse_x; var my=mouse_y; var shape=ps_shape_ellipse; var distr=ps_distr_gaussian; part_emitter_region ( global . ps , em , mx - 4 , mx + 4, my - 4 , my + 4 , shape,distr ); There are a few strange things that you might notice about the arguments passed into this function. Let’s go over them. The first argument is the particle system id. After that, we have the emitter id, the x minimum, the x maximum, the y minimum, the y maximum, the emitter shape, and the particle distribution type. The emitter shape is a global constant defined by GameMaker. Here is a list of the different emitter shapes that you can use: Particle Distribution Shape Types pt_shape_rectangle pt_shape_ellipse pt_shape_diamond pt_shape_line
128
The particle distribution type is also a global constant defined by GameMaker. There are only three different types that you can use. Here they are: Particle Distribution Types ps_distr_linear ps_distr_gaussian ps_distr_invgaussian The linear distribution is an even distribution across the region of the emitter. The gaussian distribution creates more particles near the center of the shape and fewer particles closer to the outside of the shape. The invgaussian is an inverse of the gaussian, creating more particles near the edges. Creating Particles Using an Emitter Now that the emitter is all set up, you are ready to create some particles using it. There are a few different functions that you can use now with your emitter. I’ll show each one to you and teach you what they do.
part_emitter_stream ( global . ps , em , global . pt_blood , 2 ); Once again, the arguments start with the particle system id. Then, you have the emitter id, the particle type, and the number of particles to create. The function will create a stream of particles. Every step, it will create two blood particles. This function can be placed in the Create Event of an object and will continue to stream particles even after the Create Event has ended. If you want to stop the stream of particles, you will need to call this function again and set the number of particles created to zero. Streaming is cool for water fountains, snow, rain, and such. For blood, on the other hand, our character would be constantly bleeding out. Here is how you can create a burst of blood particles:
part_emitter_burst ( global . ps , em , global . pt_blood , 10 );
129
The arguments are all exactly the same as those for the streaming function. The difference is that this is a onetime call that will stop creating particles once it has been run. I generally like bursting more often than streaming, but you can mess around with both of them. Great job! You have now learned to create a particle system, particle types to live in the system, and emitters to create those particle types. The best way to learn more about particles is to create a simple particle game and mess around with the different values for your particle emitters and particle types until you get something you like. It can be a great way to come up with new particle types, and you will have loads of fun while doing it. Surfaces If you have ever used GameMaker, you have already used a surface . Sprites associated with objects are draw on what is called the application surface. This is the room/view combination that comes up when we run our game. It is even possible to manipulate the application surface, but for now, we won’t do that. The only difference between the application surface and another surface that we might create is that the application surface is drawn on the screen automatically and your surface isn’t. In fact, if we don’t draw our surface manually, we will never be able to see it. Once we have created our surface and drawn something to it, we need to actually draw the surface itself (or pieces of it) onto the application surface so that the person playing your game will see it. Surfaces are an amazing feature of GameMaker Language, but if not used correctly, they are also a little volatile (even unstable). There is only one surface that isn’t volatile—the application surface. The application surface is the surface that normal draw events draw to. There are builtin functions that we can use to work around the volatility of surfaces. Using these functions, we can create our own surfaces and manipulate them. Surfaces can be used for lots of different things, but they are most commonly used for lighting effects and shadows. Creating a surface is very easy and can be done with this simple function. The two arguments are the width and height of the surface. Create Event my_surface = surface_create ( 640 , 360 ); 130
Because they are volatile, surfaces can be removed from memory at any time. This means that there is no guarantee that the surface will still exist the next time that we try to access it. For this reason, GameMaker Language provides a function to check for the existence of a surface before it tries to do anything with it. Draw Event if ( surface_exists ( my_surface )){ // Work with surface } else{ // Create the surface again my_surface = surface_create ( 640 , 360 ); } First, we check to make sure that the surface exists. If it does, we can use it. If not, we will need to create the surface again. Once we know that the surface is there, we can start drawing to it. To do so, we need to tell GameMaker that we want to draw on our surface and not to the application surface. Draw Event if ( surface_exists ( my_surface )){ // Tell GameMaker to draw on your surface surface_set_target ( my_surface ); // Now we can draw on the surface draw_circle_colour ( 32 , 32 , 16 , c_red , c_red , false ); // Tell GameMaker to reset to the application surface surface_reset_target (); // Make sure to draw the new surface in the room draw_surface ( my_surface , 0 , 0 ); }
131
After we have drawn to our surface, we will want to actually draw the surface in the room. Remember, surfaces are hidden until we actually show them to the player. We can do this using the draw_surface function. This function takes a surface as its first argument, with x and y coordinates as the second and third arguments, respectively. Now, we will want to modify the way that we create the surface. The surface should start as a clean slate. We can do this by drawing a black background across the entire surface. Draw Event // Create the surface again my_surface = surface_create ( 640 , 360 ); // Tell GameMaker to draw on your surface surface_set_target ( my_surface ); // Now we can draw on the surface draw_clear_alpha ( c_black , 0 ); // Tell GameMaker to reset to the application surface surface_reset_target (); Great job! Those are the basics of using a surface. There is one last thing about surfaces that we need to discuss: A surface must be destroyed at the end of the game to avoid memory leaks. This is the function we can call to do that: Game End Event if ( surface_exists ( my_surface )){ surface_free ( my_surface ); } Surfaces Example Game For our very first use of surfaces, I’m going to show you how to create a basic drawing game. This is a simple way to deal with surfaces, and it will help you get your hands 132
dirty in some code without overcomplicating things. You won’t need any sprites or backgrounds to create the simple game—just one room and one object. Let’s get started! Create a new object and name it obj_paper . This will be the object we use to control everything in our game. Add a Create Event to the object and drag over a Code Action . When the new code window comes up, write this: Create Event surface = noone ; mouse_xprevious = mouse_x ; mouse_yprevious = mouse_y ; It should be easy to recognize what this code does. We are creating three new variables. Surface is set to noone (because we haven’t created the surface yet) and the mouse’s previous two variables are set the mouse’s current position. We will use these for drawing our lines later. Now that the Create Event is finished, add a Draw Event and drag over a new, empty Code Action . This is going to be the most complicated section, but I’ll explain it all. Draw Event // Local variables to shorten code varmx = mouse_x ; varmy = mouse_y ; varmxp = mouse_xprevious ; varmyp = mouse_yprevious ; if ( surface_exists ( surface )){ if ( mouse_check_button ( mb_left )){ surface_set_target ( surface ); draw_circle ( mx , my , 3 , false ); draw_line_width ( mxp , myp , mx , my , 8 ); surface_reset_target (); }
133
draw_surface ( surface , 0 , 0 ); mouse_xprevious = mouse_x; mouse_yprevious = mouse_y; } else{ // We need to create it surface = surface_create ( 640 , 360 ); surface_set_target ( surface ); draw_clear_alpha ( c_white , 1 ); surface_reset_target (); } First, we check to see if the surface exists. Remember, we do this because they are volatile (it might not exist). If it doesn’t exist, then we create it and draw a solid white color to the entire surface. If it does exist, we check to see if the mouse button is being pressed. If the mouse button is being pressed, we draw a circle at the current mouse position and a line from the last mouse position to the current mouse position. After all that is done, we call draw_surface to actually draw the surface in the room (on the application surface) and update the mouse’s previous positions. There are three arguments that draw_surface needs. The first is the id of the surface, and the other two are the x and y positions, respectively, where the surface will be drawn. That wasn’t too bad, was it? Lastly, we will need to destroy the surface when the game ends. Add a new Game End Event and place this code into it: Game End Event if ( surface_exists ( surface )){ surface_free ( surface ); } The game is now ready for testing! Create a room, put an instance of obj_paper in the room, and run the game. We should have a white paper that we can draw on using the mouse. If the room is larger than the surface size of 640 by 360, the surface doesn’t take up the entire room, and there is a gray area where we can’t draw. If you want, you 134
can change your room size to match the surface size, but it is good for you to see that we are drawing the surface at position (0, 0) and that it doesn’t take up the whole screen.
135
Chapter 14
Physics Using Physics in your Game There are cases in which you will not want to use GameMaker’s builtin physics system. You shouldn’t assume that because the physics system can be really cool and fun that you should use it in your game. As a general rule, you probably won’t want to add a physics system to your game unless it will add to the gameplay in some fundamental way. That being said, using physics is a great way for even new GameMaker users to create lifelike and amazing worlds. Let’s get started. Making a Physics World The first things that we need to do to use the physics system is create a room, click the physics tab in the room properties, and check the little box that says “Room is Physics World.” If we forget to check this box, our physics system will not work. We should also notice three other input fields underneath the checkbox. What are those? The first is the “Gravity X” value. This can be set to create horizontal gravity in our room. A positive value would be gravity to the right and a negative value would be gravity to the left.The next is the “Gravity Y” value. This can be set to create vertical gravity in our room. A positive value would be gravity in the downwards direction and a negative value would be in the upwards direction. I usually set my gravity to about 60 in the “Gravity Y” value. The last input field allows us to change the “Pixels to Meters” conversion rate. I personally leave that alone. Physics Object Types In the physics system, there are three main types of objects that we will be creating: static objects, dynamic objects, and kinematic objects. Here is a short description of each type: Static Objects Static objects will make up the walls of our physics game. These objects are solid and cannot be moved by collisions with other objects. They will also be unaffected by gravity. 136
Dynamic Objects Dynamic objects will likely be the most common in our game. These are normal physics objects that can be pushed, pulled, and rotated based on forces. If we have a player object, it should probably be a dynamic object. Kinematic Objects Kinematic objects are like static objects, only we can move them around in the world using their x and y coordinates. An example of a kinematic object would be a moving platform or elevator. Making Our First Physics Object Now that our first physics world is ready, it’s time to create our physics object. This is actually easier than you might think. Quickly create a square sprite and center its origin. After doing that, create a new object and assign the square sprite to it. In the object’s properties, right under the “Solid” checkbox, there is a “Uses Physics” checkbox; check that box. An additional properties window will show up. I’m going to explain each additional property in detail. Collision Shape The collision shape of the object is a collection of points and edges that make up the collision detection boundary for the object. There are three types of shapes that we can set it to: Circle , Box , and Shape . Circle and Box are self explanatory. Shape isn’t too complicated: we can draw our own collision shape. This is very powerful and easy to do. Just click the button that says “Modify Collision Shape” to start creating a custom shape. We may want to click the “Modify Collision Shape” button even for the Circle and Box shapes; GameMaker tries to be smart when creating them, but it often seems to get them wrong. If it does get them wrong and we don’t check the shape ourselves, we may get strange collision errors. Density Now, let’s talk about the second property, the density of the object. This value is used to calculate the mass of the object, which is used by the physics system for momentum calculations. A larger value here will make our object heavier. When we set that value to zero, it gives the object an infinite density (which is how the system creates static objects). Restitution Restitution is the third property we can set. This value is a little harder to explain… It represents how bouncy our physics object is. 137
Collision Group As our fourth property, we have collision groups . Collision groups can be used to prevent certain physics objects from colliding with other physics objects. Linear Damping Now for property number five. The physics engine in GameMaker is attempting to simulate the real world; the linear damping value helps achieve this. In the real world, objects in the air experience air resistance, and this slows them down (for example, while in flight). In GameMaker’s physics engine, there is no air resistance. This value simulates air resistance. If we were to set this value to zero, the object would behave as if it were moving around in a vacuum. Angular Damping Our sixth property, angular damping , works like linear damping, only it operates on the rotation of an object. If we set this to zero and the object starts rotating and never interacts with another object, it will rotate forever. If we give this property a value, the rotation of the object will slow down over time. Generally, we will want to have some value here, because in the real world, air resistance would slow the spinning of an object. Friction Lastly, we have friction . This one is pretty obvious. This value controls the friction that is subtracted from an object’s motion when it is touching another object. Force vs Torque In GameMaker’s physics, there are a few different ways that we can move an object in the room. Two common ways are to apply a force or a torque to it. It is important to understand the difference between these two methods. Imagine that we build a car in our game room. We could apply a force to the car to move it, or we could apply a torque to the tires to move it. What is the difference? Well, applying a force to the car is like pushing on the car from behind. The car will start to roll and we might even be able to move the car really fast if we apply enough force, but we will never be able to spin out our tires. The better (and more realistic) method is to apply a torque to the tires. This spins the tires and moves the car forward. If we have enough torque to overcome the friction that we have set, then we will even be able to spin our tires out and burn some rubber! 138
Building a Simple Car Game Using Physics I’m really excited to teach you how to build a simple car using GameMaker’s physics engine. For this first example, we will avoid modelling the suspension because that will complicate things. We will add some random terrain generation to make things more interesting. For this example, we will need two sprites, four objects, and one room. Create the Sprites For the first sprite, create a new 80 × 24 image and then fill the entire image. After creating the rectangle sprite name it spr_car_body and click the “center” button to center the origin. For the second sprite, create a new 40 × 40 image and draw a solid circle using the circle tool. Start at the top left corner and finish at the bottom right corner. This will create a perfect circle that uses as much of the image as possible. Name this sprite spr_car_tire and click the “center” button to center its origin. Create the Objects The first object to create is the obj_collision_parent object. This object allows us to have collision be inherited uniformly across our objects (we don’t have to add a Collision Event to all of the objects in our game). This object doesn’t need a sprite. Add a new Collision Event to the object and have it collide with itself ( obj_collision_parent ). We technically don’t need any code in this event because the physics engine will handle all of the complicated behavior. We do have to put an action in here, though, or GameMaker will delete the event. Drag over a Code Action and put this comment in it.
/// Detect the collision This comment just keeps GameMaker from deleting our collision object parent. Now, we can create the car body object. Click the “add new object” button and name the new object obj_car_body . Set the sprite to spr_car_body , give the object a parent of obj_collision_parent , and click the “Uses Physics” checkbox in the object’s properties. Now, we can set the collision shape to “Box” and modify the collision shape to make sure that it lines up with the assigned sprite. Once we have done that, use these values for the physics properties. 139
Physics Properties Density: 0.5 Restitution: 0.1 Collision Group: 0 Linear Damping: 0.1 Angular Damping: 0.1 Friction: 0.2 Once we have finished setting up the object properties and the physics properties, we are ready to start adding some code to this object. Add a new Step Event and place this code in it. obj_car_body: Step Event /// Force the view to follow the car view_xview [ 0 ]= x - view_wview [ 0 ]/ 2; view_yview [ 0 ]= y - view_hview [ 0 ]/ 2; This code will force the view that we create in the room to follow the car body, even if the car body actually leaves the room. We will add more code to the car body in a minute, but for now, let’s move on to the car tire object. Create a new object and name it obj_car_tire . Set its sprite to spr_car_tire , give it a parent of obj_collision_parent , and check the “Uses Physics” checkbox in the object properties. Now, we have access to the physics properties. Set the collision shape to “Circle”, modify the collision shape to make sure that it matches the sprite, and then, set the different physics properties using these values. Physics Properties Density: 0.5 Restitution: 0.1 Collision Group: 0 Linear Damping: 0.1 Angular Damping: 0.1 Friction: 30 140
Make sure to get the friction right. If the friction is super low, our tires will spin out on the terrain and won’t be able to get any traction. Our physics properties are ready now, and it’s time to add a Create Event . Place this code in the Create Event for our tire object. obj_car_tire: Create Event /// Initialize the tire spd = 1000; This just creates a speed variable that we can use later when moving the tires. Notice, I didn’t call the variable speed . This is because speed is reserved by GameMaker and should not be used with the physics system. Now, it’s time to actually add the movement code to the tires. As we discussed just a few paragraphs ago, we need to decide if we want to use a force or a torque. If we use a force, it would simulate someone pushing the car from behind. If we use a torque, it would simulate an engine spinning the tires. We want to use a torque. Add a new Keyboard Right Key Event to our tire object and add this code: obj_car_tire: Keyboard Right Key Event /// Add a clockwise torque to the wheel physics_apply_torque ( spd ); Now, create a new Keyboard Left Event and add this code to it. obj_car_tire: Keyboard Left Key Event /// Add a counter-clockwise torque to the wheel physics_apply_torque (- spd ); The tire will now spin when we press left or right! One of the things that I love about GameMaker’s physics system is how it simplifies the code so much. We just set the rules for the world and the physics engine handles the rest. Our tires are now finished, 141
and we can go back to the car body object. Open it up and add a Create Event . In the Create Event , add this code: obj_car_body: Create Event /// Initialize the car varhalf = sprite_width / 2; vartire = instance_create ( x - half + 12 , y , obj_car_tire ); vartx = tire . x; varty = tire . y; physics_joint_revolute_create ( id , tire , tx , ty , 0 , 0 , 0 , 0 , 0 , 0 , 0 ); vartire = instance_create ( x + half - 12 , y , obj_car_tire ); vartx = tire . x; varty = tire . y; physics_joint_revolute_create ( id , tire , tx , ty , 0 , 0 , 0 , 0 , 0 , 0 , 0 ); In this code, we sometimes use half the width of our car body sprite. This number will help us to place the tire correctly. After doing that, we create the first tire and attach it using the physics_joint_revolute_create function. The second tire is created and attached in the same way, but we pass different tire.x and tire.y values. The physics_joint_revolute_create function is one that we haven’t discussed before. This function attaches physics objects or (fixtures) to each other with a rotatable joint. The first argument that we pass is the id of the car body. The next is the id of the car tire. Because we stored the id of the tire instance in the local tire variable, we can use that. Then, we pass the x and y positions of the origin of the joint. We are using the x and y positions of the tire because we already created them right where we want the joint to be. After that, there are a ton of zeros. These different arguments have to do with limiting the rotation of the joint or adding a motor speed to the joint. The last one is whether or not we want the tire to collide with the car body. We set this to 0 (or false) because we do not want them to collide. They will be touching, so a collision would mess up our car. Once we have added this Create Event , our car is ready for the physics world! The only problem is that our world doesn’t have any ground to drive on. It’s time to create the ground object that will generate our world. We are also going to have this ground 142
object draw the physics world with a neat function called physics_world_draw_debug . We probably wouldn’t want to use this function for an actual game, but it will work well in this case, and we can use it to better understand how different physics objects work. Create a new object and name it obj_ground . Give the new ground object a parent of obj_collision parent . We don’t need to check “Uses Physics” for this object, because we will be doing that with code this time. Add a Create Event and place this code in it. obj_ground: Create Event /// Initialize the ground flags = phy_debug_render_shapes | phy_debug_render_joints | phy_debug_render_coms | phy_debug_render_obb; varxx =- 100; varyy = 0; varfix = physics_fixture_create (); physics_fixture_set_chain_shape ( fix , false ); repeat ( 100 ){ physics_fixture_add_point ( fix , xx , yy ); xx += 50 + random ( 150 ); yy +=- 32 + irandom ( 64 ); yy = median ( 64 , yy , room_height - 64 ); } physics_fixture_set_density ( fix , 0 ); physics_fixture_set_restitution ( fix , 0.5 ); physics_fixture_bind ( fix , id ); physics_fixture_delete ( fix ); At first glance, this code can look daunting. I’ll explain it linebyline to help you understand it better. At the top, we are creating an instance variable called flags . This variable holds the different render flags that we want our ground object to use when it draws the physics world. As you can see, each flag describes a different part of the 143
physics world that we want it to draw. After creating the flags variable, we create two local variables called xx and yy. These variables hold the starting point for our ground generation. As we create the different ground segments, we will add and subtract from these values. We can see that the xx value is 100, which is pretty far to the left of where our room starts. This is to help make sure that we don’t create the car object too close to the edge of our ground. Now, we are ready to create our first fixture. Well, actually, you have been creating fixtures already, you just didn’t do it in code. When you check the “Uses Physics” button in a new object, it tells GameMaker that you want to attach a fixture to that object. When you set the different physics properties in the object, you are actually setting the properties of the fixture that is attached to that object. Fixtures are the physics representation of your object in the game, sort of like how sprites are the visual representation of your object in the game. The cool thing is that we can both create a fixture and set its properties using code instead of using the checkbox and filling out the physics properties fields. This gives us more control over the shape of the fixture and allows us to generate the fixture manually. In our code, you can see where we create the local variable fix and where we use physics_fixture_create and assign the id of our new fixture to the fix local variable. Once we have created the fixture, we can set its shape using physics_fixture_set_chain_shape . The chain shape allows us to add points to our fixture and it will “connect the dots”, creating a chain or, in our case, the ground. There are other fixture shapes that you can use; here are their functions: physics_fixture_set_polygon_shape physics_fixture_set_circle_shape physics_fixture_set_box_shape physics_fixture_set_edge_shape physics_fixture_set_chain_shape Our fixture shape has been set to chain. The time has come to add points to the fixture. The way we add these points will determine how rough the ground is and how good the ground looks. As you can see, I used a repeat loop statement so that we can create 100 points along our ground fixture. Inside the loop, we create the fixture point at position (xx,yy) and then we randomize the next yy point and add a slightly random value to the
144
next xx point. We’re also clamping the yy value with the median function to make sure that our points stay inside the room height. After we create all of the points for our fixture, we set the density and restitution. The last two things that we do are attach the fixture to our object using physics_fixture_bind and delete the fixture template that we created. Be aware, deleting the fixture does not delete the actual fixture that we attached to the object. It just deletes the fixture template that we have created. Now, add a Draw Event and add this code to it. I talked a little bit about this function a few paragraphs back. This function should only be used as a method to debug physics, but for this example, it simplifies the concept and allows us to see the ground that we generated. obj_ground: Draw Event /// Draw the physics world physics_world_draw_debug ( flags ); You are now seconds away from having a working car physics game with randomly generated terrain. The final step is to create the room. Create a new room and name it rm_carworld . Click the settings tab to set the room width to 640 and the height to 360. Click the physics tab, check “Room is Physics World”, and set the “Gravity Y” vector to 30.0. Now, place the car object and the ground object in the room. Next, click on the views tab and check “Enable the use of Views” and “Visible when room starts.” Set the “View In Room” w and h values to 640 and 360, respectively. Then set the “Port on Screen” w and h values to 1280 and 720, respectively. The physics game is now ready to play! This is one of my favorite physics examples. There are a few tricky parts, but by this point in the book, you should at least have an idea of what each line of code does. Be sure to show this example off to all of your friends and family. You have worked hard and learned so much. It’s time to enjoy your work a little.
145
Chapter 15
Online Multiplayer Foundation There is so much to cover regarding online multiplayer. This chapter is going to be long, and even then, it will only scratch the surface of making an online game. It will, however, teach you the basics and get you started with online multiplayer. We will not be building any MMOs, but the base you receive here will help to prepare you for larger online multiplayer projects. The first example project in this chapter only covers the absolute basics of setting up a server, a client, and the communication between them. In order to complete this first example and learn from it, you need to have a good understanding of GameMaker’s data structures and their accessors (especially maps and lists) as well as the different components of an online game. I will cover the components here, but if you are struggling with the data structures, be sure to reread Chapter 4: Arrays and Data Structures. Components of Online Games There are several different ways to have online games communicate. We will be using the clientserver model here. Alternatively, you can have two clients connect to each other, but I will not be covering that structure. Server The server is generally a computer/program system that performs processing for other computers. In GameMaker, the server can be a separate program that turns your local computer into a mini server. This program will handle connections, disconnections, and data transfers to and from the different game clients. We can create a server in GameMaker Language like this. vartype = network_socket_tcp; varport = 8000; max_clients = 4; server = network_create_server ( type , port ,max_clients ); 146
The first few lines here are used to create descriptive variables that we can pass as the parameters in the network_create_server function. When we call the network_create_server function, it will return a unique id that can be used to access the created server. As you can see, we are assigning that unique id to the instance variable server . There are a few different options for the server type. Here they are: network_socket_tcp network_socket_udp In the first edition of this book, I will only be covering network_socket_tcp . A UDP socket will generally be faster (it doesn’t spend time errorchecking the packets), but TCP is more reliable. There are many other differences, but they are beyond the scope of this chapter. Client The client is a computer that connects to the server and (hopefully) communicates with it. Often, there will be many clients connected to one server. In GameMaker, the client is a program that connects to the server and handles messages sent from the server. It can also send messages back. This is how we can create a client in GameMaker Language and attempt to connect it to the server. Unfortunately, this won’t connect yet because we haven’t handled the connection on the server end. This is how we can attempt to connect from the client side. vartype = network_socket_tcp; varip =" 000.000 . 0.000 "; varport = 8000; socket = network_create_socket ( type ); connection = network_connect ( socket , ip , port ); Here, we create variables for the type, IP, and port in order to make the code more readable. If you are running the server from your own computer, then you will need to get the IP address of your own computer. I will talk a little more about IP addresses, ports, and sockets in just a minute. First, look at where we call the network_connect function. This function will try to make a connection over a socket using a defined IP address and port. You can choose the port you would like to use, but I just use 8000 . 147
The network_connect function will return a unique id that can be used to reference the connection. If the function is unable to establish a connection, it will return a number less than 0. Sockets A socket represents a twoway connection between the client and the server. The server will actually need to keep track of multiple sockets (because it will be connected to multiple clients). You can think of the socket as the “tube” that connects the server to the client and allows data to be transferred. You have already seen how we can create the socket in the client using network_create_socket , but you still need to see how the server can get access to this socket as well. I’ll talk more about how that is done in the actual example file. IPs An IP address is a set of numbers that represents a unique address for a computer. The numbers are separated by periods. An IP address is similar to your physical home address, but used for computers. If you have a new friend that needs to find your home, you give your home address. If your friend’s computer needs to find your computer (for example, to play a game), you give your IP address. Of course, it is a little more complicated than that, and in this example, the connection will only work over a local network (you and your friend can play together, but you both need to be connected to the same local network). In order to get your computer’s IP address, you can search “cmd” (which will run the Command Prompt) and type the command “ipconfig”. This will list information about your internet configurations, including your “IPv4 Address”. The IPv4 address is the one that you will want to use for this tutorial. Be aware that your router may assign you a different IP address from time to time, so you may need to update the IP address that your client uses to connect to the server. Ports The port number is used in conjunction with your IP address. I used 8000 in this example and that should work for you. If it doesn’t, you can try some different numbers. It doesn’t matter too much, as long as your client and server are using the same number and there isn’t any other program using your chosen port number. Buffers The last term that you need to have an understanding of is buffer . A buffer is similar to a variable that can be sent through the socket, either from the server to the client or from 148
the client to the server. Buffers take data in sequential order and that data is read back out of the buffer in the same order. Basic Example: “Create the Stars” Now that we have gone over a few of the basic terms, you are ready for your first online game example. This is the simplest example game that I could come up with. Networking in GameMaker can be very complicated, and I want to make sure you understand the basics. Creating The Server To start things off, we are going to build a basic server. In this GameMaker example, you will need to create two new project files. The first one that you create should be called Server and the second should be called Client . Once all of the code is in place, you will run the server first, and then, when you run the client, it will connect to the server. Open up the server project once you have created it and add a new object to it. Name this object obj_server and add a Create Event to it. Drag over a Code Action and add this code to it: obj_server: Create Event /// Initialize the server object vartype = network_socket_tcp; varport = 8000; max_clients = 1; server = network_create_server ( type , port ,max_clients ); socket = noone; Good job! That code should look familiar to you. We are setting up the network type, the port, the maximum number of clients, and then we create the server. We are also creating an instance variable called socket that will store the reference to our connected client. In an example with more than one socket, you will want to create a ds_list to store your sockets, but for this simple example, we don’t need to do that. Now that we have created the server, we will want to make sure it is destroyed when the game ends. Add a Game End Event to our server object and place this code in it: 149
obj_server: Game End Event /// Destroy the server network_destroy ( server ); Okay, up to this point, you are probably following the tutorial and thinking that it is pretty easy. Well, so far it has been pretty easy. Nothing too fancy and nothing you haven’t seen before. Well, now things are about to get a little tricky, so pay close attention. This next bit of code will require both that you understand ds_maps and that you understand the terms that I went over in the start of the chapter. Don’t worry though, you will get it as I step you through it. Add an Asynchronous Event to the server object and select the Networking Event from the asynchronous submenu. This event will keep track of connections, disconnections, and data that is sent to our server. I’ll show you how this works. Add this code to the event: obj_server: Asynchronous Networking Event /// Check for the client vartype_event = async_load [? " type "]; switch ( type_event ) { casenetwork_type_connect: // Add the client to the socket if ( socket == noone ) { socket = async_load [? " socket "]; } break; casenetwork_type_disconnect: // Remove the client from the server socket = noone; break; casenetwork_type_data: 150
// Grab incoming data and handle it varbuffer = async_load [? " buffer "]; buffer_seek ( buffer , buffer_seek_start , 0 ); scr_received_packet ( buffer ); break; } That is a big chunk of code! Let’s take it one step at a time. In the first line, we are getting a reference to the type of Asynchronous Networking Event that is being sent. The async_load ds_map is a special map created in this event only. It has all of the information that we need about the data coming in, but it cannot be accessed outside of this event. Once we get the event type, we can handle the data accordingly. There are three types of events that could occur. The first is the network_type_connect . This means that a client is attempting to connect to our server. In this case, we will want to get a reference to that client; we can do that by using the async_load ds_map again, but instead of accessing the “type” key, we are going to access the “socket” key. This will give us a reference to the client. We just assign that information to our socket instance variable and we are done. The second event type is the network_type_disconnect event. This event fires when a client disconnects from the server. This is the easiest event to handle. We are just going to set our instance variable socket back to noone . The third, and last, event type is the network_type_data event. It is the most fun of the three and takes the most work, which you can see by the fact that, at the end of our case statement, we are calling a script that handles the data we get from the event. The first thing we do is create a local variable called buffer and assign the data from the buffer key in our async_load ds_map to it. Remember that buffers are like packages that contain the data that will be sent or received over the network. After getting the buffer, we want to use the buffer_seek function to set our read location to the start of the buffer. Data from a buffer is read in sequence. Each time you read data from the buffer, the data read is actually removed from the buffer. I’ll explain this in more depth once we are ready to talk about the code contained in the script. Once we set the buffer reading location to the start, we are ready to send that information to the script. Normally, you would send the socket information as well, so that you knew which client
151
sent the data, but because we only have one client, we don’t need to worry about that in this example. It’s time to write the code in our script. Create a new script and name it scr_received_packet . Open up the script and type this block of code. scr_received_packet ///scr_received_packet(buffer) varbuffer = argument [ 0 ]; // buffer is (id,x,y) varmessage_id = buffer_read ( buffer , buffer_u8 ); // After first read buffer is now (x,y) switch ( message_id ) { case 1: varmx = buffer_read ( buffer , buffer_u32 ); // buffer is now (y) varmy = buffer_read ( buffer , buffer_u32 ); // buffer is now empty () //Use the data from the buffer to create the click instance_create ( mx , my , obj_mouse_click ); break; } I’ve added extra comments to help you to understand how buffer reading works. The first thing that we do is create a local variable that grabs the buffer from the argument that we passed to the script. Buffers can contain whatever we want them to. We haven’t actually sent any buffers from the client yet, but I decided that our buffer will contain a message id that determines what type of message it is. Then, the x and y values are read if the message id is 1. A message id of 1, in this case, means that we want to create a mouse click object at the position of the next two pieces of data read from the buffer.
152
After we have read the message id from the buffer, you can see in the comment afterwards that the buffer no longer contains that information. All that is left is the x and y coordinates of the mouse click. I’m passing the id through a switch so that later, if you wanted to add other types of data that did other things, you could just pass a different id through the buffer and then add that case to the switch. Once we confirm that the message id is actually set to 1, we read both the x position and the y position from the buffer and assign those values to local variables called mx and my , respectively. You can also see by my comments that once each chunk of data is read, it is removed from the buffer (after the y value is read, there actually isn’t any more information in our buffer). Once the buffer is empty, we have all of the data that we need from the client to create the mouse click object and break. The last thing that we need to do to finish our simple server project is create the mouse click object. Add a new object and name it obj_mouse_click . Add a Draw Event to it and add these lines of code: obj_mouse_click: Draw Event /// Draw the click as a circle draw_circle_colour ( x , y , 4 , c_white , c_white , false ); This code will just draw a circle where the object is so that we can see it. Make sure to create a room and add your server object to it before you test the game. Your first GameMaker Language server is up and ready to go! Remember that this is only the first half of the example! Even though the server is created, ready to connect to a client, and ready to receive data, we still haven’t created the client that will connect to the server and then send that data when something happens. Creating the Client Because this example is so basic, the client is quite a bit simpler than the server. In a more complex example, your client might be almost as complicated as the server. In a later example, I will show you how you can use one project file for both the client and the server. First, though, let’s get started on our basic client. Once you have the client project created, add a new object to it and name it obj_client . This client object will need a Create Event . Add the Create Event , drag over a Code Action and add this code block to it. 153
obj_client: Create Event /// Initialize the client vartype = network_socket_tcp; varip =" 000.000 . 0.000 "; varport = 8000; socket = network_create_socket ( type ); connection = network_connect ( socket , ip , port ); varsize = 1024; vartype = buffer_fixed; varalignment = 1; buffer = buffer_create ( size , type , alignment ); You’ve seen the first part of this code earlier in the chapter. We start off by defining our type, IP, and port. Once this is done, we create the socket and the connection. Once again, if you don't’ know the IP address of your computer, you can use the comment “ipconfig” in the command prompt and look under IPv4 to find it. After creating the socket and the connection, we create a buffer to use for sending data. We give the buffer a size of 1024 (1 kB), a type of buffer_fixed (this keeps the size of the buffer from changing), and an alignment of 1. The alignment refers to the byte alignment of the data. There are other buffer types, but for this example, we don’t need to worry about them. Now that our connection has been initialized and we have a buffer created and ready for communication, the time has come to actually send data. For this example, we will send data to the server when the user clicks the left mouse button somewhere in the room. Add a new Mouse > Global > Global Left Pressed Event to our client object and add this block of code to the event: obj_client: Global Left Pressed Event instance_create ( mouse_x , mouse_y , obj_mouse_click ); buffer_seek ( buffer , buffer_seek_start , 0 ); // Write the message id
154
buffer_write ( buffer , buffer_u8 , 1 ); // Write the mouse x position buffer_write ( buffer , buffer_u32 , mouse_x ); // Write the mouse y position buffer_write ( buffer , buffer_u32 , mouse_y ); network_send_packet ( socket , buffer , buffer_tell ( buffer )); The first thing that we do is create the mouse click object. We will have a mouse click object in this project that is exactly like the one in our client. This is generally not a good way to do things because you will have duplicate code, but it’s okay here because this is a simple example. In the final example in this chapter, I will show you a better way to do this. Normally, you will either have one project that works as both the server and the client, or you will have multiple clients connecting to the same server, which will just relay the information to and from the clients. After we create the mouse click object, we set the write position of the buffer to the very start. Then, we start writing to the buffer. The first thing that we write is the message id. Next, we write the mouse_x position. Finally, we write the mouse_y position. Like I mentioned earlier, we can send whatever data that we want and in any order in this buffer, but our server needs to know how to handle it. We already wrote the code that allows our server to handle data like this. After we have written this information to the buffer, we can use the network_send_packet function to send it to the server. We need to pass three arguments to this function. The first is the socket to send the data over, the next is the buffer, and the last is the size of the buffer. You can use the buffer_tell function to find the size of a buffer. Our client is almost ready, but we still need to do two things. First, we need to add a Game End Event to the obj_client to make sure to destroy the dynamic data that we don’t need anymore. Add the Game End Event and place this code in it. obj_client: Game End Event network_destroy(socket); buffer_delete(buffer); 155
After that, we need to create the mouse click object in the client project as well. You can just copy the code over from the server project, but here it is again: obj_mouse_click: Draw Event ///Draw the click as a circle draw_circle_colour ( x , y , 4 , c_white , c_white , false ); Testing the Example Now, we are finally ready to test this example. Make sure that there is a room in the client project and that the client object has been added to the room. Once this is done, make sure that both the server and the client projects are open. Run the server project first. We shouldn’t be able to do anything in the server project, but we shouldn’t get any errors. Now that the server is running, we can run the client on the same computer. Click around in the game room of the client, and make sure that the clicks are appearing in the server window as well. If everything went well, you will get one of the most exciting feelings in the world: the joy of seeing a multiplayer game that you coded running. Getting even a basic example like this working feels great, and you should be proud of how far you have come. You will notice that if you run your client on a different computer connected to the same home network, it will still work. This example covers local area networks or LANs. If you want to use this to play a game with a friend that is connected to a different network, you can use some sort of program that allows you to set up a “fake” local area connection, or you could use your external IP address. Online TicTacToe Knowing how to send information between the server and the client is the first step, but it is important to know the limitations of this system. We want to do our best to limit the amount of information that we are sending between the client and the server. Keeping that information at a manageable size will help our game to run faster and can also help prevent “lag” in our game while the information is being sent back and forth. Have you ever been playing an online game and watched an enemy or another player object “teleport” or “jump” around on the screen because of server lag? This generally happens when the client’s x and y information on that particular object fall behind the information the server has, and the server has to resynchronize the client to the server. It looks strange and is very common, even in large online titles. We want to avoid this as much as possible. 156
For our first online multiplayer GameMaker game, we are going to build a turnbased game. This will help us to learn how to limit the amount of data that we are sending, and we will use some other neat tricks along the way. Much of the code in this example will look familiar. This is because we are going to use a system almost exactly like the Tictactoe game in the Data Structures chapter. This should make it easier for you to separate the actual game code from the online code (which we will add on top of it). Let’s start! First off, add a sprite called spr_mark . Give the sprite a size of 64 × 64. Add two subimages to the sprite. The first subimage will be our “O”, and the second subimage will be our “X”. Once we have the sprite, add a new background as well. Name the background bg_space , give it a size of 64 × 64, and draw a box outline that fills up the entire background. We will use nine of these backgrounds in our room to create the Tictactoe board. Make sure you check “use as tileset” and set both the “Tile Width” and “Tile Height” to 64. Here is a screenshot of both the sprite and the background: Sprite: spr_mark
157
Background: bg_space
Now that we have the background and sprite ready, create a new room. Name the room rm_board . Set its width to 192 and its height to 224. Now, go to the tiles tab and use the background we created to make your TicTacToe board. When you are finished, your room should look something like this. I placed the tiles lower in the room to leave some room for information at the top of the game window.
158
Room: rm_board
The next step, before adding our objects, is to set up some macros. Open up the “All configurations” macro list and add these macros and corresponding values. PLACE_MARK: 0 BLANK_MARK: -1 O_MARK: 0 X_MARK: 1 CELL_SIZE: 64
159
When we are done, the macro list should look like this: All Configurations Macro List
The time has come to start adding objects and their events/actions. Create two new objects. Name the first one obj_network and the second one obj_game . These two objects are all that we need to control the entire game. ● obj_network ● obj_game Start by opening up the network object, adding a Create Event to it, and dragging a Code Action into the event. The Code Action will contain this code: obj_network: Create Event /// Initialize the network vartype = network_socket_tcp; varip =" 000.000 . 0.000 "; varport = 8000; socket = network_create_socket ( type ); connection = network_connect ( socket , ip , port ); is_server = false; global . turn =' other '; if ( connection < 0 ){ global . turn =' mine '; max_clients = 1; 160
}
server = network_create_server ( type , port , max_clients ); network_destroy ( socket ); is_server = true; client = noone;
This bit of code is clever because it allows us to use the same game project for both the server and the client. We start by creating a socket and a connection. Then, we set two variables. The first variable is an instance variable called is_server . At first, we assume that this game is the client. Because we are the client, we set global.turn to true. If network_connect is unable to establish a connection (for example, if a server doesn’t exist), then the function returns a number less than 0. We can use this to test if there is already a server. If there isn’t already a server ( if (connection<0) ), then we need to create one. Creating the server is easy. We set the global.turn to “mine”, set the max_clients to 1, and call network_create_server to create our server with the information that we have already established. Because we aren’t a client, we don’t need the socket we created anymore, so we destroy it. Then, we set is_server to true and client to noone. We need to add a Game End Event to make sure that we destroy our socket or server. The one we destroy will depend on whether or not we are a server. Add a Game End Event and put this code in it: obj_network: Game End Event /// Clean up dynamic data if ( is_server ){ network_destroy ( server ); } else{ network_destroy ( socket ); } 161
This is a small code block. We check to see if we are a server. If we are, we destroy the server; if not, we destroy the (client) socket. Add an Asynchronous > Networking Event . This event will fire anytime the server picks up a message from the client. There are three types of messages that will be sent: an attempt to connect, an attempt to disconnect, and actual data. We will check for each of these types and handle them accordingly. Drag over a Code Action and add this code block to it: obj_network: Networking Event vartype_event = async_load [? " type "]; switch ( type_event ) { casenetwork_type_connect: if (! is_server ) break; // Get a reference to the client's socket if ( client == noone ){ client = async_load [? " socket "]; } break; casenetwork_type_disconnect: if (! is_server ) break; // Remove the reference to the client's socket client = noone; break; casenetwork_type_data: // Handle the data received varbuffer = async_load [? " buffer "]; buffer_seek ( buffer ,buffer_seek_start , 0 ); scr_handle_packet ( buffer ); break; }
162
The first thing that we do is grab a reference to the type of event that is being sent. If it is a connect event or a disconnect event and we are not the server, we just break out and ignore them. If we are the server, we make sure to add the new client when they are connecting and remove them when they are disconnecting. For the data event, it doesn’t matter if we are the server or the client, both can send and receive data. When we know that we are receiving data, we create a local variable to store the buffer. The buffer is the data structure that the data is contained in. We call buffer_seek to make sure that the buffer starts reading from the start. Then, we pass the buffer into a script that will handle the data. Before we create that script, we are going to finish up the network object by adding a Draw Event to it. This Draw Event will draw some information about the game on the screen. We are going to draw the turn and some text telling the player that they are the client or the server. Drag over a Code Action . This is the code we will add to it: obj_network: Draw Event /// Draw the game information draw_text ( 2 , 0 ," Turn : "+ global . turn ); varplayer_text =''; if ( is_server ){ player_text =' PlayerX Mark '; } else{ player_text =' PlayerO Mark '; } draw_text ( 2 , 16 , player_text ); This code draws the turn to the screen and then checks to see if the player is a server. If so, it sets the player text to tell the player that they are the X mark. If they aren’t the server, it sets the player text to tell the player that they are the O mark. Now that we have finished up with the network object, let’s write the script it uses. Add a new script and name it scr_handle_packet . ● scr_handle_packet This is the block of code we will write in the script:
163
scr_handle_packet ///scr_handle_packet(buffer) varbuffer = argument [ 0 ]; varmessage_id = buffer_read ( buffer , buffer_u8 ); switch ( message_id ){ casePLACE_MARK: // Read from the buffer vargridx = buffer_read ( buffer ,buffer_u8 ); vargridy = buffer_read ( buffer ,buffer_u8 ); // Set the mark obj_game . grid [ # gridx, gridy]=!is_server ; // Start my turn global . turn =' mine '; break; } It is important to know how the buffer is organized. For this game, I decided to organize the buffer like this: (message id, x position, y position). You can choose to organize the buffer in any way you wish. You organize the buffer when you actually send it, and you will see that part here soon. We start by getting a local reference to the buffer. Then, we read from the buffer to get the message id. We actually don’t need a message id (the message id is used to indicate what kind of information the buffer contains) for this game because we are only sending one type of data, but I designed it this way so that if you wanted to add other systems to the game (e.g., chat functionality), it would be easy to create new message ids and handle those message types accordingly. Now that we have the message id, we can switch through the different message id possibilities. I’ve stored the only message id that is possible in this game in a macro. This makes the code more readable. If the message id from the buffer matches the message id in that macro, we will step into the code after that case. Once we are in that section of code, we read the x position and the y position from the buffer and store those in local variables.
164
At this point, we have all the information that we need from the other player to update our game board. We can use a shortcut here because of the way the grid is set up. In our grid (you may remember from the first time we made a Tictactoe game), X marks are represented by a 1 and O marks are represented by a 0. The server always uses X marks. If we are the server and we receive a mark from the client, we know that the mark should be a 0. The is_server will return a 1 (true) if we are the server, so if we use the not operator on it ( !is_server ), it will return a 0. If we are not the server, then is_server will return a 0. If we use the not operator on it ( !is_server ), then it will return a 1. This is a sneaky trick that allows us to place the correct mark regardless of whether we are the server or the client. The final step of this code changes the turn back to the current player’s turn. If we are receiving a “mark this spot” message from the other player, it is currently the other player’s turn. Once we mark their spot, we can switch the turn back to us. It is finally time to write the game object’s code. Open up obj_game and add a new Create Event to it. Like I said before, much of this code will look familiar, but we are going to change a few things, so pay close attention. Drag over a Code Action and add this code to it: obj_game: Create Event /// Create the ds grid and initialize the game object grid = ds_grid_create ( 3 , 3 ); ds_grid_set_region ( grid , 0 , 0 , 2 , 2 , BLANK_MARK ); This block of code is simple enough. All we do is create the grid and set all of the cells in the grid to the BLANK_MARK value. BLANK_MARK is a macro that we created earlier; it contains the value of 1. Before we do the mouse click event and its messagesending magic, let’s add a Draw Event to the object and put this code in it: obj_game: Draw Event /// Draw the game board for ( vari = 0 ;i < ds_grid_width ( grid );i ++){ 165
for ( varj = 0 ;j < ds_grid_height ( grid );j ++){ if ( grid [ # i, j]==BLANK_MARK) continue; varsubimage = grid [ # i, j]; varmx = i * CELL_SIZE; varmy =( i * CELL_SIZE )+ 32; draw_sprite ( spr_mark , subimage , mx , my ); } } This Draw Event is almost identical to the one that we used in the previous Tictactoe example game. We loop through the different cells in the grid. If the cell is empty, we use the continue statement to skip it, because we only need to draw cells that contain marks. For all of the other cells, we create three local variables, one for the subimage (which conveniently correlates to the value that we stored in the grid), one for the mark’s x position, and one for the mark’s y position. Then, we draw the sprite spr_mark using the subimage and position data that we gathered. Add a new Mouse > Global > Global Left Pressed Event . This event will hold the code that handles the logic for setting a mark and sending that new mark information to the other player. Drag over a Code Action block and add this code: obj_game: Global Left Pressed Event /// Mark the board if ( global . turn ==' mine '){ // Grab the mouse position and convert it to a grid position vargridx = mouse_x div CELL_SIZE; vargridy =( mouse_y - 32 )div CELL_SIZE; // Make sure the square isn't already taken if ( grid [ # gridx, gridy]!=BLANK_MARK) exit; // Set the mark grid [ # gridx, gridy] obj_network.is_server; // Send the action over the network
166
}
varbuffer = buffer_create ( 1024 , buffer_fixed , 1 ); buffer_seek ( buffer , buffer_seek_start , 0 ); buffer_write ( buffer , buffer_u8 , PLACE_MARK ); buffer_write ( buffer , buffer_u8 , gridx ); buffer_write ( buffer , buffer_u8 , gridy ); varreceiver = noone; if ( obj_network . is_server ){ receiver = obj_network . client; } else{ receiver = obj_network . socket; } network_send_packet ( receiver , buffer , buffer_tell ( buffer )); buffer_delete ( buffer ); global . turn =' other ';
The first thing we do is make sure it is our turn. If it isn’t our turn, then we shouldn’t be able to place a mark. After making sure it is our turn, we grab the mouse’s position and convert it into cell coordinates on our grid using the division operator. After getting that information, we check to make sure that there isn’t already a mark in that spot and mark the board on our side of the connection. Now, we need to send this new information to the other player across the network connection. We start by creating a buffer and adding the message id, the mark’s x position, and the mark’s y position. Then, we use the is_server property of the network object to determine who the receiver will be. After getting all that ready, we send the buffer using network_send_packet , delete the buffer using buffer_delete , and change the turn so that it is the other player’s turn. The final step is to add both your network object and your game object to the room. Testing your Multiplayer TicTacToe Game Congratulations! You finished your very first online multiplayer game. The easiest way to test your game is to create an executable of your project by going to File > Create Application, then change the file type to “Single runtime executable” and press Save.
167
After your project builds, you can run the game twice and two game windows will pop up. You should be able to play Tictactoe against yourself. If you want to test it further, you can copy the executable file onto another computer and make sure that the connection still works.
168
Chapter 16
Artificial Intelligence Enemies The main difference between artificial intelligence (AI) objects and player objects is their inputs. The player object is controlled by the mouse, keyboard, or gamepad. The AI objects are often controlled based on distance, collision, and visibility checks. TopDown Game Example Let’s look at a simple method for programming artificial intelligence in a topdown game. We will use a simple state system with some distance checks to accomplish this task. First, let’s start by creating some filler sprites. Create two new sprites and name them spr_enemy and spr_player . ● spr_enemy ● spr_player I just made them both 32 x 32 circles. My player is blue and my enemy is red. After creating these two sprites, we need to create two new objects. Name them obj_enemy and obj_player . ● obj_enemy ● obj_player Open up the player object and add a Create Event to the object. Inside this Create Event , add a Code Action and place this simple code inside. obj_player: Create Event /// Initialize the player hp = 1; We are setting the hp variable to 1 for the sake of simplicity. We will use the image_alpha property of the player object to show how much health the player has left. Because this property only goes from 0 to 1, it suits our purposes to set the health to 1 as well. After finishing up the code in the Create Event , add a Step Event to our 169
obj_player . Drag a Code Action into the Step Event and type this code in the action. obj_player: Step Event /// Control the player's movement varright = keyboard_check ( vk_right ); varleft = keyboard_check ( vk_left ); varup = keyboard_check ( vk_up ); vardown = keyboard_check ( vk_down ); if ( right ){ x += 4; } if ( left ){ x -= 4; } if ( up ){ y -= 4; } if ( down ){ y += 4; } image_alpha = hp; if ( hp <= 0 ){ game_end (); } This block of code allows the player to move and causes the game to end when the player’s hp variable is less than or equal to 0. It also sets the image_alpha property equal to the value of the player’s hp. As the hp value decreases, so will the image_alpha property. This will cause the player to become more transparent as he/she loses health. 170
Now, the player object is done. There really wasn’t anything too tricky there, and most of that code should look very familiar to you by now. Let’s move on the the fun part of this example, creating the artificial intelligence for the enemy. Open up the enemy object and add a new Create Event to it. Inside this Create Event , we are going to add a Code Action and create some variables inside that action. Here is the code we will add: obj_enemy: Create Event /// Initialize the enemy state_text =' idle '; state = scr_enemy_idle; sight_range = choose ( 96 , 128 , 180 ); attack_range = 24; spd = 3; These variables will be used to control our artificial intelligence. The first variable, state_text , is only used for debugging purposes. This will allow us to draw the current state of the enemy. The next variable, state , will store a script that controls the behavior of each state. As you can see, we are setting this variable equal to scr_enemy_idle . We do this because we want all of our enemies to start out in the idle state. The third variable, sight_range , will hold either 96, 128, or 180. These numbers represent the distance our enemy will be able to see the player from. The choose function will select a random number from the three values passed to it, causing each enemy to have a slightly different sight range. The fourth variable, attack_range , is the distance that our enemies will be able to attack from. The last variable, spd , will contain the value of the movement speed for each enemy. We are making the value 3, which is slower than our player. This allows our player to escape from the enemies. Now that the Create Event is finished, it is time to add a Step Event to our enemy. This Step Event will contain a Code Action with this bit of code. obj_enemy: Step Event /// Control the states script_execute ( state ); 171
This line is simple enough. We use the script_execute function to run whatever script is assigned to our state variable. This is a clean and easy way to manage the states of our enemy object. In order to make the game feel a bit cooler, and for you to be able to visualize how this artificial intelligence works, we are going to add some neat code into the enemy object’s Draw Event . Add a new Draw Event to the enemy object and drag over a Code Action . Here is the code you will place in that action: obj_enemy: Draw Event /// Draw self and state draw_set_halign ( fa_center ); draw_set_valign ( fa_middle ); draw_self (); draw_set_alpha (. 1 ); draw_circle_colour ( x , y , sight_range , c_red , c_red , false ); draw_set_alpha ( 1 ); draw_text ( x , y , state_text ); Now that we are drawing the enemy and its sight range, we are ready to write the scripts controlling its different states. The first script that we write will control the idle state. Add a new script and name it scr_enemy_idle . scr_enemy_idle ///scr_enemy_idle() state_text =' idle '; vardis = point_distance ( x , y , obj_player . x , obj_player . y ); if ( dis <= sight_range ){ state = scr_enemy_chase; } The script for the idle state isn’t too complicated. We make sure to change the state text variable (for debugging). Then, we get the distance to the player. If that distance to the player is less than or equal to our sight range, then we can change to the chase state. 172
We are using the chase state, but we actually haven’t created the script for that state yet. Let’s do that now. Add a new script and name it scr_enemy_chase . scr_enemy_chase ///scr_enemy_chase() state_text =' chase '; vardis = point_distance ( x , y , obj_player . x , obj_player . y ); vardir = point_direction ( x , y , obj_player . x , obj_player . y ); if ( dis <= sight_range &&dis > attack_range ){ motion_set ( dir , spd ); } else if ( dis <= attack_range ){ speed = 0; direction = 0; state = scr_enemy_attack; } else{ speed = 0; direction = 0; state = scr_enemy_idle; } The chase state script starts like the idle script, by setting the state_text variable. After that, we get temporary references to the distance from (and the direction to) the player. Next, we add some if statements to check to see whether we are within the sight range, within the attack range, or outside both ranges for our enemy. If we are within the sight range, we continue moving towards the player. If we are within the attack range, we stop moving and switch to the attack state. If we are outside both ranges, then we stop moving and switch back to the idle state. Lastly, we need to write the code that will be in the attack script. Add a new script and name it scr_enemy_attack . scr_enemy_attack ///scr_enemy_attack()
173
state_text =' attack '; vardis = point_distance ( x , y , obj_player . x , obj_player . y ); if ( dis > attack_range ){ state = scr_enemy_chase; } else{ // Attack if ( alarm [ 0 ]==- 1 ){ obj_player . hp -= 0.1; alarm [ 0 ]= 30; } } With the attack script, we set the state_text variable and get a temporary distance reference from the player. After that, we check the distance. If we are outside the attack range, we swap back to the chase state. If the player is within the attack range, we can subtract from the player’s hp by 0.1; we are also using an alarm to make sure that the attack doesn’t happen every step. If it did, our player would die in less than a second. Using this alarm forces the enemy to only attack once every second. In order to use the alarm, we have to add the Alarm Event to the enemy object. It doesn’t actually need to have any code in the event because we are controlling the alarm inside of our attack script. We do still need to add the event and add a Code Action or comment because GameMaker automatically removes events that don’t have any actions in them. Let’s add a new Alarm 0 Event to the object enemy and drag over a Code Action . In the action, type this: obj_enemy: Alarm 0 Event /// This is just a comment in a code action to prevent // GameMaker from removing the event. Now, both our player and our enemy objects are ready. Create a new room, add a few enemies to it, and add a player. Make sure that you have assigned the spr_enemy sprite to the enemy object and the spr_player spite to the player object. Once the
174
objects have been added to the new room, run the game and test it. Watch the behavior of the enemies. This simple artificial intelligence example teaches the basics of using a state system for enemy AI. This is a great place to start, and we have already come so far. Great work! Try to think of one more state that we could add to these enemies that would make them more interesting. It might be fun to try adding a wandering state that makes the enemies wander around between periods in the idle state. Play around with it and have fun! Platform Artificial Intelligence Platform games can be tricky for beginners, especially when it comes to the enemies and their artificial intelligence. I will describe two common forms of platform artificial intelligence in the next section, and I’m going to teach you how both of them work. Back and Forth Enemies Many platform games have a basic enemy that simply moves back and forth in some area of space. These enemies don’t aggressively seek out the player but act as a moving hazard that the player must avoid or deal with. Most of the time, these enemies only turn around when they encounter a ledge or a wall. There are many ways to program enemies like this. One method uses invisible objects placed in the room that, when collided with, cause the enemy to turn around. This method is straightforward and easy to program. It does make it harder to build each level, though, because the designer has to worry about placing two of these objects for every single enemy that they want to add to the room. We are going to look at a different way that allows the enemies to be slightly smarter and detect (by themselves) when they should turn around. Before we start programming, we need to create two sprites and two corresponding objects. Create two new sprites and name them spr_enemy and spr_solid . ● spr_enemy ● spr_solid For this example, I made my enemy sprite a red box with a size of 32 x 32 and my solid sprite a 32 x 32 gray box. Center the origin for the enemy sprite, but not for the solid sprite. Here is an image of both of my sprites and their properties: 175
Now create two objects and name them obj_enemy and obj_solid . ● obj_enemy ● obj_solid These will be the only two objects that we need for now. We don’t need to add any events or code actions to the solid object, but you should create a room and build a
176
simple platformer level. Once you have built a basic level, you can add a few enemy objects to it. Here is a screenshot of the level that I built.
Now that the room, objects, and sprites are ready, we can start adding the events and code actions needed for our basic backandforth platform enemies. Open up the enemy object and add a Create Event to it. Now drag over a Code Action and add this small block of code in it: obj_enemy: Create Event /// Set the initial state of the object state = choose ( scr_enemy_move_right , scr_enemy_move_left ); In this line of code, we are using the choose function to pick one of the two scripts and assign it to our enemy object’s state variable. We haven’t created these scripts yet, but we will shortly.
177
Now we need to add a Step Event to the enemy that executes our current state. Here is the code that we will use: obj_enemy: Step Event /// Execute the state script_execute ( state ); The two easy parts are done now. It’s time to create the two scripts that we will be using to control the enemy’s movements. Create two new scripts. Name them scr_enemy_move_right and scr_enemy_move_left . ● scr_enemy_move_right ● scr_enemy_move_left For this code, we need to check two things. We need to find out whether the space to the right is free of any solid objects. The other thing that we need to check is whether the section under the space to the right of us is a ledge or not. Open up scr_enemy_move_right and add this code to it. scr_enemy_move_right ///scr_enemy_move_right varright_free =! place_meeting ( x + 2 , y , obj_solid ); varxpos = x +( sprite_width / 2 )+ 1; varypos = y +( sprite_height / 2 )+ 1; varno_ledge = instance_position ( xpos , ypos , obj_solid ); if ( right_free &&no_ledge ){ x += 2; } else{ state = scr_enemy_move_left; } We use the place_meeting function to check for any objects to the right of us. If there aren’t any, then right_free will be set to true. We use instance_position to check whether there is a ledge to the right of us. If there isn’t a ledge, no_ledge will be set to 178
true. We use an if statement to check both of our temporary variables. If right_free is true and no_ledge is true, we can move the the right. If not, we change the state to start moving to the left. Open scr_enemy_move_left and add this code to it. The codes are very similar, the only difference being the direction for the checks and movement. scr_enemy_move_left ///scr_enemy_move_left varright_free =! place_meeting ( x - 2 , y , obj_solid ); varxpos = x - sprite_width / 2 )- 1; varypos = y +( sprite_height / 2 )+ 1; varno_ledge = instance_position ( xpos , ypos , obj_solid ); if ( right_free &&no_ledge ){ x -= 2; } else{ state = scr_enemy_move_left; } I’m not going to explain the code in this script because it works almost exactly like the other script. Test Your Artificial Intelligence After creating both of these scripts, we should be able to run our game and watch the enemies move back and forth. Smarter Enemies Congratulations! You have created your very first platform artificial intelligence. The enemies you created are common in many platform games, but once the player has figured out their timing, they are easy to avoid. For one more example, we are going to add to the previous example and create a platform enemy that attempts to chase the cursor around the level. For this smarter enemy, we will need to add better collision checking and gravity. The enemy will only have one state but that state will be more complicated than the states of the simple enemy. Add a new object to the game and name it obj_smarter_enemy . 179
● obj_smarter_enemy We can use the same sprite used for the first enemy, or we might use a sprite of a slightly different color. Add a Create Event to the new object and place this code inside the event. obj_smarter_enemy: Create Event /// Initialize the smarter enemy hspd = 0; vspd = 0; grav = 1; jspd = 14; state = scr_chase_mouse; We create quite a few instance variables here, and we will be using these different variables to control our object. We have a horizontal speed, a vertical speed, a gravity amount, a jump amount, and our state. Add a new Step Event . This event will control the state, the collisions, and the gravity. obj_smarter_enemy: Step Event /// Control the state and collisions script_execute ( state ); // Gravity if (! place_meeting ( x , y + 1 , obj_solid )){ vspd += grav; } // Horizontal collisions if ( place_meeting ( x + hspd , y , obj_solid )){ hspd = 0; }
180
// Move horizontally x += hspd; // Vertical collisions if ( place_meeting ( x , y + vspd , obj_solid )){ while (! place_meeting ( x , y + sign ( vspd ), obj_solid )){ y += sign ( vspd ); } vspd = 0; } // Move vertically y += vspd; The first part of this code runs our current state. After that, we check for a solid under the enemy and apply gravity if there isn’t something solid there. In the middle section, we set our horizontal speed to 0 if there is a collision horizontally. After the collision check, we apply the horizontal movement speed to the enemy’s x position. Lastly, we check for vertical collisions. Because the vertical movement has a speed that could change (due to gravity), we need to make this collision check smarter. We use a while statement to move the enemy object up against solid objects in the vertical axis. Then we apply the vertical speed to the y position. The last thing that we need to do in order to get our smarter artificial intelligence moving is write the scr_enemy_chase script. Add a new script and add this code to it. scr_enemy_chase ///scr_chase_mouse() if ( point_distance ( x , y , mouse_x , y )> 16 ){ if ( x < mouse_x ){ hspd = 4; } else{ hspd =- 4; } } else{ 181
}
hspd = 0;
// Set up check variables varon_ground = place_meeting ( x , y + 1 , obj_solid ); varmouse_above =( mouse_y < y ); varwall = place_meeting ( x + hspd , y , obj_solid ); varxpos = x + sign ( hspd ); varypos = y +( sprite_height / 2 )+ 1; varledge =! instance_position ( xpos , ypos , obj_solid ); // Check for jump if ( on_ground &&mouse_above && ( wall ||ledge )){ vspd =- jspd; } The first check in our chase script is our distance from the mouse position. After that, we see if we are to the left or the right of the mouse. If we are to the left, we move right, and if we are to the right, we move left. That part is rather simple. Deciding when the enemy object needs to jump is a slightly more complicated segment. We start by setting up our different checks. We use place_meeting to see whether we are on the ground. We use the mouse’s y position to see if we are above or below it. We use place_meeting to check our movement direction to see if there is a wall. Finally, we use some position checks and instance_position to see if there is an edge. Once all of our check variables are set up, we can write the if statement that checks our different conditions. We use the and operator to make sure that we are on the ground and that the mouse is actually above us. If those two cases are true, we use the or operator to check if there is a wall or an edge. In both of these cases, our enemy will jump. Testing Your Smarter Enemy Make sure you have added the new enemy to the room, then run the game. The enemy should make an attempt to chase the cursor around the room. The artificial intelligence isn’t perfect, but it works quite well for most situations. 182
Pathfinding in a Maze One of the other topics that I will teach you is how to make an enemy that is smart enough to navigate a maze. GameMaker does a good job of simplifying this process for you, but there are still a few things that can be confusing for people who have never done it before. I’m going to show you how to harness this amazing feature of GameMaker Studio and teach you some tips that will help you to avoid pitfalls that you might encounter. The Enemy in a Maze Example Let’s start to set up our pathfinding. Open up a new GameMaker project and add two new sprites to it. These two sprites will be named spr_enemy and spr_solid . Center the enemy sprite, but leave the origin of the solid sprite at 0,0. ● spr_enemy ● spr_solid I made both of my sprites simple boxes with dimensions of 32 x 32. After creating these sprites, add 3 new objects to the game. Name them obj_enemy , obj_solid , and obj_grid . ● obj_enemy ● obj_solid ● obj_grid Assign the enemy sprite to the enemy object and the solid sprite to the solid object; leave the grid object without any assigned sprite. Create a basic room and give it a height of 640 and a width of 360. We can name the room whatever we like, but I named mine rm_maze . Add in the grid object, the enemy object, and create a maze in the room using the solid object to form the walls. Be sure to keep the wallsection objects ( obj_solid ) snapped to a 32 x 32 grid. Here is a screenshot of my room:
183
My solid objects are the dark gray ones and my enemy object is the red one. Once the room is complete, we are ready to start programming. Open up the grid object and add a new Create Event to it. Place this code in the Create Event : obj_grid : Create Event /// Using an alarm, wait one step, then create the grid alarm [ 0 ]= 1; The reason we are using an alarm here before creating the grid is to make sure that all of the solid instances have been created. We need them to be in the room before creating the grid and then adding the solid objects to the grid. There are other ways to do this (changing the creation order of your instances in the room), but this method will work for this example. Now, add the Alarm 0 Event . In this event, we will be creating the grid that GameMaker will use for the pathfinding. Once we have created the grid and added the solid objects to the grid, GameMaker can use that information to create a path between two defined points in the grid. 184
obj_grid: Alarm 0 Event /// Create the grid // Create some temporary variables varcw = 32; varch = 32; varhc = room_width / cw; varvc = room_height / ch; // Create the grid global . grid = mp_grid_create ( 0 , 0 , hc , vc , cw , ch ); // Add the walls to the grid mp_grid_add_instance ( global . grid , obj_solid , 0 ); First, we set up some temporary variables that we will use to pass to the mp_grid_create function. The cw and ch variables stand for the cell width and the cell height, respectively. Keeping these numbers as large as possible (while still ensuring that the enemy movement looks good) will reduce the likelihood of performance issues in your game. The hc and vc variables stand for the horizontal cell count and the vertical cell count, respectively. We calculate these numbers based on the room dimensions and the cell dimensions we are using. Now, we are read to create the grid. The first two arguments in our mp_grid_create function are the starting x and starting y position for the grid. The next two are the horizontal cell count and the vertical cell count. Lastly, we have the cell width and the cell height. Once we have created the grid, we need to add the solid objects to the grid to make sure that GameMaker knows that those objects should be counted as walls (which the enemy cannot walk through). We use the mp_gird_add_instance to accomplish this task. It takes three arguments: the grid we created, the instance to add, and whether or not to check precisely for that instance. A precise check will use precise collision checking on the sprite of the object passed in. Our solid object is just a box, so we don’t need to worry about precise collision checking. Now that the grid has been created, we need to make sure that we don’t end up with any memory leaks: we need to add a Game End Event , which will destroy the grid when the game is closed.
185
obj_grid: Game End Event /// Destroy the grid mp_grid_destroy ( global . grid ); We use the mp_grid_destroy function and pass the grid as an argument in order to destroy it. Good news! Your grid object is finished and the global.grid variable is ready to be used in our enemy’s smart pathfinding. Now, we just need to program the enemy. Surprisingly, because of the way GameMaker handles AI pathfinding, it is actually even easier than setting up the grid. We will program our enemy to move to a location in the maze that the user clicks on. Open up the enemy object and add a new Create Event to it. obj_enemy: Create Event /// Create the path path = path_add (); Here, we are using the path_add function to create a new path and assign it to the path instance variable. We will use this later when the user clicks a location in the maze. Now add a new Mouse > Global Mouse > Left Pressed Event to our enemy object and place this segment of code in it: obj_enemy: Global Left Pressed Event /// Find the mouse click and move towards it along a path varmx =( mouse_x div 32 )* 32 + 16; var my =( mouse_y div 32 )* 32 + 16; if ( mp_grid_path ( global . grid , path , x , y , mx , my , 1 )){ path_start ( path , 4 , path_action_stop , false ); } 186
First, we get a gridsnapped version of the mouse’s x and y position. Once we have that, we use the mp_grid_path function inside of an if condition to determine if a suitable path was found. The mp_grid_path function takes seven arguments. The first argument is the grid to use to check for a suitable path, the second is the path that the function will use to create a movement solution, the third and fourth are the starting position of the path (we use the enemy object’s x and y position), the fifth and sixth are the end position of the path (we use the mouse’s x and y position), and the final argument is a boolean value that determines if the object can move at 45 ° angles in the path. We set this to true. If the mp_grid_path function is able to find a solution, we will enter into the block of our if statement and start moving along the path that we have created. To move on this path and determine what action occurs when the object reaches its destination, we use the path_start function. For the first argument of this function, we pass the id of the path to use. Next, we pass the speed at which the enemy object should move along the path. After that, we pass the action that should be performed at the end of the path; path_action_stop will stop the enemy when it reaches the end. Finally, we pass a boolean that tells the function whether or not the path is relative to the object or to the room. A value of false indicates that the path will be relative to the room and that works just great for our example. Once again, congratulations! Run the game and test your enemy. If you click on an area of the room that is possible for the enemy to reach, it should, very cleverly, move along the shortest path until it reaches that point. If you want to learn more about how GameMaker accomplishes this task, you can research the A* algorithm. Now, you have one more weapon in your arsenal of artificial intelligence tactics, and you can make your enemies even smarter! Experiment with Artificial Intelligence Hopefully this chapter has taught you a few different tricks for artificial intelligence and you now have some ideas of your own or even ideas on how to improve the examples in this chapter. Keep in mind, smart artificial intelligence doesn’t always make your game fun; sometimes, if the artificial intelligence is too good, then the game isn’t enjoyable. Have fun with your enemies, but remember that your game is the most fun when the artificial intelligence is challenging, not punishing. And often, you can have them be challenging with very simple artificial intelligence.
187
Final Info
Contact and Kickstarter My Contact Information If you have any questions or concerns, don’t hesitate to contact me! The best way to contact me is by email, but I will list my other profiles as well. Be sure to check out my YouTube Channel for free tutorial videos on GameMaker Studio. Email:
[email protected] Website: heartbeaststudios.com YouTube: youtube.com/uheartbeast Twitch: twitch.tv/uheartbeast FaceBook: facebook.com/heartbeast.studios Twitter: twitter.com/uheartbeast Tumblr: uheartbeast.tumblr.com Thanks Before I show the Kickstarter backer list, I want to say thanks to my wife Charly. She has been a huge support for me in the most frustrating moments. I want to say thank you to my editor Slade. He has been a pleasure to work with and I look forward to working with him more in the future. I want to say thanks to my family and all my friends. I want to say thanks to everybody who emailed me with feedback on this book, especially Chris Sanyk for going above and beyond. And finally, I want to say thanks to you. Thank you for buying my book. Your support means the world to me. Stories If you found this book helpful or inspiring in a very specific way, then email me your story! I will be collecting stories from my readers and YouTube viewers and if your story strikes a chord in my soul, it might be featured in that collection, and sent out with every future copy of my book.
188
Top Kickstarter Backers Here are the top Kickstarter backers. Thank you guys! ● David Almirall $148.00 ● Steven Wise $100.00 ● George Hopper $82.00 ● Jonathon McClung $80.00 (For USA Shipped Book) Kickstarter Backer List Here is a list of all the other Kickstarter Backers. You guys and gals made this book a reality. Thank you so much! $80+ Backers Kyle Trehaeven, Floriah Fischer, Chris Ahmad, Frank Kristiansen, Gera Hmurov, Ingo Liebig, David Till, Robert Parry, Tyler Reddick, Rob Lyndsey, Jono, Markus Lange, Francis Fitzgerald, Lance Tofsrud, Svein Daniel, Solvenus, JanErik Matz, Paul Cook, David Ward, Kevin Scorey, Arthur Uiterwijk, Mathias Nervik, Eleni Merianou, Gustavo Arantes, Tetruashvili, Stelios Potamitis, Chee Lup Wan, Gil Ferrand, DarkCoolEdge, Omar Alterkait, Brodie Helmore, Jose Antunes, Jussi Kukkonen $60 Backers Christopher M. Bell, ReMeX, Lewis Allard, Donovan Anderson, Caleb Anderson, Aaron Freeze, Chinua White, Matthew Jarvis, Chris Sanyk, Demetri Mallous, Quentin Thomas, Matthew Mather, Josh Dresner, James Ryan, Jonathan Wulff, Azuz, Hugo, Zachary Mapes, Trevor Williams, Dallas Bowland, Kenneth Kline, Jonathan Bergeron, Joe Healy, Saadman Rakib, Khan, Stephen Jolly, Uriah Maravilla, Greer, Andrew Jeremy, Goetz, Christopher, Bentley, Kyle Wrigley, Dae in Cho, Scott Goldsmith, EvilLinux, Jeffrey Stockton, Gopal Vithlani, JeanDenis Haas, Josh Fields, Anna Munoz, Raighne Hogan, Hylyncks, Slade Stolar, Endless Mike, Grant Cable, Natascha Buter, Adrian Lamar, Nick, Daniel Custer, Gregory Lee II, Patrick Polk, James Kaucher, Hamed AlRyami $30 Backers Joel Ilett, Alonso, Mikael, Eulogio Enamorado, Pallares, Tom Mason, Matthew Sylvester, Marvan Alkufai, Timothy, Miguel Angel, Garcia Guerra, Jacob Duffy, Sheldon Sims, Christophe Liaret, Israel RN, Bryan Lumb, Aaron Parsons, Michael Dailly, Diall Delmer, Mats Wallin, Jonathan Johnson, Joe Jerkovic, Jordan Murphy, William Richards, Gamebot School, LLC, Devin Kaufman, Tyrone Swart, Andrew Strauch, Eduardo Augusto, Nicole Imber, Raymond Hegge, Jeff, Jonathan “Ardua”, Doyle, 189
Stephen Maden, Carlos, Joe Wilcox, Jon Bursey, Marcio Mattos, Anucha, Wongkarnkah, Gibo, Bob Thulfram, John George, Freed Castillo, RCMADIAX LLC, Faisal Alkubaisi, Baron Beckenham, Bloomer, Javier Martinez, Jeff Watson, Pokemonrey6, John Peterson, Leonid, Jaime Chan, Diego Piaggio, Arthur Chan, Carron Ohree, Jonathan, Mcillwaine, Jeremy Lenzo, Jonathan Trafton, Dafydd Francis, Blue Moth Studio, Burnen Thyme, Victor, Andreas Avoukatos, Tyler Sease, Miguel Castro, Marcus Moore, Christian Belding, Chris Sheldon, Jackie, Rodrigo Gomes, Robin Playe, Krystof Horacek, Abbenano, Stuart Sulaski, Brian Danforth, David Conover, Lubin Hadalgo, Chris Wahl, LauriMartti Kojo, Andrew Rodrigue, David Farina, Melissa Musgrove, Hobbyaescala, Karin Portier, James Clark, Laritza Castaneda, Sven Sowa, Gabriel Quintana, Maik Roseboom, Tasha James, Martin Gebhardt $25 Backers Andrew Gasson, David Jagneaux, Marshall, Tim Hofstee, Hakun, Matthew, Humphries, Tehwave, Alex O’Neill, Jesse Call, Raymond Spiteri, Colonel Fubar, Ildradil, James Rozee, Josh Hash, Federico Colovini, CH Wan, ZpeedTube, Kimmo Savilampi, Philippe Abi Saab, Jonathan Foster, Thryn Henderson, Symbios, William A Burgess, Thrashonkel, Bill Loguidice, Alex Greenwood, Jermain Kanhai, Benjamin Gemmel, Plasma Toy, Studios, Remi D. Finjord, Tommy Wedin, ScyldScefing, Louis Giliberto, Pakoito WooS, Jon Trew, Kyle Frick, Alex Maskill, Matthew Vine, Thomas Herth, PhiRune, Carlos Diaz, Colby Ryan, Maulden, Carlos Martinez, Ben Pledger, Isao Sasaki, Ryan Schuzle, Brian, HyperSloth, Cheryl Howard, Arturo Sanchez, MrNeeNaw, Robert Stockamp, Kong Nyian Sii, Woggos, Toshirolshii, David Brittain, Redstart Industries, Budd Royce, Gabriel Johnson, LazySiege, Cimmarian, Daniel RyJek, Wojcik, John Hoffer, Oscar Bagger, David Batty, Ronald, Alexandre Moisan, Fabiano Martino, Alberto Morales, Gustavo Delgado, Ben Whittaker, Nick Landry, Leong Wai Yin, Hunter Harris, Kevin Pass, c3sk, Marshall Nguyen, Mikael Myntti, Josh, Sam Whillance, Kris King, Dominic, Andreas Sjostrand, Sammywhs, Simone, Claes Wiklund, Spencer, Alexander Hertzler, Malthe, Falkenstjerne, Jorge G, Josh Henry, Steven Guzman, Gunnar Hogberg, Johann Mayac, Austin Siagian, Andrew, Wooldridge, Felipe Nanni, Rogue Dues, Carolos Navarro, Roman, Fran Orellana, Matthew Hester, Ludvig, Cameron, Simwad, Deepra Smith, Ethan Swords, Paul Broad, Shiki Matsuri, Rishi Ilangovan, Juan Hernandez, RobbyTheChaotic, Janette Leis, Rafal Toczyski, Metatronaut, Antun Kesegic, Romulo Pereira de Araujo, Tom Abbott, Leo Stefaninos, Ralf Zhan, Cosimo Lattanzio, Eka Pramudita Muharram 190
$5 Backers Melissa Anderson Francisco, Slimey Jenkins, Helge Sverre, Greg Mckechnie, Nazariglez, Blue Social Network (Julian), Camijn, Jose Vizcarrondo, Shane Heres, Marilu Aguilar, Brigit Fasolino Vucic, Sean $1 Backers John Sturgill, Leonard Burns IV, Duo, Aaron Zemetres, David Mathias Simacek 2, Tomasz Michal Filip, Kaczmarek
191