Perihelion tutorial 11

From Atari Wiki
Jump to navigation Jump to search


The Atari ST M68000 tutorial part 11 – of making the mountain move to Mohammed

 

Well well, finally, as promised, we will delve into the technique of sprites; the essence of a platform or shoot-em-up game, and lots of other stuff. In fact, anything that needs something moving that is not 3D or real time rendered (that is, it’s being drawn while the program runs, and not stored previously as a picture). It was really challenging and great fun to code this one, and it’s probably the most satisfying coding experience ever, I hope I can convey the knowledge it brought me.

                      In the last tutorial, we learned something on pixels, in order to be able to address a single pixel anywhere, the data must be shifted into a correct position. Why is this? Because, each instruction except the bit instructions, deal with at least byte size. What means is that if we use instructions with byte size, all pixels “snap” at 8 pixels, because that’s the minimum addressable size. However, by shifting the data before using it in graphic instructions, we can in a way address any pixel we want to.

                      I actually suggest you load up the pre-assembled program, and both the picture files that comes with the tutorials, the pictures being AUTUMN.PI1 and SPRITE.PI1. A little note on the pictures, they are in STe palette, meaning that they will look a bit ugly on a ST, but STeem should handle this nicely. Yes, the character seen is the same one as in TUT9; Kenshin. He’s the main character in a Japanimation, a former assassin for the government who now tries to atone by living a quiet life and helping people. This series is awesome and has given me much inspiration, the first Kenshin OVA series is one of the most beautiful pieces of art I’ve ever seen.

                      So, after you’ve been impressed by the Tai Ji symbol (a.k.a Yin and Yang symbol, Yin and Yo in Japanese) bouncing around the screen, you are eager to learn for yourself, right? As you can see, the background is provided in the AUTUMN.PI1, and the bouncing ball, which is the sprite, is in SPRITE.PI1. Actually, only 14 colours are used for the background, the last two being reserved for the sprite, this isn’t necessary and the sprite may well share colours with the background. The sprite seems to appear twice, in the SPRITE.PI1, there are two balls, one of them is the sprite mask, if confusion occurs, just read on.

                      Painting the background is easy, just smack in the pixel data and set the palette, bouncing will be dealt with later, what we need to focus on now is getting the sprite nicely on the screen, and being able to put it anywhere on the screen, preferably expressing the location in X and Y coordinates for human compatibility. How exactly to put the sprite data on screen, the most obvious choice is a move instruction. This won’t do at all though, check this out.

 

                      Screen memory

                      %00000000   00001110      first word

                      %00000000   00000000     second word

                      %00000000   00001011     third word

                      %00000000   01010101     fourth word

                     

                     Pixel colours

                      $00000000    0808595C

 

                      Sprite data

                      %00000000   00000001      first word

                      %00000000   00100000     second word

                      %00000000   00000000     third word

                      %00000000   00001010      fourth word

 

                      Pixel colours

                      $00000000    00208081

 

                      Now, if we move the sprite data onto the graphics memory, we get

 

                      Screen memory

                      %00000000   00000001      first word

                      %00000000   00100000      second word

                      %00000000   00000000      third word

                      %00000000   00001010      fourth word

 

                      Pixel colours

                      $00000000    00208081

 

                      Move instructions destroys all data and replace it with the new one, in other words, the background is completely lost and the sprite has taken over completely. Doing it like this will also create an ugly squared looking sprite, since the sprite background will not be transparent. This will not do. Or instructions, on the other hand, will not overwrite the original data, we try an or instruction with the above configuration

 

                      Screen memory

                      %00000000   00001111      first word

                      %00000000   00100000      second word

                      %00000000   00001011      third word

                      %00000000   01011111      fourth word

                     

                      Pixel colours

                      $00000000    0828D9DD

                     

                      Dang! By or:ing in the sprite, we mixed the sprite with the background, this is also bad since the sprite will not look as it should, although it will create quite a nice effect and is good if you simply want a “colour distortion” effect, but we don’t want that now. An exclusive or would only flip the colours around in strange ways, and an and instruction clears data. But wait, if we clear out the sprite data, with an and, leaving the background intact, and then or in the sprite, it would all work. The sprite mask has the same look as the sprite, but is only two colours. Colour 15 where the background is, making sure all bits there are set, and colour 0 where the real sprite form is, making sure all bits are cleared. Have a look at SPRITE.PI1, and you will see clearly (well, ok, in the picture, the mask is colour 15 and the background is colour 0, but it will get inverted later, read on … ).

                     

                      Sprite mask

                      %11111111   11010100      first word

                      %11111111   11010100      second word

                      %11111111   11010100      third word

                      %11111111   11010100      fourth word

 

                      Pixel colours

                      $FFFFFFFF  FF0F0F00

                     

                      All pixels that were colour 0 (background) in the sprite, are now colour 15 (F), and all pixels that had one colour or another in the sprite are now colour 0. By and:ing the sprite mask with the background, we will make sure to clear out all sprite pixels (since they get and:ed with 0) and keeping the status of all other bits (since they are and:ed with 1). It is imperative that you understand this step, if you don’t, reread the Boolean algebra part in TUT9, check some external sources and think again, or send me an e-mail :) After applying the mask, the screen memory will look like this

 

                      Screen memory

                      %00000000   00000100      first word

                      %00000000   00000000     second word

                      %00000000   00000000     third word

                      %00000000   01010100     fourth word

                     

                      Pixel Colours

                      $00000000    08080900

                      #bbbbbbbb    bbsbsbss        b = background, s = sprite

 

                      As you can see, the background has been preserved, while everything concerning the sprite is wiped out. Now is the time to or in the sprite data; this instruction will in no way affect the background (since the background colour in the sprite is 0).

                     

                      Screen memory

                      %00000000   00000101      first word

                      %00000000   00100000      second word

                      %00000000   00000000      third word

                      %00000000   01011110      fourth word

                     

                      Pixel Colours

                      $00000000    08288981

                      #bbbbbbbb    bbsbsbss       b = background, s = sprite

 

                      To summarise; first we take an inverted version of our sprite with only two colours, and and that with the background. This clears all pixels that are concerned with the sprite and leaves the background intact. After the mask is applied, it is safe to or in the sprite data, since the previous clearing of the sprite pixels, there is no risk of mixing the sprite with the background. The background in the sprite is colour 0, thus the or instruction will have no effect on the background, the background part in the mask is colour 15 (all 1’s) and thus the and instruction will not affect the background.

                      OK, now we know how to put the sprite on screen, but we are still faced with the problem of not being able to put it anywhere. To solve this, the sprite and mask data must be shifted. Like with the putpixel, in order to put the sprite at say 0,2, we need to shift the sprite data right two bits. With the putpixel, we shifted “real time”, but there is much more data involved in a sprite, so we’ll be pre-shifting the sprite instead. When using the pre-shifted method, we assign a storage area that is 16 times larger than the sprite data, and store the sprite in that area shifted in all possible sixteen combinations we need.

                      I see a big ‘?’ in your face right now. Think about it, in the putpixel routine, we could end up shifting the pixel 15 bits to the right, at most, so what we do here with the sprite is to store all those possibilities after one another. When the time comes to put the sprite out, instead of shifting the original sprite data, all we have to do is access the storage area with the correct offset. An offset is the value added to the starting address of something. For example, the middle of the screen is the screen address with an offset of 100*160+80 = 16080 bytes.

 

                      Sprite data

                      $00001111    …

 

                      Sprite storage area

                      $00001111    …                  first position, offset 0

                      $00000111    …                  second position, offset 1

                      $00000011    …                  third position, offset 2

                      $00000001    …                 fourth position, offset 3

                      (the offset number is completely fictional, it’s not even an even number)

 

                      Let’s say we want to put the sprite at 0,2, we know what that mean, it means pointing to the start of the screen memory, shift the sprite data right by 2, and put it in place. Say a0 points to the screen memory, and a1 to the sprite storage area, then we just need to add offset 2 to a1, and a1 will point to correctly shifted sprite data. Pre-shifting is way faster than loading up the sprite data in a1, and then shift it, especially since the sprite data consists of several words that all needs to be shifted. The downside of course is loss of memory.

                      Now, there is a problem here, if we have a 32*32 pixel sprite, like in the sample program, the data for the sprite is 16*32 bytes. Arranged like this.

 

                      Sprite data

                      First 16          Last 16 pixels (W = word)

