Developing a simple game in Game Maker. Episode 1



    We continue to implement the Plants vs Zombies clone in Game Maker, studying the main features of game development in this environment. In this episode, we will cover concepts such as scripts, timelines, the other, depth keyword, redefine the Draw event, examine some useful functions, and talk about debugging games.


    Now we can arrange units as much as we like, but in this lesson we will fix it. In the o_game object in the Create event, add a new variable pEnergy, which will be responsible for storing the amount of energy (in Plants vs Zombies, these are suns). It will be possible to buy units for energy. We also add a new alarm, which will be responsible for generating new units of energy (in the original, the sun fell from above). So the event will now look like this:

    pEnergy = 50; // player energy
    alarm[0] = room_speed; // generate enemies
    alarm[1] = room_speed * 3; // generate energy units
    


    Alarm 1 event:
    /// generate energy elements
    instance_create(irandom(room_width - 40) + 40, irandom(room_height - 40) + 40, o_elem_energy);
    alarm[1] = room_speed * 6.5;
    


    Now we will make the “elements” of energy that will appear in a random place on the game screen, and then move. Create a new object, set a sprite, call o_elem_energy. In the Create event, write this code

    image_alpha = 0;
    eAmount = 25; // amount of energy to add
    alarm[0] = 1; // incr alpha
    


    image_alpha = 0 is a built-in variable that makes the sprite completely invisible when the rendering code is executed. image_alpha accepts any value from 0 (invisible) to 1 (visible). 0.5 is 50% of the visibility. With Alarm 0, we will increase alpha, i.e. make the sprite visible.

    eAmount is a user variable that will store the amount of energy a player receives when they click.

    Code for Alarm 0:

    /// Incr alpha
    if (image_alpha + 0.05 <= 1){
      image_alpha += 0.05;
      alarm[0] = 1;
    }
    


    [Optimization Corner]

    In the last episode, our “bullets” that plants shoot just flew horizontally and did it endlessly. When there will be, say, 100 such bullets there, this is not a problem, but with 1000 there may be a drawdown in performance. But in any case, you need to get rid of excess elements as soon as they ceased to be needed. Since as soon as the bullets fly out of the screen, they no longer interest us, they must be removed immediately. You can do this in 2 ways - through the standard event Other -> Outside View (or Outside Room, depending on your game) or just check to see if the object is outside the screen itself. There is no difference between them, standard events do exactly the same as the second method, just do it for you. So we add the Step event to the o_bullet object and add the following code:

    if (x - sprite_width / 2 > room_width){
       instance_destroy();
    }
    


    Here, I hope everything is clear - as soon as the left border of the sprite goes beyond the room, we destroy the instance.
    [/ Optimization corner]

    Now, let's go back to the o_elem_energy object and create the Step event. Let's write this:

    if (y + sprite_height/2 < 0){
       instance_destroy();
    }else{
       y -= 2;
    }
    


    Here we move our instance every step 2 pixels up and, if the lower border of the sprite is above the room, then we delete the instance.

    The player will need to click on the element of energy to get it. To do this, create the Left Button event. This event is triggered not only when we press (Left pressed) or release the mouse or finger on touch devices (Left released), but also when the mouse simply passes through our instance, which suits us perfectly. The code for this event could be like this:

    /// grab energy
    with(o_game){
       pEnergy += other.eAmount;
    }
    instance_destroy();
    


    However, in the first episode, I promised that we would add support for the gamepad, and it is obvious that the gamepad does not have a Left Button event (or Left Pressed, or Left Released) on the instance. And accordingly, when controlling a gamepad, there will be another way to select energy. And if so, the code above will be duplicated. However, duplication, for obvious reasons, is bad, so it's time to get acquainted with the important element of Game Maker - scripts (the Scripts folder). In fact, they are no different from the code that we write inside the events, but the way it works is different - you need to call it yourself. Scripts are very convenient for duplicate code, while scripts can still take parameters, as functions (methods) do in any other programming language.

    We will just have a case of the need to duplicate the code in the future, so instead of the code above in the Left Button event we will write:

    scr_elem_energy_grab();
    


    This is how the function call looks. You can pass parameters in brackets, but now we do not need them. Game Maker now shows an error, because this function does not yet exist. Let's fix it - create a script, just like create objects / sprites / etc., rename it to scr_elem_energy_grab and put the code above (which starts with the comment /// grab energy) into it. We save, close and now no errors and further repetition of the code when we develop a mechanism for catching energy with a gamepad.
    Now, when the Left Button event is triggered for the instance instance of the o_elem_energy object, we run the scr_elem_energy_grab script, which does the following - accesses the pEnergy variable of the instance instance of the o_game object (we have one, so we can also access it and o_game.pEnergy) and assign its value to the variable other.eAmount.

    We have already encountered the keyword other, but here its meaning is somewhat different. other.eAmount is a call to the instance variable of the object, which launched the with loop. You asked, and who launched it? Excellent! Let's think about it? The with loop belongs to the object that called it. And who called him, is he in the script? It's simple - the script is run on behalf of the instance of the o_elem_energy object and accordingly gets access to its variables. So other.eAmount is a call to the eAmount variable that we declared in the Create event of the o_elem_energy object. In Game Maker, everything is simple. If we write simply pEnergy = eAmount; then we will access the eAmount variable of the o_game instance, which does not exist, which will cause an error.

    Once again, to consolidate, writing such constructions will often have to, regardless of the complexity of the game. O_elem_energy has an eAmount variable. In the Left Button event, scr_elem_energy_grab is triggered on behalf of this object, i.e. in the script we can access the eAmount variable directly, however we add energy in the with loop. Events inside with occur on behalf of the o_game object and we can no longer directly access eAmount. But we have the other keyword, with the help of which we seem to go beyond the scope of the with loop and gain access to eAmount again, because we are inside a script run on behalf of o_elem_energy. It is very simple, and I hope you understand it.

    Remember, in the first episode we registered the event Global Left Released, which always works on the entire "canvas" of the game. When we added a new Left Button event to the o_elem_energy object, a jamb formed. After all, it turns out that when we pick up energy, the Global Left Released event of the o_game object will be launched and, accordingly, the unit will be put on the playing field. Strange behavior, right? So you need to be very careful with Global events. It’s very easy to fix this, but we’ll still redo the unit allocation mechanism, so let’s leave it for later, and in the sources on github this problem is temporarily fixed.

    We realize the equivalent of sunflowers. We create a new object and call it o_unit_enery_generator, give it a sprite and set the parent object o_unit_parent. To avoid chaos among objects / sprites / scripts, there are groups in Game Maker. Right-click on the folder Objects -> Create new group. Give her any meaningful name, for example, grp_units. In it we will store all user units. Drag them to this group. Do the same for enemy objects. The name of the groups and the sorting of objects by them was created solely for your convenience and does not affect anything else.

    There is nothing new in the code of the o_unit_enery_generator object, so I just post its contents.

    Create:
    event_inherited();
    HP = 15;
    genElemEnergyTime = room_speed * 2;
    alarm[0] = genElemEnergyTime;
    


    Alarm 0:
    /// generate energy elements
    instance_create(x,y,o_elem_energy);
    alarm[0] = genElemEnergyTime;
    


    Now that we have the energy and it is generated, it would be nice to provide the opportunity to buy units, and for this we need some GUI where we can see the amount of available energy and select the desired unit. This is what we will do.

    It's time to get to know the Draw event. Previously, it did not interest us, because arranged standard features for objects to draw their sprites. Now we need something non-standard. This is a pretty important element in learning Game Maker.

    So, add the Draw event to the o_game object.

    draw_set_alpha(0.68);
    draw_rectangle_colour(0,0, elemGUI_width, elemGUI_height, c_blue, c_blue, c_blue, c_blue, false);
    draw_set_alpha(1);
    draw_text_colour(40,25, string(pEnergy), c_black, c_black, c_black, c_black, 1);
    


    draw_set_alpha is a standard function that sets the transparency (alpha channel) of everything drawn after it. For reasons I don't understand, the draw_rectangle_colour function does not have the ability to directly set transparency, so you have to use the draw_set_alpha method additionally. Be careful, by changing the alpha channel you change the transparency of everything drawn, and not just what is written in this particular Draw event. This function also affects other objects, so as soon as you change alpha, draw everything you need, return the transparency to its original position, i.e. in 1.

    draw_rectangle_colour draws a blue-filled rectangle with the coordinates of the upper left corner (0; 0) and the coordinates of the lower right (elemGUI_width; elemGUI_height), respectively. Remember to declare these variables in the Create event with values ​​such as 200 and 50, respectively.

    draw_text_colour draws the text of the given color, at the given coordinates. Because pEnergy variable stores a number, you need to translate it into a string using string (). Although the types are not set directly in the Game Maker and you can add a number to a line without problems, it is mandatory to convert numbers to strings for the Draw event.

    [Attention]
    And now the moment, if the object has a sprite, then with the code like above it will not be drawn. Remember, in the first episode I mentioned that if we do not redefine the Draw event, then Game Maker will draw the sprite given to the object itself. So, with the code above we override the Draw event and if the sprite were set to the o_game object, then it would stop drawing. In order for the sprite to be drawn again, you need to add draw_self () in the place of the redefined Draw event you need; This line is responsible for drawing the sprite given to the object.

    Now consider another important point in Game Maker - depth. In the last episode, we have already seen him 1 time. It is time to dwell on this point in more detail. Depth determines the rendering order. The higher the Depth value, the lower the layer will draw the object. Both positive and negative can take Depth values. Example - there are 3 objects obj0 with depth 0, obj1 with depth 100, obj2 with depth -20. The drawing order is obj1 -> obj0 -> obj2. Those. if all 3 objects are on the screen in the same coordinates, obj2 will be on top of all.

    Since o_game draws a GUI, it should be above all other layers. So set it to depth of -100. You can do this in the Create event or on the page of the object itself. Depth can be changed from anywhere in the code.

    So, we are now drawing an exclusively blue rectangle with the inscription of the amount of available energy. But you need to draw the choice of unit. This can be done in two ways - to make the unit’s selection buttons separate objects and simply place them in coordinates on this rectangle, or you can draw icons directly in o_game and catch click events in the Step event (yes, you can do this instead of separate events). The second method is more sophisticated, but the first is simpler and specifically in this case more correct.

    So, we need objects, activating which we can choose which particular unit we will now put. In fact, for this purpose, 1 object is suitable for us. Create it and call o_gui_unit.

    Create event:
    unitID = -1;
    isActivated = false;
    


    Left Pressed Event:

    with(o_game){
       isPlaceUnitClick = false;
    }
    with(o_gui_unit){
       isActivated = false;
    }
    isActivated = true;
    


    The first 3 lines are just that protection against incorrect clicks, so that when you click on the GUI, an object is not placed on the playing field. You can remove this, or add the line isPlaceUnitClick = true; in the Create object o_game.

    Draw event:

    if (isActivated){
       draw_rectangle_colour(x - sprite_width/2 - 3, y - sprite_height/2 - 3, x + sprite_width/2 - 3, 
                             y + sprite_height/2 - 3, c_yellow, c_yellow, c_yellow, c_yellow, true);
    }
    draw_self();
    if (unitID >= 0){
       draw_text(x, y + sprite_height/2 + 5, string(o_game.unitCost[unitID]));
    }
    


    What happens on this side of the object should be obvious - in the Left Pressed event, we go through all instances of the o_gui_unit object and change the value of the user variable isActivated to false, which makes it not our current selection, and we make the current instance active. If it is not clear, ask comments.

    Also set the depth of the object to -110. In general, any number, but less than o_game since the blue background should be under the units, and not above. Either manually change, or in Create, you can write depth = o_game.depth - 10;
    Never add 1-2 units of depth relative to another object. Always leave a little (5-10 units) in case you have to change something.

    One slippery moment. If the object does not have a sprite, and we call the draw_self () function; there will be an error, because there is nothing to draw. Therefore, in this case, specify any sprite. All the same, he will not have time to draw, and we will set the desired one later. In general, it’s better not to do this, but to put, for example, a check.

    Now you need to change a little o_game. The Create event will now look like this:

    pEnergy = 50; // player energy
    alarm[0] = room_speed; // generate enemies
    alarm[1] = room_speed * 3; // generate energy units
    elemGUI_width = 200;
    elemGUI_height = 50;
    isPlaceUnitClick = true;
    enum units{
       shooter = 0,
       energyGenerator = 1
    }
    unitCost[units.shooter] = 50;
    unitCost[units.energyGenerator] = 25;
    var unitSprite;
    unitSprite[units.shooter] = spr_unit_shooter;
    unitSprite[units.energyGenerator] = spr_unit_energy_generator;
    var u = instance_create(100, 25, o_gui_unit);
    u.unitID = units.shooter;
    u.sprite_index = unitSprite[u.unitID];
    u.isActivated = true;
    u = instance_create(100 + sprite_get_width(unitSprite[units.shooter]) + 10, 25, o_gui_unit);
    u.unitID = units.energyGenerator;
    u.sprite_index = unitSprite[u.unitID];
    


    A lot of code and part of it is clearly superfluous, but it looks nothing and is excellent for training purposes.
    enum is the keyword. With it, we simply create a global array, the elements of which can be accessed so units.shooter, which will give us 0 - this is the value of this variable. Next we declare a custom array with unit prices. One could not use enum, then these 2 lines would look like this:

    unitCost[0] = 50;
    unitCost[1] = 25;
    


    But then looking for or recalling what unit 0 is and what unit 1 can be difficult and lazy.

    Next, we create conditionally speaking buttons (more precisely, the o_gui_unit object) and set some properties for them.
    We used to set a sprite for an object through the Game Maker interface, but this can also be done manually through sprite_index. Which we, in fact, did.

    Now since we have prices for objects and there is some kind of GUI with which we can choose which unit we need to install now, it's time to change the Global Left Released code to

    /// place user unit
    if (!isPlaceUnitClick){
       isPlaceUnitClick = true;
       exit;
    }
    var tBgWidth = background_get_width(bg_grass);
    var tBgHeight = background_get_height(bg_grass);
    var iX = mouse_x - mouse_x % tBgWidth + tBgWidth/2;
    var iY = mouse_y - mouse_y % tBgHeight + tBgHeight/2;
    if (instance_position(iX, iY, o_unit_parent) != noone){
       exit;
    }
    var currID = -1;
    with(o_gui_unit){
       if (isActivated){
          currID = unitID;
          break;
       }
    }
    if (pEnergy >= unitCost[currID]){
        pEnergy -= unitCost[currID];
        switch (currID)
        {
            case units.shooter: 
                 instance_create(iX, iY, o_unit_shooter);    
                 break;
            case units.energyGenerator: 
                 instance_create(iX, iY, o_unit_energy_generator);   
                 break;
        }
    }
    


    In the first check, we look at this click on the field, or on our freshly created GUI - i.e. when you click on the o_gui_unit object. The next 8 lines are familiar to us from the last episode, and then we will analyze.

    In the with loop, we go through all the instances of the o_gui_unit object and see which one is currently active. As soon as we come across an active one, we write the unitID value of this instance into the currID variable. we need unitID for knowing the price and which object we will set. In the next test, we see if the player has enough energy to buy this unit, if enough, we remove the corresponding amount of energy and see which unit needs to be put. Everything is as always simple.

    If you, like me, can’t wait to check what happened, we start it and, oddly enough, it works. And I really wanted to talk about debugging.

    In the future, I will have to resort to the “How to Draw an Owl” method, because there will be nothing new from the theory, and if you paint elementary things, a series of these publications will drag on for a long time. The Github code will be available, so I will focus only on new things, if you do not mind.

    It would be nice to make lawn mowers as in the original, which work when zombies approach them. Then they begin to move in a straight line, destroying all the enemies in their path. Lawn mowers are not eternal, after the first operation they disappear. There is nothing new from the theory in the implementation of lawn mowers. Implement yourself and check. The logic will be this - add a collision event with o_enemy_parent to the lawn mower and add a code that starts moving our lawn mower to the right and destroys the instance with which it has a collision. Also, do not forget to put a check for going beyond the screen (in the object itself) and automatically set it in o_game.

    It's time to get to know Timeline. This concept is similar to the alarms you know, but they act as separate entities. Timelines allow you to execute code at specific steps you specify (frames). Not the most commonly used feature in Game Maker, but it’s suitable for some purposes. It’s not very easy to work with them in the current version of Game Maker - it’s difficult to choose the right step, it takes time. For convenience, you need to invent your own crutch - to write a level creation system directly from the game. But since the crutch is different for each game, there is nothing to blame for the creators of Game Maker. In the case of this particular game, I would do something like this - we create a hotel room where you can place enemies. Then, relative to their x-coordinate on the screen, generate code for the timeline. We will calculate the y-coordinate later (or random). If it’s really fast,

    For training purposes, we will make the generation of enemies in the game waves through Timeline. We will need the main timeline, which will trigger the waves. You probably need to make each level a separate timeline.
    Create an empty timeline, call tl_level0 and click on the Add button. Enter the desired step, for example, 120, and confirm. Further, we are given the opportunity to use all the same opportunities as inside any event of any object. It will be necessary to create enemies in the code, so as not to duplicate the creation code, we will use scripts. For example, add the code - scr_generate_enemy_wave (3); to the event of steps 120, 240, 600, 1200. And for the next few steps (waves), we will pass a larger number as an argument.

    A new moment, above we used a function that takes no arguments, this time we will try something new. In this line, we pass the number 3 to the script. The script itself will look like this:

    /// scr_generate_enemy_wave(maxEnem);
    var maxEnem = argument0;
    var enCount = 1 + irandom(maxEnem);
    while(--enCount >= 0){
        var tBgHeight = background_get_height(bg_grass);
        var cycleCnt = 0;
        var eY, eX = room_width + sprite_get_width(spr_enemy_zombie)/2 + 1;
        do{
          if (++cycleCnt > 200) break;
          eY = irandom(room_height - room_height % tBgHeight);
          eY = eY - eY % tBgHeight + tBgHeight/2;  
        }until(instance_position(eX, eY, o_enemy_parent) == noone)
        instance_create(eX, eY, o_enemy_zombie);
        show_debug_message("enemy created");
    }
    


    As before, comments with /// are used for pure convenience, but just such a syntax when the script name matches that comment allows you to display a hint about the number of arguments accepted by the script when you call it.

    String var maxEnem = argument0; we assign the value of argument numbered 0 to the script. argument0, argument1, argument2 etc. - these are standard keywords. Those. by calling scr_generate_enemy_wave (3); we put 3 in argument0 variable.
    Further, we simply place the enemy on the playing field, after checking if any unit is already under it.

    [Do you know?]

    In Game Maker, there is no way to not pass an argument if it is used in a script. If you forget, it's okay, Game Maker will not compile the game and will remind you where you forgot to pass the argument.

    Pay attention to show_debug_message ("enemy created") ;? This is the most primitive but most commonly used debugging method. This standard feature allows you to send a message to the Game Maker console. The string you passed to this function will be displayed. In our case, it will be “enemy created”.

    [Do you know?]

    Although Game Maker’s help says that when creating the release version of the package, the show_debug_message functions are ignored, in fact it’s better to comment on this function before the release, especially if you transfer large amounts of data. In some of the older versions of GM, I transmitted a large amount of information through show_debug_message every step, which significantly reduced performance even in the release version of the game. Maybe it was a bug, but better comment on these lines before release.

    Let's go back to the debugging information in Game Maker after we figure out how to run timelines. Add the line alarm [2] = room_speed * 6; // set timeline for enemy generation in the Create event of the o_game object.

    In the Alarm 2 event, write the following code:

    timeline_index = tl_level0;
    timeline_position = 0;
    timeline_running = true;
    


    We set the current timeline - tl_level0, set the position to 0 (step 0) and start. The position can be any, just remember that it is set in steps (frames), and not by number or time. Game Maker has many functions that allow you to work with timelines, including on-the-fly, during the game itself, so creating a level editor is greatly simplified with their help.

    Since we have already talked about debugging, then let's dwell in more detail. More often than not, show_debug_message is enough, but to find performance bottlenecks using get_timer (); calculate what is calculated the longest. But you can use the tools provided by Game Maker. They appeared in version 1.4, before that the functionality of the debugger was more modest. Here is a screenshot of the current version.



    Use about the same as any other debugger. There is a step-by-step run, a pause, viewing local variables of objects, global variables, etc. The debugger is launched via the F6 button. But then again, the debugger is a more professional tool and it is often easier to do without it.

    Let's look at some more useful functions for debugging / optimization. Undoubtedly, the useful function show_debug_overlay displays on top of the game its use of CPU / GPU. It shows how much time is needed for drawing (Draw events), how much it takes to process the input, how much it takes to clear the screen, how much it takes to clear the memory and a lot of different information that can be found in the help.

    Another pretty useful feature is show_error for custom errors. At the same time, the standard form of errors in itself is quite informative (who called this code, in which line the error and the value of global / local variables), and most often its information is enough.

    In general, YoYoGames (Game Maker developer company) took care that there would be as few problems as possible and most often you will not need debugging in addition to show_debug_message in conjunction with get_timer.

    Having looked through the available graphics on GraphicRiver, I did not find suitable sets. It would be possible to do with squares and circles (which apparently will be in my performance), but in order to study the use of animated sprites, you need to find something appropriate. To do this, turn to OpenGameArt. A great site for finding initial graphics and music for prototypes.

    Having found the necessary images (for example, these _1_ , _2_ ), download them and add to our game. Replace the zombie sprite from the previous episode with a normal, animated one. You can do it this way, go to our sprite, then Edit Sprite -> File -> Create from Stip. Then this window will open, where you can immediately cut the sprites.



    I am more comfortable doing this in a third-party graphics editor. Let's increase them, say up to 64 pixels in height (Edit -> Transform -> Stretch). By the way, in the built-in graphical editor of Game Maker there are a lot of useful functions, you can play around.

    Now everything seems to be fine, but if you run the game, the animation frame rate is very high. This is because the standard animation frame rate is 1 frame per step. Changing it is very simple. In the Create event of the desired object, change the value of the variable: image_speed = 0.2; Now the frames will change every 5 steps.

    Game Maker also has support for SWF and Spine animations. By the way, someone would share a relatively simple Spine animation with a skeleton for performance tests.

    Well, in the end we’ll add another unit to learn a little more about the frequently used Game Maker features. You can find it on Github, it is called o_enemy_zombie_fast. It has some interesting points. Consider the Step event:

    if (isActive){
        with(o_unit_parent){
           if (distance_to_point(other.x, other.y) <= 220){
              other.cHspeed = other.cHspeed2;
              break;
           }
        }
    }
    event_inherited();
    


    The first thing that catches your eye is that the event_inherited function can be called anywhere in the code, not necessarily at the beginning. This is sometimes very useful. And one more point specifically in this case, controversial by the criterion of optimality, but nevertheless this function is very useful - distance_to_point. It calculates the distance between (x; y) the object that caused it and some point, the coordinates of which we transmit. The distance_to_object function is also useful, it calculates the distance between (x; y) your object and the nearest instance passed in the object parameters.

    In general, in Game Maker, many standard useful functions have already been prepared that save time on writing your own code.

    So, the game is gradually taking the desired shape. It will now look something like this:



    Having studied the contents of these 2 episodes, you already know about 70% of what is needed to create a game of any complexity in Game Maker. As you can see, making games in this environment is really very easy, you just need a little desire.

    An approximate plan for the next episode:

    - we will remake the way the units
    are arranged - we will add support for the gamepad
    - several new units
    - we will remake the playing field
    - we will implement victories / defeats
    - we’ll talk about rooms and how to adapt games for different screens again
    — learn how to work with View

    Project on Github

    Some features of Game Maker with a focus on mobile development, which I will not mention in this series of publications
    - built-in support for Google Play Services, Game Circle (Amazon) and Game Center (achievements and leaderboards)
    - built-in support for in-app for mobile devices
    - support for Facebook API
    - extension market for Game Maker
    - physics engine Box2D
    - shaders

    Only registered users can participate in the survey. Please come in.

    How to write further?

    • 40.8% in more detail 49
    • 28.3% As of now 34
    • 18.3% Standing 22
    • 12.5% Do not write. I am a game developer and I do not need additional competition 15

    Also popular now: