© Yadu Rajiv 2018
Yadu RajivDeveloping Turn-Based Multiplayer Gameshttps://doi.org/10.1007/978-1-4842-3861-5_3

3. Making Your First Game

Yadu Rajiv1 
(1)
Bangalore, Karnataka, India
 

Before we create a game within GameMaker Studio (GMS) , it is good practice to have your ideas detailed out on paper. I begin almost all my game projects on paper. Designing a game on paper gives you a clear picture of all the rules, systems, mechanics, and the interactions that can happen in a game. Prototyping on paper makes your ideas tangible, playable, easy to test, and changeable early on. Different people have different approaches to Game Design; I practice two different methods that work the best for me. During jams or when you are on a very tight schedule, you would go the tried and tested way of a game mechanics as a first approach. Here, you decide what the player is going to do in the game and then build other layers of information (backstory, the game world, non-playable characters, other interactions, etc.). The second approach that I take is to build out layers of information from a central premise (theme). This approach is slightly more time-consuming and not always successful, but the process brings a lot of clarity, depth, and understanding to the game you are building. For example, you want to build a game around the theme “Heist”; you start by asking questions to yourself about the theme and those involved in it. What is this game about: a heist. Who is stealing and what? From where? And why? Are they alone? If not, how do the others help? Where does the player get his “jobs” from? What motivates the player/avatar? Who are the antagonists? What motivates them?

The idea here is to build a system of interconnected information sets that eventually lead to a mechanic. In the above example, what is the game about gives you a rough idea of what the player does in the game? When you dig deeper by asking how, you are forced to think in that direction, making choices along the way—one outcome could have been that the player must avoid security systems to steal items from specific locations and escape. Answering one question then leads you to more questions—how does the player avoid security systems? What happens if an alarm is tripped? Do they steal one item or more? How does the player find them in these locations? How will the player escape? These questions bring more clarity to your design and point directly to mechanics, interactions, and features that need to be implemented.

Sketching the Game

For our first small game, we will design and create a Space Shooter. In this game, the player controls a space craft, you shoot at waves of enemies that attack you for points, and you avoid getting shot at by your enemies. The game ends when you die and your high score is saved for eternity, or until the next player beats it. Let’s start by sketching out what the game could look like (Figure 3-1).
../images/456885_1_En_3_Chapter/456885_1_En_3_Fig1_HTML.jpg
Figure 3-1

A peek into my notebook; a sketch of the whole game—title screen, game screen, and game over screen, all annotated with quick notes for future reference

Putting it Together

Now that we have a sketch of how our major screens will look and what components they might contain, let us organize them into different rooms, and then we may be able to organize each object and item accordingly as well in the resources (Figure 3-2). In simpler terms, rooms work like scenes, and you have instances of objects in these rooms. Each instance can listen to events and act accordingly. All the resources in the Resource panel are available to us via scripts or events so we can access, use, and manipulate them dynamically.
../images/456885_1_En_3_Chapter/456885_1_En_3_Fig2_HTML.png
Figure 3-2

A diagramatic representation of our game and its components

Starting Our New Project

Let’s start by creating a new project using the GameMaker Language and resizing the room to 1024 pixels and 768 pixels for width and height, respectively, like we did in the previous chapter. Once that is done, let’s create the background image for the room. In the resources panel on the right side, right-click on sprites and then select Create Sprite. A new sprite called sprite0 is created and you can now see the sprite in your workspace.

The newly opened Sprite Editor can roughly be divided into three parts (Figure 3-3).
  • On the left, you have the sprite’s name and other properties and functions.

  • On the right side toward the top, you see some animation controls and a timeline with one frame in it.

  • Toward the bottom, you see a preview of the image contained in that selected frame.

../images/456885_1_En_3_Chapter/456885_1_En_3_Fig3_HTML.jpg
Figure 3-3

Resizing the new sprite for our title

For now, we have one frame and it is 64 pixels in width and 64 pixels in height and is empty (transparent). Since we created this sprite to be used as the title background for our game, let’s change its size. In the properties to the right you have our sprite’s name; let’s change that to sprTitleBackGround . It is good practice to name your assets with a prefix (in this case, spr, for sprite) so that you can have unique names for different kinds of assets and can easily differentiate between them.

The second thing we want to do is to change the size of the image. Right below where we changed the Name, the current Size of the image is shown; click the button here with arrows to four corners. In the new window that pops up, select Resize Canvas, and change the size of the image to 640 pixels and 480 pixels for width and height, respectively, and then click apply.

Now to edit our image. With the first frame of our sprite selected, click on the Edit Image button in the Sprite Editor. Clicking this button opens this sprite in the internal image editor.

Tip

At this moment, if you need more space to work with, you can quickly collapse and expand all the docked panels by hitting the F12 key on your keyboard. The same functionality is available as a button on the toolbar as well.

The Image Editor within GMS2 is powerful but lacks the kind of functionality that a dedicated image editing and manipulation program provides. For the time being, I’m using the Image Editor to create a placeholder sprite that we can change later. Now let’s add it to the background of the room. Click on the Background layer in the Room Editor, and in the properties, click where it says No Sprite. In the window that pops up, select the sprite that we just created. If you do not need the background to be tiled or stretched, be sure to disable these options, as they may affect performance. Now it should say sprTitleBackGround instead of No Sprite, and you will also notice that the background in the room has also changed (Figure 3-4). Run the game to see it in action.
../images/456885_1_En_3_Chapter/456885_1_En_3_Fig4_HTML.jpg
Figure 3-4

Our game running with a sprite for a background

Creating Objects and Their Instances

Now that our title screen is partially done, let’s add the main two buttons with which the player will interact: the Start button and the Quit button. For that we need to create objects. Objects are GMS’s way of storing items that go into the game; so once you create and store an object in your project, you add an instance of that object into a room simply by dragging and dropping it.

Let’s create an object now by right-clicking on Objects in the Resources Panel on the right. As soon as you hit Create Object, a new object is created for you and is brought to focus in the workspace. Similarly to the Sprite Editor, the Object Editor shows you the current object’s properties. Let’s rename this object to objBtnStart—short for object-Button-Start. We will use this as our Start button. You will notice that there is no sprite selected for this object. Without selecting a sprite for an object, that object will not be visible in the game (even when it is marked visible). It is also worth mentioning that an object without a sprite that is marked visible can have visual representations drawn to screen via the Draw and Draw GUI events.

Let’s give players something to see and click on; create a new sprite by clicking the plus button right above where it says No Sprite in the Object Editor. This instantly creates a sprite for you to edit. Like we did with the background, let’s change the name and size of the button sprite. We will call this sprite sprBtnStart and change the canvas width and height to 128 pixels and 64 pixels, respectively. For the time being, let’s just fill the image with a solid color of red and key in start using the text tool available in the Image Editor.

Once you are done editing, close the Image Editor and open the Room. From the Resources Panel drag and drop the newly created objBtnStart into the room (make sure you have the instances layer selected in the Room Editor). Let’s quickly do the same and create a new sprite called sprBtnQuit for the Quit button and add it to a new object called objBtnQuit . Once this is done, let’s add an instance of this object into the room.

Tip

At times, you will end up having to make many copies of both sprites and objects; to make your life a lot easier, you can duplicate almost everything in the Resources Panel by just right-clicking on the item you want and hitting Duplicate. Although if you see yourself creating too many duplicates, maybe it is a sign that parents, variables, or instance creation code should be used. Having a clear hierarchy will help manage and maintain your projects better as they grow bigger.

For the time being, the buttons that we have created are not functional (Figure 3-5). We want them to behave and function like buttons when interacted with. We can achieve this by working with events.
../images/456885_1_En_3_Chapter/456885_1_En_3_Fig5_HTML.jpg
Figure 3-5

Our non-functional buttons in the running game

Working With Events

Games, as you know, are perpetually running applications—they are constantly doing something in the background, even if your players are not doing anything. Your game is constantly updating even when there is no user input; it could be updating physics, animating sprites, updating objects and their properties, drawing objects and their sprites to the screen, playing audio, dealing with user input from many devices, and more. With each update and when new information is processed, GMS2 sends out messages to anyone who wants to listen about these changes and updates. These happenings that GMS2 encounters are called events. For example, when the user presses a key on the keyboard, GMS2 finds out and tells anyone who is listening that a key press event has occurred. Similarly, when the player clicks with their mouse, a mouse click event for that specific mouse button is fired.

All objects can listen for events, some of which are specific to that object and its instance and some events that are global in nature. To clarify, an event like Create is fired when an instance of that object is created for the first time in a room; similarly, the Destroy event is called when the instance of that object is destroyed. Whereas, events like Room Start/End and Game Start/End are not particularly associated with the instance of any object, but all objects can listen for these events and get notified by the system when they occur.

In the case of our buttons, we need to find out when the player clicks on the button. To achieve this, let’s start by editing the objBtnQuit Object in the Resources Panel on the right side (double-click to edit). After opening the object in the workspace, click on the Add Event button in the connected Events Panel for the object. In the pop-up menu, select Mouse and then Left Released to add this event. This event will get fired when the player clicks and releases their left mouse button on our object. You would have noticed that a new code window has now opened. Let’s write some code here.
/// @description Handling quit button click
show_debug_message("Quits the game");
The first line is a description of what the code does; for us, it handles the click event on the Quit button. The second line prints a message into our Output console. You can see what the code window looks like in Figure 3-6.
../images/456885_1_En_3_Chapter/456885_1_En_3_Fig6_HTML.jpg
Figure 3-6

Adding the Left Mouse Released Event to our button

Tip

show_debug_message(...) is a built-in debug function that we will use extensively in the chapters to come. It is a handy way to output data to the console when debugging.

Run the game and click the Quit button to see what happens. You will see Figure 3-7.
../images/456885_1_En_3_Chapter/456885_1_En_3_Fig7_HTML.jpg
Figure 3-7

Running the game and clicking the button

Now that we have the Quit button working, lets implement the code needed to quit the game. To quit a running game, GMS2 has a handy function: game_end() . Calling game_end() destroys all objects and rooms and exits the application. Since some platforms like iOS, Windows UWP, PS4, the Switch, and the browser targets handle how applications are closed, calling the game_end() function can cause an error and your game to crash. Hence, we need to check what platform we are on and then call this function.

What we will do is to add a Create event to both objBtnQuit and objBtnStart and show the appropriate button on the right platform. If the game_end() function can be called, we will let objBtnQuit be, but if it cannot, then we will use the instance_destory function to remove the instance of that button.
if(os_type == os_ios || os_type == os_uwp || os_type == os_xboxone || os_type == os_ps4 || os_type == os_switch || os_browser != browser_not_a_browser) {
        instance_destroy();
}
Listing 3-1

Code for the Create Event in objBtnQuit

In the Create event for objBtnStart, if we are to remove the Quit button, then we need to reposition our start button to the center horizontally.
if(os_type == os_ios || os_type == os_uwp || os_type == os_xboxone || os_type == os_ps4 || os_type == os_switch || os_browser != browser_not_a_browser) {
        x = room_width/2 - sprite_width/2;
}
Listing 3-2

Code for the Create Event in objBtnStart

The os_type variable holds a value corresponding to the current platform on which the game is running. We check if we are on a valid platform that supports quitting the game and then show only the right button.

Now that we have a working Quit button, let’s do the same with our New Game button. The New Game button takes the player to a new session of out space strike game. Before we add any code to the New Game button, we need to create a new room. Let’s go ahead and create a new room by right-clicking on the Rooms List in the Resources Panel. In the Resources Panel, right-click on the newly created room and rename it to rmGame.

In the Left Mouse Released event for objBtnStart, we add the code go to our newly created room.
room_goto(rmGame);

The room_goto() function takes one parameter, which is the number of the room or the name of the room as it appears in the Resources Panel. Run the game to see the Start button in action. Since we have no content in the new room, you will be greeted by an empty black room. In the next section, we look at adding more content and functionality into our game.

To view up to this point in the project, please open the file gms2_ch03_01.yyz.

Creating the Game

Since our goal is learning the interface, we won’t dwell too much on making the art but, rather, will import existing artwork. All the assets used in our game can be found in the assets folder along with the projects for Chapter 1.

Our first job is to create our player avatar. Let’s do so by creating a sprite and importing our player ship into GamerMaker Studio. To create a new sprite, you need to right-click on the sprites section in the Resources Panel, and then click on Create Sprite. Once the sprite has been created, it will come into focus in our workspace. Click the Import button on the Sprite Editor to import our player’s avatar spacecraft. Finally, rename the sprite to sprPlayer.

