|
Volume Number: | 1 | |
Issue Number: | 5 | |
Column Tag: | Mousehole |
Animation Contest
By David E. Smith
Last month we presented a complete application shell program in assembly that supported desk accessories, cut and past and “about” dialog boxes. But the application itself was rather boring, simply drawing nested boxes in the window. This month, we concentrate on the application itself. To eliminate confusion, we will use the simple shell program from the December issue of MacTutor that simply opens a window. This allows us to concentrate on the application, which is to animate a paddle and ball. Once we understand animation, we can combine our paddle and ball program with the shell program published last month to create a complete Mac game application. But first, the problem of animation.
Animation Contest
The Mousehole recently held a programming contest sponsored by MacTutor on the problem of moving a paddle with the mouse. “Brett” offered an interesting solution: “Have the user blink a lot and pretend!” Another interesting solution offered by “Lone Falcon” and “Chief Wizard” and expanded on by “The Jerk” was to make the cursor a paddle and constrain the cursor to a given rectangle. The trap call “_PinRect” can be used to change the rectangle in which the cursor is allowed to move. Or the rectangle can be poked into the global variable at $834 as mentioned by “The Jerk”. One problem with this solution is that of providing an “escape” for the cursor when you want to go to the menu bar to quit the game!
A simple technique to move the paddle is to draw the paddle rectange where the mouse now is and erase it from where the paddle used to be. The following example code shows how this might be done:
MOVEPADDLE: ; Copy current paddle to old paddle LEApaddle, A0 LEAoldpaddle, A1 MOVE.W 0(A0), 0(A1) ;top of rect MOVE.W 2(A0), 2(A1) ;left of rect MOVE.W 4(A0), 4(A1) ;bottom MOVE.W 6(A0), 6(A1) ;right ; Update paddle position from mouse ; _GetMouse trap previously called ; and mouse coord. saved in Mouse. ; Since paddle moves vertically, only ; the top and bottom coordinates need ; be updated. LEAMouse, A0;get mouse LEApaddle, A1 ;get paddle MOVE.W (A0), (A1) ;update top MOVE.W (A0), D0 ;get top ADD.W Paddlelength, D0 ;add length MOVE.W D0, 4(A1);update bottom ; Erase old paddle position PEAoldpaddle;push old pad. _EraseRect;erase it ; Draw new paddle PEApaddle ;push paddle PEAPaddlepattern ;push fill pat. _FillRect ;draw paddle RTS
The Paddle Blinks!
The problem with the above solution, and hence the contest, is that the paddle will blink as it moves up and down. The problem is to improve on the above technique to eliminate the blinking. The reason for the blinking is shown in figure 1.
As shown in figure 1, the paddle blinks because we are erasing and drawing part of the paddle that overlaps, causing it to blink on and off. The solution is to determine the non-overlapping part and erase only that part. One way to do this is to replace the bottom of the old paddle with the top of the new paddle. Then the modified old paddle rectange would be only the cross-hatched area shown in figure 1 as the non-over-lapping part. But that only works for the paddle moving down. If the paddle moves up, then the opposite condition applies as shown in the following code segment:
ERASEPADDLE: ; create difference rectange LEApaddle, A0 ;new paddle LEAoldpaddle, A1 ;old paddle MOVE.W (A0), D0 ;get new paddle MOVE.W (A1), D1 ;get old paddle SUB.W D1, D0;top difference BMIUP ;moving up? DN: MOVE.W (A0), 4(A1) ;update bottom JMP Eraseit UP: MOVE.W 4(A0), (A1) ;update top Eraseit: PEA oldpaddle _EraseRect
CONTEST WINNER!
The above method works fine for a rectangular paddle moving up or down. But what about the ball? The winning solution to our animation contest was posted by “Don L.” in which he explained how regions could be used in a manner similar to what was done above for rectangles. Moreover, the toolbox has a special trap for finding the difference of two regions but does not have a similar trap for rectangles. The solution then is to form a union region of the old and new paddle regions, and then take the difference between the new paddle and this union region. The difference region is then erased and the paddle re-drawn. The result is a non-blinking paddle but using regions instead of rectangles.
In our example program we use “Don L.”’s method to move the ball as a region, and the rectangular method for moving the less complicated paddle rectangle. See the subroutine MOVEPADDLE for the rectangular implmentation discussed above and the subroutine MOVEBALL for the region solution submitted by Don. The tricky part is getting the regions properly defined with NewRgn, OpenRgn and CloseRgn.
Detecting Collisions
Once we have a moving paddle responding to the mouse, and a ball moving towards us, we need to detect if the ball has hit the paddle. A simple way to do this is to use the trap _RectInRgn, which tells if the given rectange intersects with the given region. We have used this trap by pushing the paddle rectange as the given rectange, and the ball region as the given region and tested the returning byte for true or false. If true, then the ball has “hit” the paddle, and we erase the ball and start a new one. If not, then the paddle has “missed” the ball and the game ends on a click of the mouse. Of course, in the final game, we really want the ball to “bounce” and move away from the paddle if there is a hit. We will leave the problem of bouncing for next month.
ANIMATE EXAMPLE
Our sample program includes all of the topics we’ve discussed this month in a complete program. To conserve space, none of the fancy user interface support we did last month is included. The program opens a window and displays our game field with a rectangular paddle, and launches a ball. If the user hits the ball, it disappears and another ball is launched. If the user misses the ball, the game stops until the button is pressed, at which point the finder is invoked. This is the basic animation required for a pinball type of game. Make note of the INITBALL routine that sets up the three temporary regions for use with the ball, and the MOVEBALL routine which erases the ball by only erasing the non-over-lapping region. Using these techniques, the paddle and ball should move smoothly over the shaded playing field.
BALL SPEED
The speed of the ball is determined by the increment used to update the ball region. The trap “_OffSetRgn” is used to incrementaly move the ball a small distance (dh, dv). In our example, dh is set at -1 and dv is zero, making the ball move horizontally one pixel left at a time. For a faster moving ball, the horiztonal increment should be doubled in a loop, until the user misses. Try this and see how fast the ball appears to move. Note that moving the paddle very quickly does produce an effect on the speed of the ball slightly.
In figure 2 we see the output from this month’s program. The white ball moves on a field of gray towards the black paddle. The white area was added in MacPaint to reduce the amount of gray, which does not print well.
Resources and Linker Files
The resource and linker files included are the minimum necessary to get an icon to pop up on the desktop. Our icon is shown at the top of the column header, and was produced using the Icon Converter Utility from issue 2 and “included” into our resource source code.
; EXAMPLE ASSEMBLY PROGRAM ; ANIMATE (MacTutor 1-5) ; VERSION 27 FEB 1985 ; (C) 1985 MacTutor by David E. Smith ; Macro subset for Toolbox stuff MACRO _InitGraf = DC.W $A86E| MACRO _InitWind =DC.W $A912| MACRO _NewWindow = DC.W $A913| MACRO _setport = DC.W $A873| MACRO _InitFont =DC.W $A8FE| MACRO _InitMenu =DC.W $A930| MACRO _InitDialog =DC.W $A97B| MACRO _TEInit =DC.W $A9CC| MACRO _Initpack = DC.W $A9E5| MACRO _FlushEvents = DC.W $A032| MACRO _InitCursor =DC.W $A850| MACRO _GetNextEvent =DC.W $A970| MACRO _FrameRect = DC.W $A8A1| MACRO _BackPat = DC.W $A87C| MACRO _EraseRect = DC.W $A8A3| MACRO _NewRgn = DC.W $A8D8| MACRO _OpenRgn = DC.W $A8DA| MACRO _CloseRgn =DC.W $A8DB| MACRO _FrameRgn = DC.W $A8D2| MACRO _FrameRoundRect = DC.W $A8B0| MACRO _FillRect =DC.W $A8A5| MACRO _FillRgn = DC.W $A8D6| MACRO _BeginUpdate = DC.W $A922| MACRO _EndUpdate = DC.W $A923| MACRO _ClipRect = DC.W $A87B| MACRO _GetMouse = DC.W $A972| MACRO _CopyRgn = DC.W $A8DC| MACRO _OffsetRgn = DC.W $A8E0| MACRO _OffsetRect = DC.W $A8A8| MACRO _UnionRgn = DC.W $A8E5| MACRO _DiffRgn = DC.W $A8E6| MACRO _MoveTo = DC.W $A893| MACRO _DrawChar = DC.W $A883| MACRO _RectInRgn = DC.W $A8E9| ; DECLARE LABELS EXTERNAL XDEF START ; required for linker ; LOCAL EQUATES MouseDown equ 1 AllEvents equ $0000FFFF UpdateEvt equ 6 ; SET UP MANAGERS START: PEA -4(A5);push qd global ptr _InitGraf ;init quickdraw global _InitFont ; init font manager _InitWind ; init window manager _InitMenu ; init menu manager CLR.L -(SP) ; kill the restart _InitDialog ; init dialog manager _TEInit ; init text edit (ROM) MOVE.W #2,-(SP) ; set-up _Initpack ; init package mgr MOVE.L #AllEvents,D0 ;all events _FlushEvents ;flushed _InitCursor ; make cursor the arrow ;-- SET UP NEW WINDOW ON HEAP ---- CLR.L -(SP) ;return window ptr CLR.L -(SP) ;window record ptr. PEA WBOUNDS ;window rectangle PEA WINDTITLE ; window title MOVE.W #$100,-(SP) ; true = visible MOVE.W #0,-(SP) ; doc type window MOVE.L #-1,-(SP) ; window in front MOVE.W #$100,-(SP) ; true=closebox MOVE.L #0, -(SP) ; reference value _NewWindow ; make new window ; -- ACTIVATE THIS NEW WINDOW ------ LEA WPOINTER,A0 ; copy window ptr MOVE.L (SP),(A0) ; to stacksave _setport ;current window ; INIT ALL VARIABLES JSRINITVAR ; --EVENT LOOP ------------ GetEvent: JSRDOGAME ;move paddle with mouse CLR-(SP);returned event MOVE #AllEvents,-(SP) ;mask all events PEAEventRecord ; event record block _GetNextEvent ;go check the mouse MOVE (SP)+,D0 ;get event result CMP#0,D0;if 0 then no event BEQGetEvent ;loop until it happens ; JUMP TABLE OF EVENT PROCESSING MOVE What,D0 ;what to do! CMP#MouseDown,D0 ; button down? BEQEXIT ;yes so exit... CMP#UpdateEvt, D0;is it an update event? BEQUPDATE ;yes, so do it JMP GetEvent ;get next event UPDATE: ; window needs refreshing MOVE.L WPOINTER,-(SP) ;push window ptr _BeginUpdate MOVE.L WPOINTER,-(SP) ;push our window ptr. _SetPort;restore our port ;least an update draw in ;the wrong window. BSRQDSTUFF;re-draw everything MOVE.L WPOINTER,-(SP) _EndUpdate JMP GetEvent ; ---------- END OF MAIN ---------- INITVAR: ; INIT GAME VARIABLES LEAField.of.play(A5),A0 ;set-up appl. window MOVE.W #5, (A0) ;Field.of.play TOP MOVE.W #5, 2(A0) ;LEFT MOVE.W #245, 4(A0) ;BOTTOM MOVE.W #500, 6(A0) ;RIGHT PEA Field.of.play(A5) _ClipRect ;set clip region LEAPaddle(A5),A0 ;set-up paddle MOVE.W #6, (A0) ;TOP MOVE.W #8, 2(A0) ;LEFT MOVE.W #40, 4(A0);BOTTOM MOVE.W #15, 6(A0);RIGHT LEAPaddlelength(A5),A1 ;update paddle bottom MOVE.W (A0),D0 MOVE.W 4(A0),D1 SUB.W D0,D1 MOVE.W D1, (A1) ; set up regions for ball CLR.L -(SP) _NewRgn ;set new region LEABallrgn(A5), A1 ;make ball a region MOVE.L (SP)+,(A1) ;save handle in ballrgn CLR.L -(SP) _NewRgn ;set new region LEAoldballrgn(A5), A1;make old ball a region MOVE.L (SP)+,(A1) ;save handle in oldballrg CLR.L -(SP) _NewRgn ;set new region LEAunionrgn(A5), A1;make union ball a region MOVE.L (SP)+,(A1) ;save handle in unionrgn JSRINITBALL ;set-up ball RTS ; ------ init ball subroutine ------ INITBALL: LEABall(A5),A0 ;set-up ball rect MOVE.W #100, (A0) ;TOP MOVE.W #300, 2(A0) ;LEFT MOVE.W #109, 4(A0) ;BOTTOM MOVE.W #309, 6(A0) ;RIGHT LEABallwide(A5),A0 ;set up rounded corners MOVE.W #6, (A0) LEABallhigh(A5), A0 MOVE.W #6, (A0) _OpenRgn;init ball region PEABall(A5) MOVE.W Ballwide(A5),-(SP) MOVE.W Ballhigh(A5),-(SP) _FrameRoundRect MOVE.L Ballrgn(A5),A0 MOVE.L A0,-(SP) _CloseRgn _OpenRgn;init oldball region PEABall(A5) MOVE.W Ballwide(A5),-(SP) MOVE.W Ballhigh(A5),-(SP) _FrameRoundRect MOVE.L oldballrgn(A5),A0 MOVE.L A0,-(SP) _CloseRgn _OpenRgn;init union region PEABall(A5) MOVE.W Ballwide(A5),-(SP) MOVE.W Ballhigh(A5),-(SP) _FrameRoundRect MOVE.L unionrgn(A5),A0 MOVE.L A0,-(SP) _CloseRgn RTS DOGAME: ; update game field during null event PEAmouse(A5) _GetMouse LEAmouse(A5),A0 ;current mouse LEAoldmouse(A5),A1 ;old mouse;find where mouse is MOVE.W (A0),D0 ;new mouse MOVE.W (A1),D1 ;old mouse CMPD1,D0;has y mouse changed? BEQstatck ;no, don’t update paddle ;yes, make new paddle JSRMOVEPADDLE ;update paddle position statck: JSRMOVEBALL ;advance ball JSRBOUNCE ;change ball direction RTS ; ---- MOVE PADDLE SUBROUTINE ---- MOVEPADDLE: ; copy paddle position to oldpaddle LEApaddle(A5), A0 LEAoldpaddle(A5), A1 MOVE.W (A0), (A1) ;top MOVE.W 2(A0), 2(A1) ;left MOVE.W 4(A0), 4(A1) ;bottom MOVE.W 6(A0), 6(A1) ;right ; update paddle position LEAmouse(A5),A0 ;get current mouse position LEApaddle(A5),A1 ;update paddle position MOVE.W (A0), (A1) ;update TOP MOVE.W (A0),D0 ;calculate BOTTOM ADD.W Paddlelength(A5),D0 MOVE.W D0,4(A1) ;update BOTTOM ; draw new paddle PEApaddle(A5) PEAPaddlePat _FillRect ; calculate difference rectangle LEApaddle(A5),A0 ;current paddle LEAoldpaddle(A5),A1;old paddle CLR.L D0 CLR.L D1 MOVE.W (A0), D0 MOVE.W (A1),D1 LEApaddle(A5), A0 LEAoldpaddle(A5), A1 SUB.W D1, D0 BMIup dn: MOVE.W(A0), 4(A1) ;moved down BRA wipeout up: MOVE.W4(A0), (A1);moved up BRA wipeout wipeout: PEAoldpaddle(A5) PEABackPat0 _FillRect ; update mouse LEAmouse(A5),A0 ;current mouse LEAoldmouse(A5),A1 ;old mouse MOVE.L (A0),(A1);update old mouse RTS ; ---- MOVE BALL SUBROUTINE ------ MOVEBALL: ; copy Ballrgn to oldballrgn MOVE.L Ballrgn(A5),A0 MOVE.L A0,-(SP) MOVE.L oldballrgn(A5),A0 MOVE.L A0,-(SP) _CopyRgn ; move ball region MOVE.L Ballrgn(A5),A0 MOVE.L A0,-(SP) MOVE.W dh,-(SP) ;horiz. increment MOVE.W dv, -(SP);vert. increment _OffsetRgn;offset ball rgn ; draw new BALL MOVE.L Ballrgn(A5),A0 MOVE.L A0,-(SP) PEABallPat _FillRgn ; erase old ball difference MOVE.L Ballrgn(A5),A0 MOVE.L oldballrgn(A5),A1 MOVE.L unionrgn(A5),A3 MOVE.L A0,-(SP) MOVE.L A1,-(SP) MOVE.L A3,-(SP) _Unionrgn MOVE.L unionrgn(A5),A0 MOVE.L Ballrgn(A5),A1 MOVE.L unionrgn(A5),A3 MOVE.L A0,-(SP) MOVE.L A1,-(SP) MOVE.L A3,-(SP) _Diffrgn MOVE.L unionrgn(A5),A0 MOVE.L A0,-(SP) PEABackPat0 _FillRgn RTS ; ---------- BOUNCE BALL -------- BOUNCE: CLR.B -(SP) PEAPaddle(A5) ;push rect MOVE.L Ballrgn(A5), A0 MOVE.L A0, -(SP);push rgn handle _RectInRgn MOVE.B (SP)+, D0 CMP#0, D0 ;false? BEQRETBOUNCE;yes... MOVE.L Ballrgn(A5),A0 ;no... MOVE.L A0,-(SP) ;erase ball PEABackPat0 _FillRgn JSR INITBALL;start new ball RETBOUNCE: RTS ; ------ QDSTUFF SUBROUTINE ---- QDSTUFF: ; DRAW GAME FIELD _PenNormal PEABACKPAT0 _BackPat PEA Field.of.play(A5) ;frame rectangle _EraseRect PEA Field.of.play(A5) _FrameRect;draw game rectangle ; DRAW PADDLE PEAPaddle(A5) PEAPaddlePat _FillRect PEAPaddle(A5) _FrameRect RTS ; ---- RETURN TO FINDER -------- EXIT: RTS ; return to finder ; ----LOCAL DATA AREA ---------- WPOINTER: DC.L 0 ;store window pt WBOUNDS:DC.W 40 ;rectangle top DC.W 2;left DC.W 335;bottom DC.W 508;right WINDTITLE: DC.B 24 ; title length DC.B ‘BALL AND PADDLE EXAMPLE ‘,0 EventRecord: What: DC.W 0 ; what event Message: DC.L 0 ; ptr. to msg When: DC.L 0 Point: DC.L 0 Modify: DC.W 0 BackPat0: DC.L $AA55AA55 ;playing field DC.L $AA55AA55 BallPat:DC.L$00000000 DC.L $00000000 PaddlePat:DC.L $FFFFFFFF DC.L $FFFFFFFF dh:DC.W -1;BALL INCREMENTS dv:DC.W 0 ; ------ APPLICATION GLOBALS ---- Field.of.play: DS.W1 ;frame size storage DS.W 1 ;for game field rectangle DS.W 1 DS.W 1 Paddle: DS.W1 ;frame size storage DS.W 1 ;for paddle rectangle DS.W 1 DS.W 1 oldpaddle:DS.W 1 ;frame size storage DS.W 1 ;for paddle rectangle DS.W 1 DS.W 1 Paddlelength: DS.W1 Ball: DS.W1 ;frame size storage DS.W 1 ;for ball rectangle DS.W 1 DS.W 1 Ballwide: DS.W 1 Ballhigh: DS.W 1 Ballrgn:DS.L1 ;ball region handle oldballrgn: DS.L 1 ;old ball region handle unionrgn: DS.L 1 ;temp region mouse: DS.L1 oldmouse: DS.L 1 ; ------ END OF PROGRAM ------- !START [ ) /OUTPUT Animate Example Animate /TYPE ‘APPL’ ‘ANIM’ /BUNDLE /RESOURCES Animate_rscs $ ;ANIMATE_rscs.asm ; resource file for the animate example ; created using the assembler ; signiture is creator tag ; RESOURCE ‘ANIM’ 0 ‘IDENTIFICATION’ DC.B 30, ‘animate example--Feb. 27, 1985’ .ALIGN 2 RESOURCE ‘BNDL’ 128 ‘BUNDLE’ DC.L ‘ANIM’;NAME OF SIGNATURE DC.W 0,1 ;DATA (DOESN’T CHANGE) DC.L ‘ICN#’;ICON MAPPINGS DC.W0 ;NUMBER OF MAPPINGS-1 DC.W 0,128 ;MAP 0 TO ICON 128 DC.L ‘FREF’;FREF MAPPINGS DC.W0 ;NUMBER OF MAPPINGS-1 DC.W 0,128 ;MAP 0 TO FREF 128 RESOURCE ‘FREF’ 128 ‘FREF 1’ DC.B ‘APPL’, 0, 0, 0 .ALIGN 2 RESOURCE ‘ICN#’ 128 ‘MY ICON’ ; FIRST APPLICATION ICON BIT MAP INCLUDE animate.icon.asm
- SPREAD THE WORD:
- Slashdot
- Digg
- Del.icio.us
- Newsvine