WWWW       WWWW                             first line

WWWW       WWWW                             second line

WWWW       WWWW                             third line

… and so on for a total of 32 lines

 

When we begin to shift, we want the last bit that go out the first word, to be shifted in as the first bit in the fifth word. This is comparable to the tutorials on scrolling. The last bit that go out the fifth word, should not go into the first bit of the ninth word, because then a pixel from the first line would go into the second line, but there is no room to shift it out right on the first line. So, we have to add a buffer to every line so that no data will be lost in the shift. In the last shift, the first four words will be all but empty, and the buffer will be all but full. So the sprite storage area will have to look like this.

 

Sprite storage area

First 16          Middle 16      Last 16 pixels (B = buffer, word size)

WWWW       WWWW       BBBB

WWWW       WWWW       BBBB

                      WWWW       WWWW       BBBB

… and so on for a total of 32 lines and 16 such blocks to cover all possible shifts

 

                      Even though the sprite storage area covers a total of 48 pixels, 16 of these pixels will be 0, thus not affecting the background. See the sprite as a 48 pixel wide block, with only 32 pixels coloured. Within this 48 pixel block, the 32 colour pixels will be shifted more and more to the right as X coordinates increase, then when it becomes critical, the block will move 16 pixels to the right in one sweep, and the 32 pixel colour area will be reset, starting the procedure all over again. Run the TUT11BLK.PRG, to see this clear.

                      Alright, theory part on pre-shifting done, now we need it in direct coding practice as well. First off, we’ll need a good instruction with which to shift. Sure, lsr seems a good choice, but we need to be able to preserve the bit that gets shifted out, and lsr doesn’t preserve anything. The instruction roxr, for ROtate eXtended Right, is good in this case. The extended bit is rotated in from the left, and the bit rotated out the right is saved in the carry and extended flag. So, by roxr:ing with one each time, we will save what we shift out, and shift it in the next time around. (btw, when speaking about the user bits in the status register, flags and bits are used synonymous) Looki looki

 

                                                                             (X = extended flag)

                                                                              d0 = %00001101                X = 0

                      roxr                 #1,d0                       d0 = %00000110                X = 1

                roxr                 #1,d0                        d0 = %10000011                X = 0

                roxr                 #1,d0                        d0 = %01000001                X = 1

 

                      What we do is to first copy the sprite data to the sprite storage area, then we take the data from the storage area, rotate extended right with one, and save that data into the next position of the storage area. What we have to think about when coding this is that the data from the first word, goes into the fifth word and so on. In code, it looks like this

 

                      move.l            #spr_dat,a0             original sprite data

                      add.l              #34,a0                      skip palette

                      move.l            #sprite,a1                storage of pre-shifted sprite

                     

                      move.l            #32-1,d0                  32 scan lines per sprite