Once the right image has been imported into the sprite, we move on to create an object for the player’s ship. Create a new object and rename it to objPlayer and assign our newly created sprite for it (sprPlayer).

Once you are done, drag and drop the objPlayer object onto our empty room: rmGame (see Figure 3-8).
../images/456885_1_En_3_Chapter/456885_1_En_3_Fig8_HTML.jpg
Figure 3-8

The player’s ship in the new room

Our next step is to make the player ship move according to the player’s input. For this game, we will only be using the mouse for both movement and firing bullets. We will also restrict the player’s movement to the horizontal (x) axis.

Add a new Step event to our player’s object: objPlayer . Once this is done, update the player’s x-position to that of the current mouse x-position.
x = mouse_x;

Run the game now and you will see that as you move the mouse, the ship also moves along the x-axis. Now that we have the ship moving, let’s add some firepower!

Firing Bullets

Firing and bullets are two different problems that we need to solve. Firing involves processing input from the player, firing (creating an instance of) a bullet and then waiting a fixed amount of time before firing again, if required. Bullets themselves are objects that, once created, will travel in a direction with a set speed. When they hit enemy players, they affect the enemy in some way and get destroyed.

To fire a bullet, we first need to create one. Like with the player object, we start by creating a sprite for the bullet. Once the sprite is created, create a new object and add our sprite it. Once done, rename the object to objLaserBlue . Once the object has been created add a Create event for it. Add the following code in the Create event for the laser:
vspeed = -20;

Objects have a bunch of very useful properties that we can take advantage of—one of them being the vertical speed of that object. Once the vertical speed of that object has been set, it will start and continue to move vertically with each frame. Since we want this bullet to go upward, we have set its vertical speed to -20. Now that the bullet has been created, it is a matter of firing it.

Our bullets need to be fired when the player clicks the mouse left button and when the mouse button is held down. If the mouse button is held down, it needs to fire at a fixed interval. To achieve this, let’s add two events: one for Create and the next for Global Left Down.

In the Create event for the player object, we create a new variable that is used to count the number of frames that have passed since a bullet was fired.
shotCounter = 0;
In then Mouse, Global, Left Down event for objPlayer, add the following code:
/// @description player firing
if((shotCounter % 10) == 0) {
        shotCounter = 0;
        instance_create_layer(x-12, y-12, "Instances", objLaserBlue);
        instance_create_layer(x+8, y-12, "Instances", objLaserBlue);
}
shotCounter++;
The Step events of all objects in the room get called on each frame. Our frame rate or the number of times the room is updated and drawn is fixed and is set in the Options/Main dialog in the Resources Panel (unlike the old GMS, where the fps was set per room). Our game is currently drawn at a rate of 30 frames per second (Figure 3-9). This means our Step event gets called 30 times every second. We use the instance_create_layer() function to create an instance of objLaserBlue at the player’s current position, every 10 frames.
../images/456885_1_En_3_Chapter/456885_1_En_3_Fig9_HTML.jpg
Figure 3-9

Main game options dialog, where our frame rate is currently set to 30 fps

There are two functions that let you create an instance of an object in a room and they are:
instance_create_depth(x, y, depth, obj);
and
instance_create_layer(x, y, layer_id, obj);

When using instance_create_depth() , it takes an x- and y-parameter, which determine where the new instance of the object (obj) should be placed, and a depth at which the object should be created. Since all instances of objects must be created inside a layer, GMS creates and manages a layer if you use this function. This is an internally managed layer, and you have no access to this. We use the second function to explicitly create our laser instances in our primary Instances layer. The layer_id is the name of the layer we want this instance to be created in, passed in as a string. So, when the user clicks anywhere on the screen using the left mouse button, we create two instances of our objLaserBlue object at either side of the player where our two cannons are located. Once these instances are created, because of the vspeed we have set, they move upward with each passing frame.

Adding Sound to Your Shots

To make this a bit juicier, let’s add a bullet firing sound to the mix. Before we can play a sound, we need to add it to our resources. To add a sound, right-click on the Sounds Section in the Resources Panel and select Create Sound. In the new panel that opens in the workspace, change the name of the sound to sndLaser_fire_2 (Figure 3-10).
../images/456885_1_En_3_Chapter/456885_1_En_3_Fig10_HTML.jpg
Figure 3-10

Creating a sound resource, loading an audio file, and previewing it

Click the button with three dots next to where you edited the name to load the actual sound files from your assets folder. Open and load the file named sfx_laser2.ogg. Click the Play button in the controls to hear a preview of the sound. Once we have our sound loaded, let’s add to our firing code in objPlayer’s Global Left Mouse event. Since we only want to play the sound when we fire the laser, we will add it inside our already existing if condition and our new code will look like
/// @description player firing
if((shotCounter % 10) == 0) {
        shotCounter = 0;
        instance_create_layer(x-12, y-12, "Instances", objLaserBlue);
        instance_create_layer(x+8, y-12, "Instances", objLaserBlue);
        // play laser firing sounds
        audio_play_sound(sndLaser_fire_2, 10, false);
}
shotCounter++;
The audio_play_sound() function takes three parameters, the index or name of the sound resource(it is worth noting that the name is the actual resource name in the Resources Panel and not a string), a number that lets the audio subsystem know how important playing back this sound is, and a Boolean (True or False) value stating if this sound must be looped or not. Different systems can play a different number of sounds simultaneously, and this is a limited number. The value you pass to this function as the priority dictates which sounds are played and which sounds stop playing when the channel limit has been reached. In our case, this won’t be a problem, as we are not playing too many sounds simultaneously.
audio_play_sound(index, priority, loop);
Now that we can fire lasers, let’s do a bit of housekeeping. All these lasers we have created are slowly moving outward due to their vspeed, and they will not stop until the game ends. Each laser takes up a small chunk of memory and processing power of both the CPU and the GPU; it may not be much, but when you fire of hundreds and thousands of them, they all add up to a huge number. So, to save ourselves from this memory bottleneck we must destroy these lasers once they are of no use to us. In our case, once the laser is beyond the scope of the room, we can safely say that they are of no use to us. GMS has a handy way of telling instances when they are outside of the room. GMS does it by calling the Outside Room event. To add this event, open up the objLaserBlue object and add the Event from Other/Outside Room. Once we are outside the room, we tell GMS to destroy that instance by calling the instance_destroy() function .
/// @description Clean up when outside the room
instance_destroy();
The instance_destroy() function takes two optional parameters: the first one is the id to the object to be destroyed, which by default is the calling instance, and the second optional argument is a Boolean value if the Destroy event for the object should be called, which by default is true. Apart from the calling object, you can pass in an id of an object you want to destroy. You can also destroy all instances of an object by passing in the object’s name from the Resources Panel. You can further use the special keywords other or all with this function as well.
instance_destroy([id, execute_event_flag]);

