Check sprite collisions and MSB range

In this section you are going to learn how to write your own kickstart system call, break down machine language games code, write your own sprite to background collisions, AND/EOR and bits are used, check for MSB of sprite at X position, create sprite boundary movement, and some tips with raster routine effects.

S

iggy returned to the project again. The development tool will be CBM Prg Studio. He wanted to start off talking about how the kickstart code works that loads in the program code into the VICE C64 emulator. It regenerates a SYS call.

CBM Prg Studio Generate SYS() CallThis code is used to generate a SYS call automatically to the program on every start up of the VICE C64 emulator.  To set this up, click on the Tools menu and select Generate SYS() Call. CBM Prg Studio will open a window that says Start Address. In the ‘Enter Start Address (hex) field, type in the starting address where you want to generate the call from. By default it will be set to $0801. The options below for Decorated and Plain won’t be used in this project. After you click the OK button, it will create the bytes you see below.

  • ; Sys Call to start of the program – 10 SYS (2064)
  • *=$0801
  • BYTE $0E, $08, $0A, $00, $9e, $20, $28, $32, $30, $36, $34, $29, $00, $00, $00

Understanding the Generate SYS() Call

If you look at the first type bytes $0E and $08, they are a word. together.  First you have $0E which is the ‘low byte’ and then $08 is the ‘low byte’.  Each of these two bytes together makes up an address pointer to the next line in Basic.  Looking at the next byte for $0A if you convert it to decimal it is a 10.  You can also hover over the $0A to get the pop up showing the hexadecimal conversion to decimal. It represents line 10 for the SYS Call. Keep going, this will make sense in a moment.

The $00 represents the space in the line. The value $9E is the token value for the SYS command. The next series of bytes that show $20, $28, $32, $30, $36, $34, $29 are Pet Ascii values. If you convert the hex values to decimal you will see this.

  • 32, 40, 50, 48, 54, 52, 41

Now let’s translate the bytes into the Commodore 64’s Pet Ascii values. If you are new to using programming the Commodore 64, just know that each of the characters you type on the keyboard are listed in a chart and assigned to numbers (as seen above). If you use the lookup chart you will see each digit converted to the Pet Ascii value. I have done the conversion so you can see the magic below.

  • (32) – space character
  • (40) – (
  • (50) – 2
  • (48) – 0
  • (54) – 6
  • (52) – 4
  • (41) – )

Now if you line this up you will see (2064). Taking into consideration of the earlier bytes we now get the result of:

  • SYS (2064) 

Finishing out the SYS Generate bytes, the next byte is sequence is $00. This represents the end of the Basic line. Then there are two padded zeros. They are represented as a word (read as one entity).  In all simplicity it generates the code above (as seen by the CBM Prg Studio compiler or Basic compiler … if you are using that environment). Keep in mind that the 10 is needed to create line 10 for the Basic code. If you have ever loaded in a game file using LOAD “*”, 8 you will likely see similar code. Then all you have to do is type the RUN command to execute the game.

  • $0801 10 SYS (2064)

Spelunky game code Disassembly

Spelunky CodeIt is also important to mention that games contain the kickstart code within, which is why again you will see the SYS code when you execute the LOAD “*”, 8 command when running a software title. Siggy started dissembling the game Spelunky to learn from it. Earlier we agreed to create a Spelunky clone for the project. He used a tool called Regenerator so he could create his own embedded comments to understand the code better. I have attached some code samples here in a screenshot for observation.  To create a comment, just right click on a line and select the option for Insert/Edit Side Comment. There are more things you can do like changing lines to reflect .TEXT, .BYTE, etc, but we won’t go into detail with that here.

Spelunker Character Set Screens

C64 Spelunker Title ScreenSiggy discovered as he starting looking at the Spelunker code that there are three screens running. He was able to arrive at this by taking a VICE snapshot and loading the code into Infiltrator.  This could be identified by utilizing the Charsets and Screen dialogue box. He found out that the game is using Bank 3 and the screen memory at $cc00. He then showed where the title screen existed at using the tool. It uses a character set for the title screen. It also appears to be utilizing another screen found at $f800.

