Dev 133 – Creating a videogame in GameMaker 6 – part 3 of 3

Introduction

We have reached the last part of this series of articles dedicated to Game Maker. This time we will address the most interesting topic: the use of objects. We will also see how to create the scripts to implement very complex behaviors and effects .

In the previous article we prepared the environment with everything needed to make our video game “live” . All the sprites loaded into the project now will be able to move and interact with each other. For a complete understanding of this article, however, given the complexity of the project implemented, I strongly advise readers to download the package on the FTP site.

Animating the shuttle

The Shuttle is the most complex object that we have to create because of the variety of behaviors it will have to have . So let’s start by adding a new object to the project in the usual way, by right- clicking on the tree menu under Objects and selecting Add Object . This will open the Object Properties window which is illustrated from the image in Figure 1 and we will immediately describe. It is divided into three sections; the first, starting from the top, there is the usual text box for entering the name of the object where we will write objShuttle. Just below into the panel called Sprite, it can be entered, by clicking on the icon to the right of the box where you can see the text “<no sprite>“, which sprite to associate to the object .

Figure 1 – Objects properties windows

After clicking, on the tree of the elements present in the sprite category will appear a new item. Now it is necessary go with the mouse to the item Match then Shuttle and finally click on sprShuttle. Now we have associated the image to the Shuttle object.

Let’s make sure that the check mark is present on the checkboxes called Visible and Solid. They need, respectively, to make the object in the room visible and solid. To determine collisions, as we said in the previous article, when the sprites of two objects overlap, an event is raised. What can happen, however, when the execution of the program continues at the next step? That sprites may still be overlapping, so Game Maker will launch another collision event.

In many cases we do not want this to happen; imagine for example that an asteroid enters in collision with the shuttle and that the latter, unlike the first is devoid of motion.

At each step the meteorite would continue its course and, due to the persisting overlapping against the shuttle sprite, the game would continuously launch collision events. The Solid option cause that every object that comes into contact with the Shuttle, at the next step, will be brought back to the position it occupied before causing a collision, thus avoiding the inconvenience of which we have just spoken.

Continuing with the properties window we find the Depth box. This indicates the order in which the objects are to be drawn.

Large and positive values make the object be drawn before others so that, on the screen, it will be placed behind. Conversely, a large and negative value allows the object to appear in the foreground because it is drawn last. We are interested in verifying this condition, so we will enter the value -1000.

The voice Persistent tells if an object must exist after the transition from one room to another. For our purposes, it should remain unchecked.

We don’t even need to set the Parent box which implements a sort of inheritance between objects with similar behaviors and characteristics. Also Mask must remain empty because it indicates whether the collisions for the object must be calculated on the shape of a sprite different from the one assigned.

We pass to the second section of the window where it is possible to specify which events the object must respond to. Press on the button Add Event and click the item Create in the dialog window that appears. See Figure 2.

Figure 2 – The Event Selector is the dialog box through which you can choose the events, your object must respond to, from those provided in Game Maker

The Create event is launched each time the object is created inside the room. At this point it is useful to make all the preliminary initializations that your objects needs.

Our Shuttle, for example, needs to create and initialize its own internal variables. Through one script we ensure that at its creation time they will take the expected values.

In the Scripts section under the Match/Shuttle folder we can create a new script like the one shown in Listing 1 and call it scriptInitialShuttleValues .

{
    image_speed = 0;
    direction = 0;
    speed = 0;
    health = 100;
    
    motion_set(direction, speed);
    io_clear();
    
    global.SHUTTLE_rotationSpeed = 2;
    global.SHUTTLE_accelerationFactor = 0.5;
    global.SHUTTLE_decelerationFactor = 0.1;
    global.SHUTTLE_accelerating = FALSE;
    global.SHUTTLE_direction = 0;
    global.SHUTTLE_shotSpeed = 15;
    global.SHUTTLE_collisionRay = sprite_get_width(sprShuttle) * 3;
    global.SHUTTLE_warpLevel = 100;
    global.SHUTTLE_fuelLevel = 100;
    global.SHUTTLE_missilesCount = SHUTTLE_MISSILES_COUNT;
    global.SHUTTLE_startCounter = 3 * room_speed;
}

