Welcome to episode five of our Let’s Remake Manic Miner series. Today we are going to look at making Miner Willy move around the Y axis of the screen. So, in the immortal words of Eddie Van Halen… Might as well jump! Cue cheesy music video, big hair, and flamboyant costumes… Oh, what the hell, here’s a link on YouTube so you can listen while you read!
What is Jumping?
It may seem obvious to you and me, but what actually is jumping? According to Wikipedia…
Jumping or leaping is a form of locomotion or movement in which an organism or non-living (e.g., robotic) mechanical system propels itself through the air along a ballistic trajectory.
https://en.wikipedia.org/wiki/Jumping
But what does that mean in terms of a game? Well, it’s really dependent on what you are trying to achieve. Looking at the definition above, we can pick out a few words to look at:
Locomotion, movement, propels are all basically the same thing: some form of movement.
Air… hmm, so we know we’re moving through air, but when we’re not jumping, we’re not moving through the air. Does it mean, therefore, that we just stop and float? Of course not; there is that little thing called gravity, that invisible force that causes mutual attraction between all things that have mass. We know about it, but a game doesn’t. But what does gravity do? Simply put, it constantly pulls us down towards the ground until we reach it or land on something that cannot be pulled any further. Like when you are in the upstairs of your house, you are still being pulled towards the ground, but the floor stops you from getting there. Remove the floor, and down you go. If you want to see gravity in action, watch Felix Baumgartner!
A trajectory is essentially a path of movement, and a ballistic trajectory is the motion of an object that is moving in a gravitational field.
Few! We now know, though, that our jump is comprised of a few elements, but we can pick just the two that we need: trajectory and gravity.
Gravity
Gravity is a really easy thing to implement. Effectively, you just pull your character down (increase the Y position) by an ever-increasing amount until you reach terminal velocity. Usually, this is done using a decimal value that steadily increases over time and then gets added to your Y position. To represent this, we need two variables: SPEED and GRAVITY. We constantly increase SPEED by GRAVITY and then add SPEED to our YPOS, thus moving the character down the screen. If we hit the ground or an immovable object of some form, the SPEED variable is reset to zero, ready for the process to start again. A simple jump can then be created by setting the SPEED variable to a negative number to create upward velocity.
You can learn more about jump mechanics in this great YouTube video. It’s not in English, but the subtitles are great, and it does explain jump mechanics very well.
So let’s implement this into our game, shall we? No, we’re not going to do that! It was really interesting to learn how jumping works, but this is not what Manic Miner does. But at least you know how to implement jumping for your other projects!
Manic Miner Jumping
The way Miner Willy jumps is far more prescriptive than the process we described above. When Willy jumps, he travels a predetermined path and keeps going until he hits something or lands on something, in which case he stops. If Willy completes his jump (i.e., his ending vertical position is the same as his starting position), he then falls in a straight line until he hits the ground.
While he is jumping, he cannot change direction, and he cannot jump again. Once you’re jumping, that’s it—you better hope you did it right! Of course, this is one of the skills required to play Manic Miner.
The Jump Parabola
We learned above about the ballistic trajectory, but how would we go about creating something like that without the technique described at the beginning of this episode? The answer is quite simple and quite cheaty, but because of the movement of Miner Willy, it works perfectly for us. We will use a sine wave. Not all of a sine wave, just 180 degrees of a sine wave, which creates an appropriate parabola to work with. To make things easier, though, we will define a full 360-degree sine wave. Let’s add the following code:
@InitMinerWilly
dim mwan(319) : for xpos=0 to 319 : mwan(xpos) = 1 + ((xpos mod 8)/2) : next xpos
dim jmps(359) : for ang = 0 to 359 : jmps(ang) = -(sin(rad(ang))*20) : next ang
MWD = 0 : MWWS = 1 : MWWC = 2 : MWWCC = MWWC : MWXPOS = 16 : MWYPOS = 80
gosub @SetOFS
return
There is not much further to explain here, as we have been through for/next loops and arrays, but what about the sin and rad commands?
Command | Description |
---|---|
Sin | Calculates the sin of an angle, returning a floating point number as a result. |
Rad | Converts angles expressed in degrees into radians. A radian is approximately 57 degrees. |
Now we need to make sure that we adjust Miner Willy’s Y position on the screen by the jump. But, before we can do that, we need to be able to identify the state that Willy is in (hopefully not the state that we find our hero in at the beginning of Jet Set Willy!). Let’s talk a little bit about finite state machines.
What is a Finite State Machine?
This will likely be a new concept for a lot of people, but it’s really very simple. It is a mechanism for allowing something to be in exactly one of an infinite number of different states at any given time. This model can be used for many different types of applications, but it is massively used within gaming, particularly in instance-based languages such as Games Maker Studio from YoYo. They are used, amongst many other things, for indicating the state of a character—what it is currently doing.
Examples of this could be idle, walking, shooting, flying, or, in our case, jumping.
So what we can do is create a series of variables to capture Willy’s state.
@InitMinerWilly
FALLING = 1 : JUMPING = 2
dim mwan(319) : for xpos=0 to 319 : mwan(xpos) = 1 + ((xpos mod 8)/2) : next xpos
dim jmps(359) : for ang = 0 to 359 : jmps(ang) = -(sin(rad(ang))*20) : next ang
MWD = 0 : MWWS = 1 : MWWC = 2 : MWWCC = MWWC : MWXPOS = 16 : MWYPOS = 80 : MWSTATE = FALLING : MWJMPANG = 0 : MWVSPD = 0
gosub @SetOFS
return
We’ve defined two variables, FALLING and JUMPING, which have different values. We’ve then created a new Miner Willy variable, MWSTATE (Miner Willy State), so we can determine what our hero is currently doing. By default, because of the effect of gravity, we say that the default state of Miner Willy is falling.
Finally, we’ve created MWJMPANG (Miner Willy Jump Angle) so we know at what position in the jmps Miner Willy is currently in, and finally MWVSPD (Miner Willy Vertical Speed) so we can ultimately make Willy drop to the floor (this is simulating our gravity, if you like). We’ll use this later on, but it’s better to get the definition out of the way.
We’ve got our different states, and we’ve assigned the default to Willy. Our next task is to actually make Willy jump. This happens when the fire button on the joystick is pressed, so we need to make a small amendment to our joystick routine, but there is also some setup that we need to do, such as signaling that Willy is jumping, etc. Let’s create a new subroutine first.
@StartJump
MWSTATE = JUMPING : MWJMPANG = 2 : MWVSPD = 0
return
As you can see, we are setting MWSTATE to be JUMPING, setting our current jump angle (MWJMPANG) to be 2, and resetting the vertical speed (MWVSPD) so that we are not moving vertically – We’ll explain that later on!
Let’s read the joystick fire button and call the @StartJump subroutine.
@ReadJoystick
JL = jleft : JR = jright : JF = fire : JSTCK = (JL or JR)
if JF then gosub @StartJump
if not(JSTCK) then MWSPD = 0 : else MWSPD = MWWS : if JL then MWD = -1 : gosub @SetOFS : else MWD = 0 : gosub @SetOFS
return
Nothing is going to happen if we run our routine now, because we are not using any of the new variables we have just created. So let’s affect Miner Willy with our jump logic.
@DisplayMinerWilly
if MWSTATE = FALLING then MWVSPD = 1 : else MWVSPD = 0 : MWJMPANG = MWJMPANG + 2 : MWSTATE = JUMPING : if MWJMPANG > 180 then gosub @ResetJump
if MWD = 0 then MWXPOS = MWXPOS + MWSPD : else MWXPOS = MWXPOS - MWSPD
sprite 1,MWXPOS,MWYPOS,mwan(MWXPOS) + MWOFS
return
We’re saying here that if Miner Willy is FALLING, then his vertical speed (MWVSPD) is 1, so as to move him down the screen. else, we set his vertical speed to 0, increase the jump angle by 2, and set his state to JUMPING. We then check MWJMPANG and if it has reached 180 degrees, we call the @ResetJump subroutine to stop Willy from jumping.
The @ResetJump subroutine looks like the following:
@ResetJump
MWSTATE = FALLING : MWJMPANG = 0
return
In this routine, we simply reset Miner Willy’s state to FALLING and clear the MWJMPANG.
Finally, we can use all of the above to affect the Y position of our hero:
@DisplayMinerWilly
if MWSTATE = FALLING then MWVSPD = 1 : A = MWYPOS : else MWVSPD = 0 : A = MWYPOS + jms(MWJMPANG) : MWJMPANG = MWJMPANG + 2 : MWSTATE = JUMPING : if MWJMPANG > 180 then gosub @ResetJump
if MWD = 0 then MWXPOS = MWXPOS + MWSPD : else MWXPOS = MWXPOS - MWSPD
sprite 1,MWXPOS,A,mwan(MWXPOS) + MWOFS
return
So, you can give that a run, and you will see that Miner Willy now jumps when we press the fire button. BUT… and it’s a big but… You can press fire again while Willy is jumping, and it all goes wonky. Not only that, but you can change directions! Argh! Don’t panic; it’s easily solved. We can just use our state engine on the joystick input routine and bypass some stuff—whoop!
@ReadJoystick
if MWSTATE = JUMPING then goto @EndReadJoystick
JL = jleft : JR = jright : JF = fire : JSTCK = (JL or JR)
if JF then gosub @StartJump
if not(JSTCK) then MWSPD = 0 : else MWSPD = MWWS : if JL then MWD = -1 : gosub @SetOFS : else MWD = 0 : gosub @SetOFS
@EndReadJoystick
return
So, what we are doing here is essentially saying: If Miner Willy is jumping, then I don’t want to read the joystick any more and change our speed or jumping variables, so just goto the @EndReadJoystick label instead. Once you’ve jumped, you’d better hope that it was the right jump to make.
Let’s Apply Gravity
We’ve not done anything with our gravity as of yet, so we can do that now. Remember, gravity continuously pulls an object down, so it’s really very simple to implement. Let’s create a new subroutine to apply gravity to our MWYPOS.
@ApplyGravity
MWYPOS = MWYPOS + MWVSPD
if MWYPOS > 80 then MWYPOS = 80
return
Literally, all we are doing here is adding Miner Willy’s vertical speed to his Y position. By doing it this way, we can simply change the value of MWVSPD to 0 when he hits something. I’ve put a quick if statement in there on the MWYPOS variable just so that we can stop Willy from falling off the bottom of the screen. We’ll remove that once we get a background and some collision detection in place.
All that is left is to apply the gravity:
@DisplayMinerWilly
gosub @ApplyGravity
if MWSTATE = FALLING then MWVSPD = 1 : A = MWYPOS : else MWVSPD = 0 : A = MWYPOS + jmps(MWJMPANG) : MWJMPANG = MWJMPANG + 2 : MWSTATE = JUMPING : if MWJMPANG > 180 then gosub @ResetJump
if MWD = 0 then MWXPOS = MWXPOS + MWSPD : else MWXPOS = MWXPOS - MWSPD
sprite 1,MWXPOS,A,mwan(MWXPOS) + MWOFS
return
And there we go! Miner Willy walks and jumps now! Yippee.
And so, we’ve reached the end of another exciting episode of Let’s Remake Manic Miner. As always, the source files can be found in the GitHub repository.
In our next episode, we will start to look at how to build an environment for Miner Willy to walk around in. Until then… Happy STOSing.