Spelunker Game GraphicsNext he loaded up Infiltrator’s Sprite Viewer to show that the game’s sprites were loaded in at $e000. Then he obtained a running VICE Snapshot and opened it in Infiltrator. Returning back to the Charsets and Screens viewer, he showed where the in-game graphics resided at memory location $f800. I have provided another screenshot so you can get a glimpse of what it looks like.  He found it interesting the the elevator that is used to navigation up and down through the screens was not actually a sprite, but instead made up of characters.

Clicking in the memory Screen area in Infiltrator, Siggy verified that memory locations $fc00 – $ffe7 was another screen for the game.  He says this is using something known as “double buffering”.  Further evaluation showed another part of the title screen contained within registers $cc00 – $cfe7. “This is the third screen”, he stated. It uses a raster interrupt for the character set graphics. There is a multi-color bottom control panel.

Spelunker Entering the ElevatorThen he clicked in the Charset area within the Charsets and Screen tool and showed where another part of the starting game screen began at memory location $c800. This is the part where is shows the message ‘Now entering the Elevator’ before the game begins. He has the character set ordered in such a way that when he has a character set displaying for the bottom panel, the characters for the top one is what reflects at the top (that shows the mini animation). Yet when the ‘entering the Elevator’ screen is shown, the characters around it are darkened out.

Spelunker SpritesHe went through the Spelunker character sets, but had difficulty ripping them. He showed however, that he had captured the character set and demonstrated this with the Character Editor in CBM Prg Studio. He stated that the programmers did not use the whole character set. He also demonstrated where the game used another character set to make up the title screen. Not bad for a game written in 1984.

After this, he loaded up the Sprite Editor to show the sprites taken from the snapshots. There are 64 sprites in the game. He credited that the animation was alright, but the sprites were very small compared to other games for the time. Siggy thinks this may have better detection for background collisions. The collision code for our remake of Spelunker is centered around Endurian’s code samples. So we can improve this by making the sprites much larger to add appeal to the revision.

Spelunker Clone collision analysis

Spelunker Clone CollisionsFor our game, Siggy said he extrapolated further with his own trim algorithmn and still kept some of Endurian’s collision detection intact. It expands it out depending on which way the game character is walking. He ‘trimmed’ it so it would be pixel perfect on the sides of the sprites. He chimmed in though that he hasn’t implemented this for Y variation since there are some ‘tricky parts’ to the logic. He loaded up the framework created to show this on exhibit. You can see this verified when the sprite bumps up against a center part of a character occupying that area.  He mentioned that if you ‘really fooled around the sprite could sink into the character in the center’, but there is no point in which it would be critical. A gamer would really have to work at it and that’s why he plans to implement a check to fix the trip in the top and bottom parts of any type of dilapidation.

Examining the Sprite Code

Siggy’s wanted to provide an explanation of how he implemented the sprite collision for our trial game. He started at the label called MoveSpriteLeft.  This deals with the X flag. It checks if the sprite has reached the 0 mark at 255 at close to the middle of the screen and then it sets the X flag. He says “if we move we increment a delta value”.  So every pixel is move the delta value is increased. When the delta reaches 7, it is reset back to zero. This checks for the pixels in character from 0 to 7. It tells that the space within the character is roughly around the center of the sprite. He claims that calculating this every frame could be done, but it seems like a lot of overhead. So instead he set the sprite in the label SpriteToCharPos. The next paragraph will explain that further.

In the label SpriteToCharPos here he sets the sprite to a character position. This is identified as PARAM 1 being equal to the X position and PARAM 2 being  equal to the Y position. It puts the sprite at a delta of 0, 0 to that character. This is to ensure that the delta value is always correct without any reason to calculate them. Every time the character moves, it is updated. If it moves right, it will be incremented and left decrements the value.  Up and down will increment and decrement as well.

  • SpriteToCharPos
  • lda BIT_TABLE, x ; Lookup the bit for this sprite number (0-7)
  • eor #$ff ; flip all bits (invert the byte %0001 would become %1110)
  • and SPRITE_POS_X_EXTEND ; mask out the X extend bit for this sprite
  • sta SPRITE_POS_X_EXTEND ; store the result back – we’ve erased just this sprites bit
  • lda PARAM1 ; load the X pos in character coords (the column)
  • sta SPRITE_CHAR_POS_X, x ; store it in the character X position variable
  • cmp #30 ; if X is less than 30, no need set the extended bit
  • bcc @noExtendedX
  • lda BIT_TABLE, x ; look up the bit for this sprite number
  • ora SPRITE_POS_X_EXTEND ; OR in the X extend values – we have set the correct bit
  • sta SPRITE_POS_X_EXTEND ; Store the results back in the X extend variable
  • sta VIC_SPRITE_X_EXTEND ; and the VIC x extend register

