Archimedes Gamemakers Manual
Terry Blunt

Chapter 5 - Making it Move

5.1 Objects

You are probably familiar with the idea of using sprites to represent moving characters, but it may not have occurred to you that it is often practical to define all objects as sprites and store them in a sprite file. When they are loaded by your game they will be instantly available in the form required.

It may be useful to fool players into thinking there is far more movement in the game than there really is. The player will usually be concentrating mainly on a small area of the screen and doesn't really notice how much is going on over the whole screen. This is particularly true of graphic adventures where the player is moving only a single character. If you plan your game so most of the action is in the vicinity of the player's character with just the occasional creature or object appearing at the edges you'll get the desired result.

Another way of increasing apparent activity is to have groups of (say) four low priority objects that, instead of being moved with every loop of the game, only move on every fourth loop. This is particularly useful with slow moving objects, and provided the general action is reasonably fast, your player won't notice the subterfuge. You have gained by reducing the re-calculating time by four.

5.2 Movement

Making objects appear to move can, in its simplest form, consist of a loop of program statements as in the examples below.

Example 5.1   

Clear screen
Draw background
Loop start
   Plot objects
   Calculate new positions
   Check for collisions (including screen edges)
   Update loop conditions
   Wait for a while
   Rub out objects
Loop end

Example 5.2

Loop start
   clear screen
   draw background
   plot objects
   calculate new positions
   check for collisions
   update loop conditions
   wait for a while
Loop end

Whether you use example 5.1 or example 5.2 depends on how many moving objects you expect to have. The former is best where there are only one or two objects to be moved and most of the screen is to remain unaltered, but if there are several objects then you should bear in mind that plotting and rubbing out effectively doubles the number of object operations performed. In this case the second method is best. Example 5.3 is a variant of the first method where, instead of rubbing out the object, which can be difficult over a complex background, the background itself is first stored, then the object plotted and later, the background restored. Similarly, example 5.4 is an improved variant of the second method that would use a small piece of ARM code for whole screen storage and recovery.

Example 5.3

clear screen
draw background
Loop start
   store backgrounds where objects will be plotted
   plot objects
   calculate new positions
   check for collisions
   update loop conditions
   wait for a while
   recover backgrounds in reverse order
Loop end

Example 5.4

clear screen
draw background
store screen
Loop start
   recover screen
   plot objects
   calculate new positions
   check for collisions
   update loop conditions
   wait for a while
Loop end

Background recovery in example 5.3 must be done in reverse order for two or more moving objects in case they overlap. If this occurs the background of the second sprite will include part of the first, so you must replace each background piece under exactly the same conditions as existed when it was stored otherwise you'll see a strange progression of sprite debris whenever two of them overlap.

To get smooth movement it is vital to keep overall loop time to a minimum, and in example 5.2 the time between clearing the screen and plotting the last object. So why the deliberate wait? The answer is that all the calculating that has to be done will take a variable amount of time, and this would cause erratic re-draw time and hence jerky movement. The time delay in example 5.5 is designed to ensure that all the variation in your calculations is absorbed during the display time rather than the re-draw time. It also helps ensures that display time is longer than re-draw time, reducing flicker. Zeroing the timer at the end of the main loop ensures that the total loop time remains constant, not the time wasting loop.

Example 5.5

zero time variable
Loop start
   perform redraws
   perform calculations
   Repeat
     
   Until time variable is greater than constant.
   zero time variable
Loop end

One problem you will encounter if you use slow loops is jittering objects. This is not to be confused with flicker, which is mainly due to bad re-draw methods. At its most objectionable jittering will make objects appear doubled and overlapped. This is caused by displaying objects at the same place on two or more successive screen refreshes (as opposed to two or more program loops) and then displaying them at the next position and holding there for several screen refreshes. Your brain assesses the distance between the two plot locations and expects a steady progression from one to the next. Added to this, your eye's persistence of vision begins to break down when the time is much greater than one screen refresh. This results in the double image effect, and is particularly noticeable where there should be smooth movement of regular shaped objects and the distance between movement steps is large. Listing 5.1 demonstrates the problem. The loop is so simple that it can easily be executed in one screen refresh, but if the spacebar is held then the loop time becomes exactly twice the screen refresh.

REM > Jitter
:
MODE 13
MODE 9
OFF
SYS "OS_SWINumberFromString",,"OS_Byte" TO byte%
sc%=1
REPEAT
FOR I%=0 TO 1280 STEP 8
CLS
CIRCLE FILL I%,512,32
PRINT TAB(5,10)"Hold spacebar to see jitter";
PRINT TAB(12,12)"Escape to stop";
WAIT
IF INKEY-99 I%+=8:WAIT
SYS byte%,113,sc%
sc%=sc% EOR 3
SYS byte%,112,sc%
NEXT
UNTIL FALSE
END

The ideal solution is to make your game loop work fast enough to be within a single screen refresh and then use the WAIT command to provide both your time delay and screen refresh synchronisation. This isn't always possible however, so you must employ a combination of the following subterfuges.

  • Keep the movement step size as small as possible
  • Make the objects move irregularly
  • Change the shape, size and colours of the objects on successive plots
  • Keep the objects as small and as irregularly shaped as possible

In Listing 5.2 there is an example using the mouse to move a star around the screen. This is based on method three for movement of a single sprite. A trick is used to make background storage easy. As only one small sprite is plotted and its movement is restricted to the middle of the screen it is possible to use an odd corner of the screen as storage using the plot command for rectangle copy.

