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 will add a whole lot more events to create some interactivity. Let's go!

7. Obstacles

7.1. Obstacle placement

Could you follow the instructions from the last section? Then open your file now, else open bermuda_03.mfa and continue from there. Go to the frame editor.

Hm. The sea floor looks a bit boring right now. So let's make it more interesting by adding the corals. First make sure the grid is turned off, by opening the view menu. If there's an almost invisible box around the icon "Snap to Grid", then the grid is turned on, so click that option. Else don't.

The grid is nice if you have tiles that need to be placed seamlessly next to each other. But here we don't want that.

Click on the green "Coral 1" in the frame area and hold down your mouse. While you drag it, you will only see its outline. Now hold down the Ctrl key on your keyboard and drag the coral onto the sea floor. Next to your mouse cursor a "+" will appear, which means that you are making a duplicate of the object.
Keep holding Ctrl and release the mouse.

Holding down the CTRL key on the keyboards when dragging objects makes duplicates of them.

Voilà, you have a green coral in the game. You can adjust its position by dragging it while not holding Ctrl.

Do the same with the other two coral types. You can also put them over each other to make it look more interesting. Hold down the space bar and drag the view with the left mouse button to scroll around quickly. Go through the level and place duplicates of the corals on the sea floor.

While holding the space bar you can scroll with the left mouse button.

In the end the entire level should be nicely covered with corals here and there. Don't use too many, because later the player will have to avoid touching them.

Done? Good. Now do the same with the treasure chest. But don't use too many of them. Make sure the treasure chests do not overlap the corals, or else they will get stuck later.

You should have something like this now:

If you want, run the game now (by pressing F7). Press Escape to return to the editor.

Now select the coral objects and the chest outside of the playfield and press delete. They are of no use for us there. Make sure you don't delete any other objects, because

If you delete the last one of a type of object, it will be entirely removed from the game and any code relating to it is also deleted.

So if you don't have any occurences of an object in the playfield, you should always keep one outside of it.

7.2. Submarine versus obstacle

Remember how we set the coral objects to "obstacle" in section 1? Now we can make use of that. Open the event editor and add the following event:

+ Player - Collisions - Backdrop
> Player - Movement - Bounce

Cool! Now the player bounces off the corals. Assuming you would add more backdrops that are set to "Obstacle" later, the player would also bounce off them. Later we can replace the actions in this event to damage the player when he hits an obstacle. Run the game and see what happens!

All backdrops that are set to "Obstacle" are treated as one.

Did you notice that the submarine seems to bounce just a little bit too early? This is because we have turned off fine collision detection for all active objects in the first section! To fix this, go to the frame editor, click on the player's submarine, open the "RunTime Options" tab of the properties toolbar and check "Use fine detection".

8. Event groups and comments

8.1. Event groups

Now we're going to explore the extreme tidying powers of event groups. As we have whopping seven events already and there will be a lot more, we should take precautions that we don't lose the overview later. Are you still in the event editor? Good.

Right click on the number next to "New condition". It should be an "8". Point on "Insert" and select "A group of events". A dialog will open that asks you for a name. Enter "Player only" and click "OK".

Great! Now you have a group called "Player only". We will use this group to store all events that are only related to the player and user input. Click on the "1" of the first event with the left mouse button. Now hold down the Shift key and click on the "7". This selects the events 1 to 7.

By holding Shift you can select a list of events.

By holding Ctrl you can add or remove individual events from the selection.

Drag the selected events on the group. Nice. They are now in the group.

If you double click the name of the group, it will open or close.

You can have groups in groups.

Notice that the group has its own "New condition" line. Here you can nicely add conditions right into the group.

We will add more groups later, when there are more working objects. Often it is not exactly clear how you should group events, especially when multiple objects are affected by them. So in the end you will have to use filters to find your objects.

Besides structuring your code, event groups have another use. You can turn individual groups on and off with actions. For example you could turn off the events for the player's character when he dies.

8.2. Comments

It is good practise to add comments to your code to make it easier to find things and to understand complicated events. I will add comments where I think they are useful. Writing comments is not a focus of this tutorial, so I will leave it up to you to add comments in your version of the game.

Comments are made the same way as groups of events. Just click "A comment" instead of "A group of events".

9. Treasure chests

We already have some treasures lying around, now we want to let the player pick them up. Grab your seat because this part will be the full flavour of MMF coding. Make sure you understand everything in this chapter, because we will base many events on the principles that are described here.

