In the last tutorial I looked at creating a randomized starfield for the main menu. Since then I’ve been playing around with techniques to scroll it vertically, like the starfield on the Galaga arcade machine.
I found a bug in the code I wrote. Instead of using a pair of random(ish) numbers for the X and Y coordinates of each star, I was actually using the Y coordinate of the previous star for the X coordinate of the next one. This was causing unnecessary vertically-repeating patterns.
After fiddling around with this for a while, I decided to use a third randomish number to represent the star position with the character. This leaves me with some spare bits I can use for attribute colours without introducing more repeating patterns.
I also added a seed randomizer in my test routine—instead of advancing the ROM seed sequence by one every time you press a key, I completely randomized it to any address between
$3FFF. I used Patrik Rak’s Complementary Multiply With Carry pseudo-random number generator again for this.
Same deal as last time, except this time it scrolls vertically:
Most of the code is pretty similar to last time. The section that draws the stars is duplicated. In the first iteration, the part that creates the
set n, a opcode now creates a
res n, a instruction instead, which undraws the previous star.
37 38 39 40 41 42
; stars.asm ld a, (hl) ; Read the byte represnting the star pixel, or %10000111 ; turn it into a "res n, a" instruction ($80 10nnn111) by setting and %10111111 ; and clearing bits, ld (ResetBit), a ; SMC> then write this into the pixel-drawing code.
In the second iteration, we keep the
set n, a instruction as it was in part 12. But before we draw the star pixel, we manipulate the Y coordinate by including the
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
; macros.asm ScrollStar macro() ld a, d ; Retrieve the byte containing bits 0..2 of the Y coordinate. and %00000111 ; Calculate Y mod 8 (0..7). cp %00000111 ; If 7 then jp z, CharScroll ; do a character scroll. ld a, d ; Otherwise do inc a ; a pixel ld d, a ; scroll. inc (hl) ; Save px-scrolled offset back to StarField.Table. jp Continue CharScroll: ld a, e ; Retrieve the byte containing bits 3..4 of the Y coordinate. add 32 ; Increment those two bits by one ld e, a ; and save back the byte. This moves into the next char row. dec l ; Now retrieve the byte ld (hl), a ; containing bits 0..2 of the Y coordinate, inc l ; and xor a ; reset it to zero, ensuring we start at the top of that char row. ld d, a ; Save that back to the byte, and also ld (hl), a ; save the px-scrolled offset back to StarField.Table. Continue: mend
Doing this as a macro isn’t really necessary at all, but when reading the code, it does emphasise that the pixel-setting half is almost the same as the pixel-clearing half. It achieves this by abstracting the extra code into a different place—without any of the
jp overhead a separate routine would entail.
The stuff in the macro decides whether we’re scrolling within a character boundary, or scrolling between character boundaries. Then it sets the appropriate bits of the Y coordinate accordingly. The specific maths here is needed because of how the Y coordinate is distributed within the screen address:
In the Spectrum’s distinctive display file, the bits labelled Y0, Y1 and Y2 govern the position (0..7) within a character row. We set these bits in the first half of the macro.
The bits labelled Y3, Y4 and Y5 govern the position of the character row (0..7) within the current screen third. We set these bits in the second half of the macro.
The bits labelled Y6 and Y7 govern which screen third the character row appears in. Because we’re repeating the same stars in all three thirds, we set these bits in the
84 85 86 87 88 89 90
; stars.asm ld b, 8 ; Fast way of doing ld bc, $0800 (size of a screen third). add hl, bc ; hl is now a pixel address in the middle screen third. ld (hl), a ; Draw the same single pixel star here, too. add hl, bc ; hl is now a pixel address in the bottom screen third. ld (hl), a ; Draw the same single pixel star here, too.
$0800 to the screen address using the
add hl, bc instruction is an inefficient way of doing this. All we really need is to add
h. Or even better, flip one or two bits of
h. I might optimise this part another time.
I made a list of 32 of my favourite starfields using this new version of the randomizer test routine. I particularly like how it tends towards drifts of stars that look like constellations.
This is my favourite starfield (seed address
$26B1), complete with the scroll effect:
Next time, I’ll explore setting attribute colours to make the stars twinkle. Cheers!