;; This example demonstrates how to draw a compressed sprite on the screen. ;; ;; The compressed sprite data uses control codes to determine the following: ;; 1. end of line ;; 2. draw x bytes containing only opaque pixels ;; 3. skip x bytes containing only transparent pixels ;; 4. draw x bytes which have mask and data ;; ;; Compressed sprites are a good way of storing large sprites that are not rectangular ;; in shape and which have a lot of transparent pixels around them. ;; ;; The idea is to save both space, and to save time drawing them - no need to mask every byte ;; as is done with a standard sprite drawing routine. ;; ;; Clipping a compressed sprite is more difficult because it's not so easy to determine which area to draw and ;; what not to draw. ;; ;; There are 2 drawing functions. One which shows the sprite, the other shows the control codes. ;; ;; When control codes are drawn: ;; - flashing pink/blue shows the fully opaque regions ;; - white shows fully transparent regions ;; - grey shows the masked regions ;; ;; You can see that we don't draw anything on the right side. In a normal sprite routine we would be masking the ;; pixels in this area although nothing would be drawn and we would waste time for a large sprite. ;; ;; Other ways to draw a large sprite would be to split it into smaller sprites and draw them together. org &8000 ;; turn off listing; remove for pasmo assembler nolist scr_set_mode equ &bc0e txt_output equ &bb5a mc_wait_flyback equ &bd19 km_test_key equ &bb1e scr_set_ink equ &bc32 sprite_height equ 25 ;; sprite height in lines sprite_width_pixels equ 24 ;; sprite width in mode 0 pixels sprite_width_bytes equ sprite_width_pixels/2 ;; sprite width in bytes ;;----------------------------------------------------------------------------------------------------- start: ;; set mode to 0 ld a,0 call scr_set_mode ;; put something on the screen so we can see the sprite masking working ld d,'A' ld b,24 l2: ld c,20 l1: ld a,d call txt_output dec c jr nz,l1 inc d dec b jr nz,l2 ;; make table of screen addresses for the start of each scanline call make_scr_table ;; draw sprite call draw_sprite_cur_coords ;;--------------------------------------------------- main_loop: ;; wait for VSYNC call mc_wait_flyback ;; get current coords and store them in prev coords ;; we compare new coords to previous coords to determine ;; if we should erase and then redraw the sprite. ;; this avoids continuous flicker if the sprite is not moving ;; however, the sprite can still disapear when moving around ;; the screen because there is a time when the sprite has been ;; deleted, the monitor has drawn it when it has been deleted ;; and we then draw the new version too late. ld hl,(sprite_coords) ld (prev_coords),hl ;; check keys call do_keys ;; wait for next interrupt ;; this ensures our code takes longer than the vsync time ;; so we can always wait just for the start of the vsync halt ;; draw the sprite if it has moved call redraw_sprite jp main_loop ;;----------------------------------------------------------------------------------------------------- redraw_sprite: ;; coords changed? ld hl,(prev_coords) ld bc,(sprite_coords) or a sbc hl,bc ld a,h or l jr nz,rs1 ;; no change in x or y, so no update ret ;;----------------------------------------------------------------------------------------------------- rs1: ;; restore background (where sprite used to be) ld hl,(prev_coords) ld de,sprite_background ld b,sprite_height ld c,sprite_width_bytes call sprite_background_restore draw_sprite_cur_coords: ;; store background pixels where sprite is now located ld hl,(sprite_coords) ld de,sprite_background ld b,sprite_height ld c,sprite_width_bytes call sprite_background_store ld hl,(sprite_coords) ;; uncomment this line to draw data to show sprite codes ;;call draw_sprite_code ;; uncomment this line to draw sprite for real call draw_sprite ret ;;----------------------------------------------------------------------------------------------------- ;; initialise a table make_scr_table: ld hl,&c000 ld b,200 ld ix,scr_table mst1: ld (ix+0),l ld (ix+1),h call scr_next_line inc ix inc ix djnz mst1 ret ;;----------------------------------------------------------------------------------------------------- scr_table: defs 200*2 ;;----------------------------------------------------------------------------------------------------- ;; input conditions: ;; H = x byte coordinate (0-((scr_width_chars*2)-1)) ;; L = y coordinate (0-((scr_height_chars*char_height_lines)-1)) ;; output conditions: ;; HL = screen address get_scr_addr: push bc push de ld d,0 ld e,h ;; DE = x coordinate ld h,0 ;; HL = y coordinate add hl,hl ;; x2 for offset into table (2 bytes per y line) ld bc,scr_table add hl,bc ld a,(hl) inc hl ld h,(hl) ld l,a ;; HL = screen address for start of line ;; now add on X add hl,de pop de pop bc ret ;;----------------------------------------------------------------------------------------------------- ;; input conditions: ;; HL = screen address ;; output conditions: ;; HL = screen address (next scanline down) ;; scr_next_line: ;; go down next scan line ld a,h add a,8 ld h,a ret nc ;; add on amount to go to next char line ld a,l add a,&50 ld l,a ld a,h adc a,&c0 ld h,a ret ;;----------------------------------------------------------------------------------------------------- code_eol equ 0 code_transparent equ &40 code_mask equ &80 code_opaque equ &c0 ;; this function draws a special byte for each of the codes. It is used ;; to show how the sprite is described. draw_sprite_code: call get_scr_addr ld de,sprite_pixels ld b,sprite_height dsc_next_line: push bc push hl dsc_next_code: ld a,(de) ;; get code/count and &c0 jr z,dsc_spr_eol ;; end of line? cp code_transparent jr z,dsc_spr_trans cp code_mask jr z,dsc_spr_mask ;; drop through to opaque ;; draw opaque pixels ld a,(de) and &3f ld b,a inc de dsc_op1: ld (hl),&ff inc hl inc de djnz dsc_op1 jr dsc_next_code ;;------------------------------------------------ ;; skip fully transparent pixels dsc_spr_trans: ld a,(de) ;; get count and &3f ld b,a inc de ;; skip bytes on screen dsc_st1: ld (hl),&30 ;; increment screen address inc hl djnz dsc_st1 jr dsc_next_code ;;-------------------------------------------- ;; mask pixels dsc_spr_mask: ;; get count ld a,(de) and &3f ld b,a inc de dsc_mask1: ld (hl),&3 inc hl inc de inc de djnz dsc_mask1 jr dsc_next_code ;;-------------------------------------------- dsc_spr_eol: inc de pop hl call scr_next_line pop bc djnz dsc_next_line ret ;; this is the real sprite drawing code draw_sprite: call get_scr_addr ld de,sprite_pixels ld b,sprite_height ds_next_line: push bc push hl ds_next_code: ld a,(de) ;; get code/count and &c0 jr z,ds_spr_eol ;; end of line? cp code_transparent jr z,ds_spr_trans cp code_mask jr z,ds_spr_mask ;; drop through to opaque ;; draw opaque pixels ld a,(de) and &3f ld b,a inc de ds_op1: ;; read pixels ld a,(de) ;; write directly to screen ld (hl),a inc hl inc de djnz ds_op1 jr ds_next_code ;;------------------------------------------------ ;; skip fully transparent pixels ds_spr_trans: ld a,(de) ;; get count and &3f ld b,a inc de ;; skip bytes on screen ds_st1: ;; increment screen address inc hl djnz ds_st1 jr ds_next_code ;;-------------------------------------------- ;; mask pixels ds_spr_mask: ;; get count ld a,(de) and &3f ld b,a inc de ds_mask1: ;; read mask ld a,(de) ;; mask with screen and (hl) ;; store this temporarily ld c,a inc de ;; get pixels ld a,(de) ;; combine with masked screen pixels or c ;; write back to screen ld (hl),a ;; increment screen address inc hl inc de djnz ds_mask1 jr ds_next_code ;;-------------------------------------------- ds_spr_eol: inc de pop hl call scr_next_line pop bc djnz ds_next_line ret ;;----------------------------------------------------------------------------------------------------- ;; the compiled sprite pixel data ;; - normal pixel data (uncompressed) would be 300 bytes long. ;; but we should also consider how long it would take to draw. sprite_pixels: ;;288 bytes defb &042,&081,&0aa,&044,&0c1,&0cc,&000,&042,&0c2,&0cc,&03c,&081,&055,&028,&000,&042 defb &0c3,&09c,&03c,&09c,&000,&042,&0c2,&09c,&03c,&081,&055,&028,&044,&081,&0aa,&050 defb &0c1,&0f0,&081,&055,&0a0,&000,&042,&0c2,&0cc,&03c,&045,&081,&0aa,&050,&0c1,&0f0 defb &081,&055,&0a0,&000,&041,&081,&0aa,&044,&081,&055,&088,&0c1,&03c,&045,&0c1,&0b4 defb &081,&055,&0a0,&000,&043,&0c1,&00c,&081,&055,&008,&043,&0c2,&00c,&0b4,&000,&041 defb &0c9,&00c,&00c,&00c,&00c,&00c,&00c,&00c,&00c,&0b4,&000,&081,&0aa,&004,&0c7,&00c defb &00c,&00c,&00c,&00c,&0c0,&00c,&081,&055,&080,&000,&081,&0aa,&004,&0c5,&00c,&00c defb &00c,&00c,&00c,&000,&081,&0aa,&004,&0c5,&00c,&00c,&00c,&00c,&00c,&000,&081,&0aa defb &004,&0c4,&084,&00c,&00c,&00c,&081,&055,&008,&000,&0c5,&00c,&084,&00c,&00c,&00c defb &081,&055,&008,&000,&0c1,&00c,&081,&055,&080,&0c3,&00c,&00c,&00c,&000,&0c1,&00c defb &081,&055,&080,&0c3,&00c,&00c,&00c,&000,&0c1,&03c,&041,&081,&0aa,&010,&0c1,&030 defb &081,&055,&020,&000,&0c1,&03c,&041,&081,&0aa,&010,&0c1,&030,&081,&055,&020,&000 defb &0c1,&03c,&041,&081,&0aa,&010,&0c1,&030,&081,&055,&020,&000,&081,&055,&028,&041 defb &081,&0aa,&010,&0c1,&030,&081,&055,&020,&000,&042,&081,&0aa,&010,&0c2,&030,&030 defb &000,&042,&081,&0aa,&010,&0c2,&030,&030,&000,&042,&081,&0aa,&010,&0c2,&030,&030 defb &081,&055,&020,&000,&042,&081,&0aa,&010,&081,&055,&020,&081,&0aa,&010,&081,&055 defb &020,&000,&042,&081,&0aa,&010,&0c1,&0cc,&081,&0aa,&010,&0c1,&064,&081,&055,&088 defb &000,&042,&081,&0aa,&044,&0c1,&0cc,&081,&0aa,&044,&0c1,&0cc,&081,&055,&088,&000 ;; sprite ;;----------------------------------------------------------------------------------------------------- ;; check keyboard ;; ;; cursor keys do_keys: ld a,0*8+0 call km_test_key jr nz,move_up ld a,0*8+1 call km_test_key jr nz,move_right ld a,0*8+2 call km_test_key jr nz,move_down ld a,1*8+0 call km_test_key jr nz,move_left ret ;;----------------------------------------------------------------------------------------------------- move_up: ;; do not move up if we are already on line 0 ld a,(y_coord) or a ret z dec a ld (y_coord),a ret ;;----------------------------------------------------------------------------------------------------- move_down: ;; avoid going off bottom by checking for bottom-most y position ld a,(y_coord) sub 200-sprite_height ret nc ;; greater than or equal to this pos then don't increment ld a,(y_coord) inc a ld (y_coord),a ret ;;----------------------------------------------------------------------------------------------------- move_left: ;; avoiding going off left side ld a,(x_coord) or a ret z dec a ld (x_coord),a ret ;;----------------------------------------------------------------------------------------------------- right_side equ (40*2)-sprite_width_bytes move_right: ld a,(x_coord) sub right_side ret nc ld a,(x_coord) inc a ld (x_coord),a ret ;;----------------------------------------------------------------------------------------------------- ;; H = x byte coord ;; L = y line ;; DE = address to store screen data ;; B = height ;; C = width ;; store a rectangle from the screen into a buffer sprite_background_store: call get_scr_addr sprite_back_height: push bc push hl sprite_back_width: ld a,(hl) ;; read from screen ld (de),a ;; store to buffer inc hl inc de dec c jr nz,sprite_back_width pop hl call scr_next_line pop bc djnz sprite_back_height ret ;; H = x byte coord ;; L = y line ;; DE = address to store screen data ;; B = height ;; C = width ;; ;; restore a rectangle to the screen sprite_background_restore: call get_scr_addr sprite_reback_height: push bc push hl sprite_reback_width: ld a,(de) ;; read from buffer ld (hl),a ;; write to screen inc hl inc de dec c jr nz,sprite_reback_width pop hl call scr_next_line pop bc djnz sprite_reback_height ret ;; a buffer to store screen behind sprite sprite_background: defs sprite_height*sprite_width_bytes prev_coords: prev_y_coord: defb 0 prev_x_coord: defb 0 sprite_coords: y_coord: defb 0 x_coord: defb 0 ;; make this executable for pasmo, uncomment for pasmo assembler end start