Contact: Rando Wiltschek

Game creation tutorial
Bermuda Triangle

Welcome back to the game creation tutorial. I hope you could follow the instructions so far. In this section we'll add some challenges to the game.

12. Damage

Completed the last section successfully? Then open your file now, else open "bermuda_04.mfa" and continue from there. In this chapter we will specify what happens when the player is damaged or destroyed.

12.1. Animations

The plan is this: if the player's submarine is damaged once, it will look damaged. If it is damaged again, it will look destroyed and sink. Then the player loses a life and gets a new sub. We will use this system for the enemies too.

So we need "damaged" and "destroyed" animations. They are not included in the bermuda_01 to _04 files. That's because I just made them. So you can either import them now, or take a go yourself and create them. If you do that, keep in mind that you have to create two new custom animations that are set to loop, with the hotspot and action point set to the correct position.

I have put them in the file "bermuda_04_gfx.mfa". Here's how you import them, if you decide not to make them yourself:

Leave the game open and go to the frame editor. Then open the file "bermuda_04_gfx.mfa". It should appear in the workspace toolbar where you can unfold "Frame 1" by clicking on the + in front of it. In "Frame 1" are the objects with two new animations each. As we have not used the Enemy and UFO objects in code yet, delete them from the frame area of the frame editor and replace them with the new objects, by drag and drop. Just drag them from the "Frame 1" to your frame area.

You can delete objects from the frame area by selecting them and pressing "Del" on your keyboard.

If you delete the last instance of an object and it is not created by events, the object will be removed from your game. All conditions and actions using it are deleted too. This happens without warning.

We are already using the Player object in events. So if we delete it, our code will be completely messed up or useless. There are two ways how we can get the new animations now. They are both useful to know because depending on the situation, one or the other will be faster. Because I'm such a nice person I will explain both.

a) Copy animations one by one

This way is nice if you only have one animation to copy or if you have more custom stuff in the object and you don't want to replace it completely. So in our case you'd have to do it twice.

Open the animation editor, by right clicking on the Player object in "Frame 1" and selecting "Edit", or simply by double clicking on it. In the lower left, in the "Animations" list, scroll down so you can see the new animations.

Custom animations are always at the end of the animations list, to confuse new users, to give the scrollbar a purpose and to make life just that little bit more challenging. Or maybe just because all the other animations are being used by the default movements and thus always need to be there.

Right click on the "Damaged" animation and select "Copy". Click "OK" or press "Enter" to close the animation editor.

Edit (double click) the Player object in the frame area. Right click in the "Animations" list and select "New". Enter "Damaged" and click "OK". The new animation appears at the bottom of the list. Right click on it and select "Paste".

Now do the same steps again for the "Destroyed" animation.

b) Event replacement

This way is nice, if you completely want to replace one object by another, which is the case for our game. So if you later decide that the player should look like the UFO and shoot with coral reefs and pick up sea mines from the ground, this is the fastest way to do it.

For this, drag the Player object from "Frame 1" onto the frame area, somewhere next to the old Player object. MMF2 will notice that they are not the same and rename it to "Player 2". Don't delete the old one yet. Open the event editor.

Right click on the old player object and select "Replace by another object". In the dialog that shows up select the new player object and click on "OK".

Any reference to the old player object has now been transfered to the new one. Now you can delete the old one from the frame area and rename the new one to "Player". For convenience I move the new player object icon to the position of the old one, in the event editor.

You can order objects in the event editor by drag and drop.

Good. Whichever way you use, you should have the same result now: a player object that contains the new animations. Make sure you have the new enemy and UFO animations too, so when we add code to them later, you don't have to do this again.

12.2. Loops

Now we'll make the player handle damage. Currently, when he touches an obstacle, the screen will shake and he drops his current treasure. Later there will be more reasons for this to happen - for example being hit by a torpedo. So it would be convenient to have the consequences of being damaged at a central point and not copy it for each cause.