first_sprite

                      move.l            (a0)+,(a1)+              move from original to pre-shifted

                      move.l            (a0)+,(a1)+

                      move.l            (a0)+,(a1)+

                      move.l            (a0)+,(a1)+                          32 pixels moved

                      add.l              #8,a1                                   jump over end words

                      add.l              #144,a0                               jump to next scan line

                      dbf                 d0,first_sprite

 

                      First, point to the sprite data, jump over the palette and load up the sprite storage area, which is a ds.l 3072; 16 bytes per line, plus 8 for the buffer totalling 24 bytes per scan line. The sprite is 32 lines and there should be 16 such blocks. This adds up to 24*32*16 = 12288 bytes, which is 3072 long words. In the loop, just copy data from the sprite picture to the storage area, the buffer word area is skipped since it contains nothing at this time. Now comes the challenging part, writing the generic pre-shift.

 

                      move.l            #sprite,a0           point to beginning of storage area

                      move.l            #sprite,a1           point to beginning of storage area

                      add.l              #768,a1               point to next sprite position

                     

                      move.l            #15-1,d1              15 sprite positions left

positions

                      move.l            #32-1,d2               32 scan lines per sprite

line

                      move.l            #4-1,d3                 4 bit planes

plane

                      move.w          (a0),d0                  move one word

                      roxr                #1,d0                     pre-shift

                      move.w          d0,(a1)                   put it in place                       

                      move.w          8(a0),d0                 move one word

                      roxr                #1,d0                      pre-shift

                      move.w          d0,8(a1)                 put it in place

                     

                      move.w          16(a0),d0                move one word

                roxr                #1,d0                       pre-shift

                      move.w          d0,16(a1)                put it in place

 

                      add.l              #2,a0                        next bit plane, also clears X flag

                      add.l              #2,a1                        next bit plane

 

                      dbf                 d3,plane         

 

                      add.l              #16,a1                       next scan line

                      add.l              #16,a0                       next scan line

 

                      dbf                 d2,line           

 

                      dbf                 d1,positions

 

                      First off, load up the storage area in a0 and a1, and make a1 point to the next storage area. This one is empty and should contain the sprite data shifted one bit to the right. Since we have already filled the first position in the storage area, 15 positions are left. 32 lines to each sprite and 4 bit planes to each line. Since all these are treated the same way, we only need one big loop so to speak.

                      Now comes the fun part, put the first word in d0, this word comes from the previous storage position. Rotate it, and put it in at the next storage position. Now the extended flag holds the bit that was shifted out the right, and this one needs to be shifted in on the left in the first word in the next word cluster. So, a byte offset of 8 (4 words) is added when fetching and storing the next word. The buffer must also come into play, so the last word will get a byte offset of 16. Now, we have pre-shifted three words.

By adding 2 to both a1 and a0 we will be at the next bit plane. It will also clear the extended flag, which is good because otherwise a bit from the last word might come over to the first word on the next bit plane, which is undesirable. Repeat for all four bit planes. We have now moved a line of the sprite. After the four bit planes have been rotated, a0 and a1 will point to the first word in the second 16 pixel cluster, or 8 bytes from the beginning of the data. By adding 16, we will point to the next scan line (16+8 = 24). Repeat for 15 positions. Pretty compact explanation, yes? A graphical representation follows

 

Storage area with data, beginning at $0

16                  16                  16 pixels

WWWW       WWWW       WWWW

… for 32 lines

0  2  4  6        8 10 12 14     16 18 20 22   byte offset     

 

Storage area without data, beginning at $768

16                  16                  16 pixels

0000              0000              0000              (each 0 is word size)

… for 32 lines

0  2  4  6        8 10 12 14     16 18 20 22   byte offset

 

a0 = $0

a1 = $768

 

move.w          (a0),d0

roxr                d0                    C = leftmost bit from W offset 0

move.w          d0,(a1)             put rotation in 0 at offset 0

 

