Welcome to episode four of our Let’s Remake Manic Miner series. Today we are going to look at animating Miner Willy as he moves around the screen.
How We Normally Do It
Animating characters is usually straight-forward; in fact, you could say it’s probably one of the easiest things you can do with your sprite. Effectively, you just need to cycle around a series of images while your character is moving, and then when the character stops, you stop cycling around the image. If the character changes direction, you reset the animation frame for the start animation of the new direction.
So, let’s do that, shall we? No, we won’t do that, because that is not how Manic Miner works, it does something that I never really thought of before, but it’s quite a clever trick that Matthew Smith did, and it saves a bunch of processing time too! Let’s ask ourselves…
What Would Matthew Smith Do?
I’ve spent long hours looking at how Miner Willy is animated, and it had me puzzled for a while. I tried various different things, but I could just not get it to look right. After a bit of a break and doing some other things and thinking, my mind came to a simple conclusion: Willy’s animation frame is tied to his X position on the screen. This is why he keeps animating when jumping, and also why, no matter how you’ve moved about on the screen previously, you always end up with the same frame of animation when Miner Willy is in the same position.
You can try this out for yourself by playing the Central Cavern level and walking left to the brick wall. Miner Willy will stop mid-walk and not animate any further. Move around the screen, jump, do random things—when you walk back to the left, the frame of animation is always the same! Try some other things too, such as walking off the first platform. Miner Willy will always be in exactly the same frame of animation. If we were to use the standard method as described above, this would most certainly not be the case. Therefore, his animation frame is tied to his X position!
Now, this sounds like a bit of a cop-out, but in fact, it’s absolute genius! If you base the character’s animation frame on their current X position, there are no complex animation calculations such as counting frames or resetting when you change direction—none of that! Therefore, you are saving lots of CPU instructions and making your game run faster, which, when working with something like the ZX Spectrum, the more you can squeeze out of the CPU, the better. So, when I say genius, that’s exactly what it is.
How Do We Do This In STOS?
So, how do we replicate this within STOS? Well, it’s really pretty simple really, we just define an array that holds the correct animation frame to show at the different horizontal positions on the screen. We know that our Miner Willy sprite has just four frames of animation, and we know that sprite #1 is walking to the left. Therefore our Miner Willy sprites are as follows:
|1||Miner Willy walking left – Frame 1|
|2||Miner Willy walking left – Frame 2|
|3||Miner Willy walking left – Frame 3|
|4||Miner Willy walking left – Frame 4|
|5||Miner Willy walking right – Frame 1|
|6||Miner Willy walking right – Frame 2|
|7||Miner Willy walking right – Frame 3|
|8||Miner Willy walking right – Frame 4|
So, what we can do is predefine which sprites to use and store them in an array so that we don’t have to be calculating which frames to be using all the time. We can then use the direction variable (MWD) to adjust the frame accordingly by an offset. The less calculations we have to perform per frame, the quicker our game logic will run.
Let’s modify our initialisation routine so that we precalculate the frames that represent each X position on the screen.
@InitMinerWilly dim mwan(319) : for xpos=0 to 319 : mwan(xpos) = 1 + ((xpos mod 8)/2) : next xpos MWD = 0 : MWWS = 1 : MWWC = 2 : MWWCC = MWWC : MWXPOS = 16 : MWYPOS = 80 return
We’ve introduced some new commands there, so let’s walk through them and determine what they are doing.
|dim mwan(319)||Here we are setting up array MWAN (Miner Willy ANimation) and we are telling it that we want enough storage to hold 320 values. When defining arrays in STOS the first value is always 0, so in the instruction here we are defining values 0 to 319, so, 320 spaces.|
But what is an array? Think of an array as a list of values that can be accessed by an index number. In our case we have said we want 320 indexed values to be stored. If we tried to store any more, STOS would generate an error message as in affect, we would be overwriting other memory.
Arrays can be single or multi-dimensional, but we won’t get into that just yet. Our example here is a single dimensional array.
|for xpos=0 to 319||Here we are starting a for/next loop that is going to generate values between 0 and 319 within the XPOS variable. This value represents the number of pixels across the screen.|
|mwan(xpos) =||We are addressing our array here, and passing the XPOS variable as the value index, this accessing indexes 0 to 319.|
|1 + ((xpos mod 8)/2)||We’re doing a little bit of mathematics here on on our XPOS value, and introduced the MOD command. MOD returns the modulus of a value, that is to say it’s used to divide two numbers and only return the remainder. In this instance we are dividing XPOS by 8 and returning the remainder.|
But why are we doing this? Well, the XPOS value as we know will always be increasing in value from 0 to 319. By using the MOD command, we can ensure that we always get a calculated value of between 0 and 7, no matter what the value of XPOS is.
We then divide that value by 2, which results in a value of 0 to 3.
We add 1 to that result so we end up with a value of 1 to 4 – or an animation number – whoop!
|next xpos||Here we are simply finishing off the for/next loop. All instructions between the for and the next will be executed the number of times specified in the for command.|
Our MWAN array now contains the appropriate animation frame to use based on the X position on the screen. We now need to update our sprite command to make sure we are using the appropriate frame.
@DisplayMinerWilly if MWD = 0 then MWXPOS = MWXPOS + MWSPD : else MWXPOS = MWXPOS - MWSPD sprite 1,MWXPOS,MWYPOS,mwan(MWXPOS) return
Let’s build and run our project to see what we have. If you move your joystick left/right now, you should see Miner Willy walk – look at him go! But, one major draw back, he’s always facing to the left. That’s because our frames of animation we have calculated only register frames 1 to 4. We need a way to make him face to the right. Remember that MWD variable we created previously? That’s where this comes in, because we can now use that to create an offset to point to the correct animation frame.
We could do some complex mathematics here to transform the frame number to the right place, but complex maths means more processor time, so let’s just keep it simple and quick instead. We will create an offset flag that holds a number that needs to be added to the frame. If Willy is walking to the left, then the offset will be 0. If Willy is walking to the right, then the offset will be 4. When we add this offset to the animation frame number, Willy will be facing in the correct direction. Ok, so let’s make a few code changes:
Firstly, we create a new subroutine called @SetOFS, this is what we will use to set what our frame offset should be, using the MWOFS (Miner Willy OFSet) variable.
@SetOFS if MWD = 0 then MWOFS = 4 : else MWOFS = 0 return
next, we need to call this new subroutine from a couple of places.
@InitMinerWilly dim mwan(319) : for xpos=0 to 319 : mwan(xpos) = 1 + ((xpos mod 8)/2) : next xpos MWD = 0 : MWWS = 1 : MWWC = 2 : MWWCC = MWWC : MWXPOS = 16 : MWYPOS = 80 gosub @SetOFS return
@ReadJoystick JL = jleft : JR = jright : JF = fire : JSTCK = (JL or JR) if not(JSTCK) then MWSPD = 0 : else MWSPD = MWWS : if JL then MWD = -1 : gosub @SetOFS : else MWD = 0 : gosub @SetOFS return
As you can see on the second section of code, we are calculating the new offset when MWD changes.
And finally, we now need to utilise the offset on the Miner Willy sprite.
@DisplayMinerWilly if MWD = 0 then MWXPOS = MWXPOS + MWSPD : else MWXPOS = MWXPOS - MWSPD sprite 1,MWXPOS,MWYPOS,mwan(MWXPOS) + MWOFS return
Let’s build our project and see what we have. Success! Miner Willy is walking in the direction he is facing. Well, he’s running actually, so we will need to slow that down a little bit, but we can tackle that later.
For now, that’s the end of this episode. Check in for the next episode where we will be making Willy jump. Remember, you can find the various episode source codes and assets on the Manic Miner Github page.