1. The Basics
  2. Let's make a Mario game
  3. Let's refine our Mario game

Let's refine our Mario game.

In the previous tutorial we made a Mario game. And it was awesome. Now it's time to visit some more advanced topics when it comes to games, and we'll introduce all of them as Mario game mechanics. This tutorial will cover the following things:

Teleporting

Let's start off by implementing teleporting. The concept is pretty straight forward:

There are many ways in which to listen for teleport events, including using a teleport Pickup, a teleport Trigger region, or that old favourite: an actual teleporter. So let's implement one of those, to get a feel for the whole thing. First we extend our previous tutorial's Mario with a "crouching" state when you press 'S', so that we have something to monitor for teleporting, and then we'll set up our actual teleport.

Alright, that's a fair bit of code, but you should be able to see what's going on at this point. Two new states have been introduced, the crouch and crouch-jump states, and they require input handling to be extended so that we can change to them, and don't change away from them when we shouldn't reasonably be able to.

So let's make a teleporter.

a teleporter
Something like this

You might recognise that as a Megaman (or Rockman if you prefer) teleporter. Super convenient! we're going to turn this into an in-game teleporter by setting up the following:

With this, we'll have a simple teleport procedure: actor jumps onto teleporter, actor crouches, teleporter teleports actor to its new location. So, let's make a teleporter; we make it a BoundedInteractor, just like we did with the Muncher in the previous tutorial, because we need to attach boundaries to this things:

Alright, done. Of course we need to define this special "Teleporter Boundary" too:

The important bits happen on lines 22 through 38, and specifically on lines 30 through 38: we "intercept" the function that is normally used to determine how to update an actor position. When an actor is moving around freely, this is isolated code and we can't see what's happening, but when an actor is standing on a boundary, the boundary is asked whether the intended motion of the actor is "legal" or not, and if it's illegal, how it should be changed to make it legal.

For instance, an actor standing on an incline, while subjected to gravity, would LIKE to fall straight through the incline, because gravity points straight down, but this would be illegal movement. Instead, the incline will change the illegal movement into legal movement along the surface of the incline, so we slide left or right, rather than going straight through. This happens in the redirectForce function, so we can hijack that in order to make teleportation happen.

This function is called every frame as long as an actor is standing on a boundary, so what we do is check: "is the actor crouching? If so, it's teleport time! ... if not, just let the normal code for force redirecting do its thing", which you see on line 37. You may also see that lines 25, 26, and 27 don't seem to do anything - they just immediately call the superclass's function. This is mostly to appease Processing.js, which (thanks to JavaScript and Processing being slightly different in how they deal with super/sub classing) makes everything work. Without them, the call on line 37 will actually call the function on line 30, which would be very inconvenient.

Anyway, back to what our code does: when an actor crouches, we detach it from the boundary, set its state to idle instead of crouching, and then teleport them to their new location. return STATIONARY on line 35 is required because the redirectForce must always return a list of numbers that represent the new x and y directions of an actor's intended movement. Even if we don't want them to move, we have to explicitly say this, so we return the list "no movement in the x direction, and no movement in the y direction", which we've stored in a variable called STATIONARY on line 22.

Finally, we want to actually make use of these teleporters. So let's take our original LevelLayer and strip out everything except the ground, and add some teleporters (for testing purposes, we're going to make the level layer only one screen size wide and height):

Alright, cool. We now have a level with teleporters. Let's try this out! We should now be able to jump onto a teleporter, press 'S', and teleport across the gaping canyon in the level. We should also able to just walk off the cliff, although that won't do us much good to get the other side...=D

Canyons mean nothing to us

Niiiice... but not very Mario. You've seen the idea behind teleporting, so let's take what we know, and apply it to the only possibly correct Mario teleporter: green, tubish, pipe-like "brrp-brrp-brrp" teleporters!

a pipe head a pipe
You know the ones I'm talking about

