Mode2 MaskedSprite Plotter

=Mode 2 Masked Sprite Plotter = These routines will allow a sprite (with Mask) to be plotted anywhere on a Mode 2 screen. Simple adjustments can be made to make it work for other screen modes.

Provisos and restrictions

 * The sprite must be in the format generated by Swift, see the Swift Sprite documentation contained in the Swift download for details.
 * The mask is any pixel set to a value of 0 (usually black). A true black can be obtained by redefining colour 8 to actual colour black.
 * No check is made to see if the sprite is going to be partially (or wholly) off screen. If it goes off screen without you adding in checking code then corruption of your program code may result, or at the very least the sprite will appear partially on the other side of the screen.
 * To make full use in a program you'll need an sprite erase routine.
 * Written in P65 Ophis assembler.

The easy way
Download a copy of Swift (if you've not already got it) and download the Swift project in the download section below which has all the code. Once extracted (and your Swift installation is set up for P65) from the Zip just load the project into Swift and Run !

The hard way
The code and data has been written in vanilla P65 with only the includes being Swift specific. If you want to assemble without Swift just change the items the includes are referring to to paths to where you have stored those files.

Code
 .org $1900 .include "AcornConstants" ;______________________________________________________________________________________   ;______________________________________________________________________________________       ; constants .alias ScreenStartHighByte $30     ; high value of start of screen .alias MaskTable $900              ; mask table address. A page aligned table of                                      ; of data to make masked sprite plotting quicker. ; You must ensure this data is loaded into memory ; before the sprite plotting routine is called. ;______________________________________________________________________________________  ;______________________________________________________________________________________   ;______________________________________________________________________________________   ; memory locations used ; used by ScreenStartAddress,PlotSprite .alias XOrd $74                  ; X ord passed to ScreenStartAddress .alias YOrd $75                  ; Y ord passed to ScreenStartAddress .alias XYScreenAddress $78       ; and $79, contains the returned calculated ; screen address from ScreenStartAddress ; Used by PlotSprite .alias SpriteGraphicData $70     ; and $71. Holds the address (low in $70, high                                      ; in $71) of the sprite data. The format of which ; is width, height, followed by the actual graphic ; data in column order. See Swift Sprite Data ; Formats document for further explanation. ; Used by Demo code .alias SpriteCollection $7A      ; and 7B, address of the sprite collection. A                                      ; sprite collection is literally a collection of                                      ; sprites all together in an structured order ; The structure of which should be documented. For ; this demo the structure is described in the ; Swift Sprite Data Formats document. The ; particular sprite collection used here is                                      ; "column order, no fixed width or height" ;______________________________________________________________________________________  ;______________________________________________________________________________________   ;______________________________________________________________________________________    ; workspace ; memory only used within routines, not as passed or returned params .alias Temp $9F                  ; ScreenStartAddress : holds the high byte ; result of a multiplication of the X ord by 8 .alias Width $72                 ; PlotSprite : width of current sprite to plot .alias Height $73                ; PlotSprite : height of current sprite to plot .alias YStartOffset $76          ; PlotSprite : Offset to add to the screen address ; of a block of column sprite data to get the ; actual screen address to plot at. Used with ; indexed addressing. See PlotSprite for more info ;______________________________________________________________________________________  ;______________________________________________________________________________________   ;______________________________________________________________________________________   Main: JSR SetUpGameScreen lda #Sprite                               ; Do the same with hi byte sta SpriteCollection+1 ; Plot first sprite lda #8                                     ; set X to 8 sta XOrd lda #45                                    ; Y to 45 sta YOrd ; get address of first sprite ; First 2 bytes of sprite collection are the offset address of the first sprite in the ; collection. This value added to the start to the Sprite collection address will give ; the location of the sprite 1 in the collection. Swift actually supports a method for ; storing absolute addresses here which would make this even quicker as you would ; just load in the address from the first two bytes and that would be the address of ; the sprite in the collection, saving time and bytes. But to make this code ; usuable for none swift users the relative address method has been used for the ; accompaning sprite collecton ldy #0                             ; Get Low offset value lda (SpriteCollection),Y clc                                        ; adc #Sprite                       ; add to start of sprite collection address sta SpriteGraphicData+1            ; store in out pointer to the sprite for the plot ; routine. jsr PlotSprite                     ; Plot the sprite ; Plot second sprite in the sprite collections lda #20                            ; X=20 sta XOrd lda #130                           ; Y=130 sta YOrd ; get address of second sprite ldy #2                             ; offset address is a bytes 3 and 4 of collection ; (index 2 and 3) lda (SpriteCollection),Y           ; again, the values are offsets, so same as for clc                                ; Sprite number 1 adc #Sprite sta SpriteGraphicData+1 jsr PlotSprite                    ; Plot the sceond sprite rts ;______________________________________________________________________________________  ;______________________________________________________________________________________   ;______________________________________________________________________________________  PlotSprite: ; Plots a masked Mode 2 sprite to mode 2 screen. 0 denotes a masked pixel. ; your sprite most wholly fit on the screen, partly off the screen in any direction is ; not permitted and will break the code. However this is suitable for a lot of games. ; entry params ; XOrd                       : is X position to plot sprite at    ; YOrd                        : is Y position to plot sprite at, corrupted on exit ; SpriteGraphicData          : 2 zero page bytes holding address of sprite graphic ;                            : data, for description of this data structure see the ;                            : Swift Sprite Data Formats document. ; workspace ; Width                      : Width of sprite ; Height                     : Height of sprite ldy #0                         ; set index to the beginning of the sprite data lda (SpriteGraphicData),Y      ; From the first byte of the Sprite data get the width sta Width                      ; of this sprite and store it  iny                             ; move index pointer (Y) to next byte of sprite data lda(SpriteGraphicData),Y       ; From the second byte of the Sprite data get the sec                            ; height but for use later on we actually set the height sbc #1                         ; to one less than it actually is. This is because later ; we want to add the height to the start of the ; sprite data later on for each column of data in the ; sprite. This will give the index into the sprite ; data where we want to pull data from. So if height ; is 1 for example this would actually mean read from ; the sprite data +1 which would actually be one ; greater than where we wish to actualy get data from ; So it needs to be one less. By using 4 cycles here ; we save 10 cyles (using a DEY) later in our column ; loop for a typical 5 byte wide sprite. Not much at a                                 ; all, but better saved than wasted ! ; we'd lose on a 1 byte wide sprite but break even on                                 ; just a 2 byte wide sprite and save more cycles on                                  ; anything above that, an most sprites are move than ; 2 byte wide in a typical game. Anything less and you ; should consider a specialised plot routine. sta Height                     ; Store it. ; The YOrd passed in is top of sprite, however the plotting a sprite to screen works ; more quickly if we plot from the bottom of the sprite back to the top (visually  ; upwards but actually going downwards in the sprite data and screen memory).The speed ; increase is due to flags that get automatically set when operations hit zero. So ; counting down in data rather than upwards is usually quicker and more effiecient on  ; memory as we save on a CMP instruction. So we manipulate the YOrd here to be the ; value of Y at the bottom of the sprite. There is obviously a small time penalty for ; doing this here, but it saves more then we lose in the main loop of plotting. clc                         ; carry currently set, ensure cleared for addition adc YOrd                    ; A already contains height from previous operation so                                ; add YOrd passed in to it. sta YOrd                    ; store result back in YOrd. Note : this is why we state ; that Y Ord is corrupted on exit in the paramter list ; above. jsr ScreenStartAddress      ; we've now got eh YOrds we wish to start plotting at                               ; so jump to this routine to get the address of that ; part of the screen in memory. ; now we've got the screen start address for X,Y in XYScreenAddress ; what we now want to do is get the start of the character row for this address and ; use the X register to index to that address in order to perform efficent plotting. ; Manipulating the memory locations for the screen address in the middle of a sprite ; plot loop is not a good idea as it will effect speed performance. So we seperate out ; the row lines and store those in X and a Temp var for easy recall when plotting the ; next column of sprite data. See ; http://www.retrosoftware.co.uk/wiki/index.php/Calculate_Screen_Address) for  ; explantion of character rows and line rows.  ; first we'll get the line row component of the address and put it in X and our   ; preservation var.  lda XYScreenAddress                ; get low byte address of screen address    and #7                             ; keep just the lowest 3 bits (values 0 to 7), the                                     ; line row component fo the character row for this                                     ; screen address.  tax                                ; transfer to X as we're using this in indexed                                      ; addressing.  debug2:                                     sta YStartOffset                   ; Preserve the row line component for use later,                                      ; as we'll need it again when plotting the next ; column of sprite data. ; now we get the address of the character row that the XYScreenAddress is part of lda XYScreenAddress and #248                          ; mask off line row component to leave character ; row component. sta XYScreenAddress               ; low byte of screen address is now pointing to                                     ; the start of the character row for this plotting ; address. ; now we come to something that can throw some people off.... ; To move blocks of data in memory (in this case sprite data to screen) we need to ; used indexed addressing. There are two main types available to use for what we need ; to do. "Post indexed indirect addressing" and "Absolute Indexed Addressing". Look ; them up in the advanced user guide. To use post indexed to read from the sprite ; memory and write to the screen memory would take 6 cycles per instruction, so 12 ; cycles all together for the read from sprite and write to screen combined. ; However Ablsolute Indexed would take 5 cycles per instruction, or a saving of 2 ; cycles per read/write to screen. May not sound much ? But that saving is for every ; single byte of sprite data. An average sprite of say 5 bytes (10 pixels across) by ; 20 bytes down would be 100 bytes of data to read and write for one sprite, or 200 ; cycles to potentially save, multiply this again by the number of sprites you have ; in your game, say 5 and that's 1000 CPU cycles saved. You could certainly write ; a lot of your game logic code in the time saved in your sprite plotter ! ; The slight downside is that there is some setting up to do per sprite which equates ; to about 40 cycles per sprite. But still a large saving overall. If however you ; were plotting a lot of small sprites (less than 20 bytes in size) this might not ; be the most efficient way to plot and you would need to re-evaluate whether to ; have a second more optimsed plotter for small sprites. ; The other downside is that the Absolute Addressing technique has to write it's base ; address into the code (so it's basically self modifing code). This would make this ; tehnique absolutly useless if ran from ROM where you cannot have self modifing code ; as you cannot write to ROM. But in mose games this is not an issue. ; ok, all that said, we need to copy our screen start address to plot at and our ; sprite address to read from into our plotting code. sta ScreenPixelAddress+1         ; A has screen address low byte, Store in the ; memory location of the STA $FFFF instruction below sta ScreenPixelAddressForLoad+1  ; store here also, this is part of the code that ; pulls in the current byte on the screen for use ; when working out which pixels to plot (as this is                                   ; a masked sprite). lda XYScreenAddress+1            ; Get high byte of screen address and store in  sta ScreenPixelAddress+2          ; high byte of address to plot at in the code below sta ScreenPixelAddressForLoad+2  ; and in this location too. this is part of the code ; that pulls in the current byte on the screen for ; use when working out which pixels to plot (as this                                    ; is a masked sprite). ; we need to store the start of the actual graphic data into the code so it can ; read from it. Currently the address passed in SpriteGraphicData is the very start ; of a sprite, but the first two bytes contain width and height. So we need to ; add 2 to this address and store this value in our code. ; note we are not clearing the carry flag prior to this addition as it is set to  ; cleared on exit from the ScreenAddressStart routine which was called above. lda SpriteGraphicData            ; get low byte value of sprite data address adc #2                           ; to move past width and height sta SpritePixelAddress+1         ; store in code below as discussed for ABS indexed ; addressing lda SpriteGraphicData+1          ; get high byte value of sprite data address adc #0                           ; add in any carry from previous addition sta SpritePixelAddress+2         ; store in code below as discussed for ABS indexed ; addressing ; ok the main plot bit, X register cotains index to start of screen location when ; combined with the character row address in XYScreenAddress, which itself has been ; transferred to ScreenPixelAddress+1,2 and ScreenPixelAddressForLoad+1,2 below ; Y register is uised to index into the sprite data, initially set to height as we ; are plotting from the bottom of the sprite upwards. ; we plot 1 column of sprite data at a time. PlotXLoop:                                   ; the column to plot loop ldy Height                                 ; set Y to the height of the sprite, in                                                ; fact height is set to height less 1, ; so Y will be height less 1 also. See ; notes above, but basically we save ; 2 cycles per pass of the column loop ; here by not having a DEY instruction. PlotLoop:                                  ; plots the full column of data SpritePixelAddress: lda $FFFF,Y                              ; dummy address, will be filled in by                                                 ; code, loads the sprite pixel STA pokeme+1                             ; store it to LSB of this address, so it                                                ; acts as an index into a mask table for ; this byte ScreenPixelAddressForLoad: lda $FFFF,X                              ; dummy screen address, will be filled ; in by code, get's byte at screen ; location to plot at     pokeme: and MaskTable                            ; and the byte at the screen address ; with the mask table, this address has ; been altered by the above code ; note that because we're using the LSB ; of this address to act as an index the ; Mask tabel must be aligned on a page ; boundary for this to work ORA pokeme+1                             ; now OR the sprite pixel byte with the ; masked byte from the screen, remember ; it was stored at pokeme+1 to act as an                                               ; index into the mask table ScreenPixelAddress: sta $FFFF,x                              ; finally store the combination of                                                 ; screen and sprite byte back to screen dex                                      ; move to next byte up      bmi MoveToNextScreenAddresBlockUp         ; New column ? if yes branch to code to                                                ; alter addresses. ReturnFrom_MoveToNextScreenAddresBlockUp: ; return from above jump, note quicker ; overall to have a return branch here ; as it will only occur once in 8 DEX's,                                               ; saving the 7 cycles for the loss of 3 ; braching if not 0 would have cost 7 ; cycles for the gain of 3, so overall 4 ; cycles faster dey                                      ; decrement the pointer to the sprite ; data, if it hits zero we need to                                                ; adjust to start of next column bpl PlotLoop                               ; keep on plotting, still got pixels ; left in the column to plot dec Width                                  ; decrement width in bytes beq EndPlotSprite                          ; has all sprite been plotted out, if so                                                ; exit ; still got some columns to plot so adjust sprite data to start at top of next ; column (we add the Y index on later to get to bottom of column data) sec                                       ; note height is actually 1 less than the lda SpritePixelAddress+1                  ; real height, so we actually set the adc Height                                ; carry flag here instead of clearing it    sta SpritePixelAddress+1                   ; to effectivly add the real height ; See above as to why height is 1 less ; than real height (saves us some time                                              ; at start of column plot loop). bcc NoIncToSpritePixelHighByte inc SpritePixelAddress+2 clc                                       ; clear ready for next operation NoIncToSpritePixelHighByte: lda XYScreenAddress                       ; move to next column on screen adc #8                                    ; sta XYScreenAddress sta ScreenPixelAddress+1 sta ScreenPixelAddressForLoad+1 BCC NoIncToXYScreenAddressHiByte           ; the vast majority of times (31 out of                                                 ; 32) this jump occurs, so save 3 cycles ; 31 times by not having to add any carry ; to the hi byte of the Screen Address. ; Every 32 positions we use 2 extra ; cycles. If a sprite is split over a                                                ; boundary then will always plot a bit ; slower. But generally performance will ; be quicker. inc XYScreenAddress+1                      ; if here carry is set, increment the hi                                                ; byte to relect this carry. We don't do                                               ; a normal ADC as this would be overall ; slower when your just adding a carry ; and no other value NoIncToXYScreenAddressHiByte: lda XYScreenAddress+1 sta ScreenPixelAddress+2 sta ScreenPixelAddressForLoad+2 ldx YStartOffset bpl PlotXLoop                                ; The overflow flag (negative flag) ; will never be set here as the max ; Y offset that can occur is 7 so it's                                                ; always a positive loaded into X, note ; 0 counts as positive also EndPlotSprite: rts MoveToNextScreenAddresBlockUp: ; yes we moved to another character row up on screen, so change base address of screen ; start and start plotting from that sec                                        ; we do this by subbing $280 from value to                                               ; get to next row lda ScreenPixelAddress+1 sbc #$80 sta ScreenPixelAddress+1                   ; again we need ot store these in the two sta ScreenPixelAddressForLoad+1            ; locations required. lda ScreenPixelAddress+2 sbc #2 sta ScreenPixelAddress+2 sta ScreenPixelAddressForLoad+2 ldx #7                                     ; reset X to 7  (bottom of screen column) bne ReturnFrom_MoveToNextScreenAddresBlockUp ; end MoveToNextBlockUp ;______________________________________________________________________________________ ;______________________________________________________________________________________  ;______________________________________________________________________________________    SetUpGameScreen: ; Sets up the game screen to how we want it ; Switch to mode 2 lda #$16 jsr oswrch lda #2 jsr oswrch ; now in mode 2 ; redfine logical colour 8 to physical colour 0 (black), as we use logical colour 0 ; as the mask, so 8 becomes the new black lda #$C ldx #LogicalColour8ToActualColour0 jsr OSWORD rts LogicalColour8ToActualColour0: .byte 8                                ; logical colour 8 .byte 0                                ; physical colour 0 (black) ;______________________________________________________________________________________ ;______________________________________________________________________________________  ;______________________________________________________________________________________        ScreenStartAddress: ; calculates the screen start address for a mode 2 screen given X,Y ords ; no check is made to see if X and Y are valid on screen ords, if they are not then ; the address returned will not be valid and writing to it could well corrupt the ; main program ; entry params ; XOrd is X     ; YOrd is Y,     ; exit results ; XYScreenAddress,XYScreenAddress+1  contain the calculated screen address ; Carry flag will be cleared. ; workspace ; Temp ; The calculation for this routine is : ; result=ScreenStartAddress+((((Y div 8)*640)+(Y and 7)) + X * 8) ; first we are going to perform the (Y div 8)*640 part of the calculation ; this will give us the character row start (not the pixel row yet) ; The Y ord is actually in pixels (0 to 255 down the screen) There are 8 pixel rows ; per character row (see screen layout diagram) ; so divide Y by 8 to get character rows lda YOrd                         ; load A with the value of the Y ordinate and #248                         ; make lower three bits zero (see next paragraph) lsr                              ; effect divide by 2 lsr                              ; effect divide by 4 ; but hang on... we need to divide by 8 but we've only divided by 4 ! ; well the 640 lookup table is a table of 2 byte values, so to index into it we   ; need to set the index to 2 times it's value to index to the correct value ; by only dividing by 4 effectivly means our value is already 2 times bigger ; so we save on an ASL instructionm =1 byte and 2 cycles. ; The and #248 at the begining ensured that the last bit not LSR's out of the ; accumulator is set to zero, in fact we could have just used AND #251 if we'd   ; wanted to jsut to zero the bit that would be left ; we use a 640 multiplication look up table to speed things up. Referring to the ; screen layout, the start of each character row jumps by 640 in memory compared ; to the previous and there are 32 character rows down the screen (256 pixel rows) ; so we need 32 entries in our multiplication table and as each entry is two bytes ; each (as results can go beyond what a single byte can hold), the total table size ; is 64 bytes. To look up 0 * 640 would mean the low byte of the result is in the ; very first byte and the high byte is in the second byte of the table. ; 1 *640, would have the result in byte 2 and 3 etc. etc. tay                              ; we will use register Y to index into the table ; so move the index value into Y   lda LookUp640,Y                   ; look up low byte value first sta XYScreenAddress              ; and store in low byte of result iny                              ; move the index to point to the high byte in the ; table lda LookUp640,Y                  ; get the high byte result of the mulitplication sta XYScreenAddress+1            ; store in the high byte of the result ; we have now got the value of (Y div 8)*640 in our result bytes ; now add (Y AND 7) to the result bytes, this will give us the pixel (rather than   ; character row) value. All we are doing is wanting to add in the lowe 3 bits of Y   ; to the result of the character row. See the screen layout diagram if unsure. lda YOrd                         ; get the Y    and #7                            ; strip out all but lower 3 bits ; note that normally you would ensure the carry flag is clear here by having a CLC ; instruction. However right at the begining of this routine we masked out the lower ; 3 bits of A and then used LSR, this would ensure the carry flag is currently clear ; as no other instructions have been used that effect it since then ; Note that it's critical the AND #248 occurred before the LSR, you could have had ; code that came to the same result if you'd LSR, LSR then AND #1 to set bit 0 to 0 ; but you would have then not known the state of the carry flag adc XYScreenAddress              ; add lower 3 bits of Y to result. Even though the ; current result is a 16 bit value we only need to                                       ; add to low byte as will never trigger a carry as                                      ; only adds a max value of 7 to a value that will ; always be at least 8 less than 255 sta XYScreenAddress              ; store the new result ; now add this result to the ScreenStartAddress ; note again we know that the carry is clear, so no CLC ; also note the screen start address is $3000 (i.e. lower value is 00) we need ; only add the high byte value to the high byte of our current result (no point   ; adding 0 to anything as the result would not change !) lda #ScreenStartHighByte            ; high byte value of screen start address adc XYScreenAddress+1               ; add to current high byte value sta XYScreenAddress+1               ; store result back in high byte value ; now add the result of X*8 to the current result ; For every X pixel we need to add 8 bytes, for this we won't use ; a look of table as it's quite easy and quickish to multiply by 8. ; It is true that it would be quicker with a look up table but it would take ; about 320 bytes for only a small gain ; first X*8 ; carry will still be clear, no need to clear lda #0 sta Temp    ; temp store for hi byte value of result lda XOrd asl         ; effective * 2 ; no need to do a rol for high byte as max result at this point is                 ; 79 *2 =158 asl         ; effective * 4 ; the max result now will be 79*4=316 ( $13C), so the carry contains ; the high byte if any to add to the high byte of the result, put it in                ; a temp var rol Temp    ; will effectivly clear carry as well, see below asl         ; effective * 8 rol Temp    ; could be another carry so roll into temp ; will effectivly clear carry as well (as temp was 0 at start and max                 ; two shifts occurred, see below for use of cleared carry       ; ok got result of X*8, now add to current result    ; no need for CLC as carry flag will be 0 as we know that the max result possible is     ; 79 *8 =632 which is $278, so only ever will have a max value of 2 in Temp so all     ; that would  have been ROL'd out when doing the "rol Temp" into carry will have     ; been 0's    adc XYScreenAddress               ; add A (low value of multiply to low value of                                      ; result sta XYScreenAddress              ; store in low value of result lda Temp                         ; high byte result of multiplication adc XYScreenAddress+1            ; add to high byte of result with any carry sta XYScreenAddress+1            ; store in high byte of result rts ; end ScreenStartAddress ;______________________________________________________________________________________  LookUp640:                          ; a 640x multiplication table .incbin "LookUpTable640" Sprite:                            ; The sprites we're plotting .incbin "Sprite"                   ; "column order, no fixed width or height" ; Swift format byte will have been stripped from ; start.

LookUpTable640 00 00 80 02 00 05 80 07 00 0A 80 0C 00 0F 80 11 00 14 80 16 00 19 80 1B 00 1E 80 20 00 23 80 25 00 28 80 2A 00 2D 80 2F 00 32 80 34 00 37 80 39 00 3C 80 3E 00 41 80 43 00 46 80 48 00 4B 80 4D

Mask Table (must be page aligned) FF AA 55 00 AA AA 00 00 55 00 55 00 00 00 00 00 AA AA 00 00 AA AA 00 00 00 00 00 00 00 00 00 00 55 00 55 00 00 00 00 00 55 00 55 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  AA AA 00 00 AA AA 00 00 00 00 00 00 00 00 00 00 AA AA 00 00 AA AA 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  55 00 55 00 00 00 00 00 55 00 55 00 00 00 00 00  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  55 00 55 00 00 00 00 00 55 00 55 00 00 00 00 00  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

Sprite collection Data

NB : The file included in the Swift Project download is not exactly the same as this as it has a single byte pre-fixing the data. This tells Swift the format of the sprite collection. Here, the data is presented as a Sprite Collection less this prefix. Swift, when it assembles the project auto strips this for you (unless you specifically tell it not to).  04 00 E6 00 08 1C 00 00 00 00 00 00 00 00 14 3C 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 14 14 3C 28 00 14 3C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 14 00 00 00 14 3C 28 15 00 14 3C 3C 00 15 00 14 68 68 3C 3C 14 14 14 00 14 3C 3C 3C 3C 00 00 14 3C 03 03 03 3C 3C 28 15 2A 14 3C 3C 68 94 3C 3C 68 28 28 3C 14 14 00 14 14 28 28 3C 3C 16 16 3C 3C 28 00 2A 14 3C 3C 94 C0 3C 3C 3C 3C 3C 3C 14 14 28 3C 3C 3C 00 3C 3C 28 00 00 00 00 00 00 00 3C 94 68 3C 68 94 68 68 3C 3C 3C 3C 3C 3C 14 00 00 3C 28 00 00 00 00 00 00 00 00 00 00 28 68 94 68 3C 68 3C 3C 3C 3C 3C 68 3C 68 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80 28 80 28 94 68 94 68 94 68 80 28 00 00 07 11 00 00 00 00 11 11 11 22 22 22 22 22 22 22 22 11 00 00 11 33 26 26 04 00  00 00 00 00 00 00 00 00 00 33 11 33 0C 0C 18 18 0C 0C 0C 0C 04 04 04 00 00 00 00 33 0C 0C 0C 0C 0C 0C 0C 0C 0C 30 0C 0C 0C 0C 00 00 22 33 0C 0C 24 24 0C 0C 0C 0C 08 08 08 00 00 00 00 00 22 33 19 19 08 00 00 00 00 00 00 00 00 00 00 33 00 00 00 00 22 22 22 11 11 11 11 11 11 11 11 22 00

The Boot File

VDU 19,8,0,0,0,0 OSCLI "LOAD MASK 900" OSCLI " LOAD SpriteP" COLOUR 128 : REM Change to any background colour you wish (values from 128 to 135) CALL &1900

Downloads
[[Media:Mode2SpritePlotter.zip|The enitre Swift Project]] (Highly recommended, just load into Swift and run !, contains all files you need even if not running in Swift )