9.1. Global and alterable values

Letting the player carry stuff is a bit tricky, but it can be done in four events. We require two extra variables for this.

The first is an alterable value of the player's submarine. If you select the player's submarine in the frame editor or in the workspace toolbar and open the "Values" tab in the properties of it, you can add a name for our alterable value by clicking on "New". Then double click on "Alterable Value A" and enter "Fixed Treasure".

Most object types have 26 alterable values. Every duplicate of an object can store its own numbers for every alterable value.

Those objects also have 10 alterable strings. Instead of numbers they can store text.

We will use this alterable value to store which treasure chest the player is carrying.

The second variable is a global value. You set it up almost the same way. Select "bermuda" in the workspace toolbar and open the "Values" tab of its properties. Click "New", double click on "Global value A" and enter "Else".

An application has (almost) unlimited global values and strings. They carry over from one frame to another.

Global values can be accessed very fast and are not related to any object which makes them very useful for general purposes, e.g. temporary values.

Most of the time you would use a global value to carry information over from on frame to the next. For example for options, player statistics, results of the last level, etc. We will use this global value in a special way - to compensate for MMF's lack of an ELSE statement.

9.2. IF, THEN, ELSE

Good. Our variables are ready now, let's use them by making our ELSE statement. But first create a new group and call it "Treasure". In there add the following events:

+ Player 1 - Joystick - Read joystick state: Remove the arrow by clicking on the box next to it, select fire button 2
> Special - Change a global value - Set: Else = 1

Here we process the input for the fire button two (by default CTRL). Fire button one (Shift) will be used for torpedos later. This event will reset our handmade ELSE statement. Note how the condition turns red - it's an immediate condition - this means it will not be checked in order with all other events, but only at the point when it is called - in this case upon pressing the fire button two.

+ Player 1 - Joystick - Read joystick state: Remove the arrow by clicking on the box next to it, select fire button 2
+ Treasure - Collisions - Overlapping another object: Player
+ Player - Alterable values - Compare to one of the alterable values: Fixed Treasure Equal 0
> Player - Alterable values - Set: Fixed Treasure = Fixed( "Treasure" )
> Special - Change a global value - Set: Else = 0

When creating this event, remember that you can copy conditions by dragging them from one event to the "New condition" line or on another condition while holding CTRL.

This event will be processed exactly after the last event, when the second fire button is pressed. But here we also check if the player is over a treasure box and his alterable value "Fixed Treasure", that we set up earlier, is 0 - which for us means that he is not carrying a treasure. This is our IF statement. Note that the condition "Overlapping another object: Player" selects exactly the treasure box(es) that the player is over - so all other treasure objects stay unaffected.

Conditions of an object select all objects that meet this condition.

When this event triggers, we set the alterable values "Fixed Treasure" of the player to "Fixed( "Treasure" )" - here it means that the player will carry exactly the treasure with that fixed value. This is a function of the treasure object, which returns the fixed value of the treasure box. What is a fixed value?

Every instance of an object has a fixed value - a number that is unique from all other objects and can always identify this instance.

What is a function?

A function returns a string or value from an object. Most functions use parameters to specify the desired result.

E.g. the function Max(x,y) always returns the higher value. Max(5, 3) would return 5. In our case Fixed( "Treasure" ) returns the fixed value of the object "Treasure".

Functions of objects always return the value of the first instance of that object type. So if you have two objects of the same type selected, only the values of the first one will be returned. This is important because it will be your main source of problems when using MMF.

The last action sets our global value "Else" to 0. So if this event is executed, we don't want the next event to run and vice versa. That's what ELSE is all about. Phew. The hardest part is behind us now. Add the next event:

+ Player 1 - Joystick - Read joystick state: Remove the arrow by clicking on the box next to it, select fire button 2
+ Special - Compare two general values: Else Equal 1
> Player - Alterable values - Set: Fixed Treasure = 0

Again we process the fire button two input. But here we check if the value "Else" is still 1. If the last event was executed it would be 0, so the current event won't run, and the other way around. A working ELSE statement!

For comparing to the "Else" value we use the "Compare two general values" condition. You will use this one very often - basically every time you want to compare two formulas or do not want to make a selection with a condition.

"Compare two general values" does not select/deselect objects.

The action in this event will set the player's "Fixed Treasure" value to 0, which means he doesn't carry a treasure. Now we only need to find the treasure with the same fixed value as the player's "Fixed Treasure" variable and attach it to the submarine. Add this:

+ Treasure - Alterable Values - Compare to fixed value: Equal Fixed Treasure( "Player" )
> Treasure - Position - Select position: Relative to Player, X = 2, Y = 11

This condition will be checked every cycle of the engine, that means every frame. It checks if there is a treasure object that has a fixed value equal to the player's "Fixed Treasure" variable. If there is none, the event will not run. If there is one, the condition selects it and the action places it under the submarine.

In the event list editor it should look like this:

 

 

 

 

 

Run the game now. If you've done everything right, you should be able to pick up a treasure by pressing CTRL and release it by pressing CTRL again.

Hm. Obviously the player is behind the treasure chests. Let's quickly fix that by adding this event:

+ Special - Always
> Player - Order - Move in front of object: Treasure

This will make sure that the player is in front of the treasures at any time.

9.3. Treasure must sink!

If you release the treasure, it looks a little bit unnatural. We can fix that by making the treasure sink back to the ground. To make this cool, we need a variable within the treasure object. So open it's properties and the "Values" tab, and create a variable called "MaxY".

Add the following event:

+ Storyboard controls - Start of frame
> Treasure - Alterable Values - Set: MaxY = Y( "Treasure" )

This event will run once, at the very start of the game. "Start of frame" does not refer to the frame cycles, but to the frame of the storyboard, that contains all the objects. The action initializes the value MaxY with the Y position of the treasure object.

X refers to the horizontal position of an object, counting from the left. Y refers to the vertical position, counting from the top.

Add:

+ Treasure - Position - Compare Y position to a value: Lower MaxY( "Treasure" )
> Treasure - Position - Set Y coordinate: Y( "Treasure" ) + 1
> Treasure - Alterable Values - Add to: Alterable Value B, Random(200)

This event will trigger every frame ("frame" as in "frames per second") for all treasure objects that have a vertical position that is higher than their variable "MaxY".

It will set their vertical position to their current position plus 1 - so they move downwards. It will also add a random value between 0 and 199 to their Alterable Value B. This makes no sense at this point, but we will use this for an effect later, so we can already add it.

9.4. Score

Easy. In the "Player only" group, add this event:

+ Storyboard controls - Start of frame
> Player 1 - Score - Set score: 0
> Player 1 - Number of lives - Set number of lives: 3

Here we initialize score and lives. So if the player restarts the game they are back to what they were. In the "Treasure" group, add this event:

+ Treasure - Position - Test position of "Treasure": click the arrow at the lower left that says "Is the object outside?"
> Treasure - Destroy
> Player 1 - Score - Add to score: 200
> Player - Alterable values - Set: Fixed Treasure = 0

If the player somehow gets the treasure out of the frame, he gets 200 points and the treasure is removed. The variable "Fixed Treasure" of the player is set back to 0 - he doesn't carry a treasure anymore now.

Try it.

Noticed that the player starts with 200 points? Weird? No. We still have the original treasure box outside of the frame! So go to the frame editor, and delete the treasure box that is not in the playfield.

10. Effects

This is starting to get good. Let's add a special "you are under water, didn't you notice?"-effect! For this we'll be using the Bubble object that you can find in the "Objects" folder of our game and also barely visible in the frame area.

10.1. Bubbles

For the bubble we will create a very simple custom movement. But first create a new group called "Bubbles" and add an event to create some bubbles:

+ Special - Always
+ Special - Limit conditions - Restrict actions: 0, 0, 0, 5 (= 50 miliseconds)
> Create object: Bubble, relative to: Player, X: -16, Y: 1
> Bubble - Direction - Select direction: 0, 16 (left and right)
> Bubble - Position - Set Y coordinate...: Y( "Bubble" )-3+Random(7)

Looks already overwhelming? This is what happens here:

Always is true every frame. At a framerate of 50 frames per second (default) this means every 20 miliseconds. But that's more often than we want. So we use "Restrict actions" to only let it be true once per 50 miliseconds.

Then we create a bubble object next to the player object, with an offset of (-16, 1) - so more to the left and one pixel down (where the rotor of the submarine is). In the same event we also set some values for the bubble.

If you run actions for an object after the creation action, in the same event it has been created, only the created object is affected - duplicates of the object are left alone.

Here we pick a random direction (left or right). If you look into the object's animations, you will see that the left and right directions have different bubble pictures for variety. Then we change the Y coordinate - that is the vertical position on the screen. As we don't want to position it somewhere totally else, we first get the Y position of the object with Y( "Bubble" ). Then we subtract 3 and add a random value between 0 and 6 - so the bubble will randomly be placed a few pixels higher or lower.