Hiding the Cursor

Before we move on to creating enemies we can shoot at, let’s do a bit of cleaning up. Right now, when you move the mouse cursor around, you can see that your ship follows it in the x-axis. The problem is your mouse cursor can be a distraction. Let’s hide the mouse cursor when we are playing the game.

Like the Create event in objects, the room also has a Create event. This event is called once when each room is created. As good practice and for ease of management, we will create a script resource and then call this script resource from our Room’s Create event.

To create a script resource, right-click on the scripts section in the Resources Panel and select Create Script. The newly created script opens in your workspace; let’s rename it to something memorable. Since this is code that gets executed once when rmGame is created, let’s rename to scrGameCreate. In our script, let’s write the code to hide our mouse.
/* hide the mouse */
window_set_cursor(cr_none);

window_set_cursor() takes in one parameter, which is what cursor to use. We pass in a system constant, cr_none, which tells GMS that we are not using a cursor. Now that we have the code in place, let’s execute this script when the room gets created.

To access a room’s Create event, you must open a room in the Room Editor. In the Resources Panel, under Rooms, double-click to open rmGame in the Room Editor. In the Room Editor’s properties panel, on the left toward the bottom, you can find that room’s properties (Figure 3-11).
../images/456885_1_En_3_Chapter/456885_1_En_3_Fig11_HTML.jpg
Figure 3-11

Click the Creation Code button to see the code that gets executed when this room is created

Click the button labeled Creation Code, and this will open the room’s Create event. Any script you write here will be executed once when the room is created. From here we are going to execute the Script Resource we have created.
scrGameCreate();

We will call scrGameCreate() just like any other function from within the room’s Create event.

Now when we run the game and click on the Start button, the mouse cursor is hidden from us in the new room. At the onset, it might seem a bit too much to do these two extra steps, but in the longer run having all your scripts in one place makes for easy editing and management.

Going to Space

We have a ship on the screen; we can move it around and shoot some lasers. To make it a bit more like we are in space, let’s add some backgrounds. Just like we created the background for the main menu, we will first create a sprite with the images we want to use as our background for our game. The only difference is that instead of selecting just one image, we will use four images. Load the four background images from the assets folder UI/bg into a sprite, and rename it to sprBg (Figure 3-12). In the Sprite Editor, make sure that you have set the animation speed to 0, so that these sets of backgrounds are not animated.
../images/456885_1_En_3_Chapter/456885_1_En_3_Fig12_HTML.jpg
Figure 3-12

Setting our newly created sprite as the background for the room

Open rmGame and select the background layer-I have renamed mine to backgroud_space. As we did before, click on the button labeled No Sprite and select our newly created sprBg as the sprite to use. Since our background image is smaller than the room size, let’s set it so that the image can be tiled vertically and horizontally. To do this, check the two check boxes labeled Horizontal Tile and Vertical Tile. Now that we have them tiled, let’s animate them! GMS lets you set a horizontal and vertical speed for its background layers. Let’s change Vertical Speed to 5 so that the background moves from top to bottom. Run the game now and you will see that we are now flying through space. Feel free to play around with the horizontal and vertical speed components to see what happens.

To make things a bit more interesting, let’s change the background space image every time you start the game to a random one. Our sprBg sprite already contains four subimages—all we need to do is to change the background image of the layer to a random one every time the room starts. To do this we will add some code to the Create event of the room. Earlier in the chapter we created a script resource to be executed when rmGame is created; we can reuse the same script and append changes to it to set our random space background.
/// @description this script is executed when rmGame is created
/* hide the mouse */
window_set_cursor(cr_none);
/* set a random image from our background sprite as our backround for this room */
var lay_id = layer_get_id("Background_space");
var back_id = layer_background_get_id(lay_id);
var bg_index = irandom(3);
layer_background_index(back_id, bg_index);

Right below where we hid the mouse cursor, we use the layer_get_id() function to get the id for our background layer, here called Background_space . Once we have the id for the layer, we use that id to get the id associated with the background for the layer. We can then use one of the many layer_background_* functions to manipulate the background. Here we need to randomly change the index of the background. Our sprite contains four frames, and these subimages are indexed from 0 to 3, which makes four images. By default, the first image in the sprite is used. We use the irandom() function to generate a number between 0 and 3 (inclusive) and then use the layer_background_index() function to set the index of the background. Now, every time you start the game, it will have a different background.

Upgrading the Main Menu

Before we wrap up and move to tackling Enemies, let’s change our placeholder background and buttons in the main menu. To change the background for the main menu, open the sprTitleBackGround sprite in the Sprite Editor. In the Sprite Editor, click on the import button and select our new background from the assets folder: UI/bgMenu.png. Once the background is changed, similarly import two button sprites from the for each button. The Start button has two states represented by two images in the assets folder: btnStart.png and btnStart_over.png. When you import the images into the existing sprBtnStart sprite , be sure to select both the images. Once both the images are imported into the sprite, be sure to set its animation speed to 0. Similarly import the two images, btnQuit.png and btnQuit_over.png, for the sprBtnQuit sprite . Once the button sprites are in place, let’s add a small bit of code to make use of the new images we have added.
../images/456885_1_En_3_Chapter/456885_1_En_3_Fig13_HTML.jpg
Figure 3-13

Our new main menu and buttons on the left. The player on a random space bg moving and shooting. To view up to this point in the project, please open the file gms2_ch03_02.yyz.

In objBtnStart add two more events: Mouse enter and Mouse leave. Add the following code to each of them, respectively.
/// @description Button hover
image_index = 1;
and on the Mouse leave event
/// @description Button Hover leave
image_index = 0;

What this essentially does is when the player moves the mouse cursor over an instance of the button object, we swap the current subimage with the second one (here with the index 0). When the mouse leaves, then we swap back to the initial frame of the image. Repeat the same with objBtnQuit, and we are good to go.

To view up to this point in the project, please open the file gms2_ch03_02.yyz.

Handling Enemies

Now that we can shoot, let’s add some targets to shoot at. What we will do in this section is to add enemy sprites and objects, make our lasers collide with them, destroy the enemy sprites and replace them with an explosion, add sounds for those explosions, update the player’s score for destroying each enemy, and finally have a mechanism in place to spawn multiple enemies automatically.

Let’s jump right in and create a new sprite from the Resources Panel; once the sprite is created rename it to sprEnemy01 and use the Import button to import an image from the assets folder—enemies/ spaceShips_003.png. Once the image is in place, add a new object to our resources. Once the object is created, rename it to objEnemy01 and associate our sprite to the object.

Just like we did with our lasers, to make our enemy move toward us and once the enemy moves outside of the room, we need to destroy it. Start by adding a Create event to the enemy object. In the Create event, we will set the vspeed variable so that the object moves down toward us.
/// @description Initialized
// set out vertical speed, moves the object from top to bottom
vspeed = 6;
// value of this ship - points given for shooting it down
shipPoints = 10;
// activate alarm number 0 after room_speed * 5 = 5 seconds in frames
alarm[0] = room_speed * 5;
Apart from the vertical speed, we will also create an instance variable called shipPoints and set it to 10. Instance variables are variables that are accessible within the scope of an instance, which means that variable, once set, can be accessed and updated from any event within the object. Since we want the ships to start coming into the screen from outside of the room, we can’t use the Outside room event for housekeeping like we did with the laser beams. So, in the third statement, we set an alarm for 5 seconds. An alarm is exactly that—it is a timer that is unique to an object; when a set amount of time expires, an event is fired. Each object can have up to 11 alarms and when setting an alarm, one should also create a corresponding Alarm[0…11] event as well. GMS alarms use frames instead of seconds; what this means is that when one wants an alarm to go off after 1 second, the value that you set the alarm to is the number of frames in 1 second. In our case, we use the built-in variable called room_speed (which is by default set to 30 frames per second), which equals the number of frames in a second, and multiply it by five to get the number of frames in 5 seconds. So eventually when 5 seconds elapse, the Alarm 0 event for the object gets called, and the following code is executed, destroying our enemy space ship.
/// @description destory this object
instance_destroy();
Now that the enemy is ready, let’ create a new layer for it in the room. Create a new instance layer called Enemies in rmGame via the Room Editor. Add our enemy object to this layer by dragging and dropping it into the room. Run the game to see the enemy flying toward you.
../images/456885_1_En_3_Chapter/456885_1_En_3_Fig14_HTML.jpg
Figure 3-14

The enemy flying toward the bottom of the screen on the left and the enemy in the room on the right

Shooting the Enemy

We now have an enemy on the screen and we can shoot lasers. Putting them together, we now must add functionality to the enemy so that it can handle collisions with our lasers. Whenever the enemy collides with the laser we have fired, it should destroy itself, destroy the laser object that it was hit with, update the score, play a sound, and then play an explosion animation.

First, let’s add a way to save our score. Let’s create a global variable to hold our player’s score for each level. A global variable is a variable that is accessible across rooms and instances. We are creating a global variable since we will need access to the score even after the game ends. Since the score is something that is reset at the beginning of each level, let’s add a global variable to rmGame's create event. Let’s open our script resource scrGameCreate and add a global variable here, as it is called every time rmGame starts.
/// @description this script is executed when rmGame is created
/* hide the mouse */
window_set_cursor(cr_none);
/* set a random image from our background sprite as our backround for this room */
var lay_id = layer_get_id("Background_space");
var back_id = layer_background_get_id(lay_id);
var bg_index = irandom (3);
layer_background_index(back_id, bg_index);
/* our score */
global.playerScore = 0;

We will append our playerScore variable to the end of the script. To create a variable in the global scope, you use the word global followed by the dot operator and then a variable name you would want to use—in our case, playerScore. Initially, when the room starts, we set the playerScore global variable to 0. Every time an enemy gets hit with our lasers, we will add the enemy’s value to our score.

GameMaker provides an easy way to check for collisions between objects. Open the objEnemy01 object in the Object Editor; in events, add a new Collision event and select objLaserBlue from the dropdown list (Add Event/Collision/objLaserBlue). This event gets called every time there is a collision between objLaserBlue and objEnemy01 instances.
/// @description Getting shot
global.playerScore += shipPoints;
instance_destroy(other);
instance_destroy();

In the first statement, we increment the global variable global.playerScore with the points we get for shooting this ship. In the second and third statements, we destroy our ship along with the other object that collided with us—in this case, the built-in variable other contains the laser that hit the enemy ship.

Run the game and shoot the enemy to see both your bullet and the enemy disappear. This looks pretty good, but we should juice it up with some audio and an explosion animation. Let’s load a sound file for our explosion and create a new sprite for our animation. The sound folder inside assets contains a file called snd_boom_0.wav . Create a new sound resource and load this sound into it; once the sound is loaded, rename the newly created sound resource to sndEnemyHit. Create a new sprite for our explosion animation, rename this to sprBoom, and import all the images inside the effects folder in assets. Once the import is complete, set the speed of the animation to 30 frames per second. Create a new object resource called objBoom for our explosion object and add sprBoom as its sprite.