REM > Star
:
ON ERROR PROCerror:END
PROCinitialise
PROCsprites
PROCbackground
MOUSE RECTANGLE 0,scale%,1280-scale%,1024-scale%*2
REPEAT
MOUSE x%,y%,b%
UNTIL b%=0
RECTANGLE x%,y%,scale%,scale% TO 0,0
REPEAT
ox%=x%:oy%=y%
MOUSE x%,y%,b%
RECTANGLE 0,0,scale%,scale% TO ox%,oy%
RECTANGLE x%,y%,scale%,scale% TO 0,0
SYS sprite%,plot%,area%,star%,x%,y%,style%
WAIT
UNTIL b%
PROCtidy
END
:
DEFPROCerror
MODE 12
PRINT REPORT$ " @ ";ERL;
ENDPROC
:
DEFPROCtidy
VDU4
COLOUR 127
COLOUR 128
PRINT TAB(0,29);
ENDPROC
:
DEFPROCinitialise
*Pointer 1
MODE 13
OFF
PRINT TAB(10,10)"Please Wait"
SYS "OS_SWINumberFromString",,"OS_SpriteOp" TO sprite%
DIM block% 19
block%!0=4
block%!4=5
block%!8=-1
SYS "OS_ReadVduVariables",block%,block%+12
xeig%=block%!12
yeig%=block%!16
size%=&2000
DIM area% size%
area%!0=size%
area%!4=0
area%!8=16
init%=256+9
def%=256+15
select%=256+24
mask%=512+29
getpix%=512+41
putpix%=512+44
plot%=512+34
writeto%=256+60
style%=8
SYS sprite%,init%,area%
scale%=64
VDU 23,132
VDU%00000001
VDU%00000010
VDU%00000100
VDU%00001000
VDU%00011000
VDU%00100100
VDU%01000010
VDU%10000001
VDU 23,133
VDU%00000000
VDU%01000000
VDU%01000000
VDU%01000000
VDU%01111100
VDU%00000100
VDU%00000100
VDU%00000100
RESTORE+9
READ numcols%
DIM cola%(numcols%)
DIM colb%(numcols%)
FOR I%=0 TO numcols%
READ cola%(I%)
READ colb%(I%)
NEXT
ENDPROC
DATA 4
DATA %111111,%111111
DATA %010101,%101010
DATA %000000,%010101
DATA %010101,%000000
DATA %101010,%010101
:
DEFPROCsprites
LOCAL s%,x%,y%
s%=scale% DIV 2
x%=scale%
y%=scale%
star%=FNdefsprite("star",x%,y%)
PROCstar(s%,s%,s%)
PROCmasksprite(star%,x%,y%)
SYS sprite%,writeto%,area%,0
ENDPROC
:
DEFFNdefsprite(a$,x%,y%)
LOCAL add%
x%=x%>>xeig%
y%=y%>>yeig%
SYS sprite%,def%,area%,a$,0,x%,y%,MODE
SYS sprite%,writeto%,area%,a$
SYS sprite%,select%,area%,a$ TO ,,add%
=add%
:
DEFPROCstar(x%,y%,s%)
LOCAL i,p,q,r,a%,b%,c%,d%,e%,f%,i%,t%,u%
p=PI/2.5
q=p/2
r=p/4+p
t%=s% DIV 2
u%=s%*3
GCOL %100000+128
CLG
FOR i%=0 TO numcols%
i=i%*p+r
a%=x%+COS(i)*s%
b%=y%+SIN(i)*s%
c%=x%+COS(i+q)*t%
d%=y%+SIN(i+q)*t%
e%=x%+COS(i+p)*s%
f%=y%+SIN(i+p)*s%
g%=x%+COS(i+q)*u%
h%=y%+SIN(i+q)*u%
GCOL cola%(i%) TINT &C0
MOVE a%,b%
MOVE x%,y%
PLOT&55,c%,d%
GCOL colb%(i%) TINT &80
PLOT&55,e%,f%
NEXT
ENDPROC
:
DEFPROCmasksprite(add%,x%,y%)
LOCAL I%,J%,c%
x%=x%>>xeig%
y%=y%>>yeig%
SYS sprite%,mask%,area%,add%
FOR J%=0 TO y%-1
FOR I%=0 TO x%-1
SYS sprite%,getpix%,area%,add%,I%,J% TO,,,,,c%
IF c%=%100000 SYS sprite%,putpix%,area%,add%,I%,J%
NEXT
NEXT
ENDPROC
:
DEFPROCbackground
OFF
COLOUR %001100+128
COLOUR %110011
FOR I%=0 TO 14
PRINT TAB(0,I%) STRING$(40,CHR$132);
NEXT
COLOUR %111100
COLOUR %000011+128
FOR I%=15 TO 31
PRINT TAB(0,I%) STRING$(40,CHR$133);
NEXT
VDU5
GCOL 127
MOVE 128,740:PRINT "Use the mouse to move the star"
MOVE 128,680:PRINT "Observe the bottom left corner"
MOVE 128,600:PRINT "Press a mouse button to stop"
GCOL %100000
RECTANGLE FILL 0,0,1280,scale%
ENDPROC

Apart from the movement routine itself there is nothing new in this program. The first RECTANGLE command moves a patch of screen to the bottom left corner, and the second, after sprite plotting and a suitable wait period, moves it back again.

5.3 Animation

Animation is often confused with general movement. However, while most games involve considerable movement it has only been with the advent of high resolution graphics and fast processors that real animation has been practical. This is where characters move arms and legs to give an impression of realistic walking or running. Objects such as cars have wheels that appear to rotate and monsters can slowly materialise instead of just flashing onto the screen.