move.w          8(a0),d0           as you see, first word of second cluster

roxr                d0                     bit preserved and shifted from offset 0

move.w          d0,8(a1)           put it at offset 8

 

move.w          16(a0),d0         first word last cluster

roxr                d0                     rotate, carry bit may now be set

move.w          d0,16(a1)          at offset 16

                     

add.l              #2,a0                  next bit plane, watch offset

                      add.l              #2,a1                  also clears X flag

 

                      Finally, a0 and a1 will both be at offset 8, the first word of the first bit plane, by adding 16 to this, the offset will be 24, the value for a whole line, effectively putting us at the beginning of the next line. That concludes the pre-shift of the sprite.

                      The mask data has to be pre-shifted a bit differently. Where the sprite colour is, we need the mask to be 0, and where the background is, the mask must be 1, as explained above. A look at the sprite picture will show that the sprite colour area is colour 15, all 1’s, and the background is colour 0, all 0’s. For the mask to be correctly pre-shifted, we need to invert it, making the background all 1’s and the sprite colour area all 0’s. When shifting, we must also always be shifting in 1’s, not 0’s as the case was with the sprite data.

                      The instruction not, for NOT :), will take any value and invert it, this means changing all 1’s to 0’s and all 0’s to 1’s. In order to have all bits except those concerning the sprite colour area set, we must make sure to put 1’s in the buffer area. Also, at the beginning of each plane loop, we must also make sure that the highest bit of d0 is set, so that 1’s are shifted in. Other than that, the sprite and mask pre-shift share ideas. The mask area is as big as the sprite area. Even though this isn’t necessary since all bit planes in the sprite look alike, we could have reduced the size by ¾, but for ease of understanding, this was not done.

 

                      move.l            #spr_dat,a0

                      add.l              #34+160*32,a0        skip palette and sprite

                      move.l            #mask,a1                  load up mask part

                      move.l            #32-1,d0                  32 scan lines per sprite

first_mask

                move.l            (a0)+,(a1)                 move from original to pre-shifted

                      not.l               (a1)+                         invert the mask data

                      move.l            (a0)+,(a1)

                      not.l               (a1)+                           invert the mask data

                      move.l            (a0)+,(a1)

                      not.l               (a1)+                            invert the mask data

                      move.l            (a0)+,(a1)                           

                      not.l               (a1)+                            invert the mask data

                      move.l            #$ffffffff,(a1)+             fill last two words...

                      move.l            #$ffffffff,(a1)+              ... with all 1's

                     

                      add.l              #144,a0                          jump to next scan line

                      dbf                 d0,first_mask

* the picture mask has been copied to first position in pre-shift              

 

                      move.l            #mask,a0                  point to beginning of storage area

                      move.l            #mask,a1                   point to beginning of storage area

                      add.l              #768,a1                     point to next mask position

                     

                      move.l            #15-1,d1                  15 sprite positions left

positions_mask

                      move.l            #32-1,d2                  32 scan lines per sprite

line_mask

                      move.l            #4-1,d3                     4 bit planes

plane_mask

                      move.w          (a0),d0                      move one word

                      roxr                #1,d0                         pre-shift

                      or.w               #%1000000000000000,d0 make sure most significant bit set

                      move.w          d0,(a1)                      put it in place                       

                     

                      move.w          8(a0),d0                    move one word

                      roxr                #1,d0                         pre-shift

                      move.w          d0,8(a1)                    put it in place

                     

                      move.w          16(a0),d0                  move one word

                      roxr                #1,d0                         pre-shift

                      move.w          d0,16(a1)                  put it in place

 

                      add.l              #2,a1                           next bit plane

                      add.l              #2,a0                           next plane, clears X flag (bad)

 

                      dbf                 d3,plane_mask

 

                      add.l              #16,a1                          next scan line

                      add.l              #16,a0                          next scan line

 

                      dbf                 d2,line_mask

 

                      dbf                 d1,positions_mask

 

                      Unlike the sprite pre-shift where we could set up the storage area with direct memory moves from the sprite picture to the storage area, here we move data, and then perform the not instruction to invert the data. Also, instead of just skipping the buffer area like in the sprite, here we fill it with 1’s. The bit plane loop is almost identical, with the one exception that the first shift must be guaranteed to shift in a 1, not a 0. A simple or instruction will make sure the most significant bit is set. That was that, all pre-shifting done.

                      The method which we use to get the coordinates is the exact one found in tutorial 10. So when we send in our coordinates, we will be provided with a pointer to the screen address, and the number of shifts to be done in d0. The number in d0 is an offset to for the sprite data and mask data. By putting the address to the sprite data in an address register, multiplying d0 with 768 and adding that to the address register, we will get a pointer to correctly shifted sprite data. The reason for number being 768 is that it is the size of a sprite block.

                      OK, now comes the problem of actually moving the sprite. We can put a sprite at any coordinate we want, but we can’t move it yet. A simple bounce routine here, the sprite will move with a certain X speed and a certain Y speed, and change direction when it hits “walls” (edges of the screen). What we need is a heading, and a speed. For simplicity, we express the heading as either 1 or 0 for both X and Y respectively. 1 is towards bottom right and 0 is towards upper left. X heading is either right or left, and Y heading either up or down. The X and Y speed is how many pixels to move the sprite in desired direction each VBL. So with an X heading of 1, and an X speed of 2, the sprite would move 2 pixels right each VBL.

                      What the move routine needs to do is to add X and Y coordinates in accordance with heading and speed, as well as checking for wall hits. When a wall hit occurs, the sprite must change direction. A change in direction simply means flipping between 1 or 0 in heading. This might be a good time to tell about the equ, for EQUals method. Any label can have an equ applied to it, meaning that whenever one uses the label, it is replaced by the equ. Easy huh?

 