Now that we have our resources in place, we need to make sure that the audio is played when the explosion happens, and then the explosion should remove itself from the room once it has played through the animation one complete time. We will play the audio in the Create Event for the sprBoom object so that the explosion sound gets played when sprBoom is first added into the room.
/// @description Create the boom object with audio
audio_play_sound(sndEnemyHit,1,false);
Now, we need to find out if our animation has played through at least once. To do this we will use the built-in instance variables, image_index, and image_number. The image_index variable returns the current index of the subimage that is being displayed. When our animation is played back, GMS cycles through the subimages for the current instance of the sprite at a rate of a given frames per second or frames per game frame, which is set in the Sprite Editor. This rate can further be controlled using the image_speed instance variable, which acts like a multiplier. By default, the image_speed is 1; changing it to 0.5 will half the animation speed, and changing it to 1.5 will double it. GMS changes the image_index variable to the appropriate number internally as it cycles through each of these subimages. The image_number variable is a read-only variable that returns the number of subimages the sprite contains. Keep in mind that the image_index is 0-based and image_number is 1-based, which means if you have one subimage, image_index for the sprite will be 0, whereas the image_number for the sprite will be 1. Armed with this information, we can find out exactly when the animation has finished playing in an instance of objBoom and remove it like shown here:
/// @description remove once done playing
if(image_index >= image_number - 1) {
        instance_destroy();
}
Each time the Step event is called, we check to see if the current image_index is greater than or equal to the last image index (computed by subtracting 1 from the total number of subimages—the image_number). If we have displayed the last frame of the sprite, then we destroy the sprite. The other much simpler way to do the same would be to add an Animation End event in the objBoom Object. The Animation End event (located in Other events) is triggered when the animation for the sprite attached to the object comes to an end. You can use the instance_destory() function from within the Animation End event to achieve the same results. Add an Animation End vent to objBoom and add the following lines of code there:
/// @description remove once done playing
instance_destroy();
Now that we have an explosion with sound ready, let’s add it to the collision code in our enemy object.
/// @description Getting shot
global.playerScore += shipPoints;
var boom = instance_create_layer(x, y,"Enemies", objBoom);
boom.vspeed = vspeed;
instance_destroy(other);
instance_destroy();

When the enemy is hit by our laser, we use the instance_create_layer() function to add the objBoom object at the current x- and y-position of the enemy ship. We go a bit beyond this as well. If we examine the instance_create_layer() or instance_create_depth() function, we can see that the function returns the id of the newly created object. This means that as soon as you create an object, you have access to it to do what you please.

When a new instance of objBoom is created we save its id to a local variable called boom. Through boom, we change the vertical speed of the newly created explosion animation. We do this to avoid a small visual discrepancy; since we are simulating moving forward and the enemy is moving backward, a sudden static animation on the screen stands out from the rest. To avoid this, we set the explosions vspeed.

Spawning Enemies

We have one enemy on the screen right now. It would be tempting to keep placing more and more enemies in the room, but at some point, it becomes a difficult exercise—the amount of variation that you can create reduces drastically after placing a small number on screen. To avoid this, we will create a factory that produces enemies constantly at a set interval.

Let’s start by creating an object from the Resource Panel and renaming it to objEnemySpawner . In the Object Editor, be sure to uncheck the box for visible. This will make sure that our spawner object itself will not be visible. You can go ahead and choose or make a sprite if you want so that you have a visual cue in the Game Editor. It is a good practice to have custom sprites for your invisible objects, as it becomes easier to find them in rooms and edit them. By default, an object without a sprite is represented by a question mark symbol inside a grey circle.

Once you have your spawner object made, add it to our rmGame room. Double-click on the instance, and in the Instance Editor, click Edit Object. We want our spawner to produce enemy ships at a constant interval; the last time we needed a way to destroy enemy ships after an interval, we used an alarm; let’s do the same here. We will set up an alarm so that when the alarm interval is up, an enemy ship is created at a random location right outside the top part of the room.
/// @description Enemy Spawnner Initiated
// speed at which enemies should be spawning
spawnSpeed = room_speed * 1.5;
alarm[0] = spawnSpeed;
In the Create event of the objEnemySpawner object, we set an alarm to go off one and a half times the room_speed—that would mean every 1.5 seconds.
/// @description Alarm spwans enemys when called
var ex = irandom_range(50, room_width - 50);
var ey = -100;
instance_create_layer(ex, ey,"Enemies",objEnemy01);
alarm[0] = spawnSpeed;
In the Alarm 0 event, we pick a random point between 50 and room_width—50 is for our enemy’s x-position, and the y-position is 100 pixels outside and above the room. We then use the instance_create_layer() function to create an objEnemy01 object at this location. In the last line, we reset the alarm to go off in another 1.5 seconds.
../images/456885_1_En_3_Chapter/456885_1_En_3_Fig15_HTML.jpg
Figure 3-15

Setting up the spawner

Run the game to shoot some enemies and to see them spawn every 1.5 seconds.

Displaying Our Score

Every time we shoot an enemy, our global.playerScore global variable gets updated. All that is left to do is to show it to the player. To do this we will create an object and, using its draw event, draw the score onto the screen. Before we go ahead, install the two fonts provided in the assets UI/fonts folder. We will use one of these fonts to draw the text on the screen. But before we can use these fonts, make sure you install them on your computer. On Windows or a Mac, you can open the font file by double-clicking on them and clicking the Install button that is displayed in the window that opens.

To use the font we have installed, we need to create a font resource. Right-click on the Fonts section in the Resources Panel and select Create Font. In the Font Editor that opens in the workspace, rename the font to fntHUD. Then from the dropdown below, select one of the fonts we just installed-KenVector Future Thin. Change the size of the font to 14, and we are done. Now that the font resource is in place, let’s draw some text.

To show our score, let’s create a new object in the Objects section. Let’s rename it to objScore. Since we will be doing the drawing, we don’t need a sprite. Double-click the objScore object to open it in the Object Editor. Add a new Draw GUI event to the object. The Draw GUI event is a special event that is called every frame and is used to draw user interface elements. Unlike the Draw event, Draw GUI draws elements are special events that are designed for drawing GUI elements. They are unaffected by position, rotation. or scale of the cameras and viewports in a room. It is important to understand that even with views enabled, the draw coordinates do not change and (0,0) will always remain at the top left-hand corner of the application surface or the display. For more nuances of Draw events, please do check the related documentation: https://docs2.yoyogames.com/source/_build/2_interface/1_editors/events/draw_events.html
/// @description Displaying the score
// set the font and alignment
draw_set_font(fntHUD);
draw_set_valign(fa_middle);
draw_set_halign(fa_left);
// draw the text
draw_text(x, y, "SCORE: " + string(global.playerScore));

Before we draw the score, we use the formatting functions to set the font to fntHUD. We then set the vertical and horizontal alignment of the text, and finally we draw the text at the x- and y-location of our object itself. This means the text will be drawn where we place this object. The final parameter in draw_text() is the actual string to be drawn.

We add together the text “SCORE: ” with the playerScore; we use the string function to convert the playerScore from a number to a string. Without this conversion, when you try to add a string with a number, GMS will throw an error. Place objScore close to the bottom left of the screen and run the game to see our score text in action. To view up to this point in the project, please open the file gms2_ch03_03.yyz.
../images/456885_1_En_3_Chapter/456885_1_En_3_Fig16_HTML.jpg
Figure 3-16