Random(n) returns a value between 0 and n - 1.

Run it. Whee, a trail of.. uh.. circles! As they will stay forever right now, you should see the game become slower quite fast. You might even reach the current object limit. This is bad, because when you reach that, things become unpredictable and messy.

So we need to remove them and.. ah yeah, move them!

10.2. Custom movement

This one should be easy. Nothing new. All old stuff. Add this:

+ Always
> Bubble - Position - Set X coordinate...: X( "Bubble" )-1+Random(3)
> Bubble - Position - Set Y coordinate...: Y( "Bubble" )-2+Max(0, 1-Random(3))

So. We move it one pixel left and randomly 0, 1 or 2 pixel right. Then we move it 2 pixel up, and randomly 0 down, 0 down or 1 down. Didn't get the last one? That's what we use Max(x, y) for. Here in two of three cases, nothing will happen and in one of three 1 is added.

Max(x, y) returns the value that is larger. Min(x, y) returns the smaller value.

That's it already. Your first custom movement. Easy, wasn't it? Yeah I think so too. Usually it's a liiittle bit more complicated. But we'll get there. Let's add some more stuff to make the bubble effect complete.

+ Bubble - Position - Test position of "Bubble": Click "Is the object outside?"
> Bubble - Destroy

This makes sure the bubbles are eventually destroyed again and don't hang around in memory and push our object count towards critical.

+ Always
> Player - Order - Move in front of object: Bubble

This will look a bit better and put less focus on the bubbles. Subtle but good. You should have something like this now:

 

 

Run the game and see what happens!

10.3. Create some more!

While we're at it, let's have some more bubbles. Open then properties of the treasure object, add a variable and call it "Bubble". Add the following events in the "Bubble" group:

+ Always
> Treasure - Alterable values - Add to: Bubble, 5

This will add 5 to the variable "Bubble" of every treasure chest. Impressive.

+ Treasure - Alterable values - Compare to one of the alterable values: Bubble Greater 1000
> Create object: Bubble, relative to: Treasure, X: 0, Y: -2
> Bubble - Direction - Select direction: 0, 16 (left and right)
> Bubble - Position - Set X coordinate...: X( "Bubble" )-5+Random(11)
> Treasure - Alterable values - Set: Bubble = Random(1000)

When the "Bubble" variable higher than 1000, a bubble is created, offset a bit horizontally, and the "Bubble" variable is set to a value between 0 and 999. This means every time it happens we set a random time until it will happen again.

Remember when you added

> Treasure - Alterable Values - Add to: Alterable Value B, Random(200)

in chapter 9.3.? Notice how it changed to

> Treasure - Alterable Values - Add to: Bubble, Random(200)

So when the treasure box sinks, it will cast more bubbles than normally.

10.4 "Force feedback"

Now that you have learned to use the random(n) functions, let's use it for another effect. Add a variable to the player submarine and call it "Shake".

This time we won't add any events - we will replace / modify existing ones. So find this event in the "Player only" group:

+ (Player) collides with the background
> Player - Bounce

And add this action to it:

> Player - Alterable values - Set: Shake = 200
> Player - Alterable values - Set: Fixed Treasure = 0

If the player hits an obstacle, the screen will shake (not yet, but soon) and he will drop his treasure, if he is carrying one.

Now find this event:

+ Always
> Storyboard controls - Center display at (0, 0) from (Player)

Delete the action of this event.

All actions of an object in an event can be deleted by dragging an empty "action grid" of the same object (or type) from another event onto the actions.

Alternatively, individual actions can be deleted by rightclicking and selecting "delete".

Add this action:

> Storyboard controls - Scrollings - Center horizontal position of window in frame: X( "Player" )+Random(Shake( "Player" ))*0.1
> Player - Alterable values - Set: Shake = Max(0, Shake( "Player" )-8)

Woah, many brackets. But again this may look more complex than it is. In the first action, we do no longer center X and Y on the player, but only X. As the playfield can't scroll up or down, we do not need Y. Then we get the player's position with X( "Player" ) and add a random value between 0 and Shake( "Player" ) that is then divided by 10 (or multiplied by 0.1). So if the variable "Shake" of the player is 0, random(n) will always return 0.

In the second action we set the "Shake" variable to itself - 8, so it gets lower quickly. And the max(x, y) function makes sure that it never goes below 0. Because then Random(Shake( "Player" )) would produce results different to 0 again.

If that didn't make sense, run the game and see what happens.

Note that it only works when you've moved a little further in the level. To fix this, edit the action

> Storyboard controls - Scrollings - Center horizontal position of window in frame: X( "Player" )+Random(Shake( "Player" ))*0.1

and change it to

> Storyboard controls - Scrollings - Center horizontal position of window in frame: Min(Frame Width-160-20, Max(160, X( "Player" )))+Random(Shake( "Player" ))*0.1

This time we use Min(x, y) and Max(x, y) to clamp the returned value of the player's X position between 160 (half the window width) and the frame width - 160. We subtract another 20 there, because that's the maximum shake the view can have. Didn't get the last one? Don't worry, just copy and paste it for now. When you've used Min and Max and the scrolling a bit more you'll easily get it.

11. Rotor propelled grenades

11.1. Move

Let's get the torpedos ready for launch. As the player is always facing right, they only have to move right. We have created a custom movement in the last chapter, so now we use a predefined movement for a change. Go to the properties of the "Torpedo" object and open the "Movement" tab. For "Type" select "Vector". If it is not there, then you should update MMF2 to the latest version. This will also save you some trouble as plenty of bugs are fixed. Vector movement is nice for many purposes. It allows you to move an object in a straight line in any of 360 directions, and optionally change it's direction and speed through the gravity /acceleration options. The only draw back is that you have a lot less control than with a custom movement. But for this purpose it's more than enough.

Set Speed to 0. We can tweak this later if it doesn't look good.

Set Direction to 0. On a 360° scale, 0 represents right, while 180 would represent left.

For Gravity / Acceleration set Strength to 2000. This is the force that is applied to the object.

Set the Direction to 0. This means the object will be pulled to the right with a force of 2000, effectively speeding up the torpedo as it goes.

11.2. Shoot

Add a new group and call it "Torpedo". Add these events:

+ Player 1 - Joystick - Read joystick state: remove the up arrow, select Fire button 1
+ Special - Limit conditions - Restrict actions: 0, 0, 0, 50 (= 50 1/100)
> Create object: Torpedo, relative to Player, X = 0, Y = 0, click "Options" and tick "Action Point"
> Torpedo - Direction - Select direction...: 0 (right)
> Torpedo - Order - Move behind object: Player

This event will trigger when the player presses his Fire button 1 (by default Shift), but we restrict it to only once every half a second.

We create the torpedo relative to the player's submarine. But here we use the "Action Point" as a reference, instead of the "Hot Spot". Every object has an action point and a hot spot that are defined for every frame of it's animations. I have set the action point to the position, where the torpedos should appear.

Objects rotate around their hot spot. It usually is used to define the center of the object.

Action points are used to track a position relative to an object, according to it's animation. It is mostly used to define the point from where an object shoots.

+ Torpedo - Position - Compare X position to a value: Greater X Right Frame+60
> Torpedo - Destroy

X Right Frame is a function that returns the X position of the pixel that is furthest to the right, but still visible.

If the torpedo is out of sight to the right and a bit further, we destroy it, so the torpedo won't hit enemies that can't even be seen yet later.

11.3. Bubble

Go to the "Bubble" event group and find this event:

Left click on the number and press CTRL-C on your keyboard to copy the event. Press CTRL-V to paste a copy at the same position.

Now right click on "Restrict actions for 00"-05" on the new event that you made and select "Edit". Change the 5 to 15 and click "OK".

Double click on the first checkmark in the event. This will open a "mini event list editor" which lets you see the order of actions.

Double clicking on the actions grid opens a list of actions.

We have to replace the "Create Bubble" action, but maintain the order, so the new "Create Bubble" action stays at the top, or else the actions will be applied to all bubbles instead of the one that is created.

Right click on the "Create Bubble" action and select "Replace". In theory we could achieve the same by editing this action, because we create the same object - but if you wanted to create a different object than the bubble, replacing is the only way.

In the "Replace" dialog right click on "New Objects" and select "Create object". Select the "Bubble" and click "OK". Make it relative to the "Torpedo" and set X = -6 and Y = 0. Click "Options" and check "Located: In direction of Torpedo". This will make the position you select relative to the direction of the torpedo. So should we ever decide to have a torpedo that goes the other direction, the bubbles will still be created at the right position.

Done? Run the game and press Shift!

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