M/C Part 20 What we are looking for is a multi-purpose, masking, chopping and dish-washing bit of code which is easy to use, easy to understand, and easy to write. Well, it is the time of year for miracles... To start with, let's think about the basics. A sprite (for those of you who haven't a clue what I've been talking about for the past two pages) is a little graphic image that moves, probably with animation. "Masking" refers to the way a "hole" is cut out of the background before our image is put on. This avoids big square blocks (or big square sprites!) The simplest way to do this is to have two sets of data for each pixel of the sprite. The first is the "mask" - either 0000 or 1111 for each pixel, to indicate background to be cut out. The bitwise function AND will do this for us simply, so 0000 will cut out a pixel, and 1111 will leave it intact. The second set of data is the colour. Each pixel's colour (0-15) is stored and ORed with the hole. Obviously, for bits of background to leave alone, the colour is 0, or it becomes corrupt. Incidentally, this isn't the only way to mask. There's the good old "bitwise merge" (XOR, AND, XOR) for one. But I prefer simplicity. (NOT the kind you find in the female hygiene section of Asda. Incidentally, in the superstore they built on top of old Muirton Park, I'm convinced the female hygiene shelves are in the same place as the referee's changing room was!) Righty ho, we know how to mask. Because each pixel is stored in half a byte, the simplest way to handle the data is to have a mask byte followed by a colour byte for two pixels. (Followed by a mask byte, colour byte, mask, colour, etc...) And the usual way to store this is from top left to bottom right. That deals with the data. But what else does our routine need to know? - Address of data - Address of store for background - Coords - Size - How much to chop off We'll take coords as being the actual coords we use for first pixel we print, regardless of whether the sprite is hanging off the left side/ top of the screen. Size? Well, we need to know the number of pixels to print in the x and y directions, together with the total width of the sprite, so we can work out where to tke each line of data from. Depending on the type of application, you might want to store the background the sprite is being printed on. This routine will do that, but if you don't want to, you could always leave the lines marked with *** out. The store will hold the position, x and y sizes and the background colours. A wipe_sprite routine is included later on. We'll also assume that the screen is to be paged into upper memory, and the the sprite data is paged in at this time. If not, you could always copy it temporarily into the space above the screen, or somewhere in lower memory. The data address going into the routine will be for the whole unchopped sprite. One more thing - the routine is for mode 3 or 4. In mode 3, each byte is 4 thin pixels, but to make matters a LOT simpler we'll assume that "fat" coords are being used. Oh, and these coords must all be even, in both modes. Now, that's quite a lot of information to pass into the routine, and so we'll need to use both sets of registers: ENTRY:- Address of data HL Address of back store IX Real width (x) C Pixels to print - x C' y B' Coords of first pixel - x E y D Pixels to skip - x E' D' EXIT:- Address of first byte after used background store IX Background store holds screen address; size (x,y); data... A handy fact: since each pair of pixels is stored together and each is four bits (1 nibble) wide, but every pixel is stored as by a mask nibble and a colour nibble, two pixels = 2 * 2 * 4 bits = 2 bytes. PSEUDO CODE =========== print_sprite: find address to use for first pixel data address = HL + (D' * C) + E' find screen address to use for first pixel for each line in turn (do this B' times) store screen address pointer > store sprite address pointer mask & copy each byte in turn (C' bytes) restore sprite address & add C bytes restore screen address & add 128 bytes repeat (Looks simple, doesn't it!) ASSEMBLY CODE ============= print_sprite EXX LD A,E' EXX CALL add_hl_a ; = HL+E' EXX LD A,D' EXX LD B,C ps1 CALL add_hl_a DJNZ ps1 ; = HL+E'+(D'*C) SCF ; RR D ; Screen address in DE RR E ; LD A,C LD (ps_real_x),A EXX PUSH BC EXX POP BC SRA C LD (IX+0),E *** LD (IX+1),D *** LD (IX+2),C *** LD (IX+3),B *** ps2 PUSH BC PUSH DE ; Store pointers PUSH HL ; ps3 LD A,(DE) LD (IX+4),A ; Store background *** INC IX *** AND (HL) ; Cut out hole INC HL OR (HL) ; Add colour LD (DE),A ; Onto screen INC HL INC E DEC C JR NZ,ps3 ; Loop for whole line POP HL LD A,(ps_real_x) CALL add_hl_a ; Add on C POP DE LD A,128 CALL add_de_a ; Add on 128 POP BC DJNZ ps2 LD C,4 *** ADD IX,BC ; (B = 0) *** RET ps_real_x DB 0 Sometimes there are commands you wished existed which, erm, don't. So we make them with little routines you might think of as macros. Like adding a 16 bit reg pair with A: add_hl_a PUSH AF ADD A,L LD L,A LD A,H ADC A,0 LD H,A POP AF RET add_de_a EX DE,HL CALL add_hl_a EX DE,HL RET Pretty cool, eh? Well, actually, no. The problem with this kind of routine is speed - if you want to use it in a game you'd probably want to customise bits and pieces. But it's not too bad for slower applications. As to wiping the sprite off the screen? As promised... (Enter with IX = address of store, screen paged in) wipe_sprite LD L,(IX+0) LD H,(IX+1) LD C,(IX+2) LD B,(IX+3) LD DE,128 ws1 PUSH BC PUSH HL ws2 LD A,(IX+4) LD (HL),A INC L INC IX DEC C JR NZ,ws2 POP HL ADD HL,DE POP BC DJNZ ws1 RET Now, suppose we had a lot of masked images to print, and then wipe off. Here's three routines which let you do that, provided you have a big enough space to store all the backgrounds. CALL THIS BEFORE YOU START TO PRINT. print_init LD HL,back_store+2 LD (backstorepoint),HL DEC HL LD (HL),0 DEC HL LD (HL),0 RET CALL TO PRINT EACH SPRITE. ENTRY AS FOR print_sprite, EXCEPT THAT YOU IGNORE IX. print_mult LD IX,(backstorepoint) CALL print_sprite LD HL,(backstorepoint) LD (IX+0),L LD (IX+1),H INC IX INC IX LD (backstorepoint),IX RET CALL TO WIPE ALL SPRITES. wipe_mult LD IX,(backstorepoint) LD L,(IX-2) LD H,(IX-1) LD (backstorepoint),HL PUSH HL POP IX LD A,H OR L RET Z CALL wipe_sprite JR wipe_mult For the last two routines, ensure the screen is paged into upper memory. .