Siggy added tracking variables in the code that shows the x position (XPOS), y position (YPOS), Character x position (CHRX), character y position (CHRY), Delta x (DLTX), and Delta y (DLTY). CHRX and CHRY are relative of the sprite’s initial position to the background position characters.

Now in clarification of how the delta values operate, he moved the sprite up a little and the delta values in decreased. If the sprite is moved down, the delta values increase. He states the top and bottom of the sprite is flush with the coordinates (CHRX, CHRY) when the game is loaded.

Darren then asked “Where did you figure out the sprite x,y to character x,y?” Siggy responded that “it is done when we position the sprite so we only have to do it once.”  In the label SpriteToCharPos the bit table is loaded when the game is loaded (in BIT_TABLE). I was curious to see the code, so Siggy scrolled to that part of the code. It is seen here for example.

  • BIT_TABLE byte 1, 2, 4, 8, 16, 32, 64, 128

He explained it in concept for us, stating “If the sprite number is 0 (code – lda BIT_TABLE, x), its bit is ‘1’. IF its sprite ‘2’, its bit is ‘2’. This continues in sequence evaluating the code against the table. He inserted, “Basically if I give it a sprite number, it gives him back the bit he has to change for a couple of registers.” It provides the correct bit that saves calculating it.

How the BIT_TABLE works

He then reviewed the next section of code to provide emphasis on the extended X flag. The extended X flag is stored in one byte, one bit for every single sprite, and its important to get that right. He stated “For this one sprite I’ve got to change 1 bit”. So in lda BIT_TABLE, x the bit is looked up that has to change. He mentioned that in the BIT_TABLE when he gives it the sprite number he then knows the bit he has to change. This is actually Boolean math. Parts of the code is listed here again for clarity

  • SpriteToCharPos
  • lda BIT_TABLE, x ; Lookup the bit for this sprite number (0-7)
  • eor #$ff ; flip all bits (invert the byte %0001 would become %1110)
  • and SPRITE_POS_X_EXTEND ; mask out the X extend bit for this sprite
  • sta SPRITE_POS_X_EXTEND ; store the result back – we’ve erased just this sprites bit

AND and EOR Explained

In the basic boolean operations are AND or EOR (exclusive or). The AND is used to make a mask to weed bits out. So if the bit found in eor #$ff is a one (00000001). Then the code will spit out a 1 if its an AND. With the EOR if either one is a 1, Siggy stated it will ‘spit out a 1’. The AND command can be used to replace bits (masking values out). He provided an example “I want to replace bit 4” then I ‘AND’ everything turned on except bit 4. Everything else will retain its value and bit ‘4’ will then be ‘0’. Then ‘EOR’ can be used to ‘or’ that bit in there. Then it must be saved. Exclusive (EOR) or flips every bit that its set for.

To go into an example, he stated, with the eor #$ff (binary – 11111111), he is taking the value of the bit table and flipping it. For instance if he is using sprite 1 it will return the value of ‘1’. After this every bit is turned on, except 1. Then he uses (and SPRITE_POS_X_EXTEND) as a ‘mask’ on the sprite x position. He is using exclusive EOR to create the mask. The AND is used to get the data out that has to remain intact and then it is stored (sta SPRITE_POS_X_EXTEND). All this does is  store the result back and just erased that sprite’s bit, so it’s zero. It’s about quickly erasing that bit.

