Most of the questions I get asked about writing games for the Spectrum are about graphics – so the next two posts will cover two such questions on that topic. The first question regards how to move objects around the screen at rates other than multiples of a pixel, such as moving 2 pixels every 3 frames.
If we were writing our game using a modern language on a modern system, this wouldn’t even be a problem – the position of the object would be stored using floating-point numbers, which we could adjust however we like safe in the knowledge that the system will render our object at exactly the right place – easy. But we’re not a modern language or system, and on the face of it can’t do either of those things – we don’t have any floating-point registers, and have to sort out the actual rendering ourselves – but it can still be done.
“But…” you think “…the Spectrum CAN do floating point – you can do it in BASIC – so why not just use that?“. Well, you could… but I promise you that your game would run so slowly as to be unplayable. The calculator routines in the Spectrum’s ROM are not the kind of thing to even consider running in a tight game loop where you’re pushing for speed. Actually, and possibly without knowing it, your game is already doing something very similar to what is required here when it does render your object…
Consider an object which is at the horizontal position of 18 pixels from the left of the screen. You need to split that into both the character position, and bit within the character, in order to draw it – so 18 pixels is 2 pixels in on the 3rd character, or 3.25 characters – floating point! You’ve used the top five bits to describe the character position, and the lower 3 bits to describe the bit position. This is called a ‘fixed-point’ system, and by splitting how the 8 bits in the byte are interpreted allows us to have 32 different character positions, and the bit position in 8ths. So for the movement of our object we just need to use a similar system to control how much we move it by each turn.
How you want to do this is up to your needs – but you’ll probably want to have a byte whose usage is split between the whole number of pixels to move, and the fractional part which allows for sub-pixel movement (if your object will never move as faster as one pixel each turn then you could not have a whole-number section at all). For the sake of an example, we could give over the top 3 bits of our movement byte to whole numbers – so our object could travel at it’s fastest at nearly 8 pixels a turn, and give the bottom 5 bits over to the fraction – so at it’s slowest it would move 1/32nd of a pixel each turn. Once you’ve made that decision it’s just a case of formatting the speed you want to move in same way, add it to your movement byte, and then take those top 3 bits and use them to update the actual position of your object, reseting them afterwards. You’ll likely need another byte somewhere, perhaps a bit in a ‘player flags’ byte – to control in which direction your object is moving, as that will dictate whether you add, or subtract, from the actual position.
Using a system like this allows not only for sub-pixel movement, but can also be used to add smooth acceleration & deceleration to your object – in a racing game perhaps, or as momentum in a platform game – such as I used in Horace in the Mystic Woods, and in Gem Chaser & Gem Chaser 2 games.
So to put this into practice, we first need a store for our object’s position, sub-position, and speed:
POS defb 0 SUBPOS defb 0 SPEED defb 0
For this example we’ll split the sub-position straight down the middle so the highest four bits are the ‘whole number’ section, and the lower four are for the ‘Fraction’. Remembering that the speed must also take this format, we can set our speed to two whole units using binary 0010 0000 (32), one unit using 0001 0000 (16), or half a unit as 0000 1000 (8), or a quarter 0000 0100 (4), or anything between 1/15th and 15 units. We’ll choose 1/4:
ld a, %00000100 ld (SPEED), a
To update the position of the object we need to update the sub-position first, and then the position from that. Updating the sub-position is done by simply adding the speed to it:
ld a, (SPEED) ld b, a ld a, (SUBPOS) add a, b ld (SUBPOS), a
The sub-position now needs to be checked to see if it has anything in it’s ‘whole number’ section, as if so, that needs to be added onto our actual position:
ld a, (SUBPOS) and %11110000 jp nz, UPDATE_POSITION
The UPDATE_POSITION routine will first reset the whole number section of the sub-position, and then add the whole number section onto the position:
UPDATE_POSITION ; Read, and take a copy, of the sub position. ld a, (SUBPOS) ld b, a ; Only keep the fractional part, and re-store. and %00001111 ld (SUBPOS), a ; Grab the copy of the sub position. ld a, b ; Shift it down so it reads as the whole number. rra rra rra rra and %00001111 ; Update the position. ld b, a ld a, (POS) add a, b ld (POS), a
The first time this is ran the sub-position will be the same as the speed – 1/4. The second time that will be updated to 2/4, then 3/4, then 4/4 – or 1 and 0/4. Once it reaches that whole number the position is increased by that 1, and the sub-position resets back to zero again. It’s taking four passes so update our position, which matches the speed of 1/4 unit.
If the speed was faster, 3/8 for example, the sub-position would be 3/8, then 6/8, then 9/8 – or 1 and 1/8. The position would be increased by 1, and the sub-position reset back to 1/8. It would then continue to 4/8, then 7/8, then 10/8 – or 1 and 2/8. The position would be increased by 1 again, and the sub-position reset back to 2/8. Continuing a third time would produce 5/8, then 8/8 – or 1 and 0/8. The position would be increased by 1 again, and the sub-position reset to zero. This means that position moved in 3, then 3, then 2, passes – i.e. it’s irregular, but that again matches our speed.