We're going to use the same idea, but work it out slightly different:

There is nothing in this plan that isn't going to work. Let's get to it:

Of course, we'll need to actually define this pipe class, too:

We do a few things here. First off, we reserve head, body, and trigger as properties that we can call from somewhere else, like the Level Layer that we create the pipe in. Because the two sprites and the trigger need to be added to a layer, we make them accessible.

Then, in the constructor, we set up the head and body as simple sprites. To make positioning easier, we also align them so that if we position them on (x,y), that will be their left/bottom corner, using align(LEFT,BOTTOM).

After that we add the boundaries. We box in the pipe, and add another boundary inside the pipe so that we can't "fall through" the pipe if we're inside it. This is important, because we're going to be inside it when we teleport!

The teleporting we effect by adding a trigger region that lives inside the pipe, so that when something falls through the lid (when that gets disabled), they fall onto the trigger region, and get teleported.

So far, so good! Now, the Lid and TeleportTrigger look as follows:

This is similar to the previous teleport boundary, except that we don't immediately teleport any actors, we simply disable the boundary, so they'll fall through, and hit the teleport trigger - sneakily hidden behind the pipe's graphics:

So what does this look like? Well, to show this off best, let's look at this in two different ways. One with boundaries and triggers shown (in debug mode) without drawing the pipe graphics, and one in "game view", where we just see the graphics, with boundaries and triggers hidden:

In debug view
In game view

How cool is that?

Moving between layers

Now that we have teleporters covered, I think you can see where any discussion about moving between layers is going:

more teleporting.

Moving between layers is essentially a three step "teleport":

  1. Take an actor out of the current layer
  2. Insert the actor into the desired layer
  3. Set the actor's position/impulse/etc values in this new layer

So let's implement this. First, let's make a simple level again, this time with two layers, and a "shared" Mario:

Notice that we've added a new class, LayerSuperClass. We're going to use that as a super class for layers, housing some of the code that we want to be able to call from any layer. Like layer teleporting:

Now it's just a matter of calling this new moveToLayer(...) method whenever we want to fling Mario from one layer to another:

Here, we've changed our original background layer to have some ground, in addition to a background sprite, and we changed our original main layer so that it only has the left half of the ground, and nothing else.

We then modify the draw() methods for both layers, and you'll see that when mario "falls out of the level layer" (which is when his height is beyond the layer's height), we move mario to the background layer rather than resetting the sketch, in a position just above the screen so that it'll look like he's fallen into the background. Try it yourself: run the following sketch and watch what happens when Mario jumps off the edge:

Falling into the background

So now we know how to move between level layers. In 2D sidescrollers this might seem like a neat trick, but more a gimmick than something important to the game experience, but for top-down 2D games moving between layers is very important. For instance, a level might have a room with floors at different heights - you don't want actors on bits of the floor at height A to interact with actors on bits of the floor at height B. This is taken care of by making each "height" its own layer, and moving actors between layers as they move up and down across the floor.

Level swapping

This brings us quite naturally to level swapping. Level swapping is distinctly different from moving an actor around between layers, because it changes the game library's underlying "active screen" from one screen to another. While a single screen can have multiple layers, all of which are updated and drawn each frame, a single game can have multiple screens, but only one screen is active at a time. While it is updates and drawn to your screen, none of the other screens get updated. For all intents and purposes, they have been turned off and shelved for possible use later. Or maybe not.

Demonstrating how to swap levels requires we even have two levels, so let's first set that up:

These two classes will be our level and level layer superclasses. They will house all the code that levels, and level layers, have in common. So let's implement level one:

And then let's implement level two:

Excellent! Let's run this and see what happens:

Level swapping in a game

As you can see, we're swapping levels quite successfully. Except each level has its own Mario instance, and while this is "correct", it's not very useful. We'd like to be able to move mario from one level to the next. So let's change our code a little to effect that:

Player powers

Using decals

Animation trajectories and paths