5.3.1 Colour Changing

If you are using a 16 colour mode you can get an interesting pseudo animation using flashing colours. These can be independently redefined in fractional amounts for each of the two colours that make up the flash. There are also two FX commands that let you change the flash rates. The best use of this technique is for small detail, such as silly eye movements or flapping ears.

Another pseudo animation technique is palette swapping. Again this is really only suited to 16 colour modes. In modes with fewer colours it isn't practical, and the 256 colour modes are too complicated to be worth manipulating this way for such a simple effect. In brief, the idea is that you break up the movement you wish to animate into, say, eight of the possible positions of the moving object. The completely overlapping sections are then drawn in the object's colour. All the other parts are drawn in different colours, which have been temporarily re-defined to the current background colour using the extended form of the COLOUR keyword. Then all you need do is alternately 'switch on' each colour in turn by re-defining it to the desired object colour to produce the animation.

An example of this, Listing 5.3, also demonstrates the use of flashing colours, giving two forms of seemingly asynchronous animation.

REM > AnimVDU19
:
MODE 9
OFF
ON ERROR PROCerror:END
PROCinitialise
PROCdraw
:
VDU19,14,18,240,240,192
col%=1
REPEAT
WAIT
COLOUR col%,0
col%+=1
IF col%=colmax% col%=1
COLOUR col%,7
UNTIL FALSE
END
:
DEFPROCerror
MODE 12
IF ERR <>17 PRINT REPORT$ " @ ";ERL
*FX 9 25
*FX 10 25
ENDPROC
:
DEFPROCinitialise
*FX9 100
*FX10 12
FOR I%=0 TO 14
COLOUR I%,0,0,0
NEXT
COLOUR 15,240,240,240
COLOUR 15
PRINTTAB(10,0) "Press Escape to stop"
num%=11
ymax%=64
colmax%=14: REM reduce for different effects
col%=0
DIM x%(num%),y%(num%),dx%(num%),dy%(num%)
x%()=640
y%()=ymax%
IF RND(-1)
FOR I%=0 TO num%
dx%(I%)=(num%>>1)-I%
dy%(I%)=RND(5)+(num%-ABS dx%(I%))*2+ymax%
NEXT
ENDPROC
:
DEFPROCdraw
GCOL 14
FOR i=0 TO PI STEP .05
POINT 640+RND(180)*COS i,RND(96)*SIN i
NEXT
REPEAT
col%+=1
IF col%=colmax% col%=1
GCOL col%
flag%=0
x%()=x%()+dx%()
y%()=y%()+dy%()
dy%()=dy%()-1
FOR I%=0 TO num%
POINT x%(I%),y%(I%)>>2
IF y%(I%)<=ymax% THEN
flag%+=1
dy%(I%)=0
dx%(I%)=RND(9)-5
y%(I%)=RND(ymax%)
ENDIF
NEXT
UNTIL flag%>=num%-1
COLOUR 14
PRINT TAB(16,31)"YIPPIE!!";
ENDPROC

The Roman candle effect produced depends on having most of the plotted points invisible for most of the time. You can see what really was plotted more easily if you temporarily change the line that calls PROCdraw to:

PROC draw:VDU20:STOP

The more colours that are used the greater the distance between visible spots, and therefore the better the effect. You can prove this by changing the value of colmax% in the initialising. In many instances it will be possible to restrict the range to around six colours, and allowing another two for flash effects you still have eight colours for all your genuine animated sprites.

5.3.2 Animating Sprites

As noted earlier the Archimedes doesn't have a dedicated system for sprite animation. However considerable flexibility is provided by the sprite commands available, and it is relatively easy to produce film-type animation. As well as giving more realism to moving objects this technique can be used as yet another method of fooling people into thinking there is more action than there really is.

In figure 5.1 there are two sprite film-type animations. The first consists of a single moving object within a sprite. For clarity this is just an 'x' with the fine line showing the track the object will seem to follow. Only nine frames have been used where in reality you'll need more depending on the overall size of the sprite.

If the whole frame is now moved around the screen reasonably slowly while using each sprite in turn, the object will appear to weave erratically. Selecting each sprite in turn is simply a matter of putting all their addresses in an array when they are created, then picking them out of the array as below.

index%=0
REPEAT
 PROCsprite(address%(index%))
 index%+=1
 IF index%=9 index%=0
 REM any other bits
UNTIL end%

The second film is a modification of the first. Instead of a single object we have three identical ones, a,b and c, all at different points on the same track. This is rather like getting something for nothing. We now seem to have more objects but need fewer sprites to make the film. You could, of course, keep the same number of sprites and use a longer, more complicated track.

When using this form of animation, the film sequence would be interleaved with any other similar films and also with any single sprite object movements.

5.4 Collisions

Most games use variants of two basic methods of handling collisions. These are by comparing coordinates between objects and by looking at the pixel colours at the location an object is about to move to. We will look at these first, then go on to look at other techniques and refinements.

5.4.1 Coordinate Collisions

This type of collision is calculated from the X,Y positions and movement vectors of each object. In it's simplest form you simply compare the X and Y coordinates of one object with the X and Y coordinates of the other.

Quite often programmers waste a lot of processing time making redundant collision tests. As there is a need to test every moving object , they therefore loop through testing each object against every other object. If you think about it, you will realise that there is no need to check a stationary object. Things will bump against it, but it will never bump against them. Also you don't need to check against an object that hasn't yet moved. All objects that are going to move will normally do so in the same pass, so an object may actually move out of the way. It would therefore be a cheat to assume a collision.