Shooting and scoring

Our Enemies Fight Back

In this section we will see the enemy fighting back, giving us a taste of their red lasers. We will also deal collision detection for both the lasers and the enemy ships. If we get hit with their lasers or bump into an enemy ship, we explode and it is game over. Our ship will explode like the enemy ships, and once that is done, we’ll move to the next room showing us a game over screen as well as our score. If our score is the same as or more than the high score for the session, then we tell the player that we have a high score and save it too.

Teaching the enemies to shoot is very similar to what we do to shoot; we will start by loading a laser for the enemy. Load laserRed07.png from assets/laser and create a sprite called sprLaserRed . Since both our player and enemy lasers are almost identical, we can just duplicate the existing blue laser, so go ahead and duplicate objLaserBlue and rename it objLaserRed. The only change we must make will be the vspeed ; since objLaserRed must travel down toward the bottom of the screen, its vspeed will now need to change to a positive number. Open the Step event for objLaserRed and change the vspeed to 20.
/// @description The laser shot
vspeed = 20;
Now, we will set up an alarm and an Alarm event to handle that alarm in the objEnemy01 object. Since we already have an alarm, we will now use the next available alarm, which is Alarm 1.
/// @description Initialized
// set out vertical speed, moves the object from top to bottom
vspeed = 6;
// activate alarm number 0 after room_speed * 5 = 5 seconds in frames
alarm[0] = room_speed * 5;
// value of this ship - points given for shooting it down
shipPoints = 10;
// speed at with the enemy fires
fireSpeed = room_speed * random_range(0.2,2);
// every fireSpeed frames, do this!
alarm[1] = fireSpeed;
The fireSpeed variable stores how fast our alarm should go off, and we choose a random a time between 0.2 seconds and 5 seconds, which means that some enemies might shoot at us fast (every 0.2 seconds), while some fire late (after 2 seconds).
/// @description When this alarm goes off fire!
instance_create_layer(x, y, "Enemies", objLaserRed);
// play laser firing sounds
audio_play_sound(sndLaser_fire_1, 10, false);
alarm[1] = fireSpeed;

In the Alarm 1 event, we create an instance of the objLaserRed object at the enemy ship’s x- and y-location using instance_create_layer(). We play the same laser firing sound we used for the player and then reset the alarm to fireSpeed. Run the game to see how your enemies now fight back.

They can shoot all they want, but you would have noticed that none of those lasers are hitting us. Let’s add a Collision event to the player object, objPlayer. Add a collision event to objPlayer with the objLaserRed object.
/// @description We got shot!
var boom = instance_create_layer(x, y,"Instances", objBoom);
boom.vspeed = vspeed;
//destroy the laser
instance_destroy(other);
// destroy our ship
instance_destroy();

Just like we did with the enemy object when it collided with our blue laser, we create an instance of the boom object, which has the explosion sound and animation. Then we destroy the laser we got shot with using instance_destroy() and passing the other (in this case the objLaserRed object). And then we destroy the ship itself.

If you run the game now, you will see that you are getting shot and destroyed, but the game keeps on going. Once your ship is destroyed, after the explosion is done, we need to take the user to a game over screen and present our score. To do this, let’s make a new room.

Create a new room and call it rmGameOver . Create a new sprite in the Resources Panel and load up the background image (bgGameOver.png) that we are going to use in this room from assets/UI folder. Once the image is loaded, set the sprite as the background for the room. If our newly created room is bigger than the other rooms, change the width and height of the room to 640 and 480 pixels, respectively, in the Properties Panel on the left in the Room Editor.

Now that we have a game over screen, let’s tell GMS to show it once our ship is destroyed. How can we achieve this? We want the game over screen to show up once our ship is destroyed and the explosion is done playing. We are using objBoom for the sound and explosion animation for both the player and enemy ships. If we make any changes to objBoom, they will affect not just us but also the enemies as well. Since we don’t want the game over screen to be shown when we shoot down enemies, let’s duplicate the objBoom and make a new object specially for the player.

Go to the rmGame room and duplicate objBoom; rename it objBoomPlayer and open it in the Object Editor. In the Animation End event, make these changes.
/// @description remove once done playing
instance_destroy();
window_set_cursor(cr_arrow);
room_goto(rmGameOver);

In the Step event, we check if the animation has finished playing, and once it is done, we remove the instance of the explosion from the room using the instance_destroy() function. Once the instance is removed, we set our cursor to an arrow and then use the room_goto() function to go to the rmGameOver room.

Now that we have our new objBoomPlayer object, in our Collision event with the red laser, we need to create a new instance of objBoomPlayer instead of objBoom; let’s do that now.
/// @description We got shot!
var boom = instance_create_layer(x, y,"Instances", objBoomPlayer);
boom.vspeed = vspeed;
//destroy the laser
instance_destroy(other);
// destroy our ship
instance_destroy()

Everything else remains the same, except for the last parameter passed to the instance_create function . Instead of objBoom, we pass in the newly created objBoomPlayer object. Run the game and see what happens when you get shot. You will see that once we get shot, the sound and the explosion animation are played and our new game over room is shown.

The second way that the game can get over is by us colliding with the enemy ships; if we hit any enemy ship, both ships are destroyed and the game over screen is shown. Most of the collision code remains the same, except we need to now destroy the enemy ship and add an animation for that as well. Create a new Collision event for objPlayer with objEnemy01.
/// @description collision with the enemy
// create an explosion object for our player
var boom = instance_create_layer(x, y,"Instances", objBoomPlayer);
boom.vspeed = vspeed;
// create an explosion object for our enemy (other) and destroy it
with(other) {
        var boom = instance_create_layer(x, y,"Enemies", objBoom);
        boom.vspeed = vspeed;
        instance_destroy();
}
// destroy our ship
instance_destroy()

The code is very similar to our collision code with the laser, the only difference here is that instead of just destroying the other object with the instance_destroy() function, we add an extra step of creating an instance of the objBoom for the enemy ship. We do that using the with statement . As seen in the last chapter, the with statement is a special statement used to switch the scope of the code executed within its braces to be in the scope of the object passed to it. Here we switch the scope from the player object’s instance to the other instance (being an instance of objEnemy01). Once in the other instance’s scope, we create a new instance of objBoom at the x- and y-coordinates of the enemy. We also set the objBoom instance’s vspeed to the enemy ships vspeed. Once this is done, we destroy the enemy instance by calling the instance_destroy() function from within the scope of the with statement.

