User blog:SlackerPrime/Per-Pixel Scroller pt. 2 - The good ol' sine scroller

Alright, I hope you saved the program from last part, because you're gonna need it. Today, we make our scroller a bit fancier. We will be implementing the tried-and-true sine scroller by using GCOPY. Before we jump in, however, I should mention the use of new variables and constants, and a bit of math is coming. Additionally, this code is going to reduce the framerate. Not by much, but it will. Alright, we're gonna get the new constants out of the way first. Put these up where your other constants are defined.

DEPTH=15 WRES=4 WSKP=WRES-1 WHLF=FLOOR(WRES/2) It will be easier to understand these constants when we implement them, so we're going to move straight onto the @SINE routine. This routine is a bit scary, so I'm going to introduce it and then walk you through it.

@SINE FOR I=0 TO 255 STEP WRES ANG=SOFS+(((I+WHLF)/255)*360)*PI/180 CY=DEPTH*SIN(ANG) GCOPY I,CSY,I+WSKP,CEY,I,CSY+CY,TRUE IF CY>1 THEN GFILL I,CEY,I+WSKP,(CEY+CY)+1,0 IF CY<1 THEN GFILL I,CSY,I+WSKP,(CSY+CY)-1,0 NEXT RETURN

Some of you probably died a little inside when you read that, and I understand. This was a bit finicky to get working properly, and I haven't even optimized it yet. Let's discuss what's going on here as best as we can. WRES is our warp resolution, what defines how smooth our sine curve is. In implementation, this determines how many vertical lines the FOR loop scrolls at once. Here, each GCOPY copies a WRESx16 pixel region and offsets it by CY (here, WRES is 4). WRES is used to make this routine faster. Scrolling in 1x16 regions is way too slow, but 4x16 scrolls at close to 60FPS with minor loss in quality. It takes a WRES of at least around 10 to get the same framerate as without @SINE, but that just looks bad.

After the copy, we use some conditional GFILLs to cover up the old graphics. We only do these if CY!=0. If it does, we don't need to cover anything up because the GCOPY put the graphics in it's original place. FOR ~ NEXT ~ STEP, and we're done, RETURN.

Now that that's out of the way, it's math time!

CY is the distance from the screen's center, in pixels. GCOPY adds this onto it's destination-y coordinate, so the copied block is offset from it's original position. CY itself is calculated using SIN(ANG) and multiplied by DEPTH. This is the amplitude of our sine curve, or essentially how tall it is. Here, we used a nice 15 pixels. ANG is calculated with a seemingly complicated formula, but it's a bit simpler when demonstrated in steps. This is the angle value, in radians, we supply to SIN so we get a nice smooth curve. It is first calculated in a range of 0-360 as a function of I, like this. ANG=((I+WHLF)/255)*360 I+WHLF is the x-coord in the middle of the current pixel region. This value is taken as a percentage of 255 and multiplied by 360, so the curve changes as we go along the screen instead of everything being at the same height. Afterward, this value is converted to radians using the degrees-to-radians formula. ANG=ANG*PI/180 Why didn't I use RAD to do this? Because RAD won't take values outside of the range 0-360, and that's dumb. After that, we add an offset SOFS so the sine curve slowly rolls. ANG=ANG+SOFS Combine all of these steps, and we get the ANG formula you see up there. See, not too bad right? But there's one thing: we haven't seen SOFS yet. Now we will. Update @LOOP accordingly.

@LOOP VSYNC 1 GOSUB @DRAW GOSUB @SINE GOSUB @FLIP SOFS=SOFS-0.05 TEXTX=TEXTX-SPEED GOTO @LOOP SOFS is deccremented every frame so our sine wave shifts a bit every frame, creating a nice rolling effect. Also notice we removed the one-line @SCROLL routine and put it's whopping one line of code into our loop. Optimization at it's finest. Well, I have to say, that was a bit exhausting, but with a nice payoff. Run this thing and you get a sine scroller, in it's full glory! Oh, and here's today's full source.

ACLS

CLEAR

PA=0

PB=1

GPAGE 0,PA,PB

SCALE=2

PAL=0

TEXTX=256

TEXTY=95

SPEED=2

DEPTH=15

WRES=4

WSKP=WRES-1

WHLF=FLOOR(WRES/2)

ST$="Hello!"

CSY=TEXTY-((8*SCALE)/2)

CEY=CSY+((8*SCALE)-1) DIM ST(LEN(ST$)) FOR I=0 TO LEN(ST$)-1 ST(I)=ASC(MID$(ST$,I,1)) NEXT

@LOOP VSYNC 1 GOSUB @DRAW GOSUB @SINE GOSUP @FLIP SOFS=SOFS-0.05 TEXTX=TEXTX-SPEED GOTO @LOOP

@DRAW GCLS FOR I=0 TO LEN(ST$)-1 GPUTCHR TEXTX+((8*SCALE)*I),CSY,"BGF",ST(I),PAL,SCALE NEXT RETURN

@FLIP SWAP PA,PB GPAGE 0,PA,PB RETURN

@SINE FOR I=0 TO 255 STEP WRES ANG=SOFS+(((I+WHLF)/255)*360)*PI/180 CY=DEPTH*SIN(ANG) GCOPY I,CSY,I+WSKP,CEY,I,CSY+CY,TRUE IF CY>1 THEN GFILL I,CEY,I+WSKP,(CEY+CY)+1,0 IF CY<1 THEN GFILL I,CSY,I+WSKP,(CSY+CY)-1,0 NEXT RETURN

In the next lesson, we'll be going over an additional effect we can apply and begin to wrap things up. NOTE: Try setting WRES to 1, and you'll see just how slow it is.  