All you need do is create a set of arrays representing a stack of objects, with stationary ones at the bottom of the stack. The arrays should contain the object's X,Y coordinates and their sizes. Starting with the first moving object you check for collisions with those lower in the stack. If you are restricted to small, fast moving objects, then you could just compare the coordinates and assume a collision when they are within one pixel size of each other. This is seldom practical though, normally you need to compare the distance between the objects with the sum of their sizes. This is achieved with a simple piece of Pythagoras as shown in figure 5.2 where the size is defined as the radius of a circle that contains the object.

Collision detection could also include screen edges which should then be mapped as four large stationary objects outside the actual screen limits. These would be so big that their perimeter is almost a straight line as far as the screen is concerned.

If you study listing 5.4 you will see how collisions can be handled in practice. There is, unfortunately, a rapid loss of speed as the number of objects increases, so in the collision example I've allowed for no stationary objects and used fairly ordinary screen edge testing. Even so you will see jitter unless you are using an ARM 3 machine. This is mainly because, for simplicity, filled circle drawing is used rather than sprite plotting.

REM > CoOrdinate
:
ON ERROR PROCerror:END
PROCinitialise
REPEAT
PROCplot
PROCupdate
UNTIL FALSE
END
:
DEFPROCerror
MODE 12
IF ERR<>17 PRINT REPORT$ " @ ";ERL
ENDPROC
:
DEFPROCinitialise
MODE 12
MODE 9
OFF
SYS "OS_SWINumberFromString",,"OS_Byte" TO byte%
max%=3
xmax%=1280
ymax%=1024
DIM x%(max%)
DIM y%(max%)
DIM s%(max%)
DIM dx%(max%)
DIM dy%(max%)
FOR I%=0 TO max%
s%(I%)=8+RND(10)*4
x%(I%)=s%(I%)+RND(xmax% DIV 4-s%(I%)DIV 2)*4
y%(I%)=s%(I%)+RND(ymax% DIV 4-s%(I%)DIV 2)*4
dx%(I%)=RND(6)*4-12
dy%(I%)=RND(6)*4-12
NEXT
PRINT TAB(9,7) "Co-Ordinate Collisions"
PRINT TAB(10,11) "Press Escape to stop"
IF INKEY 100
sc%=1
ENDPROC
:
DEFPROCplot
WAIT
SYS byte%,113,sc%
sc%=sc% EOR3
SYS byte%,112,sc%
CLS
FOR I%=0 TO max%
GCOL I%+1
CIRCLE FILL x%(I%),y%(I%),s%(I%)
NEXT
ENDPROC
:
DEFPROCupdate
FOR J%=1 TO max%
FOR I%=0 TO J%-1
a%=x%(J%)-x%(I%)
b%=y%(J%)-y%(I%)
c%=s%(I%)+s%(J%)
IF a%*a%+b%*b%<c%*c% PROCbounce
NEXT
NEXT
FOR I%=0 TO max%
IF x%(I%)<s%(I%) OR x%(I%)>xmax%-s%(I%) PROCdx
IF y%(I%)<s%(I%) OR y%(I%)>ymax%-s%(I%) PROCdy
NEXT
x%()=x%()+dx%()
y%()=y%()+dy%()
ENDPROC
:
DEFPROCbounce
a%=dx%(J%)-dx%(I%)
dx%(J%)=dx%(J%)-a%
dx%(I%)=dx%(I%)+a%
b%=dy%(J%)-dy%(I%)
dy%(I%)=dy%(I%)+b%
dy%(J%)=dy%(J%)-b%
ENDPROC
:
DEFPROCdx
a%=ABS dx%(I%)
IF x%(I%)<s%(I%) dx%(I%)=a% ELSE dx%(I%)=-a%
ENDPROC
:
DEFPROCdy
b%=ABS dy%(I%)
IF y%(I%)<s%(I%) dy%(I%)=b% ELSE dy%(I%)=-b%
ENDPROC

A point that might cause confusion is the use of two mode changes. The first only serves to ensure that there is enough screen memory available for bank switching. It takes exactly twice the memory of the required mode, so will give a Bad Mode error if there isn't enough space for two screen banks in the required mode.

If you are using ARM code for collision calculations then considerable speed increase can be made if instead of taking the square root for the hypotenuse you compare the squared distance with the squared value of the sum of the object sizes. Only simple MUL instructions are needed in this case, whereas square rooting would require your own routine in ARM code.

If all objects are about the same size then you can cheat by using pre-calculated values for the object sizes that approximate to the squares of the sum of the sizes. You will find that larger objects overlap a little, while smaller ones don't quite meet, but you can actually use that to your advantage, and if the game is moving reasonably fast the player won't notice anyway.

This works well for fairly regular objects that fit reasonably inside a circle but will fail for long, thin objects or those which have long appendages. There are two solutions to this. The first is to treat the object as a cluster of objects - Indeed, it may pay to plot it as a cluster rather than a single sprite. Alternatively, if the object can be fitted approximately into an ellipse you can modify the size parameters of each object by a factor dependent on their orientation and collision angle. This is pushing the method to the limit, so I'll leave it to you to work out if you wish.

There's nothing more annoying than a game that indicates a collision when you can see clear background between the objects that have supposedly collided. On the other hand, most people will consider that they were lucky to get away with it if there is a slight overlap without a collision. The rule therefore is always to give the player the benefit of the doubt. This is best done by assuming player objects to be slightly smaller than they really are when calculating for collisions. The exception is where the player is firing at the enemy. In this case, it pays to calculate on the basis that the player's missiles are slightly bigger than they are.