It is important to clarify that each object has a certain amount of internal variables that determine its position, direction, speed of motion, display speed of the sprite frames and others.

In the previous script we set the sprite frame display speed to zero, since under normal conditions they would be displayed sequentially and continuously. A e have set to zero its direction and speed, while the health at the maximum value. We will use the other variables later.To run the script, select the action folder called Control and drag the green arrow icon from the Code section to the Actions colum . A window will appear as in Figure 3 where you can specify the script to associate with the action.

Figure 3 – An example of the window that appears when you want to call a script via the graphical interface

Now we will make the shuttle rotate to the right or left according to the arrow keys. 

We then add two new events by clicking on Add Event -> Key Press -> Left and then Add Event -> Key Press -> Right .

We add another script that serves to change the direction of the Shuttle each time the arrow keys are pressed. In Listing 2 we are shown what we have to write in scriptShuttleRotation.

/*  PARAMETRI:
            argument0 : Shuttle's rotation direction

                        SHUTTLE_ROT_A : Counterclockwise rotation
                        SHUTTLE_ROT_O : Clockwise rotation */
{
    // Change the shuttle's direction of translation
    if (argument0 == SHUTTLE_ROT_A)
    { 
        global.SHUTTLE_direction += global.SHUTTLE_DirectionSpeed;
    }
    else if (argument0 == SHUTTLE_ROT_O)
    {
        global.SHUTTLE_direction -= global.SHUTTLE_DirectionSpeed;
    }
}

You will have noticed that the script does not make changes to the internal variables of the object, but only to custom ones. The constants SHUTTLE_ROT_A and SHUTTLE_ROT_B are set in the Global Game Settings dialog, reachable from the tree menu, under Constants .

We follow the usual steps to attach the script to the events Key Press / Left and Key Press / Right. In the textbox argument0 on Parameter Window we will insert, respectively, the values values SHUTTLE_ROT_A for Left and SHUTTLE_ROT_B to Right.

At this point, however, the Shuttle is not yet able to rotate and this task will be carried out by a script that we will hook into another eventStep is an event that is performed at every frame update of the game; here it is possible to place all the actions that must be performed with a regular time interval.

This script is presented in Listing 3 

{    
    // Change the sprite's frame accordingly to the 
    // direction and the total number of frames
    image_index = (sprite_get_number(sprShuttle) / 360) * (global.SHUTTLE_Direction - 90); 
    
    // Ask the object to move in the wanted direction at the
    // specified speed
    motion_set(direction, speed);   
    
    // Checks the health creates an object showing the 
    // explosion if it raise the value of 0
    if (health <= 0) instance_create(x, y, objShuttleExplosion);
}

In practice what the code does is nothing more than to associate, at each step, the correct frame of the Shuttle sprite according to the value contained in the variable

global.SHUTTLE_Direction

The reason for this choice is simple; the intention is to avoid duplicating the code for the two rotation events. The first statement of the listing sets the variable image_index. This represents the index of the frame of the sprite to be visualized, obtained through the following expression, and on the number of frames present in the sprite, obtained from the function

sprite_get_number(sprShuttle)

and the direction the Shuttle should take.

The call

motion_set(direction, speed)

set the direction and speed of motion, while the line

if (health <= 0) instance_create(x, y, objExplosionShuttle)

creates an instance of the object objExplosionShuttle, at the current coordinates of the Shuttle, when its vital energy ends.

Let’s now make the Shuttle move forward, so let’s add the Key Press / Up event. In Figure 4 it is shown how it is possible to obtain by means of the predefined actions as a sort of control flow, while in Box 1 is present a verbal description of the actions performed .

Figure 4 – This screenshot – taken from the finished project – shows how it is possible to create control flows directly through the graphic interface