This is where loops come into play. Loops increase MMF's power beyond imagination. Okay, maybe that's a bit much. But almost. Let's put it like this - if you're going to make a complex game, you will probably not make it without loops. For the programmers among the readers, a loop is a parameterless method that does not return anything, crossed with a repeat loop.

The power of a loop is that you can run one or more events as often as you want and "inject" them at any point in your code, all before the screen is redrawn. Or you can treat instances of an object individually, instead of as a group. For programmers this may sound trivial or even inadequate - but imagine you'd not have it at all. Of course loops also have their weaknesses. I won't cover them all indepth here, but just mention them as a "warning":

If you create or destroy an object (in an event or loop) it will not "completely" exist or be removed until MMF reaches the end of the current event cycle.

A loop does not know where it was run from or what objects were selected when it was started.

You cannot abort a loop cycle. If you stop the loop from within it, the current cycle will run to the end first.

An infinite loop will make the application freeze and it won't respond anymore.

You can not use immediate conditions in a loop, because the "On loop" condition is already immediate.

To understand what a loop exactly does, there is a video tutorial that nicely visualizes the effects of loops: "The definite guide to fastloops!" by MrMan.

In our case, we'll use a loop that is only run once at a time, but from different events. So in the "Player only" event group add an event at the end:

+ Special - On loop: "Damage player"

One line above your new event, you see the event of the player colliding with a backdrop. Drag the checkmark from the player object's actions down to the actions of the new event. This copies all the actions:

You can drag and drop actions between different events and objects of the same type.

So now you have

+ Special - On loop: "Damage player"
> Player - Bounce
> Player - Alterable values - Set: Shake = 200
> Player - Alterable values - Set: Fixed Treasure = 0

As we don't want the player to bounce every time he gets damage, but only when he hits an obstacle, right click on the checkmark and select "Delete - Bounce".

Nice. Now every time we "call" the loop "Damage player" he will drop his treasure and the screen will shake. Let's quickly fix the "Player collides with background" event. Delete the "Set shake to 200" and the "Set Fixed Treasure to 0" actions from it. Add this action:

> Special - Fast loops - Start loop: "Damage player", 1

Of course we also want to employ the new animations now, so let's add another event:

+ Special - On loop: "Damage player"
+ Player - Animation - Which animation of "Player" is playing?: Stopped
> Player - Animation - Change - Animation sequence...: Damaged

And another one:

+ Special - On loop: "Damage player"
+ Player - Animation - Which animation of "Player" is playing?: Damaged
> Player - Animation - Change - Animation sequence...: Destroyed

So if the player is not damaged, make him damaged and if he's damaged make him destroyed. Sounds good, right? But there's a problem if you think about it. The loop runs through this one event after the other. In the beginning the player is running the animation "Stopped", so his animation will be changed to "Damaged". But when we reach the next event, his animation is already "Damaged", so he will be changed to "Destroyed" immediately!

Here we have to realize for the first time that events are run one after the other and not all at once. So change the order of the two events - you can achieve this via drag and drop.

Unfortunately, it will still not work. At this point a bug in MMF2 comes into play. Even though it looks as if the "Stopped" animation of the player was running right now, MMF2 believes it is not. So the "Player animation Stopped is playing" condition will never be true. Hopefully this bug will be fixed in a later version of MMF2. But for now, to solve this, we have to explicitly tell MMF2 to run that animation in the beginning.

So go up and find the "Start of Frame" event. Add this action:

> Player - Animation - Change - Animation sequence...: Stopped

Run the game and test the new animations.

13. Destruction

At this point, when the player reaches the "Destroyed" animation, he can still move around as he pleases. That will stop now. If he looks like burned cheese, he gotta sink! First we have to add a little something to this event:

Found it? Good. Add this action:

> Player 1 - Player control - Ignore control

Unless we restore control again, no player input will be processed. Add a new event:

+ Player - Animation - Which animation of "Player" is playing?: Destroyed
> Player - Position - Set Y coordinate...: Y( "Player" )+1

This is basically the same movement as for the treasure chest - if the player looks destroyed, he sinks 1 pixel per frame. As we want him to sink all the way to the ground, we have to take care of more events though. Find this event:

Add this condition:

+ NOT Player - Animation - Which animation of "Player" is playing?: Destroyed

This is a "negated" condition. This means it will trigger / fire / be true when the opposite condition is met. So in this case, when the animation "Destroyed" is NOT playing. Probably it would be more consistent to write "-" instead of "+ NOT", but that notation might be too subtle to notice.

A condition can be negated by right clicking on it and selecting "Negate".

Anyhow - from now on, the player will only collide with background when he is not destroyed. While we're already at it, let's modify another event. Open the "Bubbles" group and find this event:

Duplicate the event by selecting it and pressing CTRL-C and then CTRL-V, or right clicking the event and selecting "Copy" and then "Paste".

Now replace the "Always" of the first one by

+ Player - Animation - Which animation of "Player" is playing?: Destroyed

and the "Always" of the second one by

+ NOT Player - Animation - Which animation of "Player" is playing?: Destroyed

A condition can be replaced by right clicking on it and selecting "Replace".

Now we have separated the event into two cases. If your game becomes very complex and you have to differentiate a lot of conditions in one event, you can end up making dozens of copies for different cases, as MMF2 unfortunately does not have an ELSE or a SWITCH / CASE statement (yet) - although there are third party extensions that allow you to achieve an ELSE effect. And in section 1 I showed you how to simulate an ELSE statement with a global variable.

Edit the first event to match this:

+ Player - Animation - Which animation of "Player" is playing?: Destroyed
+ Special - Limit conditions - Restrict actions: 0, 0, 0, 5
> Create object: Bubble, relative to: Player, X: 0, Y: 0
> Bubble - Direction - Select direction: 0, 16 (left and right)
> Bubble - Position - Set X coordinate...: X( "Bubble" )-9+Random(19)
> Bubble - Position - Set Y coordinate...: Y( "Bubble" )-5+Random(11)

So you end up with this nice code. What happens is that if the player is not destroyed, the bubbles come out of the propellers at the back of his sub - if he is destroyed they come out of his sub everywhere.

You may wonder why we keep modifying old events, instead of doing it right in the first place.

The answer is easy: because this is the way you will be doing it later when you make a game on your own. Even if you plan ahead really well it is still impossible to foresee some modifications of existing code.

Find:

Change to:

+ NOT Player - Animation - Which animation of "Player" is playing?: Destroyed
> Player - Order - Move in front of object: Bubble

Remember when we made this event? The purpose was to put less focus on the bubbles. But when the player gets destroyed, we want that focus back. So here we go.

Now, back to the player group. Look at this event:

It keeps the player from falling through the ground. We want to catch exactly the moment when the player touches the ground. So above this event, insert those new events:

+ Player - Position - Compare Y position to a value: Greater 200
+ Player - Animation - Which animation of "Player" is playing?: Destroyed
+ Player 1 - Compare to player's number of lives: Greater 0
> Player - Animation - Add backdrop: No effect on collision
> Player - Position - Select position...: relative to Player, X=0, Y=-100
> Player - Animation - Change - Animation sequence...: Stopped
> Player 1 - Number of lives - Subtract from number of lives: 1
> Player 1 - Player control - Restore control

If he's destroyed, touches the ground and has lives left, we reposition him, change his animation back to "Stopped" so he looks a bit more fresh, subtract a live and reenable his controls. Make sure this event is before the event that sets his Y position to 200. Else it will never be true.

14. The famous flashing reincarnation effect

Being almost a convention in arcade games, when the player respawns he flashes and is immortal for a very short time. This is an effective way to make sure he is not instantly killed again right after appearing, plus it draws the player's attention to his new position.

For this effect we require a new alterable value in the "Player" object. Let's call it "Flash". We will use it to determine when the player should stop flashing.

Go back to the event editor and look at the event we just made in the last chapter.

Add two actions:

> Player - Visibility - Flash object: 0, 0, 0, 15
> Player - Alterable values - Set: Flash to -15

This will make the player flash until we launch the action "Reappear". Add this event:

+ Special - Limit conditions - Restrict actions: 0, 0, 0, 10
> Player - Alterable values - Add to: Flash, 1

So ten times in a second, 1 will be added to Flash. Which means after one and a half seconds Flash is 0, with a precision of 1/10 of a second. Notice that it will keep adding even after passing 0 (which we wait for here), but that causes no problem for us. If you think "Oh, but won't it slow down the game / waste resources if it has to add 1 ten times a second, even if it doesn't have to?", I tell you "If you add a condition to check if it is below 0, this condition is checked every frame (50 times a second) - which is much worse than just performing the action. So just don't worry about performance at this point."

Let's check for Flash to reach 0. Add this event:

+ Player - Alterable values - Compare to one of the alterable values: Flash is Equal 0
> Player - Visibility - Reappear

The Flash variable has a nice side effect - we can use it to check if the player is invincible. So let's do that. Find this event in the Player group:

Add this condition:

+ Player - Alterable values - Compare to one of the alterable values: Flash is Greater or equal 0

Done. So while the player is invincible he can go through obstacles, and later also through enemies and missiles. Of course if his invincibility expires while he's in an obstacle he is done for.

15. Distortion

15.1. Extensions

MMF2 comes with plenty of extensions for all kind of special purposes. Some provide new functions for calculations, some let you import special file types, store data in a better way, let you access system properties or create network applications. MMF2 comes with a couple of Bonus Packs that contain very reliable and well tested extensions. You can find them on the Clickteam Forum on the right side under "Quick File Links". Actually you should go and get them right now, because they're so good you don't want to miss them. So close MMF2 and install the Bonus Packs (you probably will have to insert your MMF2 CD). Note that there are more extensions than just the Bonus Pack extension.

To get those extensions, there's the "Extension Updater", a handy tool that allows you to get the latest version of all your extensions and all the other ones that you might be missing. You can find it below the links to the Bonus Packs, or here. If you download that archive, run the "FusionUpdater.exe" inside of it. Once it has started and loaded, check "Hide up to date extensions". There are extensions for all kind of purposes, from storing bits, parsing XML or modyfing strings to better object movements and finding difficult paths. The one we are looking for is called "Perspective". Find it, click on it and then press the button "Install/Update selected extensions". There you go. Close the program.

15.2. Perspective

Start MMF2 again.

Great! Now let's insert an extension. Go to the frame editor and right click on a spot of free background. Then select "Insert Object".

In this dialog box you will also find other objects that you frequently need - like "Active", "Backdrop", "Quick Backdrop", "Counter" and "String".

The one we are looking for is called "Perspective". Find it, select it and click "OK".

Now select the object and change the properties as you see them here:

Run the game and see what it does. If you want you can play around a bit with the "Zoom value", which says how strong the effect shall be - or try the other available effects to see what else is possible. If you don't like the effect, just delete the object and skip ahead to the next chapter.

Some extensions can be quite computational expensive and slow down your game. This is one of them. So if your computer can not cope with it, you better not use it.

Note that currently the player's submarine and the bubbles are not affected by the extension. This is because they are actually in front of it. The bubbles are in front of it, because all new objects that you create at runtime appear in front of all other objects. The player's submarine is in front of it, because we have an action in the "Bubbles" group, where we tell it to be in front of the bubbles - so also in front of the "Perspective" object.

This is not a big problem with the player in the center of the view. But we want to move the view independent of the player later and then it will look odd. So let's fix it.

Create a new event group and call it "Display". Make sure it is the last of the groups. The point of this group is to fix the order of objects at the end of the event cycle, so it overrides whatever happens in the other groups. Maybe we will also use it to handle other interface issues, if there should be any.

Create this event in it:

+ Special - Always
> Perspective - Order - Bring to front

Go back to the frame editor. As we never have to touch the extension again, right click on it and select "Lock".

16. Independent scrolling

For your convenience here's a new version of the MMF2 file, for the case that you couldn't follow one of the steps: "bermuda_05.mfa". Now we'll make the scrolling of the screen independent from the player. This will make the game a little bit more dramatic: the player can't go back and he has to quickly decide what he is going to do.

There are many ways this could be achieved. The way I'm going to do it makes use of an object, that represents the view or the camera. To get this object, go to the frame editor, right click and choose "Insert Object". From the list that shows up select "Active" and click "OK". Now click with the mouse somewhere in the frame area, where you want to place the new object. If you click with the right mouse button the action will be cancelled.

Right click on the new object and select "Rename". Call it "Camera". In the object list to the left, drag it into the "System" folder. If you double click on the object, the animation editor will open. You can edit it's first frame to differentiate it from other objects so it's easier to find it by it's icon. In the game the object will be invisible later, so this is only for you.

Edit the properties of the Camera object and change it's movement to "Vector" and set it up like in the picture to the right:

You can click "Try Movement" to see what happens.

In the display options turn "Visible at start" off, to hide the object when the game runs.

This will make the object move forward at a steady speed but not accelerate it. It also allows us to change the speed with an action later.

Go to the event editor. In the "Player only" group, find the event that centers the view on the player. This event will no longer be "player only". So we move it to the "Display" event group. You do that via cut and paste, or drag and drop.

While holding CTRL you can click on the number of events or comments to add or remove them to/from your selection.

By pressing CTRL-X you can cut one or more events - by pressing CTRL-V you can paste them again.

Edit the action that centers the frame: right click on it and select "Edit". Replace X( "Player" ) by X( "Camera" ). The shake value will still come from the player. Looking back at it now, it should probably be stored in the camera instead of the player, but it is not so important.

Create a new event:

+ Storyboard controls - Start of frame
> Camera - Position - Set X coordinate...: 0
> Camera - Movement - Set Speed...: 75

If you run it now, you will notice that the view will only move once the camera object has moved past the center of the screen. I think that's good so the player can figure out what's going on before it "starts". If you don't like it, change

> Camera - Position - Set X coordinate...: 0

to

> Camera - Position - Set X coordinate...: 160

Now we gotta make some adjustments to the player. What happens when he hits the edge? I prefer to just restrict him to stay within the borders. There are two more or less obvious ways to do it. The first is to have an event for left and right each, that check if he's out of limits and then put him back inside. The other uses the min( ) and max( ) functions in one event. We call this "clamping".

We will insert this in the "Player only" group after the "Y position of Player > 12" event.

To insert an event, comment or group, right click on the number of an existing event and select "Insert".

Here's the event to add:

+ Special - Always
> Player - Position - Set X coordinate...: Max(Min(X( "Player" ), Max(X( "Camera" )+140,300)), X( "Camera" )-140)

This looks really complicated. But actually it's not:

X( "Camera" )-140 is the border to the left (the camera is in the center, 140 is roughly half the window size).

X( "Camera" )+140 is the border to the right.

300 is roughly the width of the view.

Max(X( "Camera" )+140,300) is whichever is larger - the right border, or the position 300. Without this, the right border would be in the middle of the screen, right at the beginning of the level - so we push it out to 300.

Min(X( "Player" ), Max(X( "Camera" )+140,300)) is whichever position is smaller - the player's current position, or the right border.

Max(Min(X( "Player" ), Max(X( "Camera" )+140,300)), X( "Camera" )-140) is whichever position is larger - the player's current position, or the left border.

So in the end the position will be either the player's current position, or the border that he crossed. I hope that made sense. This is very useful to know, because it is very fast to calculate and does multiple things in one.

Last thing to do is make the camera stop when the player dies. So find the event where the player is respawned. Duplicate it and change it to this (changes are marked in bold):

+ Player - Position - Compare Y position to a value: Greater 200
+ Player - Animation - Which animation of "Player" is playing?: Destroyed
+ Player 1 - Compare to player's number of lives: Equal 0
> Player - Animation - Add backdrop: No effect on collision
> Player - Position - Select position...: relative to Player, X=0, Y=-100
> Player - Animation - Change - Animation sequence...: Stopped
> Player 1 - Number of lives - Subtract from number of lives: 1
> Player 1 - Player control - Restore control