number           equ                2                   

                      move.l            #number,d0                         same as move.l #2,d0

 

                      One can say that equ’s, are constants. It’s good practice to have as many equ’s as possible, because if you realize you have to change a constant, you only need to change it in one place instead of every place the constant appears. X speed and Y speed is a good example (unless you want variable speed), X coordinate is a terrible thing, since it needs to change all the time. Actually, I think it’s best to express the move routine in pseudo code first.

 

                      If (x_coord > 319 – 32 – x_speed + 1) Then

                                            x_heading = 0

                      If (x_coord < 0) Then

                                            x_heading = 1

                      If (y_coord > 199 – 32 – y_speed + 1) Then

                                            y_heading = 0

                      If (y_coord < 0) Then

                                            y_heading = 1

 

                      First we check to see if the heading needs change, as long as the sprite is in any way outside the screen coordinates, we need to change the heading. Since we check the heading before we move the sprite, and move the sprite before drawing it, the sprite will never be drawn off screen. The only trouble here is where all numbers come from. Think of it first without x_speed added, every VBL the sprite just moves one pixel. Then the formula is x_coord > 319 – 32. This is easy to grasp, the X coordinate must not be more than the screen can hold, which is 319, minus the width of the sprite itself of course, which is 32.

The so called “hot spot” of the sprite is the upper left corner. This is the point against which all sprite coordinates are measured. We say that the sprite is at coordinates 13,13, but this really means that the sprite hot spot is at 13,13. Exactly what pixels the sprite inhabits is unknown to us, since the sprite can have any form, but for simplicity, we think of the sprite as a square, with the coordinates in the upper left corner. Thus, when seeing if the sprite hits the right wall, we take the coordinates of the upper left corner, the hot spot, and add the width of the sprite.

The x_speed is also to be taken into account. Imagine the sprite moving with 100 pixels per VBL, then the sprite will be way outside the screen if it’s anywhere over the right half of the screen, so the sprite is only ok if it’s on the left half of the screen, obviously, the speed must be taken into account. Think of the speed as just enlargement to the sprite. The Y check works exactly the same way, but with a different max coordinate for obvious reasons. It looks a bit different in assembler though.

 

cmp               #319-32-x_speed+1,x_coord

blt                  x_right_ok                        see if x is < 319-32 for width

move.w          #0,x_heading                    if x >=319, change heading

x_right_ok

 

cmp               #0,x_coord

bgt                 x_left_ok                             see if x is > 0

move.w          #1,x_heading                       if x <=0, change heading

x_left_ok

                     

cmp               #199-32-y_speed+1,y_coord

blt                  y_low_ok                         see if y is < 199-32 for lines

move.w          #0,y_heading                   if y >=199, change heading

y_low_ok

 

cmp               #0,y_coord

bgt                 y_high_ok                            see if y is > 0

move.w          #1,y_heading                       if y <=0, change heading

y_high_ok

                                           

                      We check if the X coordinate is lesser than the number, and if it is, it’s ok and a little branch will skip the changing of the X heading. Whereas the pseudo code If statements took place if the check was true, our checks affect if the statements are false. This may look messy, but it’s really quite simple, just take a second look at it. We also need to update the coordinates, here’s some more pseudo code.

 

                      If (x_heading = 0) Then

                                            x_coordinate = x_coordinate – x_speed

                      Else

                                            x_coordinate = x_coordinate + x_speed

                      If (y_heading = 0) Then

                                            y_coordinate = y_coordinate – y_speed

                      Else

                                            y_coordinate = y_coordinate + y_speed

 

                      No problem there, just change the coordinates according to speed and heading. In assembler it becomes more troublesome though.

 

                      cmp               #0,x_heading                  check x heading

                      bne                x_move_right                  if 1, move right, otherwise left

                      sub.w             #x_speed,x_coord          move sprite left

                      bra                 x_move_done                 done moving sprite in x

x_move_right

                      add.w            #x_speed,x_coord           move sprite right

x_move_done                                                                                      

 

                      cmp               #0,y_heading                  check y heading

                      bne                y_move_down                if 1, move down, otherwise up

                      sub.w             #y_speed,y_coord         move sprite up

                      bra                 y_move_done                 done moving sprite in y

y_move_down

                      add.w            #y_speed,y_coord            move sprite down