Our game over screen is functional; it tells people that the game is over. But, it would be better if we show what their score is, if they beat their high scores, and a way to get back to the main menu where they can either choose to play again or exit the game.

Let’s try to get that score and high score message on the screen. To do this, we need a way to store our high score. At the start of the session the high score is reset to 0. But with each session, the high score is set to the highest score the player has achieved. We need to declare a global variable to store this high score value.

Our first impulse is to add a global variable in the menu, but let’s create a new room to store and initialize all global variables that we need to create and initialize once across the game. This gives you access to one point where all initialization for the game can happen, and this will not change even if a menu might. Let’s add a new room called rmInit in the Rooms section in the Resources Panel. Once this is done, drag the newly created rmRoom item to the top of the list so that rmInit is the first room in the Rooms section. The first room in the Rooms section is the first room to be run by GMS. Let’s resize the room and add a script for the Create event for this room.

First add a new script in the Scripts section of the Resources Panel and rename it to scrInitCreate. Then, open the rmInit room in the Room Editor and in the Properties Panel, change the width and the height of the room to 640 and 480 pixels, respectively. In the properties panel, click on the checkbox labeled Persistent. Making a room persistent is a way of telling GMS that it shouldn’t destroy the room once we leave it. All rooms and objects that are not persistent are destroyed when the player switches a room. Once this is done, open the Creation Code for the room.
 scrInitCreate();
In the Creation Code, call our scrInitScript script, which for the time being is empty. Let’s open the scrInitCreate Script to initialize our new high score global variable.
global.highScore = 0;
room_goto(rmMenu);

In the first line, we add the global variable called global.highScore and set its value to 0. In the second line, we tell GMS to go to the rmMenu room.

Tip

To quickly open rooms, scripts, sprites, or objects from code, click on the item’s name with the middle mouse button (wheel button) or select the item and then hit the F1 key.

Now that we have the highScore global variable, what we will do is to duplicate the objScore object and rename it to objGameOverMessage . Add it to the Instances layer of the rmGameOver room. Place it in the middle of the room. The objScore object we duplicated was used to display the score; let’s edit it to show the score as well as a message if we have the high score.
/// @description Displaying the score
// set the font and alignment
draw_set_font(fntHUD);
draw_set_valign(fa_middle);
draw_set_halign(fa_center);
// draw the text
if(global.playerScore >= global.highScore) {
        global.highScore = global.playerScore;
        draw_text(x, y, "Your Score is " + string(global.playerScore) + " You have the new High Score!");
} else {
        draw_text(x, y, "Your Score is " + string(global.playerScore));
}

Since we are keeping the object in the middle of the screen, we will change the horizontal alignment to fa_center from fa_left. We then check if the player’s current score is higher than or equal to the high score for this session. If the player’s score is higher, then we set our high score to the player’s current score and show a message whicthath tells the player that they have the new high score. If not, we just display the player’s current score.

To take us back to the main menu, let’s add a button. Let’s duplicate and existing button so that we can reuse all the events for the button. Once you duplicate a button, rename it to objBtnMenu . Open the object in the Object editor. Near the preview of the sprite, click on the New Sprite button. Once the Sprite Editor opens, rename the new sprite to sprBtnMenu and import two images from the assets/UI folder—btnMenu.png and btnMenu_over.png.

Go back to the Sprite Editor and click on the Left Released event to edit it. Remove the existing code in the event and replace it with the following code:
/// @description Handling menu button click
room_goto(rmMenu);
When the user clicks and releases the button, we go to the main menu of the game. To view up to this point in the project, please open the file gms2_ch03_04.yyz
../images/456885_1_En_3_Chapter/456885_1_En_3_Fig17_HTML.jpg
Figure 3-17

Game Over

Adding Music

Our game is well-rounded, but it lacks a bit of excitement. Let’s fix that and add some music to make things interesting. Just like we added the sounds, start by creating a new Sound resource in the Resource Panel. Since this is a background score, we will rename it to bgmDistantStar .
../images/456885_1_En_3_Chapter/456885_1_En_3_Fig18_HTML.jpg
Figure 3-18

Loading our background score

Click the button next to the name of the resource to load our music file. Navigate to the assets/sound folder and import the mp3 file titled Distant_Star.mp3. Once the file is loaded, we will edit some attributes of the new sound resource we have just created. Right below where we edited the name of the resource, you will see a section for the sound’s attributes. These decide how the sound resource should be treated when we export the game. We will set this to Compressed and Streamed, as our resource is going to be a large mp3 file, and we do not want to keep the file in memory, but rather decompress it and play it back in real time. Smaller resources like our explosion and laser firing sounds were kept as uncompressed, as they are smaller and do not take too much memory.

After creating the sound resource and loading the file, we will now play it back when the game starts. We already have a script that is executed in the Room Creation Code of the rmInit room; let’s open it up and tell GMS to play our background score there. Open the scrInitCreate script from the Resources Panel and edit as given below.
global.highScore = 0;
audio_play_sound(bgmDistantStar, 100, true);
room_goto(rmMenu);

The only change here is the piece of code that tells GMS to play the audio. We use the audio_play_sound() function and pass it the sound resource that we want to play—in our case, the new sound resource we created with the mp3 file. The second parameter is the priority—we pass it to 100, telling GMS that this sound resource is off high priority. The third parameter tells GMS that this sound should be looped once it finishes playing, and that is it, we have finished our game!

Run the game to see it all in action. To view up to this point in the project, please open the file gms2_ch03_05.yyz.

Challenges

Now that we have created a small game, here are some challenges for that extends the game that we have built so far.
  • Make the game juicier by adding sounds when you hover over buttons. In this challenge, you are required to bring our dull menus to life by adding sound effects to the button interactions.

  • Add new enemy types that behave differently. In this challenge, you are required to add more enemy objects that behave differently from the enemy we currently have; the different enemies should have a different sprite as well as different behavior.

  • Add other elements like meteorites that interfere with both player and enemies alike. In this challenge, you are required to add elements in the game with which you can interact, like meteorites and satellites; players as well as enemies will need to avoid them to stay alive.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.196.59