Processor Status RegisterIn the next snippet of code we  load the parameter of 1 (PARAM1). This represents the sprite’s x position. Then it is stored in a variable (SPRITE_CHAR_POS_X). The next sequence will compare it to 30. The value of 30 is the point when 255 clocks over.  The next part is branching to noExtendedX if the value is less then 30. BCC is just another way of using the less than operator.  There is a great chart that Siggy pulled up for further illustration. I have attached the screenshot here.  This is an overview of how the computer manages variables flags in the system. He told us how BCC works. He stated, “Zero if it is carried (an addition goes to the carry) and true if it is not.” This sets a flag the carry is true (cleared), otherwise it is set. So it BCC is less than 30 then the flag is set and the operation is carried out.  So bcc @noExtendedX is executed then we don’t need to set the extended bit.  The code is listed here for the comparison again as a refresher.

  • lda PARAM1 ; load the x pos in character coords (the column)
  • sta SPRITE_CHAR_POS_X, x ; store it in the character x position variable
  • cmp #30 ; if X is less than 30, no need to set the extended bit
  • bcc @noExtendedX 

Check MSB of Sprite X

Moving down to the next section of code we are loading our bit from the BIT_TABLE. Then we ‘or’  the sprite extended to it. Finally it is stored in a variable and in the VIC X extend register ($d010 – 53264).  So once again that bit is (ora) or’ed in. We are ora’ing in the bit we masked out and storing it. This code determines if the sprite has crossed the boundary or not.

  • lda BIT_TABLE, x ; look up the bit for this sprite number
  • ora SPRITE_POS_X_EXTEND ; Or in the X extended values – we have set the correct bit
  • sta SPRITE_POS_X_EXTEND ; Store the results back in the X extend variable
  • sta VIC_SPRITE_X-EXTEND ; and the VIC X extend register
  • @noExtendedX
  •  ; Setup our Y register so we transfer X/Y values to the 
  • ; correct VIC register for this sprite
  • txa ; first transfer the sprite number to A
  • asl ; multiply it by 2 (shift left)
  • tay ; then store it in Y
  • lda PARAM1 ; load in the X Char position
  • asl ; 3 x shift = multiplication by 8
  • asl
  • asl
  • asl
  • clc
  • adc #24 – SPRITE_DELTA_OFFSET_X ; add the edge of screen (24) minus the delta offset
  • ; to the rough center 8 pixels (1 char) of the sprite
  • sta SPRITE_POS_X, x ; save in the correct sprite x variable
  • sta VIC_SPRITE_X_POS, y ;save in the correct VIC sprite pos register 

The noExtendedX is explained next. First the sprite value is transferred to the accumulator (txa) and then it is multiplied by two (asl), which shifts it left. Then it is stored in y (tay). Siggy stated that ‘Y now holds the offset to the correct sprite register.  Next the character is loaded in the X position (lda PARAM1) and then it’s multiplied by eight (asl, asl, asl). This gets the X position in ‘pixels’.  Then the carry flag is cleared (clc). It’s almost a good practice to use clc when doing any kind of math.

Darren then interjected saying “I was having a hard time figuring out why my animation was skipping frames. It came down to him not clearing the carry flag. Siggy agreed wholeheartedly.

Calculate center of sprite X

Sprite calculated to borderThe next piece of code shows adc #24 – SPRITE_DELTA_OFFSET, X. The adc stands for ‘add with carry’ and is simply the processor doing basic addition. So here we are taking the value of 24 and subtracting it from the variable SPRITE_DELTA_OFFSET_X. This value gets you to the center of the sprite.  Looking at the website dustlayer.com, Siggy confirmed that it is the difference between sprite 0 which is off screen and the sprite that is visible on the screen. Darren realized that is where the sprite can disappear behind the screen border.  This equates to counting the pixels between the border edge and onto the screen where the sprite can be seen. Siggy believed that if you had the sprite at position 0, then that is the exact width of the border to the screen edge. It makes sense because a sprite is 24 pixels across at the X location.