y_move_done                                                                                                           

First, a check to see if X heading is 0, if it is, move to the left, otherwise move to the right. If we move to the left, we subtract the X coordinate by the X speed, and we must also make sure to jump past the move to the right. The Y part is exactly as the X part. Again, just look one more time at the code and if it seems confusing, write it down on paper if you must and go through the different possible branches, it’s not too complex once you structuralize it.

Hoah, this takes time to explain, hope you’re still with me ‘cus we are almost done. Now we know how to pre-shift, apply the sprite and move it. One would think that we have all we need, there is just one more thing to take into account. If we would apply the things we know and fire away, we would have a sprite that moves over the screen and leaves a trail. The damn thing will never go away, making it most ugly. Why? Because the background must be restored when the sprite has passed it. 

So, on every VBL, the background must first be restored, then it must be saved after the sprite coordinates are updated, since the save and restore routine is dependent on the sprite coordinates. Then we can paint the sprite. The save routine just copies a sprite sized block from the screen memory into a save buffer, and the restore routine copies the data from the buffer onto the screen.

What we have now is a main routine that restores background, moves the sprite (rather update the sprite coordinates), saves the background, apply the mask and lastly paint the sprite. All of this is so fast, that we don’t even have to bother with double buffering, so we pull a fast one and just skip that. Here comes the entire source code, don’t panic, most of the stuff will be familiar. 

x_speed         equ                2                    how many x coord to move each VBL

y_speed         equ                1                    how many y coord to move each VBL

 

jsr                  initialise

 

* pre-shifting sprite

move.l            #spr_dat,a0              original sprite data

add.l              #34,a0                       skip palette

move.l            #sprite,a1                 storage of pre-shifted sprite

                     

move.l            #32-1,d0                  32 scan lines per sprite

first_sprite

move.l            (a0)+,(a1)+              move from original to pre-shifted

move.l            (a0)+,(a1)+

move.l            (a0)+,(a1)+

move.l            (a0)+,(a1)+                   32 pixels moved

add.l              #8,a1                              jump over end words

add.l              #144,a0                          jump to next scan line

dbf                 d0,first_sprite

* the picture sprite has been copied to first position in pre-shift              

 

move.l            #sprite,a0                  point to beginning of storage area

move.l            #sprite,a1                  point to beginning of storage area

add.l              #768,a1                      point to next sprite position

                     

move.l            #15-1,d1                            15 sprite positions left

positions

move.l            #32-1,d2                             32 scan lines per sprite

line

move.l            #4-1,d3                               4 bit planes

plane

move.w          (a0),d0                             move one word

roxr                #1,d0                                pre-shift

move.w          d0,(a1)                              put it in place                       

                     

move.w          8(a0),d0                              move one word

roxr                #1,d0                                   pre-shift

move.w          d0,8(a1)                              put it in place

                     

move.w          16(a0),d0                            move one word

roxr                #1,d0                                   pre-shift

move.w          d0,16(a1)                            put it in place

 

add.l              #2,a0                           next bit plane, also clears X flag

add.l              #2,a1                           next bit plane

 

dbf                 d3,plane        

 

add.l              #16,a0                                 next scan line

add.l              #16,a1                                 next scan line

 

dbf                 d2,line           

 

dbf                 d1,positions

* pre-shift of sprite done, all 16 sprite possitions saved in sprite

 

                     

* pre-shifting mask

move.l            #spr_dat,a0

add.l              #34+160*32,a0                   skip palette and sprite

move.l            #mask,a1                             load up mask part

                     

move.l            #32-1,d0                             32 scan lines per sprite

first_mask

move.l            (a0)+,(a1)                move from original to pre-shifted

not.l               (a1)+                                   invert the mask data

move.l            (a0)+,(a1)

not.l               (a1)+                                   invert the mask data

move.l            (a0)+,(a1)

not.l               (a1)+                                   invert the mask data

move.l            (a0)+,(a1)                           

not.l               (a1)+                                   invert the mask data

move.l            #$ffffffff,(a1)+                      fill last two words...

move.l            #$ffffffff,(a1)+                      ... with all 1's

                     

add.l              #144,a0                               jump to next scan line

dbf                 d0,first_mask

* the picture mask has been copied to first position in pre-shift              

 

move.l            #mask,a0                   point to beginning of storage area

move.l            #mask,a1                   point to beginning of storage area

add.l              #768,a1                      point to next mask position

                     

move.l            #15-1,d1                             15 sprite positions left

positions_mask

move.l            #32-1,d2                             32 scan lines per sprite

line_mask

move.l            #4-1,d3                               4 bit planes

plane_mask

move.w          (a0),d0                                move one word

roxr                #1,d0                                   pre-shift

or.w               #%1000000000000000,d0 make sure most significant bit set

move.w          d0,(a1)                             put it in place                       

move.w          8(a0),d0                           move one word

roxr                #1,d0                                pre-shift

move.w          d0,8(a1)                           put it in place

                     

move.w          16(a0),d0                            move one word

