Logical Units and clipping
To allow us to crop our sprites, we'll need to move our sprite
around 'logical space' - only a portion of this will be the visible
screen. We'll use this to crop the sprite. |
![]() |
We're designing our code for screens of 256x192 - and we'll use 'Logical Co-ordinates' which define a visible screen of 192x96 (2 pixels per logical unit) The visible logical screen is in the center of the logical space area to allow the sprite to be 'partially offscreen' on any side. |
![]() |
We need to crop any offscreen part to
get to the first visible pixel of the sprite. After each line We also need to crop the unused pixels to the next visible pixel |
![]() |
We're using 256
color mode today, but our graphic is only 4 color... also the
colors are inverted, but this is because we're using XORed and
we've colored the background purple so we can see the drawable
area. |
![]() |
Logical Cropping
Our cropping routine will work out the X,Y pos in bytes, and width
and height + any skipped pixels from the source data registers B,C is the X,Y co-ordinate in logical units registers H,L is the Width,Height in logical units register IY is the address of the source bitmap data. First we set up the default getnextline routine (used for uncropped sprites) next we zero D,E - they are used for temp values |
![]() |
Ok... lets crop the top of the sprite... First we remove the ypos of the first visible pixel from the draw ypos (C)... if the result is greater than zero, then nothing is off the screen at the top. if the result is less than zero we need to crop... we convert the negative to a positive and compare to the height of our sprite (L), if the amount to crop is not less than the height then the sprite is completely offscreen. Anything else is the number of lines we need to remove from the top, we store this in E and set the new 'draw position' to Ypos (C) =0 |
![]() |
Next we do the same for the bottom, We add the height to the Ypos, and subtract the height of the logical screen, if it's over the screen height (greater than zero) we need to crop again - the result is the amount to crop |
![]() |
We've calculated the top (E) and bottom (D) crop... we now use these to calculate the new height of the sprite (L). | ![]() |
We then skip over any bytes in the source (IY) based on the number
of lines of bytes we need to remove from the top. |
![]() |
now we do the same for the X axis First we remove the xpos of the first visible pixel from the draw Xpos (B) ... if the result is greater than zero, then nothing is off the screen at the left. if the result is less than zero we need to crop... we convert the negative to a positive and compare to the width of our sprite (H), if the amount to crop is not less than the width then the sprite is completely offscreen. Anything else is the number of lines we need to remove from the left, we store this in E and set the new 'draw position' to Xpos (B) =0 |
![]() |
Next we do the same for the right, We add the width (H) to the Xpos, and subtract the width of the logical screen, if it's over the screen width (greater than zero) we need to crop again - the result is the amount to crop from the right (D) |
![]() |
We've calculated the left and right crop... We use these
to calculate the bytes to skip after each line of our sprite
(SpriteHClip) If this is zero, there's no horizontal crop We need to crop, so we calculate the new width in H. We also need to turn on the slower cropping routine via self modifying code |
![]() |
We need to alter the start byte to compensate for any left hand clipping | ![]() |
We need to convert out Xpos and Width to a number of bytes, and
our Ypos and height to a number of lines. We clear the carry to tell the calling routine we cropped the sprite successfully (it needs drawing) and return. |
![]() |
If the sprite is completely offscreen, there's no point trying to draw, so we set the carry and return | ![]() |
Logical Units and clipping
When we want to draw our sprite, we first run the DoCrop routine
to calculate the required cropping. If there's anything to draw, we then calculate the VRAM destination with GetScreenPos, and run the ShowSprite routine |
![]() |
The screen is 256x192, and each pixel is 1 byte - making 48k in
total. We page the VRAM into the &2000-&3FFF range... this is usually ROM, and if means we can still use IM1 if we've paged it to RAM. We therefore split the screen into six 8k blocks, and select the one we need using NextReg &51 |
![]() |
The showsprite routine transfers bytes from the source (now in DE)
to VRAM in HL after each line GetNextLine moves the HL destination down one pixel line. This routine is switched via self modifying code if the sprite is cropped. |
![]() |
GetNextLine will increase the HL address, but the 48k screen is split into eight 8k banks, so if we go over the boundary of one bank, we need to page in the next with NextReg &51 If the sprite is cropped, we need to use the GetNextLineWithClip to remove the un-needed source bitmap bytes. |
![]() |
![]() |
We've only looked at the basic cropping and
sprite drawing routines here. Next time we'll take a peek at the use of stack misuse to speed up reading and writing, and the double buffering code. |
Next time we'll look at the 'Double buffering' and 'Stack misuse' that allow for no flicker and faster sprite drawing. |
![]() |
![]() |
Lesson
S31 - Sprite clipping on the Spectrum Next (Part 2/2) Lets take a look in more detail at our sprite routine, this time we'll look at the double buffer, and stack misuse. |
![]() |
![]() |
Stack misuse is a trick where we use the PUSH
or POP commands to quickly read or write data, by pointing the SP
register to the start of data we want to read, or end of data we
want to write, we can use these commands to quickly transfer data. BUT because the SP register is not pointing to a normal stack, we probably can't use CALLS or interrupts. |
![]() |
Double Buffering
We're going to use two buffers. One will shown to the viewer while
the other is being drawn, we'll then swap the two in hardware and
repeat. Each buffer is 48k, and uses 3x 16k banks or 6x 8k banks. We use banks 8 and 11 for the two screen buffers, We select the visible screen buffer with NextReg &12 |
![]() |
We use 16 banks when selecting the buffer, but for paging it into
Z80 ram we use 8k banks and nextreg &51 We page the VRAM into the &2000-&3FFF range... this is usually ROM, and if means we can still use IM1 if we've paged it to RAM. We therefore split the screen into six 8k blocks, and select the one we need using NextReg &51 |
![]() |
![]() |
By paging in
vram into the range &2000-&3FFF we can still use Interrupt
mode 1! We just need to page in some ram into the &0000-&1FFF range. |
Clearing the screen
We'll use stack misuse to clear the screen - this means we need to
calculate the address of the byte after the end of the screen, as
each 2 byte write will be done by a PUSH command. We're going to page in the screen in 8k chunks, so we need to do this 6 times. |
![]() |
We're going to fill the 8k screen part with the bytes in DE. To reduce the number of loops, we do 32 'PUSH DE' commands, writing 64 bytes... using lots of repeated commands to reduce loops is called 'Unwrapping loops'... As loop conditions take time, the fewer we use, the faster the fill will be. First we back up the true stack pointer in IY. We move the end of the destination range from HL into the stack pointer... each PUSH DE decreases the stack pointer by two, so we are working in reverse. As we're misusing the stack, we stop interrupts with DI, and restore the true stack pointer in IY |
![]() |
Drawing the sprite
Because we need to recalculate the screen destination after each
line, it won't help much to use the stack for writing, instead we'll
use it to read the bitmap data with POP commands. We back up the stack pointer with self modifying code, and move the image source data address from IY to SP. We then POP two bytes of data from the source image, and write them to the screen. Once the sprite is finished, we restore the stack pointer address. |
![]() |
Our "get next line" routine also needs to be changed. If we need to clip the sprite, we need to update the stack pointer to skip over the source bytes that are not needed. We then have the routine to handle the change of the HL VRAM destination. It also handles the bank switching. |
![]() |
Logical Units and clipping
To allow us to crop our sprites, we'll need to move our sprite
around 'logical space' - only a portion of this will be the visible
screen. We'll use this to crop the sprite. |
![]() |
We're designing our code for screens of 256x192 - and we'll use 'Logical Co-ordinates' which define a visible screen of 192x96 (2 pixels per logical unit) The visible logical screen is in the center of the logical space area to allow the sprite to be 'partially offscreen' on any side. |
![]() |
We need to crop any offscreen part to
get to the first visible pixel of the sprite. After each line We also need to crop the unused pixels to the next visible pixel |
![]() |
We're going to
use the tilemap to draw the graphic today, so we're limited to 8x8
blocks. That's 4x4 logical units! |
![]() |
Logical Cropping
Our cropping routine will work out the X,Y pos in bytes, and width
and height + any skipped pixels from the source data registers B,C is the X,Y co-ordinate in logical units registers H,L is the Width,Height in logical units register IY is the address of the source bitmap data. First we set up the default SpriteHClip routine (We assume the sprite won't be horizontally clipped) next we zero D,E - they are used for temp values |
![]() |
Ok... lets crop the top of the sprite... First we remove the ypos of the first visible pixel from the draw ypos (C)... if the result is greater than zero, then nothing is off the screen at the top. if the result is less than zero we need to crop... we convert the negative to a positive and compare to the height of our sprite (L), if the amount to crop is not less than the height then the sprite is completely offscreen. Anything else is the number of lines we need to remove from the top, we store this in E and set the new 'draw position' to Ypos (C) =0 we mask with and %11111100 to convert to a number of tiles. |
![]() |
Next we do the same for the bottom, We add the height to the Ypos, and subtract the height of the logical screen, if it's over the screen height (greater than zero) we need to crop again - the result is the amount to crop we mask with and %11111100 to convert to a number of tiles. |
![]() |
We've calculated the top (E) and bottom (D) crop... we now use these to calculate the new height of the sprite (L). | ![]() |
We then skip over any bytes in the source (IY) based on the number
of lines of tiles we need to remove from the top. |
![]() |
We then remove the needed start bytes | ![]() |
now we do the same for the X axis First we remove the xpos of the first visible pixel from the draw Xpos (B) ... if the result is greater than zero, then nothing is off the screen at the left. if the result is less than zero we need to crop... we convert the negative to a positive and compare to the width of our sprite (H), if the amount to crop is not less than the width then the sprite is completely offscreen. Anything else is the number of lines we need to remove from the left, we store this in E and set the new 'draw position' to Xpos (B) =0 |
![]() |
Next we do the same for the right, We add the width (H) to the Xpos, and subtract the width of the logical screen, if it's over the screen width (greater than zero) we need to crop again - the result is the amount to crop from the right (D) |
![]() |
We've calculated the left and right crop... We use these
to calculate the bytes to skip after each line of our sprite
(SpriteHClip) If this is zero, there's no horizontal crop We need to work in a number of tiles, so we divide by 4 We need to crop, so we calculate the new width in H. |
![]() |
We need to alter the start byte to compensate for any left hand clipping | ![]() |
We need to convert out Xpos and Width, Ypos and height to a number
of tiles. We clear the carry to tell the calling routine we cropped the sprite successfully (it needs drawing) and return. |
![]() |
If the sprite is completely offscreen, there's no point trying to draw, so we set the carry and return | ![]() |
Screen buffers
We're using two Tilemap
buffers, One at &1800 and the other at &1C00 The bottom 3 bits of graphics register 2 define the address of the tilemap (Pattern Table) We flip the bottom bit so we draw to the alternative bank than the one showing |
![]() |
Our 'GetVDP Screenpos' function will calculate the VRAM
destination from an XY position. It has been modified to use the VisibleScreenBank to write to the correct buffer. |
![]() |
Logical Units and clipping
When we want to draw our sprite, we first run the DoCrop routine
to calculate the required cropping. If there's anything to draw, we then calculate the VRAM destination with GetScreenPos, and run the ShowSprite routine The Chibiko bitmap starts from tile 128 on the MSX1 and SMS |
![]() |
The FillAreaWithTiles will draw a grid of tiles to the screen. Where the graphic is horizontally cropped, we skip some tiles after each line with SpriteHClip |
![]() |
Clear screen
We clear the screen in pretty much the same way. We calculate the base of the Tilemap vram, then write 32x24 (one full screens worth) of zero tiles to clear the screen. |
![]() |
![]() |
Lesson
S33 - Sprite clipping on the MSX2 We drew our 'Chibiko' mascot to the screen before, but this time we're going to improve the algorithm. We'll use sprite clipping, and double buffering! |
![]() |
![]() |
Logical Units and clipping
To allow us to crop our sprites, we'll need to move our sprite
around 'logical space' - only a portion of this will be the visible
screen. We'll use this to crop the sprite. |
![]() |
We're designing our code for screens of 256x192 - and we'll use 'Logical Co-ordinates' which define a visible screen of 192x96 (2 pixels per logical unit) The visible logical screen is in the center of the logical space area to allow the sprite to be 'partially offscreen' on any side. |
![]() |
We need to crop any offscreen part to
get to the first visible pixel of the sprite. After each line We also need to crop the unused pixels to the next visible pixel |
![]() |
We're using VDP
commands today, so we'll have to define the area of the sprite to
transfer from the 'invisible area' (Ypos 512+) to the two Visible
screens (Ypos 0 and Ypos 256) |
![]() |
Double Buffer
We're using two screen buffers
- these appear in the 256x1024 VRAM area. One is at Ypos 0, the other is at Ypos 256... the sprites are at 512+ We need to use bit 5 of VDP register 2 to define the base address of the sprite. NOTE, our screen is 256x192, so there is a small amount of screen unused between the two screen buffers. |
![]() |
Clear Screen
We use the fast fill command to clear the screen. The screen is 256x192, and our VRAM base will be either from line Y=0 or Y=256 |
![]() |
Logical Cropping
Our cropping routine will work out the X,Y pos in bytes, and width
and height + any skipped pixels from the source data registers B,C is the X,Y co-ordinate in logical units registers H,L is the Width,Height in logical units register IY is the address of the source bitmap data. First we zero D,E - they are used for temp values |
![]() |
Ok... lets crop the top of the sprite... First we remove the ypos of the first visible pixel from the draw ypos (C)... if the result is greater than zero, then nothing is off the screen at the top. if the result is less than zero we need to crop... we convert the negative to a positive and compare to the height of our sprite (L), if the amount to crop is not less than the height then the sprite is completely offscreen. Anything else is the number of lines we need to remove from the top, we store this in E and set the new 'draw position' to Ypos (C) =0 |
![]() |
Next we do the same for the bottom, We add the height to the Ypos, and subtract the height of the logical screen, if it's over the screen height (greater than zero) we need to crop again - the result is the amount to crop |
![]() |
We've calculated the top (E) and bottom (D) crop... we now use these to calculate the new height of the sprite (L). | ![]() |
We update the height of the sprite in H. The MSX2 version is different to other versions! We store the final pixels to remove from the top in IYL |
![]() |
now we do the same for the X axis First we remove the xpos of the first visible pixel from the draw Xpos (B) ... if the result is greater than zero, then nothing is off the screen at the left. if the result is less than zero we need to crop... we convert the negative to a positive and compare to the width of our sprite (H), if the amount to crop is not less than the width then the sprite is completely offscreen. Anything else is the number of lines we need to remove from the left, we store this in E and set the new 'draw position' to Xpos (B) =0 |
![]() |
Next we do the same for the right, We add the width (H) to the Xpos, and subtract the width of the logical screen, if it's over the screen width (greater than zero) we need to crop again - the result is the amount to crop from the right (D) |
![]() |
We need to crop, so we calculate the new width in H. We need to alter the start pos to compensate for any left hand clipping in IYH |
![]() |
We update the Logical units to physical pixels | ![]() |
If the sprite is completely offscreen, there's no point trying to draw, so we set the carry and return | ![]() |
Logical Units and clipping
When we want to draw our sprite, we first run the DoCrop routine
to calculate the required cropping. If there's anything to draw, we then need to prepare the values for the "VDP_HMMM_ViaStack" function. This uses 6 register pairs - some of which are in the shadow regs. IX = X source IY = Y source HL' = X dest DE' = Y Dest HL = Width DE = Height |
![]() |
Logical Units and clipping
To allow us to crop our sprites, we'll need to move our sprite
around 'logical space' - only a portion of this will be the visible
screen. We'll use this to crop the sprite. |
![]() |
We're designing our code for screens of 256x192 - and we'll use 'Logical Co-ordinates' which define a visible screen of 192x96 (2 pixels per logical unit) The visible logical screen is in the center of the logical space area to allow the sprite to be 'partially offscreen' on any side. |
![]() |
We need to crop any offscreen part to
get to the first visible pixel of the sprite. After each line We also need to crop the unused pixels to the next visible pixel |
![]() |
We're going to
use the tilemap to draw the graphic today, so we're limited to 8x8
blocks. That's 4x4 logical units! |
![]() |
Logical Cropping
Our cropping routine will work out the X,Y pos in bytes, and width
and height + any skipped pixels from the source data registers B,C is the X,Y co-ordinate in logical units registers H,L is the Width,Height in logical units register IY is the address of the source bitmap data. First we set up the default SpriteHClip routine (We assume the sprite won't be horizontally clipped) next we zero D,E - they are used for temp values |
![]() |
Ok... lets crop the top of the sprite... First we remove the ypos of the first visible pixel from the draw ypos (C)... if the result is greater than zero, then nothing is off the screen at the top. if the result is less than zero we need to crop... we convert the negative to a positive and compare to the height of our sprite (L), if the amount to crop is not less than the height then the sprite is completely offscreen. Anything else is the number of lines we need to remove from the top, we store this in E and set the new 'draw position' to Ypos (C) =0 we mask with and %11111100 to convert to a number of tiles. |
![]() |
Next we do the same for the bottom, We add the height to the Ypos, and subtract the height of the logical screen, if it's over the screen height (greater than zero) we need to crop again - the result is the amount to crop we mask with and %11111100 to convert to a number of tiles. |
![]() |
We've calculated the top (E) and bottom (D) crop... we now use these to calculate the new height of the sprite (L). | ![]() |
We then skip over any bytes in the source (IY) based on the number
of lines of tiles we need to remove from the top. |
![]() |
We then remove the needed start bytes | ![]() |
now we do the same for the X axis First we remove the xpos of the first visible pixel from the draw Xpos (B) ... if the result is greater than zero, then nothing is off the screen at the left. if the result is less than zero we need to crop... we convert the negative to a positive and compare to the width of our sprite (H), if the amount to crop is not less than the width then the sprite is completely offscreen. Anything else is the number of lines we need to remove from the left, we store this in E and set the new 'draw position' to Xpos (B) =0 |
![]() |
Next we do the same for the right, We add the width (H) to the Xpos, and subtract the width of the logical screen, if it's over the screen width (greater than zero) we need to crop again - the result is the amount to crop from the right (D) |
![]() |
We've calculated the left and right crop... We use these
to calculate the bytes to skip after each line of our sprite
(SpriteHClip) If this is zero, there's no horizontal crop We need to work in a number of tiles, so we divide by 4 We need to crop, so we calculate the new width in H. |
![]() |
We need to alter the start byte to compensate for any left hand clipping | ![]() |
We need to convert out Xpos and Width, Ypos and height to a number
of tiles. We clear the carry to tell the calling routine we cropped the sprite successfully (it needs drawing) and return. |
![]() |
If the sprite is completely offscreen, there's no point trying to draw, so we set the carry and return | ![]() |
Screen buffers
We're using two Tilemap
buffers, One at &3000 and the other at &3800 Each Tilemap is 32x24, and 2 bytes per tile, so &600 bytes total We toggle bit 1 of VDP register 2 to switch between these two tilemaps. We first wait for Vblank, so that we allow the previous screen to show. So interrupts work correctly we need to define an interrupt handler which will clear the interrupt by reading in from the VDP control port |
![]() |
The CLS screen routine will select the start of the tilemap
(&3000 / &3800) We then transfer 32*24 tiles of zero to the Tilemap ram. |
![]() |
Our 'GetVDP Screenpos' function will calculate the VRAM
destination from an XY position. It has been modified to use the VisibleScreenBank to write to the correct buffer. |
![]() |
Logical Units and clipping
When we want to draw our sprite, we first run the DoCrop routine
to calculate the required cropping. If there's anything to draw, we then calculate the VRAM destination with GetScreenPos, and run the ShowSprite routine The Chibiko bitmap starts from tile 128 on the MSX1 and SMS |
![]() |
The FillAreaWithTiles will draw a grid of tiles to the screen. Where the graphic is horizontally cropped, we skip some tiles after each line with SpriteHClip |
![]() |
Logical Units and clipping
To allow us to crop our sprites, we'll need to move our sprite
around 'logical space' - only a portion of this will be the visible
screen. We'll use this to crop the sprite. |
![]() |
We're designing our code for screens of 256x192 - and we'll use 'Logical Co-ordinates' which define a visible screen of 192x96 (2 pixels per logical unit) The visible logical screen is in the center of the logical space area to allow the sprite to be 'partially offscreen' on any side. |
![]() |
We need to crop any offscreen part to
get to the first visible pixel of the sprite. After each line We also need to crop the unused pixels to the next visible pixel |
![]() |
This time we're
using Hardware sprites, so we can move smoothly... yay! BUUUUT... there's a limitation... the X co-ordinate is only one byte, so we can't smoothly move the sprite off the left of the screen. The best we can do is 'hide' one 8 pixel strip on the left by enabling bit 5 of VDP Register 0 |
![]() |
Gamegear & Mastersystem Sprite Memory (&3F00)
The SMS/GG memory supports up to 64
sprites, There are 3 memory bytes for each hardware sprite, but they are not consecutive! Look at sprite 0 - shown in black... it's Y co-ordinate is at &3F00 ... it's X co-ordinate is at &3F80... it's Sprite Number is at &3F81 Sprite Numbers can come from memory address &2000, or &0000 (The Tile Patterns) depending on the setting of register &06 |
![]() |
Logical Cropping
Our cropping routine will work out the X,Y pos in bytes, and width
and height + any skipped pixels from the source data registers B,C is the X,Y co-ordinate in logical units registers H,L is the Width,Height in logical units register IY is the address of the source bitmap data. First we set up the default SpriteHClip routine (We assume the sprite won't be horizontally clipped) next we zero D,E - they are used for temp values |
![]() |
Ok... lets crop the top of the sprite... First we remove the ypos of the first visible pixel from the draw ypos (C)... if the result is greater than zero, then nothing is off the screen at the top. if the result is less than zero we need to crop... we convert the negative to a positive and compare to the height of our sprite (L), if the amount to crop is not less than the height then the sprite is completely offscreen. Anything else is the number of lines we need to remove from the top, we store this in E and set the new 'draw position' to Ypos (C) =0 We need to deal with the 'offset' of any removed tiles to keep the movement smooth, so we AND and XOR with %00000011 |
![]() |
Next we do the same for the bottom, We add the height to the Ypos, and subtract the height of the logical screen, if it's over the screen height (greater than zero) we need to crop again - the result is the amount to crop |
![]() |
We've calculated the top (E) and bottom (D) crop... we now use
these to calculate the new height of the sprite (L). We need to round this up to a total number of sprites, so we add 3 and AND with %11111100 |
![]() |
We then skip over any bytes in the source (IY) based on the number
of lines of tiles we need to remove from the top. |
![]() |
We then remove the needed start bytes | ![]() |
now we do the same for the X axis. First we remove the xpos of the first visible pixel from the draw Xpos (B) ... if the result is greater than zero, then nothing is off the screen at the left. if the result is less than zero we need to crop... we convert the negative to a positive and compare to the width of our sprite (H), if the amount to crop is not less than the width then the sprite is completely offscreen. Anything else is the number of lines we need to remove from the left, we store this in E and set the new 'draw position' to Xpos (B) =0 We need to deal with the 'offset' of any removed tiles to keep the movement smooth, so we AND and XOR with %00000011 |
![]() |
Next we do the same for the right, We add the width (H) to the Xpos, and subtract the width of the logical screen, if it's over the screen width (greater than zero) we need to crop again - the result is the amount to crop from the right (D) |
![]() |
We've calculated the left and right crop... We use these
to calculate the bytes to skip after each line of our sprite
(SpriteHClip) If this is zero, there's no horizontal crop We need to work in a number of tiles, so we divide by 4 We need to crop, so we calculate the new width in H, rounding to a number of tiles. |
![]() |
We need to alter the start byte to compensate for any left hand clipping | ![]() |
We need to convert out Xpos and Ypos to pixels (*2), and height
and width to tiles (/4) We clear the carry to tell the calling routine we cropped the sprite successfully (it needs drawing) and return. |
![]() |
If the sprite is completely offscreen, there's no point trying to draw, so we set the carry and return | ![]() |
Using the Hardware Sprites
When we want to draw our sprite, we first run the DoCrop routine
to calculate the required cropping. If there's anything to draw, we then calculate the VRAM destination with GetScreenPos, and run the ShowSprite routine The Chibiko bitmap starts from tile 128 on the MSX1 and SMS |
![]() |
The FillAreaWithTiles will draw a grid of sprites to the screen. Where the graphic is horizontally cropped, we skip some tiles after each line with SpriteHClip |
![]() |
As the sprite is clipped, we will need fewer than 36 tiles to draw
our mascot, but these sprites will stay on the screen! We need to hide these sprites so they do not 'trail' on the screen |
![]() |
Logical Units and clipping
To allow us to crop our sprites, we'll need to move our sprite
around 'logical space' - only a portion of this will be the visible
screen. We'll use this to crop the sprite. |
![]() |
We're designing our code for screens of 256x192 - and we'll use 'Logical Co-ordinates' which define a visible screen of 192x96 (2 pixels per logical unit) The visible logical screen is in the center of the logical space area to allow the sprite to be 'partially offscreen' on any side. |
![]() |
We need to crop any offscreen part to
get to the first visible pixel of the sprite. After each line We also need to crop the unused pixels to the next visible pixel |
![]() |
We're going to
use the tilemap to draw the graphic today, so we're limited to 8x8
blocks. That's 4x4 logical units! |
![]() |
Logical Cropping
Our cropping routine will work out the X,Y pos in bytes, and width
and height + any skipped pixels from the source data registers B,C is the X,Y co-ordinate in logical units registers H,L is the Width,Height in logical units register IY is the address of the source bitmap data. First we set up the default SpriteHClip routine (We assume the sprite won't be horizontally clipped) next we zero D,E - they are used for temp values |
![]() |
Ok... lets crop the top of the sprite... First we remove the ypos of the first visible pixel from the draw ypos (C)... if the result is greater than zero, then nothing is off the screen at the top. if the result is less than zero we need to crop... we convert the negative to a positive and compare to the height of our sprite (L), if the amount to crop is not less than the height then the sprite is completely offscreen. Anything else is the number of lines we need to remove from the top, we store this in E and set the new 'draw position' to Ypos (C) =0 we add 3 to round up, and mask with AND %11111100 to convert to a number of tiles. Note: the GBZ80 has no NEG command, so we simulate one with CPL... INC A |
![]() |
Next we do the same for the bottom, We add the height to the Ypos, and subtract the height of the logical screen, if it's over the screen height (greater than zero) we need to crop again - the result is the amount to crop we mask with and %11111100 to convert to a number of tiles. |
![]() |
We've calculated the top (E) and bottom (D) crop... we now use these to calculate the new height of the sprite (L). | ![]() |
We then skip over any bytes in the source (IY) based on the number
of lines of tiles we need to remove from the top. |
![]() |
We then remove the needed start bytes from the IY pair | ![]() |
now we do the same for the X axis First we remove the xpos of the first visible pixel from the draw Xpos (B) ... if the result is greater than zero, then nothing is off the screen at the left. if the result is less than zero we need to crop... we convert the negative to a positive and compare to the width of our sprite (H), if the amount to crop is not less than the width then the sprite is completely offscreen. Anything else is the number of lines we need to remove from the left, we store this in E and set the new 'draw position' to Xpos (B) =0 |
![]() |
Next we do the same for the right, We add the width (H) to the Xpos, and subtract the width of the logical screen, if it's over the screen width (greater than zero) we need to crop again - the result is the amount to crop from the right (D) |
![]() |
We've calculated the left and right crop... We use these
to calculate the bytes to skip after each line of our sprite
(SpriteHClip) If this is zero, there's no horizontal crop We need to work in a number of tiles, so we divide by 4 We need to crop, so we calculate the new width in H. |
![]() |
We need to alter the start byte to compensate for any left hand clipping | ![]() |
We need to convert out Xpos and Width, Ypos and height to a number
of tiles. We clear the carry to tell the calling routine we cropped the sprite successfully (it needs drawing) and return. |
![]() |
If the sprite is completely offscreen, there's no point trying to draw, so we set the carry and return | ![]() |
Screen buffers
We're using two Tilemap
buffers, One at &9800 and the other at &9C00 Each Tilemap is 32x32, and 2 bytes per tile, so &400 bytes total Bit 3 of port port &FF40 selects the currently visible tilemap, we store the top byte of the tilemap base with VisibleScreenBank |
![]() |
The CLS screen routine will select the start of the tilemap
(&9800 / 9C00) We then transfer 32*18 tiles of zero to the Tilemap ram (the full width and visible height) We need to wait for H or V blank with bit 1 of port &FF41 |
![]() |
Our 'GetVDP Screenpos' function will calculate the VRAM
destination from an XY position. It has been modified to use the VisibleScreenBank to write to the correct buffer. |
![]() |
Using the Tiles!
When we want to draw our sprite, we first run the DoCrop routine
to calculate the required cropping. If there's anything to draw, we then calculate the VRAM destination with GetScreenPos, and run the ShowSprite routine The Chibiko bitmap starts from tile pattern 128 on the Gameboy |
![]() |
The FillAreaWithTiles will draw a grid of tiles to the screen. Where the graphic is horizontally cropped, we skip some tiles after each line with SpriteHClip |
![]() |
Logical Units and clipping
To allow us to crop our sprites, we'll need to move our sprite
around 'logical space' - only a portion of this will be the visible
screen. We'll use this to crop the sprite. |
![]() |
We're designing our code for screens of 256x192 - and we'll use 'Logical Co-ordinates' which define a visible screen of 192x96 (2 pixels per logical unit) The visible logical screen is in the center of the logical space area to allow the sprite to be 'partially offscreen' on any side. |
![]() |
We need to crop any offscreen part to
get to the first visible pixel of the sprite. After each line We also need to crop the unused pixels to the next visible pixel |
![]() |
We're using
hardware sprites.. wooo! We need to transfer Hsprites during Vblank, so we'll use 256 bytes of ram as a cache, and transfer them in the interrupt handler |
![]() |
Logical Cropping
Our cropping routine will work out the X,Y pos in bytes, and width
and height + any skipped pixels from the source data registers B,C is the X,Y co-ordinate in logical units registers H,L is the Width,Height in logical units register IY is the address of the source bitmap data. First we set up the default SpriteHClip routine (We assume the sprite won't be horizontally clipped) next we zero D,E - they are used for temp values |
![]() |
Ok... lets crop the top of the sprite... First we remove the ypos of the first visible pixel from the draw ypos (C)... if the result is greater than zero, then nothing is off the screen at the top. if the result is less than zero we need to crop... we convert the negative to a positive and compare to the height of our sprite (L), if the amount to crop is not less than the height then the sprite is completely offscreen. Anything else is the number of lines we need to remove from the top, we store this in E and set the new 'draw position' to Ypos (C) =0 we add 3 to round up, and mask and XOR the bottom 2 bits to calculate the offset (0-7 pixels) to keep the movement smooth as we remove tiles. Note: the GBZ80 has no NEG command, so we simulate one with CPL... INC A |
![]() |
Next we do the same for the bottom, We add the height to the Ypos, and subtract the height of the logical screen, if it's over the screen height (greater than zero) we need to crop again - the result is the amount to crop |
![]() |
We've calculated the top (E) and bottom (D) crop... we now use these to calculate the new height of the sprite (L). | ![]() |
We then skip over any bytes in the source (IY) based on the number
of lines of tiles we need to remove from the top. |
![]() |
We then remove the needed start bytes from the IY pair | ![]() |
now we do the same for the X axis First we remove the xpos of the first visible pixel from the draw Xpos (B) ... if the result is greater than zero, then nothing is off the screen at the left. if the result is less than zero we need to crop... we convert the negative to a positive and compare to the width of our sprite (H), if the amount to crop is not less than the width then the sprite is completely offscreen. We calculate the offset using AND and XOR, to allow the object to move smoothly as we remove strips of the sprite. Anything else is the number of lines we need to remove from the left, we store this in E and set the new 'draw position' to Xpos (B) =0 |
![]() |
Next we do the same for the right, We add the width (H) to the Xpos, and subtract the width of the logical screen, if it's over the screen width (greater than zero) we need to crop again - the result is the amount to crop from the right (D) |
![]() |
We've calculated the left and right crop... We use these
to calculate the bytes to skip after each line of our sprite
(SpriteHClip) If this is zero, there's no horizontal crop We need to work in a number of tiles, so we divide by 4 We need to crop, so we calculate the new width in H. |
![]() |
We need to alter the start byte to compensate for any left hand clipping | ![]() |
We need to convert out Xpos and Width, Ypos and height to a number
of pixels (by multiplying logical units by 2) we clear the carry to tell the calling routine we've a sprite to draw. |
![]() |
If the sprite is completely offscreen, there's no point trying to draw, so we set the carry and return | ![]() |
Hardware sprites
We load an Interrupt handler
in at address &0040, this will be executed by the hardware
during Vblank. We need to trasnfer out VblankInterruptHandler to address &FF80, so it can run during the interrupt. |
![]() ![]() |
We use port &FF46, which takes the top byte of the cache address, and transfers the cache to the hardware sprite data. | ![]() |
Sending the sprites!
When we want to draw our sprite, we first run the DoCrop routine
to calculate the required cropping. If there's anything to draw, we then calculate the VRAM destination with GetScreenPos, and run the ShowSprite routine The Chibiko bitmap starts from tile 128 on the Gameboy Our object is 48x48 pixels - 6x6 tile patterns, so we'll need 36 hardware sprites to draw it to the screen. |
![]() |
The FillAreaWithSprites will draw a grid of sprites to the screen
via the cache, We need to give each 8x8 tile a different hardware sprite, and each has 4 bytes of data. Where the graphic is horizontally cropped, we skip some tiles after each line with SpriteHClip |
![]() |
As we crop our object, the number of hardware sprites it will need will decrease, so we need to 'zero' any unneeded sprites, to take them off the screen. | ![]() |
![]() |
We're only
going to look at the overscan bit today, as the sprite movement
isn't anything new! If you want to know about the direction processing, please see the other simple series examples. |
Setting up the screen
We're going to set up the screen, we need to set up all the CRTC
registers - you can see the full list here We use port &BCxx to select register xx We use port &BDxx to set the selected register to value xx &0C/D define the base screen address, and also allow the 32k screen we need for overscan. |
![]() |
Here are the values we're using |
![]() |
Our screen is huge, and spans two 16k banks... Bank 1 is &8200-&BFFF Bank 2 is &C000-&FFFF Memory <&8200 is free, so we'll use it for our stack! Memory &0000-&3FFF is free so we can use IM1, Memory &4000-&7FFF is free so we can use ram bank switching but there's a slight memory 'gap' between them we'll have to deal with! To make it clear where the two 'banks' start and end, we'll fill them different colors. |
![]() |
Here is the result. | ![]() |
Calculating the screen addresses
In overscan mode, our screen is 96 bytes. While our calculations
are similar, we need to do them slightly differently to cope with
the 'bank gap' Looking at Y line (in C) - we need to take each set of bits, and work with them separately: YYYYYYYY Y line number (0-270) -----YYY (0-7) - this part needs to be multiplied by &0800 and added to the total YYYYY--- (0-31) - This part needs to be multiplied by 96 (&60) There is a gap between the two memory banks, we need to cope with Screen base for lines<128 = &8200 Screen base for lines>=128 = &C000 (subtract 128 from y) We also need to add the Xpos (in bytes) |
![]() |
We also have a Get Next Line function to move down a line. in 8 line blocks we can just add &0800 to move down a lline, but when we get to the end of the &FFFF range we need to loop back to the top by adding &C060. We also have to deal with the 'gap' between the two banks at &BFA0-&BFFF |
![]() |
Our sprite drawing routine is unchanged from before. |
![]() |
Reading the joystick
The firmware will ruin our overscan, so we're using a hardware
direct joystick reading routine. The Joystick is connected to Line &49 of the keyboard matrix. Key input on the CPC goes through the 8255 PPI and AY soundchip, we need to use the PPI to talk to the AY, and tell it to read input through reg 14 There are 4 ports we need to use Port &F6C0 = Select Register Port &F600 = Inactive Port &F6xx = Receive Data Port &F4xx = Send Data to selected Register |
![]() |