!0.......^.........^.........^.. !B \H11\H07\H10\H00 S P R I T E \H11\H07\H10\H00 D E S I G N E R !2.......^.........^.........^.........^.........^.........^.... User-defined sprites on the Spectrum? It's not that difficult! Get things moving on-screen with more machine code magic from Toni Baker. !1.......^.........^.........^.........^........ This month's block of crafty code makes weird shapes fly around all over the screen. In fact, you shouldn't find it too hard to add a few bits and pieces to the idea yourself. It's all basic- ally very simple. You design up to five different sprites - of any figure, shape, design and so on - which in this case are 16 pixels wide by 16 pixels high; imagine four user- defined graphics characters glued together in a square and you have the idea. But - and this is where the story really begins - there's more to this program than meets the eye, for it incorp- orates a rather mind-blowing idea ... !0.......^.........^.........^.. !B MOTION PICTURES !1.......^.........^.........^.........^........ You see, once you've defined a sprite, you can then specify its coordinates on the screen and its velocity across the screen. In other words, they move! Curiouser and curiouser. Once a sprite is set in motion, you can continue exe- cuting more Basic or machine code. The sprites will keep moving on-screen, simultaneously with any other program. One line of Basic can set a sprite in motion. The next Basic line will, of course, be executed in sequence - but it'll be executed whilst the sprite is moving. Thus, the setting in motion in the first place is all you have to worry about. How it all works is quite intricate, so I'll explain in a moment or two; if you find the blurb a bit too heavy going, try quaffing a cuppa or two before attempting to follow it. In the meantime, I'll tell you how to integrate my machine code with your Basic. !0.......^.........^.........^.. !B DOWN TO DESIGN !1.......^.........^.........^.........^........ The first statement of your Basic program must be a "DIM s$(x,8)"; where x is the maximum number of things you want flying around on- screen at any one time. Then, you'll want to include the statements "LET on=33013" and "LET off=33020". From here on in it's up to you. The statement "RANDOMIZE USR on" will bring the moving sprite facility into action, whereas the statement "RANDOMIZE USR off"will bring things back to normal. The sprites themselves are defined using the ordinary user-defined graphics. You can have up to five different designs on-screen at once, which are: !0.......^.........^.........^.. Sprite 1=UDGs A,B,C,D Sprite 2=UDGs E,F,G,H Sprite 3=UDGs I,J,K,L Sprite 4=UDGs M,N,O,P Sprite 5=UDGs Q,R,S,T !1.......^.........^.........^.........^........ You can define them yourself in the usual ways. The array, s$, is the one that contains all the information, however, and each element must be precisely defined. Now ... it's "pay atten- tion" time. Take a look at the box giving the explanation of the sprite parameters. (Note that in the explanations given, I've used the letter 'N' to represent one of the strings in s$; the number is completely arbitrary.) The element s$(N,8) is actually very import- ant. If it contains any character whose code is less than or equal to 64 (decimal), then the sprite is said to be inactive - that is, it'll not appear on the screen. You can make as many alterations as you like to the other elements of s$. Once all the alterations have been made, you can then alter s$(N,8) and the sprite will be active and will start moving across the screen. If such a moving sprite collides with anything on-screen, or if it hits the edges of the screen, then it'll instantly stop !0.......^.........^.........^.. !B -------------------------------- \H11\H07\H10\H00 THE SPRITE PARAMETERS -------------------------------- STRING | EXPLANATION ELEMENT | -------------------------------- s$(N,1) |CHR$ (the sprite number | - between one & five) -------------------------------- s$(N,2) |This must always be |initialised to CHR$(1) -------------------------------- s$(N,3) |CHR$ (the number of |frames between |successive movements) -------------------------------- s$(N,4) |CHR$ (the figure's Y |coordinate) -------------------------------- s$(N,5) |CHR$ (the figure's X |coordinate) -------------------------------- s$(N,6) |CHR$ (the figure's |vertical displacement |each time it moves) -------------------------------- s$(N,7) |CHR$( the figure's |horizontal displacement |each time it moves) -------------------------------- s$(N,8) |This must be set last |of all, and must be any |character whose code is |greater than 64 -------------------------------- !2.......^.........^.........^.........^.........^.........^.... In the table above, it must be noted that 'N' is used to repre- sent one of the strings in s$ - the number is arbitrary. !1.......^.........^.........^.........^........ !B moving and deactivate. Element s$(N,8) will automatically reset to CHR$(0). You can test for this occurrence in a Basic program. !0.......^.........^.........^.. A TIMELY INTERRUPTION !1.......^.........^.........^.........^........ OK, it's tea-break time! Arm yourself with a cuppa and I'll explain how it all works. The machine code is an interrupt routine. This means that the program runs itself automatically 50 times a second. Each time it runs it checks out the array s$ and shuffles sprites around the screen accordingly. The basic instruction "RANDOMIZE USR on" simply activates this interrupt procedure, whereas "RANDOMIZE USR off" deactivates it. Interrupt procedures are quite clever; however, it's very easy to muck things up and just a tiny little bug will spell total disaster and blast the poor Speccy into oblivion. This is true simple because the program runs itself once for every new TV frame, whether you want it to or not. Take a peek at the basic program that's lying around in this article. It manages to do in just a few statements what would otherwise have been quite a complicated program; it produces a figure which bounces around the screen. The first seven lines just initialise the array, s$, and line 80 starts things moving. Line 90 is the one to think about - it looks like an infinite loop, but in fact it's not; it's just waiting until the figure hits an edge. If you break out of the program at any time you'll notice that the figure will keep moving even while you type a command, until it hits a wall (when it will stop). At this point, you should type "RANDOMIZE USR off" before you do anything else. Well, that's it for this issue. I'm just going off to stick my head in a bucket of inspiration - hopefully in time for me to produce yet more gems next month. See you then. !2.......^.........^.........^.........^.........^.........^.... !B --------------------------------.------------------------------- 10 DIM s$(1,8) Dimensions the array, s$ --------------------------------.------------------------------- 20 LET on=33013 Initialise the variables 30 LET off=33020 --------------------------------.------------------------------- 40 FOR j=1 TO 8 Read the data in line 160 50 READ a 60 LET s$(1,j)=CHR$ a 70 NEXT j --------------------------------.------------------------------- 80 RANDOMIZE USR on Switches on the sprite facility --------------------------------.------------------------------- 90 IF s$(1,8)="A" THEN GO TO Waits until a sprite hits the 90 edge of the screen --------------------------------.------------------------------- 100 LET y=CODE s$(1,4) 'y' is the Y coordinate of the sprite --------------------------------.------------------------------- 110 LET x=CODE s$(1,5) 'x' is the X coordinate of the sprite --------------------------------.------------------------------- 120 IF y=0 OR y=22 THEN LET s$ Reverses the Y movement of (1,6)=CHR$ (256-CODE s$(1,6)): B the sprite if necessary EEP .03,24 --------------------------------.------------------------------- 130 IF x=0 OR x=30 THEN LET s$ Reverses the X movement of (1,7)=CHR$ (256-CODE s$(1,7)): B the sprite if necessary EEP .03,12 --------------------------------.------------------------------- 140 LET s$(1,8)="A" Reactivates the sprite --------------------------------.------------------------------- 150 GO TO 90 A loop to send the action back to line 90. --------------------------------.------------------------------- 160 DATA 1,1,2,10,5,255,1,65 Contains the data for s$ --------------------------------.------------------------------- The Basic program to get things moving on-screen - type it in and see ... !B Machine code Assembler Comments ---------.---------.------------------.------------------------- ORG 80F5 ---------.---------.------------------.------------------------- 3E80 ON LD A,#80 ED47 LD I,A I:=80 ED5E IM 2 Activate interrupt routine C9 RET ---------.---------.------------------.------------------------- ED46 OFF IM 0 Deactivate routine C9 RET ---------.---------.------------------.------------------------- 0181 IADDR DEFW 8101 Direct interrupt control to address 8101 ---------.---------.------------------.------------------------- F5 SPRITES PUSH AF C5 PUSH BC D5 PUSH DE E5 PUSH HL DDE5 PUSH IX Stack all registers used by the routine DD2A4B5C LD IX,(VARS) Point IX to array s$ DD7E00 LD A,(IX+#00) FED3 CP #D3 200F JR NZ,EXIT Jump if first variable is not array s$ DD4604 LD B,(IX+#04) B:=1st dimension of array (number of sprites) ---------.---------.------------------.------------------------- C5 SP_LOOP PUSH BC Stack this number 010800 LD BC,#0008 DD09 ADD IX,BC Point IX to start of next sprite(next array element) CD7181 CALL NXT_SPRT Treat next sprite C1 POP BC B:=remaining number of sprites to treat 10F4 DJNZ SP_LOOP ---------.---------.------------------.------------------------- DDE1 EXIT POP IX E1 POP HL D1 POP DE C1 POP BC F1 POP AF Restore all registers FF RST #38 Carry out normal interrupt procedures C9 RET ---------.---------.------------------.------------------------- CB0C LINE RRC H CB0C RRC H CB0C RRC H HL:=coded print position 011F00 LD BC,#001F 09 ADD HL,BC Move print position one square down and one left CB04 RLC H CB04 RLC H CB04 RLC H HL:=correct print position C9 RET ---------.---------.------------------.------------------------- CD4081 WIPE CALL H_WIPE Erase top half of sprite CD2981 CALL LINE Point HL to bottom half of sprite ---------.---------.------------------.------------------------- CD4481 H_WIPE CALL Q_WIPE Erase one square of sprite 23 INC HL Point HL to remaining square ---------.---------.------------------.------------------------- E5 Q_WIPE PUSH HL 0608 LD B,#08 B:=Number of rows per square ---------.---------.------------------.------------------------- 3600 WP_LOOP LD (HL),#00 Erase next row 24 INC H Point HL to next row 10FB DJNZ WP_LOOP E1 POP HL C9 RET ---------.---------.------------------.------------------------- AF TEST XOR A A:=00 CD5581 CALL H_TEST Test upper half of sprite position CD2981 CALL LINE Point HL to lower half ---------.---------.------------------.------------------------- CD5981 H_TEST CALL Q_TEST Test one square of sprite position 23 INC HL Point HL to remaining square ---------.---------.------------------.------------------------- E5 Q_TEST PUSH HL 0608 LD B,#08 ---------.---------.------------------.------------------------- B6 TS_LOOP OR (HL) If any pixel is set then A becomes non-zero 24 INC H HL points to next row 10FC DJNZ TS_LOOP E1 POP HL C9 RET ---------.---------.------------------.------------------------- 78 FIND_ADDR LD A,B This subroutine computes E618 AND #18 in HL the print pos of the F640 OR #40 square (on-screen) which 67 LD H,A has PRINT-AT coords given 78 LD A,B by registers B,C 0F RRCA 0F RRCA 0F RRCA E6E0 AND #E0 B1 OR C 6F LD L,A C9 RET ---------.---------.------------------.------------------------- DD7E07 NXT_SPRT LD A,(IX+#07) A:=activation flag FE40 CP #40 D8 RET C Return if sprite inactive DD3501 DEC (IX+#01) Count frames between movements C0 RET NZ Return if the sprite does not require moving DD7E02 LD A,(IX+#02) A:=frame interval between movements DD7701 LD (IX+#01),A Re-initialise frame count DD4603 LD B,(IX+#03) B:=y coordinate DD4E04 LD C,(IX+#04) C:=x coordinate CD6281 CALL FIND_ADDR HL:=print pos of sprite CD3A81 CALL WIPE Erase sprite DD7E03 LD A,(IX+#03) A:=y coordinate DD8605 ADD A,(IX+#05) A:=intended y coordinate FE17 CP #17 301F JR NC,NS_EXIT Jump if intended y coord out of range 47 LD B,A B:=intended y coordinate DD7E04 LD A,(IX+#04) A:=x coordinate DD8606 ADD A,(IX+#06) A:=intended x coordinate FE1F CP #1F 3014 JR NC,NS_EXIT Jump if intended x coord out of range 4F LD C,A C:=intended x coordinate ---------.---------.------------------.------------------------- C5 PUSH BC CD6281 CALL FIND_ADDR HL:=intended print pos CD4E81 CALL TEST Test for collision C1 POP BC BC:=intended coordinates A7 AND A 2008 JR NZ,NS_EXIT Jump if sprite has hit something DD7003 LD (IX+#03),B Store new y coordinate DD7104 LD (IX+#04),C Store new x coordinate 180A JR NS_DRAW ---------.---------.------------------.------------------------- DD360700 NS_EXIT LD (IX+#07),#00 Deactivate sprite DD4603 LD B,(IX+#03) B:=old y coordinate DD4E04 LD C,(IX+#04) C:=old x coordinate ---------.---------.------------------.------------------------- DD6E00 NS_DRAW LD L,(IX+#00) L:=sprite type number 2D DEC L L now in range 0 to 4 2600 LD H,#00 HL now in range 0 to 4 29 ADD HL,HL 29 ADD HL,HL 29 ADD HL,HL 29 ADD HL,HL 29 ADD HL,HL Multiply by 32d ED5B7B5C LD DE,(UDG) Point DE to graphic A 19 ADD HL,DE Point HL to required sprite graphics E5 PUSH HL CD6281 CALL FIND_ADDR HL:=print pos of sprite D1 POP DE DE:=address of pixel information ---------.---------.------------------.------------------------- CDDB81 DRAW CALL H_DRAW Draw upper half of sprite CD2981 CALL LINE Point HL to lower half ---------.---------.------------------.------------------------- CDDF81 H_DRAW CALL Q_DRAW Draw next square of sprite 23 INC HL Point HL to next square ---------.---------.------------------.------------------------- E5 Q_DRAW PUSH HL 0608 LD B,#08 ---------.---------.------------------.------------------------- 1A DR_LOOP LD A,(DE) 77 LD (HL),A Print next row 13 INC DE 24 INC H Point HL to next row 10FA DJNZ DR_LOOP E1 POP HL C9 RET ---------.---------.------------------.------------------------- !1.......^.........^.........^.........^........ !B -- from Your Spectrum #10 (Dec/Jan.1984/85) -- !$