Guerrilla Game #5.3 -- The Final Puzzle Piece
Okay! So I think I have mastered how to draw the kernel that SpiceWare outlined in StayFrosty. If make sure you read all of the blog posts under the #5 heading so you understand what I am talking about.
Remember this his how the main player character is drawn in the kernel:
ldy Heights+.SEC ... lda (FrostyImagePtr+.SEC*2),y and (FrostyMaskPtr+.SEC*2),y sta GRP0 lda (FrostyColorPtr+.SEC*2),y sta COLUP0
I The "Heights" Variable
I asked SpiceWare for some help in understanding how the Heights variable worked. It turned out my analysis was more or less correct, despite some small errors. This is how the Heights variable is set:
ldy #SECTIONS*DATA_BYTES_PER_SECTION-4-1+2+2 ; -4 for the 4 unused platform bytes ; +2 for fireball size info ; +2 for level control init LINE 2200: .plLoop lda (LevelDataPtr),y sta Heights,y dey bpl .plLoopThere are 55 bytes of data in ROM, and the Y Register is loaded with the number 54. The BPL instruction for .plLoop causes the loop at line 2200 to loop through the number zero, so we go through the loop 55 times. This loop transfers 55 bytes of data into 55 bytes of ROM. It transfers the 5 bytes of the heights of the different bands into Heights, but then it also transfers the other 50 bytes of level data into the RAM variables after heights. My analysis in #5.2 was correct.
II Preparing the Pointers for Sections 1 to 4
It was easy enough to understand how the pointers for the graphics were set up for Section 0 of the screen. This is because the bottom of the screen is the designated origin of the Y position of the main player character. But we need to adjust the Y positions of all the copies of the main player character up the screen. We will mask out all but one image of the main player character, but we still need to make the kernel attempt to draw the image 5 times up the screen before we mask it out.
So this is how we prepare the pointers for the above purpose:
LINE 2006: ldx #0 ldy #0 pfLoop clc lda FrostyImagePtr,y adc Heights,x iny ; 1 iny ; 2 sta FrostyImagePtr,y dey ; 1 lda FrostyImagePtr,y adc #0 iny ; 2 iny ; 3 sta FrostyImagePtr,y <Repeated Process for the Mask and Colours here> dey ; 2 inx cpx #SECTIONS-1 bcc pfLoopWhat is happening in the above code? This is what is happening:
- We are adding a 1 byte number to a 2 byte number. The process for doing this requires you to add the 1 byte number to the lower byte of the 2 byte number, and add any overflow to the higher byte. I did not realise this was happening! So if Heights (1 bytes) + ImagePtr(Lower byte) = 256, we add the carry put in the Program Counter to ImagePtr (Highter Byte).
- So, what the code achieves overall is this:
FrostyImagePtr1 = FrostyImagePtr0 + Height0 FrostyImagePtr2 = FrostyImagePtr1 + Height1 FrostyImagePtr3 = FrostyImagePtr2 + Height2 FrostyImagePtr4 = FrostyImagePtr3 + Height3
This makes perfect sense. We are setting the minimum Y position of each image pointer as the top height of the band of the screen below it.
III The Final Component of the Kernel: (.SEC*2)
The final element of drawing the main player character in the kernel is understanding what .SEC*2 does in the following code:
lda (FrostyImagePtr+.SEC*2),yThis is what is happening:
- .SEC is the argument passed into the kernel macro.
- This argument is converted into a pseudo-variable in the code.
- It is not a real variable because .SEC is not a piece of memory that contains data. .SEC is a set of characters that are mapped into the code depending on the value of the argument passed into the macro.
- .SEC therefore specifies a number between 0 and 4 - which tells the kernel to draw a specific band of graphics up and down the screen.
IV But What About the Mask and the Colour?
The colour pointer works in exactly the same way as the image pointer. It is like a second part of the image pointer that needs to be set up separately with its own code. The process of setting a colour pointer for a multi-band screen is perfectly isomorphic with setting the graphics pointer.
This is the data in ROM which sets out the mask used to let the main player character graphics through onto the correct band of the screen:
LINE 865: align 256 BlankGraphic repeat 50 ; 148-26 .byte 0 repend repeat 26 .byte $ff repend FrostyMask repeat 50 ; 148-26 .byte 0 rependThe data is set out in macro code, but it is easy to interpret. It is 50 bytes (scanlines) of zeroes, followed by 26 bytes (scanlines) of $FF, followed by 50 bytes (scanlines) of zeroes. The origin of the pointer is the label FrostyMask, so we need to count down 26 bytes of memory address in order to draw the main player character, and count up 50 bytes in order to blank out the main player character. Once the kernel has counted down 26 bytes and drawn the character, there are then 50 bytes of zeroes, which will blank out drawing the graphics.
The idea is to have the 26 bytes of $FF set to the correct scanline of the screen, so that when we bitwise AND the player graphics on the first line for them to show, the first like of the $FF of the mask is loaded.
V The End
That's it! Now I can get to writing my own kernel!