roxr                #1,d0                                   pre-shift

move.w          d0,16(a1)                            put it in place

 

add.l              #2,a0                         next bit plane, clears X flag (bad)

add.l              #2,a1                         next bit plane

dbf                 d3,plane_mask

 

add.l              #16,a0                                 next scan line

add.l              #16,a1                                 next scan line

 

dbf                 d2,line_mask

 

dbf                 d1,positions_mask

* pre-shift of mask done, all 16 sprite possitions saved in mask

 

                     

movem.l         bg+2,d0-d7

movem.l         d0-d7,$ff8240

                     

move.l            #bg+34,a0                     pixel part of background

move.l            $44e,a1                         put screen memory in a1

move.l            #7999,d0                      8000 longwords to a screen

pic_loop

move.l            (a0)+,(a1)+                    move one longword to screen

dbf                 d0,pic_loop                    background painted

 

jsr                  save_background            something in restore buffer

                     

move.l            $70,old_70                          backup $70

move.l            #main,$70                            put in main routine

                     

move.w          #7,-(a7)

trap                #1

addq.l            #2,a7                                   wait keypress

 

move.l            old_70,$70                          restore old $70

                                           

jsr                  restore

                                           

clr.l                -(a7)

trap                #1                                        exit

 

main

movem.l         d0-d7/a0-a6,-(a7)               backup registers

                     

jsr                  restore_background

jsr                  move_sprite

jsr                  save_background

jsr                  apply_mask

jsr                  put_sprite

 

movem.l         (a7)+,d0-d7/a0-a6               restore registers

 

rte

 

 

move_sprite

* moves the sprite one pixel in x and y

* see if any headings need to be changed

cmp               #319-32-x_speed+1,x_coord

blt                  x_right_ok                       see if x is < 319-32 for width

move.w          #0,x_heading                  f x >=319, change heading

x_right_ok

 

cmp               #0,x_coord

bgt                 x_left_ok                           see if x is > 0

move.w          #1,x_heading                   if x <=0, change heading

x_left_ok

                     

cmp               #199-32-y_speed+1,y_coord

blt                  y_low_ok                         see if y is < 199-32 for lines

move.w          #0,y_heading                   if y >=199, change heading

y_low_ok

 

cmp               #0,y_coord

bgt                 y_high_ok                            see if y is > 0

move.w          #1,y_heading                       if y <=0, change heading

y_high_ok

* all eventual heading changes now made

                     

* move sprite coordinates (change coordinates)

                      cmp               #0,x_heading                  check x heading

                      bne                x_move_right                  if 1, move right, otherwise left

                      sub.w             #x_speed,x_coord          move sprite left

                      bra                 x_move_done                 done moving sprite in x

x_move_right

                      add.w            #x_speed,x_coord            move sprite right

x_move_done                                                                                      

 

                cmp               #0,y_heading                  check y heading

                      beq                y_move_up                     if 0, move up 

                      bne                y_move_down                if 1, move down, otherwise up

                      sub.w             #y_speed,y_coord          move sprite up

                      bra                 y_move_done                  done moving sprite in y

y_move_down

                      add.w            #y_speed,y_coord               move sprite down

y_move_done                                                                                      

* finnished moving sprite      

 

                      rts

                     

 

apply_mask

* applies the mask to the background

                      jsr                  get_coordinates

                      move.l            #mask,a0

                      mulu               #768,d0                               multiply position with size

                      add.l              d0,a0                                   add value to mask pointer

                     

                      move.l            #32-1,d7                             mask is 32 scan lines

maskloop

                      rept                6                                mask is 6*4 bytes width

                      move.l            (a0)+,d0                    mask data in d0

                      move.l            (a1),d1                      background data in d1

                      and.l               d0,d1                         and mask and picture data

                      move.l            d1,(a1)+                    move masked data to background

                      endr

                      add.l              #136,a1                               next scan line

                      dbf                 d7,maskloop

                     

                      rts

 

                     

put_sprite

* paints the sprite to the screen

                      jsr                  get_coordinates

                      move.l            #sprite,a0

                      mulu               #768,d0                               multiply position with size

                      add.l              d0,a0                                   add value to sprite pointer

                                           

                      move.l            #32-1,d7                             sprite is 32 scan lines

bgloop          

                      rept                6                          sprite is 6*4 bytes width

                      move.l            (a0)+,d0              sprite data in d0

                      move.l            (a1),d1                 background data in d1

                or.l                 d0,d1                    or sprite and background data

                      move.l            d1,(a1)+               move ored sprite data to background

                      endr

                      add.l              #136,a1

                      dbf                 d7,bgloop     

                     

                      rts

                     

 

save_background

* saves the background into bgsave

                      jsr                  get_coordinates

                      move.l            #bgsave,a0

 

                      move.l            #32-1,d7                     sprite is 32 scan lines

bgsaveloop

                      rept                6                                  sprite is 6*4 bytes width

                      move.l            (a1)+,(a0)+                 copy background to save buffer

                      endr

                      add.l              #136,a1                        next scan line

                      dbf                 d7,bgsaveloop

                     

                      rts

                     

 