5.4.2 Pixel Collisions

Pixel collisions are where screen points under an object are examined to see if they match any know collision colours. Unlike coordinate collisions speed is unaffected by the number of objects any single object may collide with and these collisions can be made more tolerant of the shape of the objects.

If you are using 256 colour modes there is a way of arranging intelligent collision detection by sacrificing a little colour accuracy. Instead of using colour you can use the four tint levels. Where a value 0 is returned regard this as non collision objects in the background. 64 can be solid but benign objects and borders, 128 can be enemy objects and 192 can be player objects or missiles. Listing 5.5 is a rather crude demonstration of the basic idea behind this.

A simple CASE statement determines what action is taken for any given collision. The TINT command is used to find the tint of the point that the object would next be visiting rather than the point where it already is. This ought to prevent any overlapping. However, you will see that I've used Exclusive Or plotting of the moving object, which helps to show the overlaps that still can occur.

REM > Pixel
:
PROCinitialise
GCOL 3,%111111 TINT &C0
x%=640
y%=512
REPEAT
MOUSE nx%,ny%,b%
UNTIL b%=0
MOUSE TO x%,y%
CIRCLE FILL x%,y%,s%
REPEAT
MOUSE nx%,ny%,b%
dx%=SGN(nx%-x%>>2)
dy%=SGN(ny%-y%>>2)
WAIT
CIRCLE FILL x%,y%,s%
tint%=TINT(x%+dx%*s%,y%+dy%*s%)
CASE tint% OF
WHEN 0:PRINT TAB(15,30) SPC 9;
WHEN &40:dx%=0:dy%=0:PRINT TAB(17,30) "Boing!";
WHEN &80:dx%=0:dy%=0:PRINT TAB(18,30) "Ouch";
WHEN &C0:PRINT TAB(15,30) "Hi Friend";
ENDCASE
x%+=dx%*4
y%+=dy%*4
CIRCLE FILL x%,y%,s%
UNTIL b%>0
PRINT TAB(0,28)
END
:
DEFPROCinitialise
*Pointer 1
MODE 13
OFF
GCOL%000010 TINT 0
MOVE 640,800
MOVE 600,760
PLOT&55,680,760
GCOL%000010 TINT &40
RECTANGLE FILL 128,896,1024,128
GCOL%001000 TINT &40
RECTANGLE FILL 0,0,128,1024
RECTANGLE FILL 1152,0,128,1024
RECTANGLE FILL 128,0,1024,128
GCOL%000010 TINT &80
CIRCLE FILL 320,512,32
GCOL%000010 TINT &C0
CIRCLE FILL 960,512,32
GCOL%101010 TINT 0
RECTANGLE FILL 600,256,80,80
COLOUR%001111 TINT 0
PRINT TAB(11,10) "Use mouse to move" TAB(9,11) "Press a button to stop";
COLOUR%001000+128 TINT &40
s%=16
MOUSE ON
ENDPROC

When you run this program you will see that different types of collision are identified, even though the colours of the obstructions may be very similar.

5.4.3 Cell Collisions

You've probably not considered the idea of collisions in board games but a specialised form does take place. It's normal to split the board of (say) a draughts game into an 8 x 8 two dimensional array. As pieces are put on the board the appropriate element in the array is is set to indicate that a piece is there and which player it belongs to. In games like chess the type of piece is also indicated. When testing player moves the array element corresponding to the position being moved to is checked to see if there is a piece already in that cell.

This suggests a third form of collision detection in arcade style games. You keep all sprites to a given size, or a multiple of that size, and split up the screen into a sprite sized grid. You then have to ensure that sprites are always positioned exactly in one of these cells. From then on you can maintain a two dimensional array of the play area and can perform simple quick array tests for collisions.

Probably the best method of ensuring sprite positioning is to set up an animation sequence for all moving obects where, for the player's object, movement is initiated by the player but the actual movement is taken over by the animator for a fixed number of steps. You can include film type animation at the same time so the animation sequence gives the illusion of smooth, continuous movement. This kind of movement and collision system is probably best suited to simpler platform games and graphic adventures where you know the characters will always be at certain levels and the restricted amount of angular movement is acceptable.

In listing 5.6 there is a more complete program. This not only demonstrates cell collisions but also brings together a number of points we've looked at.