Finally we are loading the accumulator with the immediate value of 0 (lda #0).  This clears out the variable for the x and y delta values (sta SPRITE_POS_X_DELTA, x) and sets the sprite position to 0 (sta SPRITE_POS_Y_DELTA, x). This is because we are flush with the screen coordinates.

  • lda #0 ;
  • sta SPRITE_POS_X_DELTA, x ; set both x and y delta values to 0 – we are aligned
  • sta SPRITE_POS_Y_DELTA, x ; on a character border (for the purposes of collision)
  • rts

Check if sprite can move left

The next thing involved is trimming the sprite.  Evaluating the code for CanMoveLeft we start with lda SPRITE_CHAR_POS_X to do a border test. If we can’t move left then we go to trimLeft (bne @trimLeft). This is because the character X is at 0 and the delta is also at zero. Siggy stated this is because we are ‘flush up against character X 0.  We are trying to move left so it decrements. This is because we are against the edge of the screen and therefore we will be stopped.

  • CanMoveLeft
  • ; border test
  • lda SPRITE_CHAR_POS_X ; if Char X is 0
  • bne @trimLeft
  • lda SPRITE_POS_X_DELTA, x ; and delta is 0
  • bne @trimLeft
  • lda #1 ; return blocked
  • rts 

Trimming the Sprite’s Delta

The code below this is called trimLeft. The first part that shows lda SPRITE_POS_X_DELTA, x contains a delta value to tell the program if we are flush. This is the part he spoke about involving ‘trimming the edges around the sprite’.  The sprite can be wider then then we need to trim that delta. The next part shows adc SPRITE_DELTA_TRIM_X, x , which is used to add the delta trim for the X. Next the code sample masks for bits 0,1 and 2 using and #%111. The 111 in binary is equal to 7 (00000111). Every bit within 0-7 will be masked back. If it goes to bit 8 in binary, then bit 3 is set (=1) – (00001000). This is indicated by the 1 in the binary example seen here. If it goes to 8 and it is and ‘ed out, a zero is returned. If it goes to 9 and it executes and #%111 then it gives back a 1. It keeps the value between 0-7 and doesn’t allow for overflow.

Siggy exemplified further with this example. He stated, “Let’s suppose I got a value of 8” (binary = 1000).  What AND does is switches the bits on if a one is found in the comparison. However, if they are different then it returns a ‘0’ instead. Also keep in mind that if there are two 1’s, then the result will also return a 1. The result again (and #%111) adds the trim and adjusts the number between 0-7.

  • @trimLeft
  • lda SPRITE_POS_X_DELTA, x ; fetch the X delta for this sprite
  • adc SPRITE_DELTA_TRIM_X, x ; add delta trim X
  • and #%111 ; Mask the result for 0-7

The next part shows beq @checkLeft. This is where they are becoming flush. If the delta evaluated in adc SPRITE_DELTA_TRIM_X, x is zero, then a branch is triggered for beq @checkLeft. If the value is anything except 0, a collision check is not done. This is because we are moving in the character. We are not at the character’s edge, and therefore don’t need to see what the character is yet.

  • beq @checkLeft

However, if we are there and we are flush and the delta is 0 (along the X),  then we are flush up against the next character. Now we will need to do some checks.

Checking the Sprite’s Y Delta

Siggy mentioned that the first check we have to do is to check the Y delta. This is because if the Y delta is not zero, then we are not flush on the Y axis. That will affect the amount of characters we need to check on the left hand size. Yet if we are flush at x, y then we need to check the characters where the sprite’s foot is on a cross, one to the left of that, and we need to check one character above that position to cover the middle of our sprite. If we are not flush with delta, we are traveling up between characters upwards.  So we need to evaluate a check on 3 characters to make sure the entire sprite is covered.

Checking the Sprite’s Left Movement

Siggy says “the first thing we want to check is the sprite’s delta Y position”. If we are not on zero, we are not flush. This means that the sprite is spanning 3 characters. So we have to check to the left for 3 different characters. That’s what the delta is all about initially.

Examining the first line here which shows lda SPRITE_POS_Y_DELTA, x he confirmed that if the Y Delta is 0, we only need to check 2 characters. This is because we are flush with the Y axis with the character map. Yet if we are not flush, we have to check for 3. The line beq @checkLeft2 states if we are equal then jump to the label checkLeft2. If we have to check for 3 every time using calculations then cycles are wasted. Apart from the sprite direction they are moving in it is very identical to the Move Right routine.

  • @checkLeft
  • lda SPRITE POS_Y_DELTA, x ; if the Y Delta is 0, we only need to check 2 characters
  • beq @checkLeft2 ; on the direct left of the sprite base because we are
  • ; on the Y axis with the character map
  • ; Here we aren’t flush – so we have to check 3 characters
  • ldy SPRITE_CHAR_POS_Y, x ; fetch sprites Y character position (in screen memory)
  • lda SCREEN_LINE_OFFSET_TABLE_LO,y ; store the address in ZEROPAGE_POINTER_1
  • sta ZEROPAGE_POINTER_1 + 1
  • lda SPRITE_CHAR_POS_X, x ; fetch sprites x position (in screen memory)
  • clc
  • adc #39 ; add 39 (go down one row and one place to
  • tay ; store it in the Y register
  • lda (ZEROPAGE_POINTER_1), y ;fetch the character from screen mem
  • jsr TestBlocking ; test to see if it blocks
  • bne @blockedLeft ; returned 1 – so blocked

The line that shows ldy SPRITE_CHAR_POS_Y, x is loading the sprite character’s Y position. The x here represents the sprite number. So it looks up SPRITE_CHAR_POS_Y plus the ‘x’. We use that to then look up the screen line offset table (lda SCREEN_LINE_OFFSET_TABLE_LO, y). Literally you give it the line you are on and it goes to the exact character we need in screen memory.

The screen memory is looked up in the table called SCREEN_LINE_OFFSET_TABLE_LO and SCREEN_LINE_OFFSET_TABLE_HI. The accumulator is then loaded with lda SCREEN_LINE_OFFSET_TABLE_LO, y the screen line offset and ‘y’ is holding the line. So Siggy utilized an example, “If the screen character position is 12, we want the 12th line down.” The value is inserted into y (y=12). Then the SCREEN_LINE_OFFSET_TABLE is looked up. It then stores the low and high bytes using the ZERO_PAGE_POINTER variable. The ZEROPAGE_POINTER is considered a word. After this the ZERO_PAGE_POINTER now holds the address to the line we are on.

Then we see the line for SPRITE_CHAR_POS_X, x. The carry flag is cleared and then we add 39 to it (adc #39).  It goes across one and then down one. The ’39’ is one character to the left of us and one character down.

If the results came up as ’40’ then it would be directly beneath us. This is because 40 is the length of a line in characters.  It is one character down and one character to the left. Siggy stated “this is in straight character blocks”. The ’39’ represents one block down and one to the left. Then the value is stored in Y using tay. Next the zero page pointer is used to obtain the value at the address. It goes to that actual address offset by y in lda (ZEROPAGE_POINTER_1), y.

The next routine executed is jsr TestBlocking. It compares to see if it is 128. If it is greater then we are blocking. Likewise if it is less than 128, we are not blocking.  The values between 128 – 255 identify it is blocking. If its not, the code lda #0 returns a zero and exits the subroutine using rts. If it is not blocked left then it returned a 1 it branches to the line @blockedLeft showing bne @blockedLeft.

  • TestBlocking
  • cmp #128 ; is the character > 128?
  • bpl @blocking
  • lda #0
  • rts
  • @blocking
  • lda #1
  • rts

After this the subroutine called checkLeft2 replicates the same process. The only difference here is it decrements the y. Therefore it goes one character back from us.

Now it continues onto the 3rd check since it is not blocked. It gets that address plus 40, going down one character. Nothing complex is needed here. It just goes through 40 and does the test.

  • @checkLeft2
  • ldy SPRITE_CHAR_POS_Y, x ; fetch the sprites y position and store it
  • dey ; decrement by 1 (so 1 character UP)
  • lda SCREEN_LINE_OFFSET_TABLE_LO, y ; store that memory location in ZEROPAGE_POINTER
  • sta ZEROPAGE_POINTER_1
  • lda SCREEN_LINE_OFFSET_TABLE_HI, y
  • sta ZEROPAGE_POINTER_1 + 1
  • ldy SPRITE_CHAR_POS_X, x ; get the sprites X position and store in Y
  • dey ; decrement by 1 (one character left)
  • lda (ZEROPAGE_POINTER_1), y ; fetch the contents of screen mem 1 left and
  • jsr TestBlocking ; test for blocking
  • tya ; transfer screen x pos to the accumulator
  • clc
  • adc #40 ; add 40 – bringing it one row down from the
  • tay ; check made, then transfer it back to the Y
  • lda (ZEROPAGE_POINTER_1), y ; fetch the character from that screen location
  • bne @blockedLeft ; and test it for blocking

In the subroutine called CanMoveRight, it takes a slightly different approach to using trim. It checks it on the other side. It is adding instead of subtracting.

  • CanMoveRight
  • clc ; a simple right border check
  • lda SPRITE_CHAR_POS_X, x ; sprite is < $26
  • cmp #$26
  • bne @trimRight
  • clc
  • lda SPRITE_POS_X_DELTA, x ; and delta < $04
  • cmp #4
  • bcc @trimRight
  • lda #1 ; returns blocked
  • rts
  • @trimRight
  • lda SPRITE_POS_X_DELTA, x ; if Delta = 0, performs checks
  • adc SPRITE_DELTA_TRIM_X, x ;and delta trim
  • and #%111
  • beq @checkRight
  • lda #0 ; we can move, return 0
  • rts
  • @checkRight
  • lda SPRITE_POS_Y_DELTA, x ;flush check on Y – if so only check 2 chars
  • beq @rightCheck 2
  • ldy SPRITE_CHAR_POS_Y,x ; Check third character position – we are not flushed
  •  iny ; the screen character coords
  • lda SCREEN_LINE_OFFSET_TABLE_LO, y
  • sta ZEROPAGE_POINTER_1 + 1
  • ldy SPRITE_CHAR_POS_X, x ; fetch sprites x position, store it in Y
  • iny
  • iny
  • lda (ZEROPAGE_POINTER_1), y
  • jsr TestBlocking
  • bne @blockRight

Check Sprite Moving Up

Siggy mentioned here that “Up and down is the same thing only it works for a different axis. It’s checking the Y to see if we have to check it all. Its checking the X to see if we are flush.” However, he assured us that they are “all essentially the same regardless.”

  •  CanMoveUp
  • lda SPRITE_POS_Y_DELTA, x ; load Delta y value
  • beq @checkUp ; if it’s 0 we need to check characters
  • lda #0 ; if not we can just return and move
  • rts
  • @checkUp
  • lda SPRITE_POS_X_DELTA, x ; Check x delta – if 0 we only need to check one
  • beq @checkUp2 ; character above the player
  • ; also we are not flush on X and need to check 2
  • ldy SPRITE_CHAR_POS_Y, x ; fetch the sprite Y char coord – store in Y
  • dey
  • dey ; subtract 2
  • lda SCREEN_LINE_OFFSET_TABLE_LO, y ; fetch the address of screen line address
  • sta ZEROPAGE_POINTER_1
  • lda SCREEN_LINE_OFFSET_TABLE_HI, y
  • sta ZEROPAGE_POINTER_1 + 1
  • ldy SPRITE_CHAR_POS_X, x ; fetch X position
  • iny ; add one
  • lda (ZEROPAGE_POINTER_1), y 
  • jsr TestBlocking
  • bne @upBlocked
  • @checkUp2
  • ldy SPRITE_CHAR_POS_Y, x ; get the sprite Y char coordinate
  • dey ; subtract 2
  • dey
  • lda SCREEN_LINE_OFFSET_TABLE_LO, y ; fetch the address of that line
  • sta ZEROPAGE_POINTER_1
  • lda SCREEN_LINE_OFFSET_TABLE_HI, y
  • sta ZEROPAGE_POINTER_1 + 1
  • ldy SPRITE_CHAR_POS_X, x ; fetch the sprite X char coordinates
  • lda (ZEROPAGE_POINTER_1), y 
  • jsr TestBlocking
  • bne @upBlocked
  • lda #0
  • rts
  • @upBlocked
  • lda #1
  • rts

The Raster Interrupt Simplified

The concept of the raster routine is providing a high/low byte address to the line of the first interrupt contained in the file called raster.asm. The code that contains the starting line is at lda $CA sta $d012 in the IrqTopScreen routine. Also Siggy stated it is best to keep the code routine short or bad things can happen. You can watch this in the video skipping to 1:13:35.

  • InitRasterIRQ
  • sei ; stop all interupts
  • lda PROC_PORT
  • lda #$7f ; disable cia #1 generating timer irqs
  • sta INT_CONTROL ; which are used by the system to flash cursor, etc.
  • lda #$01 ; tell the VIC we want to generate raster irqs
  • ; Note – by directing writing #$01 and not setting bits
  • ; we are also turning off sprite/sprite,  sprite/background
  • ; and light pen interrupts… But those are rather shite any
  • ; and won’t be missed
  • sta VIC_INTERRUPT_CONTROL
  • lda #$10 ; number of the rasterline we want the IRQ to occur at
  • sta VIC_RASTER_LINE ; we used this for WaitFrame, remember? Reading gives the
  • ; raster line, writing sets the line for a raster interrupt
  • ; The raster counter goes from 0-312, so we need to set the
  • ; most significant bit (MSB)
  • ; The MSB for setting the raster line is bit 7 of $d011
  • ; ; is also VIC_SCREEN_CONTROL, that sets the height of the
  • ; if it’s turned on, text or bitmap mode, and other things 
  • ; so we could easily use this ‘short’ method
  • ; screen os and at 25 rows…
  • ;
  • lda VIC_SCREEN_CONTROL ; Fetch the VIC_SCREEN_CONTROL
  • and #%01111111 ; mask the surrounding bits
  • sta VIC_SCREEN_CONTROL
  • ; set the irq vector to point to our routine
  • lda #<IrqTopScreen
  • sta $0314
  • lda #>IrqTopScreen
  • ; Acknowledge any pending cia timer interrupts
  • ; just to be 100% safe
  • lda $dc0d
  • lda $dd0d
  • cli ; turn interrupts back on
  • rts
  • IrqTopScreen
  • ; acknowledge VIC irq
  • lda $d019
  • sta $d019
  • lda #<IrqScoreBoard
  • sta $0314
  • lda #>IrqScoreBoard
  • sta $0315
  • lda $CA ; number of rasterline we want the NEXT irq to occur at
  • sta $d012
  • lda #COLOR_BLUE
  • sta VIC_BORDER_COLOR
  • jsr UpdateTimers
  • jsr ReadJoystick
  • jsr JoyButton
  • lda #COLOR_BLACK
  • sta VIC_BORDER_COLOR
  • jmp $ca31

Siggy continued speaking and said, “When the raster interrupt gets to that line its gonna stop the IrqTopScreen routine. It drops everything, store everything, push all the registers onto the stack, and then the raster routine is run. The first thing is does is acknowledge the interrupt. Then it loads the address to the next interrupt (found in lda #<IrqScoreBoard). Then it evaluates the next line that is going to the interrupt. Finally it executes whatever code is stored in the border/screen setup area.

  • ReleaseRaster IRQ ; stop all interrupts
  • lda #$37 ; make sure the IO regs at $dxxx
  • sta $01 ; are visible
  • lda #$ff ; no more raster IRQ’s
  • sta $d01a
  • lda #$31
  • sta $0314
  • lda #$06
  • sta $0315 ; acknowledge any previous cia timer interupts
  • cli
  • rts

Further interrupts can be added by creating a new routine (lets call it IrqUpdateAttack) and then add it to the first routine in the area called InitRastIRQ. Then you setup the new high/low byte reference addresses and point it to the new raster line being scanned. Here is such an example.

  • lda #<IrqUpdateAttack
  • sta $0314
  • lda #>IrqUpdateAttack
  • ; Acknowledge any pending cia timer interrupts
  • ; just to be 100% safe
  • lda $dc0d
  • lda $dd0d
  • cli ; turn interrupts back on
  • rts
  • IrqUpdateAttack
  • ; acknowledge VIC irq
  • lda $d019
  • sta $d019
  • lda #<IrqScoreBoard
  • sta $0314
  • lda #>IrqScoreBoard
  • sta $0315
  • lda $D2 ; number of rasterline we want the NEXT irq to occur at
  • sta $d012
  • ; new routine codes below (replace code for the three dots)
  • jmp $ca31

Tips for Raster Effects

Siggy also pointed out that a new interrupt could be added to change the character set, such as seen in the game Spelunker, repeat sprites in different locations or any other area of interest that fits the game you are creating. Also a raster table could be setup to create a color bar effect  going down the screen.

If you decide to create the color bar the sprites can do strange things when colliding with the raster bars such as tearing. The raster must be stabilized to control this by working out how many cycles are needed, forward or backwards by adding multiple NOP (No operation) lines to manage the proper syncing.

Please follow and like us: