Fastloop Custom Platform Movement

Introduction

Once you've made your first few projects in MMF, one of the most important steps is being able to write your own custom movements through the Event Editor rather than relying on the default ones. The default platform movement is one of the most infamous elements of the Click range of software for its distinctive characteristics, including resetting your horizontal movement to 0 when you land from a jump, and becoming stuck in the walls or ceilings of the frame.

The most basic custom platform movements (those that don't use the fastloop functionality) go some way to getting around these problems, but introduce new ones as well - without using the default movements, you have to concentrate a lot more on your own collision detection, and if you're moving an object more than one pixel per frame you have to work around the danger of your object getting stuck in the obstacle backdrop because of moving too far in between checks.

This is where the idea of fastloops comes in. Normally, the event list is read from top to bottom, with any actions being carried out in order. After a read-through of the event list, the screen is updated and the process starts again. Fastloops, as their name implies, can be run many times in one cycle of the event list. The action "Start loop" under the "Special" object is used to trigger a fastloop, and on each loop, events that begin with the condition "On loop (name of the loop being run)" are run in order, as many times as are specified by you.

If none of this is making any sense, keep going - it does get better. The best way to learn how to use them is by example.

Contents

A Basic Fastlooping Platform Movement
To learn the basics of getting a platform movement working with fastloops, start here.

The Basics
Jumps and Gravity
Horizontal Movement
Changing the Movement

Additions to the Movement
Suggested further steps to improve the movement demonstrated in the first section.

Double Jumps
Acceleration and Deceleration
Variable Height Jumps
Doors and Blocking Objects
Platform-type Platforms
Moving Platforms
Conveyors
Springs
Adding Animations

A Basic Fastlooping Platform Movement

The Basics

To start off with, you'll need a player object to control and some obstacles to collide with. For the moment, just keep these as simple rectangles/squares - the actual artwork can be added later on. You don't need to add any sort of movement to the Player object, as with this custom engine, we'll be controlling its position on the screen entirely using events.

Player and bricks
Jumps and Gravity

The first consideration in a custom platform movement is handling how the player object is affected by gravity. To perform a convincing jump, the player should start off moving quickly upwards, then gradually slow down and begin moving downwards again after reaching the top of an arc. Therefore, the gravity value being applied to the player should start off with a negative value, and be gradually increased until the player is once again standing on solid ground.

Add a new alterable value to the player object and name it "Grav" (as "Gravity" is a reserved word in MMF2). We're going to use this value to store the number of pixels that the player should move per event cycle due to the effects of gravity. It should start off at 0, so leave the default value alone.

Gravity should continually accelerate the player object down the way (unless it's already safely on top of an obstacle backdrop). Start a new event group if you want to keep things a little neater, and add the event:

+ Always
-> Add 1 to Grav("Player")

We're not actually going to move the player directly using this value. Instead, we're going to use a fastloop to handle the player's movement due to gravity. The set of events we need to run in our fastloop will continually check to see whether the player can move any further up or down, and if so, move the player by one pixel. This process will be repeated as many times as is needed, so that the player passes through all the intermediate points without the screen being updated. This is different from an engine without fastloops that just moves the player object the required number of pixels at once.

Add the events:

+ On loop "gravity"
+ Grav("Player") > 0
-> Set Y position of Player to Y("Player")+1

+ On loop "gravity"
+ Grav("Player") < 0
-> Set Y position of Player to Y("Player")-1

These events look at whether the player should move up or down, and moves the player object by 1 pixel in the correct direction.

+ On loop "gravity"
+ Player is overlapping an obstacle background
+ Grav("Player") > 0
-> Set Y position of Player to Y("Player")-1
-> Set Grav("Player") to 0
-> Stop loop "gravity"

+ On loop "gravity"
+ Player is overlapping an obstacle background
+ Grav("Player") < 0
-> Set Y position of Player to Y("Player")+1
-> Set Grav("Player") to 0
-> Stop loop "gravity"

These events detect if the player has moved into a backdrop, and move the object back a pixel. The first event moves the object up again if it's moved into the ground, and the second moves it down if it's in a ceiling. When the player has moved into a backdrop, we no longer want him to keep rising or falling, so the gravity is set to 0 and we stop any further gravity loops from happening on this event cycle.

Now that the events are in place to handle the gravity, we need to allow the check to be triggered. When the player is on the ground, nothing should happen, but as soon as the gravity is not 0, we want to start the loop that we've just made.

+ Grav("Player") <> 0
-> Start loop "gravity" ABS(Grav("Player")) times.

This event will start the "gravity" loop whenever the gravity is not zero. When the loop is triggered, all events that begin with "On loop 'gravity'" will be run the amount of times we specify. Each loop will move the player by only one pixel, so we want to loop for as many times as the gravity value says we should. The ABS() is there so that we always have a positive number of loops - giving a negative number will cause the application to crash.

The last event to add here covers more familiar ground...

+ Player presses Fire 1
-> Set Grav("Player") to -10

When the player presses the jump trigger, the value of the gravity should be set to a negative number, so the gravity loop takes the player up the screen. Gravity is continually subtracted from by the first event we made, so eventually the gravity will become positive and the player will fall back down again.

Now's the time to start the application up and see the effects of these events. When Fire 1 is pressed, the player object will jump up and fall back again, never moving too far into the floor because we check for this happening in the fastloop. Also, try putting a ceiling above the player and bashing into it using the jump - you'll be able to see the effects of the event that stops the player rising into ceilings.

You'll notice that you're able to jump in mid-air as we haven't checked for the player actually being on the ground yet - but we can add this check in. There are multiple ways to do this, but to keep the event list simple at this point, we're going to add a detector object that always sits below the player, so we can test whether it's overlapping the backdrop or not.

Create a new active object, the same width as your player and 1 pixel high (you only need it to be one pixel as we're handling the movement using the fastloop method - there's no danger of it "missing" a backdrop.) As the player object will never actually be overlapping a backdrop outside of the fastloop that moves it, we need to position this object directly underneath the player so that it will be overlapping the backdrop if the player is on top of a platform.

Name the object "Detector". This object would usually be made invisible at runtime, but we want to see its effects clearly for now, so leave it visible and go into the Event Editor. Add an action to any of the "On loop 'gravity'" events to position the detector directly below your player.

Position of detector

Now copy the action down into the other "On loop" events - whenever a loop event moves the player object. This is the catch of using fastloops - you have to remember to update everything necessary on each loop, otherwise your engine may have problems - and, because the loops are performed before the frame is updated, it won't be obvious where they are.

This detector has to be checked against whenever the player presses the Jump key. It's as simple as it sounds - just change the existing "Player presses Fire 1" event to this:

+ Player presses Fire 1
+ Detector is overlapping a backdrop [NEW]
-> Set Grav("Player") to -10

This only allows the jump to take place if the player is standing on a backdrop (and therefore the detector is overlapping it). Start the application up again and see its effects.

Horizontal Movement

Now that you've got gravity working, horizontal movement is very similar - but a little simpler, because for the majority of platform games, your character's movement along the ground will be at a constant speed and you won't have to worry about acceleration or deceleration (though there's nothing to stop you adding this feature in). Another fastloop is going to be used to handle the horizontal movement.

Add a new alterable value to your object called "Horiz" - we're going to use this to store the number of pixels for the player to move horizontally on each cycle. Add another group and call it "Horizontal Movement" or something similar. Then add these events:

+ Repeat while player presses Right
-> Set Horiz("Player") to 3

+ Repeat while player presses Left
-> Set Horiz("Player") to -3

X Repeat while player presses Left
X Repeat while player presses Right
-> Set Horiz("Player") to 0

+ Horiz("Player") <> 0
-> Start loop "horiz" ABS(Horiz("Player")) times

+ On loop "horiz"
-> Set X position of Player to X("Player") + (Horiz("Player")/ABS(Horiz("Player")))
-> Position the detector directly below the player

+ On loop "horiz"
+ Player is overlapping an obstacle background
-> Set X position of Player to X("Player") - (Horiz("Player")/ABS(Horiz("Player")))
-> Position the detector directly below the player
-> Set Horiz("Player") to 0
-> Stop loop "horiz"

Most of these events are almost exactly the same as the ones for gravity, and can be just copied and pasted over with the variable names changed. The only new one is the event with the two negated "Repeat while player presses" events - this is there to allow the player to stop moving horizontally by not holding a direction.

I'm using a rather more involved formula in the two "On loop 'horiz'" events so that we can handle both left and right movement in one event, keeping the length of the list down. Adding the value (Horiz("Player")/ABS(Horiz("Player"))) to the existing X position just adds 1 or -1 depending on whether Horiz("Player") is positive or negative - the value of Horiz is divided by its absolute, giving +1 if it was positive or -1 if it was negative. In this way, we detect whether the movement should be to the left or right and perform it, rather than having two separate events for essentially the same thing.

Also notice that we need to update the position of the detector in the events with "On loop 'Horiz'" conditions - if this wasn't done, then the detector would be left behind during the fastloop.

Try the results out now - movement to the left and right should work, without any danger of the player getting stuck in the walls.

Changing the Movement

The movement we just made is a little 'fast' as far as gravity goes - you can't jump very high, and gravity pulls the player down very quickly. To stop this from happening, we can edit the way the application handles the values for gravity and jumps.

Change the first event you made (the Always: Add 1 to Grav) to this:

+ Grav("Player") < 10 - [CHANGE]
-> Set Grav("Player") to Grav("Player") + 0.5 - [CHANGE]

This does two things - first, it limits the pull of gravity on the player to ten pixels per event cycle. No matter how far the player has fallen, it won't move more than ten pixels down per cycle now, giving it a "terminal velocity". The second change makes the player's gravity increase more slowly, so the effect isn't so harsh.

The reason for using "Set" rather than "Add" here is because of the unusual way that earlier versions of MMF2 handled decimal numbers. If you're on build 243 or up, using "Add 0.5 to Grav("Player")" should have the same effect.

You can use the same idea to modify the speed the player moves at horizontally - have a go at changing some values around and look at the effects you get. Also, put in some thinner platforms and notice how the fastloop movement prevents the player from falling straight through them.

You now have a platform engine that handles both directions of movement, and will always prevent the player from moving into walls or getting stuck. You could probably create a decent platform game using this movement, but there are a number of improvements that can be added - and as the engine is based entirely on events, we can change or add to it as much as we need to.

See the example MFA, frame 1, for a demonstration of the movement built so far.

Additions to the Movement

Double Jumps

In many games, you can perform a double jump in mid-air to get an extra boost at the peak of a jump. This can be done without changes to our existing objects, but a few of the events have to be modified - particularly the way in which we're detecting that the player can jump. This time, we want the player to be able to jump once in mid-air, but only if he has jumped from the ground first (otherwise you'd be able to perform the second part of the double jump while falling from a platform).

First of all, get the program to set a value to allow us to detect when the player has jumped. One way to do this is by setting up another alterable value called "JumpCount". Again, leave it at 0 to start off with. Change the event that allows the player to jump:

+ Player presses Fire 1
+ Detector is overlapping a backdrop
-> Set Grav("Player") to -9 - [CHANGE]
-> Set JumpCount("Player") to 1 - [ADD]

(The change to the gravity-setting event is largely optional - I'm reducing it a little from 10 here as allowing a double-jump will increase the maximum jump height a bit.)

Now clear this value back to 0 when the player lands from a jump:

+ On loop "gravity"
+ Player is overlapping an obstacle background
+ Grav("Player") > 0
-> Set Y position of Player to Y("Player")-1
-> Set Grav("Player") to 0
-> Set JumpCount("Player") to 0 - [ADD]
-> Stop loop "gravity"

(You can also add the action that sets the JumpCount back to 0 to the event that stops the player rising into ceilings - this will prevent the player double-jumping after hitting a ceiling. It's all down to your preference.)

Now that we can detect when the player has jumped, we can add the event that allows a double-jump. There are two approaches to this: the first way is to change the jump event so that the player can perform the same jump again in mid-air. However, a more flexible way involves creating a new event, so that we can make the second jump different.

Add this event above the existing "Player presses Fire 1" event:

+ Player presses Fire 1
+ JumpCount of Player == 1
-> Set Grav("Player") to -6
-> Set JumpCount("Player") to 2

There's no real reason to set the JumpCount to 2 rather than 0 at this point, but it gives us a handy way to check if the player has double-jumped - and you might find yourself needing that later. We set the gravity to -6, a value closer to zero than the original jump, so that the double jump is not as powerful as the original.

The reason that this event has to be placed above the other Jump event is because MMF looks at all the events that involve the player pressing Fire 1 from top to bottom when the event occurs. If the original event was above the one we've just made, then both events would be triggered - the first event would set the JumpCount to 1, and the second one would immediately pick up on it.

If you start the application again, you'll now be able to double-jump in midair when you've jumped, but not when you've fallen from a platform. Depending on your preference for the movement, you might find it strange that you can double-jump at any point in the jump, rather than just at its peak - this can give the effect of cutting short your jump. It's easily corrected:

+ Player presses Fire 1
+ JumpCount("Player") == 1
+ Grav("Player") > -2 - [NEW]
+ Grav("Player") < 5 - [NEW]
-> Set Grav("Player") to -6
-> Set JumpCount("Player") to 2

The two conditions we've just added will limit the player to only jumping when the gravity of the player is between certain values after already jumping from the ground - therefore when near the peak of the jump. Changing these values will give the player more or less of a window of opportunity to perform the double jump. You could even add another condition to this event to only allow the player to double-jump when a certain item has been picked up, and so on.

See the example MFA, frame 2, for a demonstration of the movement with added double jumps.

Acceleration and Deceleration

Our movement is missing the "acceleration" and "deceleration" properties of the standard platform movement, but these can be added back in by changing the way that the horizontal movement is handled slightly. What we need to do is continually add or subtract to the horizontal movement, rather than just set it directly.

Look at the events for the horizontal movement again:

+ Repeat while player presses Right
+ Horiz("Player") < 3 - [ADD]
-> Set Horiz("Player") to Horiz("Player") + 0.25 - [CHANGE]

+ Repeat while player presses Left
+ Horiz("Player") > -3 - [ADD]
-> Set Horiz("Player") to Horiz("Player") - 0.25 - [CHANGE]

X Repeat while player presses Left
X Repeat while player presses Right
+ Horiz("Player") < 0 - [ADD]
-> Set Horiz("Player") to Horiz("Player") + 0.1 - [CHANGE]

And add a new event to match the last one for the other direction:

X Repeat while player presses Left
X Repeat while player presses Right
+ Horiz("Player") > 0
-> Set Horiz("Player") to Horiz("Player") - 0.1

Now start it up and test the movement out. The values that I've given here are slightly exaggerated and ice-like to show you the effect - change them around a little to experiment with different movement styles. You could add a check for a player travelling at over a certain speed and turning round to produce a skid effect, or make movement on the ground different from in the air by using the detector object we put in earlier.

Frame 2 of the example MFA contains a modified movement with acceleration and deceleration horizontally.

Variable Height Jumps

You'll have noticed that the movement we created above only supports jumps of one height - no matter how long or short a time you hold the Jump button, you'll always reach exactly the same height and come back down again. Most platformers allow you to perform a small jump when you just tap the button, and a higher leap when you hold it down. To replicate this, we have to change the way that the gravity is altered when the player presses Jump.

At the moment, the jump strength is set once when the player hits the button. In addition to this, we want to detect if the button is still being held as the jump continues, and affect the player's gravity based on how long it is held down.

One very simple way to do this is to add this event:

X Repeat while player pressed Fire 1
+ Grav("Player") < 0
+ JumpCount("Player") == 1
-> Set Grav("Player") to Grav("Player") + 0.5

(The "JumpCount" condition is only necessary if you've set up double jumps in the way described in the section above.)

Now, if the player isn't holding Jump while a jump is taking place (the player is moving upwards), MMF will add an extra value on to the gravity to get it to decelerate more quickly. Decrease or increase the value that you add to Grav on each cycle to make the effect as harsh as you need it to be.

Frames 5, 6 and 7 of the example MFA allow variable height jumps.

Doors and Blocking Objects

One of the things missing from the movement that we've built so far is detection of objects that block the player. We can check for obstacle backgrounds, but not active objects that should prevent the player from passing (such as locked doors). Because of the way that we've set up the fastloops, we have to check for these objects within the fastloop that controls the player's horizontal movement.

Create a new object to represent your door and place it somewhere to block the player. I also find it useful to set up a qualifier for door objects so that I don't have to repeat events too much - add the "Door" qualifier to the object that you've just created.

I'm going to demonstrate how to create an object that blocks the player horizontally, but there's nothing to stop you putting this in the Gravity loop instead and creating a door that will block vertically. Putting it in both would block the player from all directions.

Add this event just next to the event that stops the player moving into walls in the Horizontal Movement group. It's very similar, in fact.

+ On loop "horiz"
+ Player is overlapping Group.Doors
-> Set X position of Player to X("Player") - (Horiz("Player")/ABS(Horiz("Player")))
-> Position the detector directly below the player
-> Set Horiz("Player") to 0
-> Stop loop "horiz"

This will stop the player from moving into the object. However, we also have to consider what happens when the player is meant to be allowed through the door - for example, if the correct key or item has been collected.

For this, just create another object, calling it the "Key" or something similar. When the player collides with this key, we want to record that it has been collected, and when the player collides with the door once at least one key has been collected, the key should be recognized as used. Make a new alterable value in the Player object and name it "KeysCollected".

Now for the events. You'll probably want to create a new group, called "Keys and Doors", for the first one, as it doesn't really fit in with the other events we have so far.

+ Player collides with Key
-> Destroy Key
-> Add 1 to KeysCollected("Player")

Place this second event above the one that we put in for colliding with the door. Again, this event goes above the one that blocks the player because we want to check to see whether the player can pass through the door before blocking it and moving it back.

+ On loop "horiz"
+ Player is overlapping Group.Doors
+ KeysCollected("Player") > 0
-> Destroy Group.Doors
-> Subtract 1 from KeysCollected("Player")

The player will now be allowed through a door if it collides with it when carrying at least one key, and blocked if it is carrying no keys. In a full game you'd probably want doors to be a little more diverse than this, but the method for writing them all is much the same.

Keys and Doors

See the example MFA, frame 3, for a demonstration of keys and doors.

Platform-type platforms

The default platform movement does have one strength - it's easy to create platforms which the player can jump through and land on, rather than simply be blocked from all angles by them. It's possible to replicate this behaviour in a custom platform movement, though the method used to do it is a little more complex as it involves altering the events that we've already made to take account of the new type of platform. Additionally, we need to create these platforms as active objects rather than part of the backdrop so that we can detect them in the events list.

The method that I prefer for creating these platforms does add a bit to the object count, but I've found it to be one of the easiest ways to do it - it involves making detector-like objects that indicate where the player should be able to stand. There may be many types of these detectors, so they should all get a common qualifier as well. I've always used Group.0.

Create a new active object. We want it to be one pixel high in-game, but objects that small are difficult to grab hold of in the editor, so we're going to work around that. In the initial Right direction, create a distinctive look for the objects that's easy to click on, as you'll probably be copying it around a lot later on.

Right direction

Left direction

Now, go to the Left direction of this object (direction 16), and draw a 1-pixel-high detector, similar to the one that we put in for the player. This is the animation that we want the object to actually use in the game, so close the animation editor and set the object's initial direction to Left. This doesn't affect how the object appears in the event editor, so we keep seeing the easily graspable placeholder we put in, but while in game the objects become 1-pixel-high detectors.

Initial direction

There are another couple of properties to set - add the qualifier "Group.0" to the object, and name it "Platform" or something similar. We should set the object's "Visible at start" property off as well, though at this stage it may be useful to leave it on to see the effects of the events we're about to put in.

Line a few of these objects up next to each other, insert some non-obstacle backgrounds so that it appears that there's something for the player to stand on, and then go into the Event Editor again. We're going to have to change a few things here. All events that stop the player when colliding with a background when moving downwards need a duplicate that also stop it when it encounters one of these platform objects. Then we need to allow the player to jump when standing on one of them.

Copy and paste the event that detects for the player having landed from a jump, and change the new one so that it detects for platform objects instead. The new event is a little different because as the player can be positioned halfway through a platform, we need to detect them using our detector rather than the player object itself.

+ On loop "gravity"
+ Detector is overlapping Group.0 - [CHANGE]
+ Grav("Player") > 0
-> Stop loop "gravity"
-> Set Y position of Player to Y("Player") - 1 - [REMOVE]
-> Set Grav("Player") to 0
-> Set JumpCount("Player") to 0
-> Set position of Detector to underneath Player

Again, this event needs to be placed specifically - this time, put it above all the other "On loop 'gravity'" events. We want to stop the player from falling through the platform objects before we detect that the player should move downwards due to gravity.

Now, to allow the player to jump while standing on a platform, just copy and paste the event you have for allowing the player to jump, and change "Detector is overlapping a backdrop" so that it detects for overlapping the platform object.

+ Player pressed Fire 1
+ Detector is overlapping Group.0
-> Set Grav("Player") to -9
-> Set JumpCount("Player") to 1

These events allow the player to move around normally on the invisible platform detectors, but it will not be obstructed by them when moving upwards. Using Group.0 rather than any specific object allows you to add wider or shorter detectors at a later time without changing the event list.

See the example MFA, frame 4, for a demonstration of platforms.

Moving Platforms

Moving platforms feature in most platform games. I'm going to put them in using a similar technique to the Platform-type platforms described above, as for the most part you'll want this type of platform to allow the player to jump through it from the bottom, so go through that section first.

Create an object to represent your platform, and another object to be the detector that allows the player to land on it. We want the detector to be part of Group.0 (or whatever qualifier you designated for platforms) so the player can land on it.

Moving platform

The first thing to do is allow the platform to move. Keeping with the style of our platform movement, we want to control it using the events rather than a built-in movement. Horizontally moving platforms are the easier of the two to get working, so create a new alterable value for the Moving Platform object, call it "Horiz" and set its initial value to 2. We're going to use this value to control the horizontal movement of the platform.

Put the platform in between two obstacle backgrounds, and place the detector object so that it's overlapping the platform. This is an important step as it will eventually let the application know which detector to attach to which platform. Now we need to set up the platform's events. Create a new group underneath the other two, "Moving Platforms", and fill it with these:

+ Always
-> Set X position of "Moving Platform" to X("Moving Platform") + Horiz("Moving Platform")

+ Moving Platform is overlapping a backdrop
-> Set X position of "Moving Platform" to X("Moving Platform") - Horiz("Moving Platform")
-> Set Horiz("Moving Platform") to 0 - Horiz("Moving Platform")

+ Moving Platform Detector is overlapping Moving Platform
-> Set position of Moving Platform Detector to (0, 0) from Moving Platform

The method of movement is similar to the player (though we don't need to go to the trouble of putting it in a fastloop): continually update the X position with a new value, and move it back immediately if this move causes it to be inside a backdrop. The third event ensures that a detector that was overlapping the platform at the start of the frame stays attached to it.

If you want your moving platform to turn around in mid-air rather than when it encounters a wall, create a new object to mark the spot where it should turn around, make it invisible, and copy the "Moving platform is overlapping a backdrop" event to get it to turn around on a collision with the marker object.

If you've already got your events set up so that the player can land on objects that belong to Group.0 (see above if you haven't), then this should be all that's needed to get moving platforms working. Start it up and see what happens. You'll notice that the player lands on the platform, but doesn't actually move with it - the platform will slide out from under the player. To correct this, we need to pass the number of additional pixels to move to the player object somehow.

This event should go in the "Horizontal" group rather than the "Moving Platforms", because we need to affect the Horiz("Player") value, which tells the application how many fastloops to run to handle the movement.

+ Detector (NB. The one attached to the player) is overlapping Moving Platform Detector
+ Moving Platform Detector is overlapping Moving Platform
+ Grav("Player") >= 0
-> Add Horiz("Moving Platform") to Horiz("Player")

This needs a bit of explanation. First of all, we're checking for the platform detector under the player overlapping the detector for the moving platform, while the gravity of the player is not negative. This is a check for the player standing on the platform, rather than moving through it while jumping upwards. The "Moving Platform Detector is overlapping Platform" event allows us to "select" a moving platform object to talk about in this event - if we didn't have this condition, MMF2 wouldn't know which moving platform we were trying to get the value from.

Adding the Horiz of the moving platform to the Horiz of the player will get the player to move along with the platform by adding to or subtracting from the number of fastloops that should be performed to move the player horizontally. This event should be placed in the list before you start any fastloops (with the "Horiz("Player") <> 0" event) but after you have set the Horiz to anything else (with the "Repeat while Player presses..." events).

Another advantage of doing it this way is that background collisions are already handled - if your moving platform finishes at a wall, the player won't be carried into the wall by the movement of the moving platform. Set it up and see for yourself.

See frame 5 of the example MFA for a demonstration of moving platforms.

Conveyors

Platforms that move the player along automatically are similar in idea to moving platforms. The difference is that we don't need to move the platform on every cycle - instead, contact with the platform should affect the player's horizontal movement.

Like most things, there are a couple of ways to implement conveyors. I'm just going to have them as active objects that will be placed overlapping the background (so the background handles the collisions for us). If you want a conveyor you can jump through and land on, use the same technique as for the Platform-type Platforms section above.

Create a couple of objects to represent the left and right-facing conveyors, and place them so that they're on top of existing platforms.

Conveyors

Now set up the events. The ones for this section are very simple:

+ Detector is overlapping Conveyor Right
+ Grav("Player") >= 0
-> Add 2 to Horiz("Player")

+ Detector is overlapping Conveyor Left
+ Grav("Player") >= 0
-> Subtract 2 from Horiz("Player")

In the same way as before, these events have to go in the "Horizontal" group before you start any fastloops, and after you've set up the initial value for the player's horizontal movement due to keypresses. Putting them right under the event that handles movement platforms is the most logical place for it.

That's all there is to this section. Experiment with the values to get a movement that feels right for you - if the value you add or subtract from Horiz is greater or equal to the value set by keypresses, the player won't be able to move against conveyor belts just by walking on them.

See frame 5 of the example MFA for a demonstration of conveyors.

Springs

Springs allow the player to reach higher places than they would normally with a jump or double jump. In terms of making the movement, they're just objects that set the player's gravity to a certain value.

Create a new object to represent a spring, and place it anywhere on the ground. Duplicate one of our platform-detecting objects (see Platform-type Platforms, above) and drag it over to place it on top of the spring - this will make the spring be detected as a platform by our engine. You can leave that step out, but the spring will be a little more unpredictable as the movement into the spring won't be handled in the fastloop for gravity.

Sprite

Add this event to the Jumps and Gravity group, above the place where you start the actual fastloop:

+ Detector is overlapping Spring
+ Grav("Player") >= 0
-> Set Grav("Player") to -16

The value you give the player's gravity will vary depending on how powerful you want the spring to be. You could create a number of different spring objects and give them different strengths, too.

If you're using the technique I described above for variable height jumps, then you'll have an additional feature as a side effect - holding Jump while landing on the spring will produce a higher jump than when you just hit the spring without holding any controls. If you don't have this feature, you can still write it into the spring objects by separating the above event into two different ones:

+ Detector is overlapping Spring
+ Repeat while player presses Fire 1 - [ADD]
+ Grav("Player") >= 0
-> Set Grav("Player") to -16

+ Detector is overlapping Spring
X Repeat while player presses Fire 1 - [ADD]
+ Grav("Player") >= 0
-> Set Grav("Player") to -11 - [CHANGE]

See the example MFA, frame 6, for some examples of springs.

Adding Animations

All the sections so far have looked at getting the physics of the platform engine right, but how it appears to the player is important as well. We need to improve the look of the main character from the simple square (or static frame) that we've been using so far.

I find it easiest to make a completely different object to show to the player. In this way, we separate handling the physics of the engine from handling the animations, and any quirks that arise due to the way the object is drawn or animated won't be present (such as the common problem with standard platform movement games where a character can hang on to ledges by his nose). Create an object that contains animations for your character, including stopped, walking, and jumping/falling. I'll call this object "Sprite".

Sprite

The default platform movement handles these automatically, but like everything in this tutorial, we need some more events to replicate that behaviour. First of all, give the sprite a new alterable value called "Moving" - we're going to use this to flag whether the player is moving the object or not (as opposed to it being moved by other events such as those for the moving platforms). Create a new group called "Animations" at the bottom of your event list, and add these to it.

+ Always
-> Set position of Sprite to (0, 0) from Player

+ Repeat while Player presses Left
-> Set direction of Sprite to 16 (Left)
-> Set Moving("Sprite") to -1

+ Repeat while Player presses Right
-> Set direction of Sprite to 0 (Right)
-> Set Moving("Sprite") to 1

X Repeat while Player presses Left
X Repeat while Player presses Right
-> Set Moving("Sprite") to 0

Those events set up the direction of the sprite and let us tell whether it's moving or not, as well as continually set the position of the sprite to match the object that handles the collisions. Now we need to detect where it is:

+ Detector is overlapping the background
+ Moving("Sprite") == 0
-> Set animation of Sprite to Stopped

+ Detector is overlapping the background
+ Moving("Sprite") <> 0
-> Set animation of Sprite to Walking

And we need to do the same thing for the non-backdrop platform objects:

+ Detector is overlapping Group.0
+ Moving("Sprite") == 0
+ Grav("Player") > 0
-> Set animation of Sprite to Stopped

+ Detector is overlapping Group.0
+ Moving("Sprite") <> 0
+ Grav("Player") > 0
-> Set animation of Sprite to Walking

Finally, if the player is overlapping neither kind of platform, it's going to be falling or jumping. In this example, both of these are going to be the same animation, but they can be distinguished by looking at whether the gravity is below or above 0.

X Detector is overlapping Group.0
X Detector is overlapping the background
-> Set animation of Sprite to Jumping

See the last frame of the example MFA for a demonstration of an animated sprite moving through a platform level using this engine. This level also combines most of the features that were demonstrated in this tutorial.


Written in 2006 by David Newton (DavidN). Revision 1.