Keyboard Event for <Up> Key:
set variable global.SHUTTLE_EngineLevel to “-SHUTTLE_ENGINE_DEDUCTION”
if expression global.SHUTTLE_EngineLevel > 0 is true
      set variable global.SHUTTLE_isAccelerating to TRUE
      direction = global.SHUTTLE_Direction
      effect_create_above(ef_smoke,global.SHUTTLE_flameX, global.SHUTTLE_flameY, 0, c_ltgray);
      effect_create_above(ef_smoke,global.SHUTTLE_flameX, global.SHUTTLE_flameY, 0, c_white);
else
      set variable global.SHUTTLE_EngineLevel to 0

Box 1 – A brief description of the steps performed by the flow that manages the action of the up arrow

Since now we simply indicated that the UP key has been pressed by invoking the scriptShuttleThrust in Listing 4 which changes the values of the internal variable speed. The changes are made effective at the next step by the motion_set function within the StepShuttle script and which we saw earlier.

{
    if (global.SHUTTLE_Accelerating == TRUE)
    {
        speed += (global.SHUTTLE_AccelerationFactor * (global.SHUTTLE_EngineLevel / 100));
        
        // Prevent the engine thrust exceeding the maximum allowed
        if (speed > SHUTTLE_MAX_THRUST) speed = SHUTTLE_MAX_THRUST;
    }
    else if (global.SHUTTLE_Accelerating == FALSE)
    {
        speed -= global.SHUTTLE_DecelerationFactor;
        
        // Prevent the thrust value from being negative
        if (speed < 0) speed = 0;
    }
}

Now the Shuttle is really autonomous and able to move in all directions within the screen.

Since the room is set to be larger than the visible screen, it is necessary to determine which object to follow when moving. In the table views that shows the properties of the room roomLevel we find the voice Object Following. Setting it to objShuttle we are sure that this will always be placed in the center of the screen.

Obviously there is much more to say about the Shuttle, but I think it is appropriate to explore the project on your own and study how other operations such as firing, rocket launching, energy level control, etc. are carried out.

Once the creation of the actions and scripts is complete, it is possible to add the object in the roomLevel room by positioning it as desired within it.

Animating asteroids

Asteroids are objects that have very simple behaviors, they exhibit a motion determined by a direction and a speed in that direction. They have at their disposal a quantity of vital energy which diminishes after collisions with the Shuttle or its weapons. When the vital energy finishes they explode and, the larger ones, will fragment into smaller pieces.

We begin with tiny one, called objVeryTinyAsteroid1, and create one in the Asteroids/Tiny folder, with the sprite sprVeryTinyAsteroid1. In the Create event we insert the action found in the move action table represented by the eight blue arrows. Inside the direction box of the “Parameter” window , we insert the direction string, while in the speed box we insert random(10). This tells that when the object is created it must move in the direction indicated by its internal variable and at a random speed between 0 and 10 pixels per step.

Now let’s add the events needed to handle the collisions with the Shuttle, the bullets and the missiles. Click on Add Event -> Collision -> Game -> Shuttle -> objShuttle . The same thing must be done with the objShuttleShot and objMissileExplosion objects. Listing 5 shows the code of scriptAsteroidsEnergyManagement to be added to each collision event just created. As parameters the script receives

  • The index of the object with which the collision occurred ( objShuttle objFuocoShuttle , etc.)
  • The id of the calling meteorite instance, obtainable through the Self.id command
  • The multiplicative ratio of deduction of vital energy
/*  PARAMETERS
            argument0 : Object against which the collision occurred
            argument1 : Asteroid instance ID
            argument2 : Energy deduction ratio */
{
    // Determine the amount of energy to be deducted 
    // according to the object against which the collision 
    // occurred
    if (argument0 == objShuttle)
        energyDeduction = 20;
    else if (argument0 == objShuttleShot)
        energyDeduction = 5;
    else if (argument0 == objMissileExplosion)
        energyDeduction = SHUTTLE_MISSILE_POWER;
        
    // Recover the energy level of this object and 
    // deduct the appropriate amount and I store the value 
    // on the map
    ds_map_replace(global.energyMap, 
                   argument1, 
                   ds_map_find_value(global.energyMap, argument1) - (energyDeduction * argument2));
}

At this point the energy of the asteroids decreases with each collision, but what must happen when this ends? Let’s first look at Listing 6 . Here there is a check and if the energy level is less than or equal to zero the special effect of explosion is generated through the scriptAsteroidExplosion and, ultimately, occurs the definitive destruction of the object’s instance.

{
    if (ds_map_find_value(global.energyMap, self.id) <= 0 && global.SHUTTLE_StartCounter <= 0)
    {
        // Delete the element from the map
        ds_map_delete(global.energyMap, self.id);

        // Ring effect on collision with spaceship  
        effect_create_above(ef_ring, x, y, 0, c_ltgray);
        
        // Creates the explosion effect
        scriptAsteroidExplosion(0);     
        
        // Destroy the object 
        instance_destroy();
    }
}

To make the larger asteroids divide when exploding you need to add the line

// Launch the script that generates the
// asteroids' children

scriptSplitAsteroid(objSmallAsteroid1);

to the code in Listing 6 . For reasons of length we do not report the DivideAsteroid script that does perform nothing but generate a random number of new instances of smaller asteroids .

The enemy ship

We now introduce the object named objEnemyShip. The enemy ship doesn’t move but is able to continually make hyperspace jumps and fire in the direction of the Shuttle.

For this object must be created the events:

  • Create
  • Step
  • Collision -> objShuttle
  • Collision -> objFuocoShuttle 
  • Collision -> objEsplosioneMissile

In the Create event enter the scriptInitializeShip whose code is that of Listing 7 . This makes it possible to place the enemy ship in a random position within the room. The image_index variable halves the display speed of the animation.

{
    global.SHIP_PositionX = random(room_width - sprite_get_width(sprEnemyShip) * 2) + sprite_get_width(sprEnemyShip);
    global.SHIP_PositionY = random(room_height - sprite_get_height(sprEnemyShip) * 2) + sprite_get_height(sprEnemyShip);
    global.SHIP_FireTimer = 0;
    global.SHIP_WarpTimer = 0;
    
    x = global.SHIP_PositionX;
    y = global.SHIP_PositionY;

    image_speed = 0.5;
} 

In the Step event two scripts are inserted for timing the hyperspace jumps and the shots towards the Shuttle.

In the other events the scriptEnemyShipExplosion is set, since the enemy ship must be destroyed at the occurrence of any kind of collision. The Listing 8 

{
    psx = 0;
    psy = 0;    

    // Crea gli effetti vari
    for (contatore = 0; contatore <= 5; contatore+=1)
    {
        psx = random(50)+x-25;
        psy = random(50)+y-25;
        effect_create_above(ef_explosion, psx, psy, 2, make_color_rgb(0, random(127)+128, random(127)+128));   
        effect_create_above(ef_ring, psx, psy, 2, c_white);
    }
    
    if (round(random(2)) mod 2 == 0)
        instance_create(x, y, objEnergyBonus);
    else
        instance_create(x, y, objMissilesBonus);
    
    // Play the associated sound
    sound_play(sndEnemyShipExplosion); 
}

shows how this script creating some graphics effects, instantiates the bonuses (Extra Energy or Extra Missiles) objects at random and performs a sound effect through the function

snd_play

The controller

The controller is an object like another one to which is delegated the tasks

  • manage and organize the various phases of the game
  • initialize global variables
  • create instances of meteorites

and other. The main events to which it must respond are usually Create and Step .

Conclusions

In Figure 5 we can see an image of the final game. 

Figure 5 – An image taken during the action from the complete game

Unfortunately in the articles there is not enough space to fully show all the necessary details, so I tried to explain as much as possible to ensure that the reader is able to independently study the code. However, I am available for any necessary clarification and indeed I must confess that I greatly appreciate your e-mails.

On the FTP site you will also find another project, a clone of the Snake game that will give you more information on using Game Maker.

Bibliography

  1. Mark Overmars, ” Designing Games with Game Maker “, www.gamemaker.nl

Original article

Downloads

Leave a Reply

Your email address will not be published. Required fields are marked *