restore_background

* restores the background using data from bgsave

                      jsr                  get_coordinates

                      move.l            #bgsave,a0

 

                      move.l            #32-1,d7                     sprite is 32 scan lines

bgrestoreloop

                      rept                6                                  sprite is 6*4 bytes width

                      move.l            (a0)+,(a1)+                 copy save buffer to background

                      endr

                      add.l              #136,a1                        next scan line

                      dbf                 d7,bgrestoreloop

                     

                      rts

 

                     

get_coordinates

* makes a1 point to correct place on screen

* sprite position in d0.b

                      move.l            $44e,a1                  screen memory in a1

                      move.w          y_coord,d0             put y coordinate in d0

                      mulu               #160,d0                  160 bytes to a scan line

                      add.l              d0,a1                       add to screen pointer

                      move.w          x_coord,d0              put x coordinate in d0

                divu.w            #16,d0                     number of clusters in low, bit in high

                      clr.l                d1                         clear d1

                      move.w          d0,d1                    move cluster part to d1

                      mulu.w           #8,d1                    8 bytes to a cluster

                      add.l              d1,a1                     add cluster part to screen memory

                      clr.w              d0                          clear out the cluster value

                      swap              d0                           bit to alter in low part of d0

 

                      rts

 

 

                      include           initlib.s

 

                      section data

x_coord         dc.w              0

y_coord         dc.w              0

x_heading       dc.w              1

y_heading       dc.w              1

 

spr_dat          incbin             SPRITE.PI1

bg                  incbin             AUTUMN.PI1

old_70           dc.l                0

 

 

                      section bss

sprite              ds.l                 3072              32/2+8*32 bytes * 16 positions / 4 for long

mask              ds.l                 3072              same as above

bgsave           ds.l                 192                32/2+8*32 bytes / 4 for long

 

            The longest source to date, I’m truly starting to doubt the wisdom of putting the source code here as well as in a separate file. Anyways, starting from beginning going down, this is what it’s all about. The first two lines are the X and Y speed, you may play around with these values to your hearts content, of course, setting them both to the same value will make the sprite move in 45 degrees, while any other values will make the sprite move differently. Then, the pre-shifting of the sprite and the mask, this has been dealt with extensively and there is nothing more to add. After this, the background is also prepared, it’s just another put-degas-file-in-screen-memory. Note here, that there is a background save, this is to make sure something is in the save buffer before starting the main routine, otherwise the main routine would start off by “restoring” a blank area, effectively deleting a sprite sized block of the screen. All preparations are done, just install the main routine, as described in tutorial 9. Put our main routine in the $70 vector, to have it executed every VBL. Wait for a key press, during which the main routine will execute continuously, and make a clean exit. Now, take note of how nice and tidy the main routine is, it just consists of subroutine calls, making the structure of the program very easy to read, and isolating each major part of the program for ease of reading.

            Each subroutine in turn relies upon the get_coordinates routine, which translate the x_coord and y_coord into data intelligible to the program. As you can see in the comments at the start of the get_coordinates subroutine, what the routine does is to put a pointer to the screen memory in a0 and sprite position (offset) in d0.b (meaning the least significant 8 bits of the d0 register). Since each subroutine relies upon the get_coordinates routine, if a bug is detected in the coordinate routine, it will only have to be dealt with in one place.

           The save/restore background routines are short and simple and do little. They begin by calling the get_coordinates routine in order to get a screen pointer, the sprite position is uninteresting since they both deal with the entire sprite block.

           The sprite and mask routines are very similar. Both begin by calling the get_coordinates routine, in order to get a screen pointer and a sprite position. Then either the sprite or mask area is loaded as appropriate, and the sprite position applied as an offset. Then comes a loop of moving data from the background and sprite or mask. Then this data is either and:ed or or:ed as appropriate. The result is put back in the screen memory.

            Well, that is that. I haven’t gone into every-thing in minute detail, but by now you shouldn’t have to be baby nursed through every operation. The source code has many fun things you can do with it yourself, so test around some in the critical areas. The obvious change is the speed change, then, you can try commenting out some things in the main routine, and change an or to a move in the sprite routine for example. I think it’s a good idea to play around some with the source code, and try to predict the changes, in this way, you’ll really understand the underlying mechanics.The next tutorial will probably be a very small one, I’m even considering of calling it tutorial 11 part B, and might cover the well known “infinite trail” of sprites, since it’s ridiculously easy. Somewhere soon I suppose I’ll do one on joystick and perhaps also mouse operation. I won’t promise anything though. Big thanks again go out to Bruno Padinha, for providing valuable feedback and hitting me on the head. Damn, now I have to think of a good quote as well, this part is the hardest :)

            
Warrior Munk of poSTmortem, 2002-07-06

“Is it possible that we two, you and I, have grown so old and inflexible that we have outlived our usefulness? Would that constitute … a joke?”

- Star Trek VI, the Undiscovered Country



Go back to Perihelion tutorial 10
Proceed to Perihelion tutorial 12