Colour Cycling Effects with Lines
This is a continuation in a series of posts about writing BBC Basic programs for the @bbcmicrobot twitter bot.
The first few programs I made for the BBC Micro Bot all used the same technique. A series of lines are draw in consecutive colours and then animated using colour cycling.
To demonstrate this technique we will go through an example of writing a program to make a sideways scrolling parallax starfield.
If you are following along, you don’t need tweet every stage to @bbcmicrobot! Get an emulator like BeebEm to test out the program.
Writing the Program
We will use MODE 2 as it lets us use 16 colour values. One colour (0) will be the background and the others (1-15) will represent frames of our animation.
Starting the Line
To start with lets make a program to draw a horizontal line.
10 MODE 2
20 X=0
30 Y=RND(1024)-1
40 V=50+RND(100)
50 C=1
60 MOVE X,Y
70 X=X+V
80 GCOL 0,C
90 DRAW X,Y
We can have a look at what each line is doing:
10 MODE 2
This sets the graphics mode to MODE 2.
20 X=0
30 Y=RND(1024)-1
This sets up the start position of the line in the variables X and Y. We want our lines to start on the left hand side of the screen and move to the right so we set the initial X co-ordiante to 0 (the left hand side of the screen) and set the Y co-ordinate to a random value between 0 and 1023 to represent a random position vertically up the screen.
The expression RND(X) where X is an integer greater than 1 will generate a random integer between 1 and X inclusive. (note that RND(1) will return a random number between 0 and 0.999999).
40 V=50+RND(100)
We want each ‘star’ in our starfield to move at a different speed. This line sets V to be the horizontal velocity of the current star based on another random value.
50 C=1
We will use the variable C to store the current colour we are drawing with. For now we will initialise this to 1 (red).
60 MOVE X,Y
70 X=X+V
80 GCOL 0,C
90 DRAW X,Y
These lines of the program will draw the first line segment to the screen. First we use the MOVE statement to position the graphics cursor at the screen co-ordinate (X,Y) which is the start of the line. Then we update the X position to be at the end of the line. We just want to move the stars horizontally so we just add V to the X co-ordinate. GCOL 0,C sets the graphics colour to our C value. Finally the DRAW command will draw a line from the previous position of the graphics cursor to the now updated (X,Y) co-ordinate. The position of the graphics cursor will also be updated to be at the end of the line we have drawn.
If we run the program we will see a single red line segment being drawn.
Adding a Loop
We can add a couple of lines to repeat the section of the program that draws the line segment and updates the horizontal position.
55 FOR I=0 TO 100
100 NEXT I
We can also update our colour value C between drawing each line segment (see the section on Modulo arithmetic in the post about colour cycling).
85 C=1+C MOD 15
Our program now looks like this:
10 MODE 2
20 X=0
30 Y=RND(1024)-1
40 V=50+RND(100)
50 C=1
55 FOR I=0 TO 100
60 MOVE X,Y
70 X=X+V
80 GCOL 0,C
85 C=1+C MOD 15
90 DRAW X,Y
100 NEXT I
Now we are drawing a number of line segments with increasing colour values from 1 to 15.
Multiple Lines
Eventually the lines we are drawing go off the right hand side of the screen. When they do this, we can start a new line, starting from the left hand side of the screen, at a new random Y position and with a new random horizontal velocity.
To do this, before we draw each line segment, we check the X position for the start of the line. If it is off the screen, we repeat the logic in lines 20, 30 and 40 to setup a new line.
56 IF X>1279 X=0: Y=RND(1024)-1: V=50+RND(100)
If the IF condition is true (meaning the line is now off the right hand side of the screen), the statements on the rest of the line are executed. By using a colon we can put several BASIC commands on the same line. None of these will run if the condition is false.
We should now see output something line this:
By doing this we have duplicated the logic to setup a new star in two places in the program. This can sometimes lead to mistakes as you need to remember to make any changes to these parts of the program in both places.
We can avoid this issue and make the program shorter by removing the first instance of this logic (that only runs once when we start the program) and making the program always run the second instance of this logic on startup by starting the line so it is already off the screen.
We can delete lines 20,30 and 40 and instead write:
20 X=9999
Our program now looks like this and still gives the same result:
10 MODE 2
20 X=9999
50 C=1
55 FOR I=0 TO 100
56 IF X>1279 X=0: Y=RND(1024)-1:V=50+RND(100)
60 MOVE X,Y
70 X=X+V
80 GCOL 0,C
85 C=1+C MOD 15
90 DRAW X,Y
100 NEXT I
Colour Cycling
We can now start looking at animating the colour palette.
To begin with we can simply show one frame at a time.
Frame 0:
Logical Colour | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Physical Colour | 0 | 6 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
^ |
Frame 1:
Logical Colour | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Physical Colour | 0 | 0 | 6 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
^ |
We could set the palette for all 16 logical colours every frame but this would be slower than if we could just set the palette for a couple of colour values.
Take a look at what values changed going from frame 0 to frame 1 above.
Logical Colour | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Frame 0 | 0 | 6 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Frame 1 | 0 | 0 | 6 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
^ | ^ |
If we are animating the colours in sequence, we only need to change two palette entries each frame. We can just set the physical colour for the last frame we drew back to the background colour and set physical colour for the current frame we want to display to be the foreground colour.
We will use L to store the index of the logical colour for the current frame. We can start this with the value 1.
200 L=1
We will assume that at this point in the program L is set to the logical colour for the previous animation frame, so we can turn this off by setting the palette entry for logical colour L to the background colour (0 = black).
210 VDU 19,L,0,0,0,0
Then we can update L to be the logical colour value for the current frame and set the palette entry for this frame to be the foreground colour (6 = cyan)
220 L=1+L MOD 15
230 VDU 19,L,6,0,0,0
Then we can repeat this to play the animation.
240 GOTO 210
At this point the animation will play but too fast. We can add a line to wait for the display to finish showing the current frame before we move on to displaying the next one.
235 *FX 19
Our program now looks like this:
10 MODE 2
20 X=9999
50 C=1
55 FOR I=0 TO 100
56 IF X>1279 X=0: Y=RND(1024)-1:V=50+RND(100)
60 MOVE X,Y
70 X=X+V
80 GCOL 0,C
85 C=1+C MOD 15
90 DRAW X,Y
100 NEXT I
200 L=1
210 VDU 19,L,0,0,0,0
220 L=1+L MOD 15
230 VDU 19,L,6,0,0,0
235 *FX 19
240 GOTO 210
And running it will produce something like this:
Putting things in Perspective
Currently the number of fast stars is the same as the number of slow stars. We can give the effect some more depth by adjusting the star speeds. We want more distant stars moving slowly than close stars moving quickly.
We can do this by changing the calculation for ‘V’
56 IF X>1279 X=0: Y=RND(1024)-1:V=2000/(20+RND(100))
Really this is like we are picking a random “depth” for each star and then applying a perspective transformation to this depth to give us the star’s speed.
The key thing that makes this work is that we are dividing by the random depth value we generate.
The numbers in this statement were fiddled with manually to get a distribution that I liked the look of.
As more stars are now further away we can increase the number of iterations in the FOR loop to 1000 to see more stars.
Adding Some More Colour
Let’s show a few more animation frames at once and display them with different colours. After some experimenting with different combinations of colours I decided to make the front of the line red (1), the middle section yellow (3) and the end blue (4).
Logical Colour | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Frame A | 0 | 4 | 3 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Frame B | 0 | 0 | 4 | 3 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
^ | ^ | ^ | ^ |
We still only want to change the value of L once per frame and then base the other logical colours we are updating on this.
We want to change the palette as quickly as possible otherwise we might see output on the display between us updating the palette entries. To help this, we do the slow calculations to work out the logical colours that we are going to set up front (putting the results into K,L,M,N) and then set the palette entries one after another with no additional calculations.
Logical Colour | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Frame A | 0 | 4 | 3 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Frame B | 0 | 0 | 4 | 3 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
K | L | M | N |
The colour cycling code now looks like this:
200 L=1
201 K=L
202 L=1+L MOD 15
203 M=1+L MOD 15
204 N=1+M MOD 15
205 *FX 19
210 VDU 19,K,0,0,0,0
230 VDU 19,L,4,0,0,0
235 VDU 19,M,3,0,0,0
236 VDU 19,N,1,0,0,0
240 GOTO 201
Making it Faster
@bbcmicrobot will run for around 30 seconds allowing us to build up our animation frames before it starts to capture video.
The more lines we draw during this time the more interesting our effect will be.
We can speed up the line drawing part of the program by changing all the variables it uses to be integers.
We can also notice that the MOVE command only needs to happen when we start drawing the line strip for a new line. We can put this MOVE command on the end of the IF line.
We can adjust the number of iterations in the FOR loop so that the drawing lasts under (significantly under) 30 seconds.
The BBC Micro Bot Editor web page is useful to adjust this timing.
I set the number of iterations (which is the total number of line segments drawn) to 2500.
Our program now looks like this:
10 MODE 2
20 X%=9999
50 C%=1
55 FOR I%=0 TO 2500
56 IF X%>1279 X%=0: Y%=RND(1024)-1:V%=2000/(20+RND(100)):MOVE X%,Y%
70 X%=X%+V%
80 GCOL 0,C%
85 C%=1+C% MOD 15
90 DRAW X%,Y%
100 NEXT I%
200 L=1
201 K=L
202 L=1+L MOD 15
203 M=1+L MOD 15
204 N=1+M MOD 15
205 *FX 19
210 VDU 19,K,0,0,0,0
230 VDU 19,L,4,0,0,0
235 VDU 19,M,3,0,0,0
236 VDU 19,N,1,0,0,0
240 GOTO 201
Squishing it Down
In order to send the program to @bbcmicrobot we need to reduce the size down to less than the length of a tweet (280 characters).
Read the BBC BASIC Golf post to find more information about these techniques.
We can combine as many lines as possible into single lines of BASIC by separating them with a colon character. We cannot add more things to the end of the IF line as they will then depend on the IF condition. Certain commands like *FX need to be the last thing on a line.
After this there will be fewer lines in the program. We can renumber the program so the line numbers are single digits. To do this we can use the command:
RENUMBER 1,1
Our program now looks like this:
1 MODE 2:X%=9999:C%=1:FOR I%=0 TO 2500:IF X%>1279 X%=0: Y%=RND(1024)-1:V%=2000/(20+RND(100)):MOVE X%,Y%
3 X%=X%+V%:GCOL 0,C%:C%=1+C% MOD 15:DRAW X%,Y%:NEXT I%:L=1
4 K=L:L=1+L MOD 15:M=1+L MOD 15:N=1+M MOD 15:*FX 19
5 VDU 19,K,0,0,0,0:VDU 19,L,4,0,0,0:VDU 19,M,3,0,0,0:VDU 19,N,1,0,0,0:GOTO 4
The VDU commands can all be combined into one and shortened by using a semicolon to remove some of the zeros.
6 VDU 19,K,0;0;19,L,4;0;19,M,3;0;19,N,1;0;:GOTO 4
At this point I would switch from using an emulator to editing in the BBC Micro Bot Editor.
We can now abbreviate the BASIC statements, remove any unneeded whitespace and remove variables from the NEXT commands.
1MO.2:X%=9999:C%=1:F.I%=0TO2500:IFX%>1279X%=0:Y%=RND(1024)-1:V%=2000/(20+RND(100)):MOV.X%,Y%
3X%=X%+V%:GC.0,C%:C%=1+C%MOD15:DR.X%,Y%:N.:L=1
4K=L:L=1+L MOD15:M=1+L MOD15:N=1+M MOD15:*FX 19
5V.19,K,0;0;19,L,4;0;19,M,3;0;19,N,1;0;:G.4
This program is already under the size of a tweet but if we were struggling for size there are still other things we can do.
X%=9999
could be any big number. We can change this to
X%=1E4
Similarly, we can fiddle with the values used to generate the random V% velocity to find constants that are smaller but produce a similar look.
Making the change to the VDU command changed the timing of the program making the palette change visible on the screen.
We can mitigate this a little by change the order that we set the palette colours.
There is always room to squeeze some more size from a program, we could still remove the -1 in the random Y coordinate but at this point we can call ourselves done.
The Final Program
Our final program is 229 Characters (enough space left over to add a link in a REM statement).
1MO.2:X%=1E4:C%=1:F.I%=0TO2500:IFX%>1279X%=0:Y%=RND(1024)-1:V%=2E4/(200+RND(1E3)):MOV.X%,Y%
3X%=X%+V%:GC.0,C%:C%=1+C%MOD15:DR.X%,Y%:N.:L=1
4K=L:L=1+L MOD15:M=1+L MOD15:N=1+M MOD15:*FX19
5V.19,N,1;0;19,M,3;0;19,L,4;0;19,K,0;0;:G.4
— BBC Micro 🦉 Bot (@bbcmicrobot) March 19, 2020
Other Similar Programs
Here are some of the other BBC BASIC programs I made that use colour cycling of line strips in a similar way.
Firework
— BBC Micro 🦉 Bot (@bbcmicrobot) March 3, 2020
This program was the first program I made for @bbcmicrobot. It is very similar to the program in this post except that all the lines start in the same location, they have an X and Y velocity and gravity is also applied. The line strips are reset if they go off the bottom, left or right of the screen.
Sparks
— BBC Micro 🦉 Bot (@bbcmicrobot) March 6, 2020
This program is also very similar to the program in this post. The main difference is that the motion is vertical instead of horizontal, there is some additional vertical acceleration to the lines’ motion and some random acceleration is applied in a horizontal direction.
As a further attempt at reducing program size, the four colours to use are written to memory as a string:
4M=&800:$M="0137"
and then read individually from memory:
M?J-48
Starfield
— BBC Micro 🦉 Bot (@bbcmicrobot) March 3, 2020
This program is also very similar to the technique described in this post except the points are in 3D, the lines start as random points on a plane with a far depth value and move towards the camera. A simple perspective transform is used to convert the 3D points to 2D screen co-ordinates.
The points reset when they go behind the camera or go offscreen (to keep the program size small, this is done by checking the distance from the centre of the screen).
If we used a MOVE command to start the line we would need to repeat some calculations or store some values. Instead, the colour is changed for the first DRAW to an operation that will draw nothing to the screen (GCOL 1,0 will use ‘OR’ mode with a value 0).
Spinning Sphere
— BBC Micro 🦉 Bot (@bbcmicrobot) March 7, 2020
This line based effect is also similar to the others.
The lines around the sphere are drawn in horizontal slices one by one, similar to how they are drawn across the screen in the program in this post.
This time the lines are generated on the surface of a 3d sphere going around the sphere from the right side to the left. The 3d points then have a perspective transform applied to give a 3d effect.
Lines at the poles of the sphere are drawn first, then they move down towards the equator so that lines that when lines overlap, the more important ones are kept.
It uses the same memory string technique as the Sparks program to store the palette colours.
…
next time we will look at some similar effects but scrolling a grid.