> Camera - Movement - Stop

17. Mines

17.1. Placement

Let's get some mines into play. To make it a bit more interesting, we will not place them by hand. There are two ways we can approach this. We can either create all the mines at the start, or we can create them, while the player proceeds. The first way is easier - so let's do that.

Create a new event group, call it "Mines" and move it before the "Display" group.

Create this event:

+ Storyboard controls - Start of frame
> Special - Fast loops - Start loop: "Create mines", 40

This will run the loop "Create mines" 40 times, when the game starts. So we will have 40 mines. If you want more or less, just change this value.

Now create the event for the loop:

+ Special - On loop: "Create mines"
> New objects - Create object: "Mine", X=-30, Y=-30
> Mine - Position - Set X coordinate...: 320+Cos(Random(9000)*0.01)*(Frame Width-320)
> Mine - Position - Set Y coordinate...: 40+Random(Frame Height-120)

It doesn't really matter where you create the new mine - as long as it is out of sight. With the "set coordinate" actions we move the mine where it should be.

After creating an object, MMF selects it. So all actions affect only this instance of the object.

The formula for the coordinates looks really complicated again. So let's take it apart. X coordinate:

320 is the width of the view. This is to make sure no mines are visible at the beginning of the game.

Random(9000) gets us a value between 0 and (almost) 9000.

Random(9000)*0.01 gets us a value between 0.00 and (almost) 90.00. Why 90? Because 90 degrees are one quarter of a circle. Just wait, it'll soon make sense.

Cos(Random(9000)*0.01) returns a value between 1 and 0. Why? Because cosinus(0) is 1 and cosinus(90) is 0. If we'd use a higher value than 90, we'd get negative results, which is not desired here. But the clue is, that 1 is more likely than 0. So we turn the linear randomness into nonlinear randomness.

(Frame Width-320) is the total width of the level, minus once the width of the view.

Cos(Random(9000)*0.01)*(Frame Width-320) returns a value between 0 and frame width - 320.

320+Cos(Random(9000)*0.01)*(Frame Width-320) returns a value between the right border of the player's view at the start, and the right border of the level - and it is more likely at the right than at the left.

So what happens here, is that it is more likely that a mine is placed to the right than to the left - the density of mines increases while you play.

The Y coordinate setting is very straight forward now.

40 is the distance from the top (so the player kind of fits in there to deliver his treasure).

120 is the distance from the bottom (so mines don't overlap treasures).

The rest is just getting a random position between those two edges.

17.2. Collisions with the player

Create this event in the group:

+ Player - Collisions - Overlapping another object: Mine
+ NOT Player - Animation - Which animation of "Player" is playing?: Destroyed
+ Player - Alterable values - Compare to one of the alterable values: Flash >= 0
> Mine - Destroy
> Player - Movement - Bounce
> Special - Fast loops - Start loop: "Damage player", 1

This event is almost an exact copy of the event that lets the player get damaged from hitting an obstacle. But here we use "overlapping" for the collision. Else it can happen, if the player moves over a mine while invulnerable, that he gets stuck and the mine won't go off.

17.3. Torpedo vs. mine

I think it'd be nice if the player could shoot mines with a torpedo. So let's add that too - create this event in the "Torpedo" group:

+ Torpedo - Collisions - Overlapping another object: Mine
> Torpedo - Destroy
> Mine - Destroy

Speaking of the torpedo, we have to employ a little fix:

The condition

+ X position of Torpedo > Right Frame + 60

is not correct. "Right Frame" refers to the very right edge of the level. So change it to

+ X position of Torpedo > X( "Camera" ) + 160 + 60

Else, the player can shoot mines and other things long before they come into sight.

As we're already here, let's also keep torpedos from going through obstacles. Add this:

+ Torpedo - Collisions - Overlapping a backdrop
> Torpedo - Destroy

This will make the coral reefs feel more solid and allows the player to take cover from torpedos later.

Now go and play a bit with the mines!

If you had trouble recreating everything, feel free to continue from the file "bermuda_06.mfa".