REM > Cells
:
ON ERROR PROCerror:END
PROCinitialise
PROCassemble
PROCsprites
PROCbackground
PROCstart
:
E%=1
T%=TIME
REPEAT
IF t%(0)=0 PROChuman
FOR I%=1 TO obs%
IF t%(I%)=0 AND RND(50)=1 t%(I%)=RND(4)
NEXT
FOR I%=0 TO obs%
IF t%(I%) PROCcheck
NEXT
REPEAT UNTIL TIME-T%>3
WAIT
T%=TIME
SYS byte%,113,E%
E%=E% EOR3
SYS byte%,112,E%
CALL copy
FOR I%=0 TO obs%
PROCmove
NEXT
UNTIL FALSE
END
:
DEFPROCerror
MODE 12
*FX 15 1
IF ERR<>17 PRINT REPORT$" @ ";ERL
ENDPROC
:
DEFPROCinitialise
MODE 15
MODE 13
OFF
smax%=3         : REM number of sprites (-1)
DIM list%(smax%): REM sprite addresses
size%=&3000
DIM area% size%
SYS "OS_SWINumberFromString",,"OS_Byte" TO byte%
SYS "OS_SWINumberFromString",,"OS_SpriteOp" TO sprite%
!area%=size%
area%!4=0
area%!8=16
SYS sprite%,&209,area%
norm%=&222
spec%=&234
DIM scale% 15
scale%!0=1
scale%!4=4
scale%!8=1
scale%!12=1
DIM cells%(9,7) : REM position cells
obs%=4          : REM number of moving objects
DIM n%(obs%)    : REM film slide number
DIM t%(obs%)    : REM movement direction
DIM x%(obs%)    : REM obvious!
DIM y%(obs%)
ENDPROC
:
DEFPROCassemble
DIM block% &80
block%!0=148
block%!4=7
block%!8=-1
SYS "OS_ReadVduVariables",block%,block%+12
C%=block%!16    : REM screen size
DIM A% C%       : REM stored screen start
B%=block%!12    : REM screen base
D%=A%+C%        : REM stored screen end
memory=0
screen=1
size=2
memoryend=3
bank=4
lowreg=4
highreg=11
link=14
FOR I%=0 TO 2 STEP 2
P%=block%
[ OPT I%
.copy
CMP bank,#2
ADDEQ screen,screen,size
.copyloop
LDMIA (memory)!,
STMIA (screen)!,
CMP memory,memoryend
BLT copyloop
MOV PC,link
;
.store
CMP bank,#2
ADDEQ screen,screen,size
.storeloop
LDMIA (screen)!,
STMIA (memory)!,
CMP memory,memoryend
BLT storeloop
MOV PC,link
]
NEXT
ENDPROC
:
DEFPROCsprites
J=0
FOR I%=0 TO 3
GCOL%001111
CLS
J+=PI/32
FOR I=0 TO PI*2 STEP PI/8
MOVE (COS(I+J))*64+64,(SIN(I+J))*64+64
DRAW 64,64
NEXT
GCOL%000011
CIRCLE FILL 64,64,14
PROCdefsprite(0,0,128,128,I%)
NEXT
ENDPROC
:
DEFPROCdefsprite(x%,y%,a%,b%,n%)
SYS "OS_SpriteOp",&110,area%,STR$ n%,,x%,y%,x%+a%,y%+b% TO,,list%(n%)
PROCspritemask(list%(n%),area%,sprite%)
ENDPROC
:
DEFPROCspritemask(N%,R%,S%)
LOCAL A%,B%,F%,J%,I%
SYS S%,&21D,R%,N%
SYS S%,&228,R%,N% TO ,,,A%,B%
FOR J%=0 TO A%-1
FOR I%=0 TO B%-1
SYS S%,&229,R%,N%,J%,I% TO,,,,,F%
IF F%=0 SYS S%,&22C,R%,N%,J%,I%,0
NEXT
NEXT
ENDPROC
:
DEFPROCbackground
CLS
GCOL%111111
FOR I%=128 TO 1023 STEP 128
LINE 0,I%,1279,I%
NEXT
FOR I%=128 TO 1279 STEP 128
LINE I%,0,I%,1023
NEXT
FOR I%=0 TO 7
cells%(0,I%)=1
cells%(9,I%)=1
SYS sprite%,norm%,area%,list%(0),0,I%<<7,8
SYS sprite%,norm%,area%,list%(0),1152,I%<<7,8
NEXT
FOR I%=1 TO 8
cells%(I%,0)=1
cells%(I%,7)=1
SYS sprite%,norm%,area%,list%(0),I%<<7,0,8
SYS sprite%,norm%,area%,list%(0),I%<<7,896,8
NEXT
COLOUR%110000+128
GCOL%110000 TINT 0
RECTANGLE FILL 96,0,1088,80
PRINT TAB(4,30) "Z left   X right   ' up   / down"
PRINT TAB(13,31) "Escape to Exit";
E%=1
CALL store
ENDPROC
:
DEFPROCstart
FOR I%=0 TO obs%
x%(I%)=I%+2<<7
y%(I%)=RND(4)+1<<7
cells%(I%+2,y%(I%)>>7)=1
NEXT
ENDPROC
:
DEFPROChuman
IF INKEY-98 t%(0)=1
IF INKEY-67 t%(0)=2
IF INKEY-80 t%(0)=3
IF INKEY-105 t%(0)=4
ENDPROC
:
DEFPROCcheck
IF n%(I%) ENDPROC
a%=x%(I%)>>7
b%=y%(I%)>>7
CASE t%(I%) OF
WHEN 1:IF cells%(a%-1,b%) t%(I%)=0 ELSE cells%(a%-1,b%)=1
WHEN 2:IF cells%(a%+1,b%) t%(I%)=0 ELSE cells%(a%+1,b%)=1
WHEN 3:IF cells%(a%,b%+1) t%(I%)=0 ELSE cells%(a%,b%+1)=1
WHEN 4:IF cells%(a%,b%-1) t%(I%)=0 ELSE cells%(a%,b%-1)=1
ENDCASE
IF t%(I%) n%(I%)=8:cells%(a%,b%)=0
ENDPROC
:
DEFPROCmove
CASE t%(I%) OF
WHEN 0:PROCstill
WHEN 1:PROCleft
WHEN 2:PROCright
WHEN 3:PROCup
WHEN 4:PROCdown
ENDCASE
ENDPROC
:
DEFPROCstill
SYS sprite%,norm%,area%,list%(0),x%(I%),y%(I%),8
ENDPROC
:
DEFPROCleft
x%(I%)-=16
SYS sprite%,norm%,area%,list%(3-n%(I%)AND3),x%(I%),y%(I%),8
n%(I%)-=1
IF n%(I%)=0 t%(I%)=0
ENDPROC
:
DEFPROCright
x%(I%)+=16
SYS sprite%,norm%,area%,list%(n%(I%)AND3),x%(I%),y%(I%),8
n%(I%)-=1
IF n%(I%)=0 t%(I%)=0
ENDPROC
:
DEFPROCup
y%(I%)+=16
IF n%(I%)>2 AND n%(I%)<6 scale%!12=3 ELSE scale%!12=4
SYS sprite%,spec%,area%,list%(0),x%(I%),y%(I%),8,scale%
n%(I%)-=1
IF n%(I%)=0 t%(I%)=0
ENDPROC
:
DEFPROCdown
y%(I%)-=16
IF n%(I%)<4 scale%!12=n%(I%)+4 ELSE scale%!12=12-n%(I%)
SYS sprite%,spec%,area%,list%(0),x%(I%),y%(I%),8,scale%
n%(I%)-=1
IF n%(I%)=0 t%(I%)=0
ENDPROC

You will notice some ARM code in PROCassemble. This is the screen storing and recovery system, discussed earlier, for complex backgrounds. An indication of its efficiency is that it takes about twice as long to execute as the CLS command. This is very much faster than a CLS followed by only a couple of plotting commands so is dramatically faster than re-plotting all the sprites round the edge of the screen.

When moving objects horizontally film animation is used to give the impression of rolling wheels, while sprite scaling is used on the y axis for stretching and squashing the wheels as they move vertically.

Instead of drawing directly into the sprites as normal I've drawn to the screen so that you can see the animation sequence as it is built up. You will notice that only four sprites need to be defined as the spokes then overlap.

The sprites used are rather large and slow to plot, so due to the relatively long loop time of about two screen refreshes there should be a considerable amount of jitter associated with the movement routine, but as the sprites are so irregular and change with every re-plotting the action looks reasonably smooth.

To avoid the need for screen edge testing and array subscript range problems the screen is surrounded by sprites, all of which are flagged in the array. By making a small alteration to the array and X and Y division factors you could move the limiting array elements off screen giving possibilities for off screen collisions.

As a final point you can easily identify the type of collision and therefore the most appropriate action by using different values for different objects in the array, as you would for the chess game mentioned before.

5.4.4 Pointer Collisions

One further form of collision is the mouse pointer. The Wimp is designed to be able to identify which window or icon the pointer is currently over. Therefore all you need do is make a window's sprite background the scene and overlay selected areas with sprite icons that blend with the picture. You now have a true desktop game. You can go further by re-defining the pointer to say, cross hairs, and use the mouse to control a form of alien zapping game. Unfortunately speed will be poor and a little variable, but it gives scope for pushing the computer and your programming to the limit. I've given no example of this as I don't want to get embroiled in managing the Wimp, but Figure 5.3 shows how the sprites would be positioned and recognised. You will see that I've used two icons to enclose the area of the stream in the picture. This avoids the possibility of selecting the stream over a large area of land, as could occur with a single larger icon.

If you want to investigate this area more fully you will have to study the Programmers Reference Manual. I suggest that initially, you look at the idea of separating the pointer from the mouse, (page 301) then redefining the the pointer (page 331), and moving the pointer independently (page 338).

To understand how to make best use of the pointer/icon system under the Wimp you will need to pay particular attention to pages 1138 - 1140, 1146, 1180, 1189

Finally, it is sometimes practical to use a combination of two methods. You may, for example, decide that it is most efficient to use pixel tests to establish that some form of collision has taken place, backed up by coordinate examination to establish precisely what has been met.

5.4.5 Look Ahead

One problem with collision tests is that of a skip-over. This is most likely to occur with pixel testing and is due to an object moving a greater distance than the distance between it and a small obstruction. The effect is that the object seems to pass through the obstruction. The way to resolve this problem is to compare the object's movement vector with the size of the smallest obstruction in the game. If the vector is larger then you need to make one or more intermediate tests along the line of movement and in steps smaller than the size of the obstruction.

A similar problem arises where you have glancing hit situations. In this case, the vectors of the moving objects don't intersect but are close enough for a collision to take place, allowing for the object sizes. If this is a problem then you should consider additional off-axis tests. This all tends to slow things down and add to the complexity. As usual, some compromise will probably be needed. Figure 5.4 shows both the look-ahead and off-axis situations to make the points clearer.

5.5 Scrolling

A familiar feature of wordprocessors, spreadsheets, etc. which is taken for granted is scrolling. There are many games that use scrolling to good effect. On most machines, including the Archimedes, vertical scrolling is the easiest. There are two scrolling methods you can use, hardware scrolling and software scrolling .

5.5.1 Hardware Scrolling

Hardware scrolling is very much faster than software scrolling because nothing moves. All that changes is the start address of the screen. When the screen is displayed the computer hardware starts at the memory address given. To scroll through the screen memory you just increment this address by the number of bytes for one screen line. Logically, if you move the start address up through the screen memory you will eventually reach the point where there isn't enough left for a full screen. In this case, the hardware just subtracts the total screen size from the address at this point to bring it back to the beginning of the screen memory area, then carries on from there. For continuous scrolling you need to perform the same wrap around, bearing in mind that the SYS call used works with an offset to the screen area rather than an absolute address.

Due to the way screen memory is laid out, if you ensure that there is sufficient set aside you can produce a continuous band of scenery of several screen sizes and then scroll it vertically. You will need to select each screen bank in turn for drawing and ensure that the top edge of each screen exactly matches the bottom of the next. The top of the last screen must also match the bottom of the first.

Alternatively, for simpler effects, you can continuously re-draw the top line if you scroll downwards, or the bottom line if you scroll upwards. A simple scrolling example based on this latter arrangement is shown in listing 5.7. In this example both the display and VDU writing offsets are incremented together. However, you can have them scrolled independently, allowing you to plot quite large objects on a hidden part of the screen, then scroll them into view.

REM > Scroll
:
ON ERROR PROCerror:END
PROCinitialise
REPEAT
base%!1+=line%
IF base%!1>=size% base%!1-=size%
WAIT
SYS "OS_Word",22,base%
a%+=RND(15)-8
b%+=RND(15)-8
GCOL %011101
MOVE 0,0
DRAW a%,0
GCOL %100000
DRAW b%,0
GCOL %001000
DRAW 1279,0
UNTIL INKEY 1>-1
END
:
DEFPROCerror
MODE 12
PRINT REPORT$ " @ ";ERL
ENDPROC
:
DEFPROCinitialise
MODE 13
OFF
DIM block% 19
block%!0=6:REM line length
block%!4=150:REM total screensize
block%!8=-1
SYS "OS_ReadVduVariables",block%,block%+12
line%=block%!12
size%=block%!16
DIM base% 5
?base%=%11
base%!1=0
a%=512
b%=768
PRINT TAB(9,30) "Press any key to stop"
ENDPROC

I've used simple lines to produce a river effect but you can easily add sprites on top of the scroll action. If you do, you must remember to add the scrolling offset to their screen position when you rub them out, ready for the next screen refresh.

Although it is possible to scroll sideways using this method it is difficult to get a smooth effect and the results are not really worth the effort. This is because the screen start address can't be offset by just one byte but it has to be a number of words depending on the current screen mode. You therefore need to re-draw quite a wide block before scrolling. Diagonal scrolling is even more difficult using this technique, although it would be interesting to see a practical way of doing it.

5.5.2 Scrolling in Software

This method involves re-plotting every point of the screen, offset by the amount of movement required. As before, you will then need to re-draw the newly exposed areas. One simple, elegant solution for this re-plotting is to define a sprite to be the entire screen area, then re-plot the sprite offset by the degree of scroll movement you want. This makes for extremely easy, albeit rather slow, re-plotting. Provided you take care of the necessary edge filling you have the basis for a simple, all directions, scroll. However, it is really best suited for the lower resolution, 16 colour modes. This is demonstrated in Listing 5.8

REM > SpriteScrl
:
ON ERROR PROCerror:END
PROCinitialise
REPEAT
SYS sprite%,get%,area%,"S",0,xl%,yl%,xh%,yh% TO ,,add%
x%=(INKEY-98)-(INKEY-67)<<2
y%=(INKEY-105)-(INKEY-80)<<2
WAIT
SYS sprite%,put%,area%,add%,xl%+x%,yl%+y%
IF x% PROCvert
IF y% PROChoriz
IF RND(20)=1 PROCblot
UNTIL FALSE
END
:
DEFPROCerror
*FX 21
MODE 12
IF ERR<>17 PRINT REPORT$ " @ ";ERL
ENDPROC
:
DEFPROCinitialise
MODE 9
OFF
SYS "OS_SWINumberFromString",,"OS_SpriteOp" TO sprite%
size%=&14000
DIM area% size%
area%!0=size%
area%!4=0
area%!8=16
init%=256+9
get%=512+16
put%=512+34
SYS sprite%,init%,area%
COLOUR 8,128,128,128
PRINT TAB(10,2) "Eight direction scroll"
PRINT TAB(2,5) "Z left    X right   ' up    / down"
PRINT TAB(10,26) "Press Escape to exit"
xl%=160
xh%=1120
yl%=256
yh%=768
back%=8
GCOL 128+back%
VDU 24,xl%;yl%;xh%;yh%;
CLG
ENDPROC
:
DEFPROCvert
IF x%>0 PROCleft ELSE PROCright
ENDPROC
:
DEFPROChoriz
IF y%>0 PROCbottom ELSE PROCtop
ENDPROC
:
DEFPROCleft
GCOL back%
LINE xl%,yl%,xl%,yh%
ENDPROC
:
DEFPROCright
GCOL back%
LINE xh%,yl%,xh%,yh%
ENDPROC
:
DEFPROCtop
GCOL back%
LINE xl%,yh%,xh%,yh%
ENDPROC
:
DEFPROCbottom
GCOL back%
LINE xl%,yl%,xh%,yl%
ENDPROC
:
DEFPROCblot
GCOL RND(7)
r%=RND(63)
CIRCLE FILL xl%+r%+RND(960-r%*2),yl%+r%+RND(512-r%*2),r%
ENDPROC

You will see that I've used only the central portion of the screen. If you change the constants xl%, xh%, yl%, yh%, you will see the need for this. Also, the edge filling only consists of simple background line re-draws. You can improve on this by using an algorithm that can break down drawn objects into vertical and horizontal lines. I've split the re-drawing routine up so that you can see where the line re-drawing could be replaced by your improved filling algorithm.

The overprinted circles should, ideally, be sprites. As before you will need to make allowances for the movement of these sprites when you rub them out.

5.5.3 ARM Code Scrolling

This is by far the best scrolling arrangement if you want really complicated scrolling action. It is covered more fully in the ARM code chapter, so the only comment I'll make here is that such routines will enable you to scroll any part of the screen in any direction, and even two parts in different directions.

previousmain indexnext

 
© Sigma Press & Terry Blunt 2015