For animation purposes, I'll get the discussion going by discussing what might be needed as part of a simple animation package, and provide a few simple working routines to start you tinkering with the subject.
Before we go any further, please don't confuse the term "sprite" here used for animated graphics with the same word used for pointer environment objects (mouse pointers, clickable objects), normally a separate subject unless you go out of your way to use PE sprites as game sprites.
The animation you can do in BASIC is limited and the QL does not have dedicated video cards, so any animation is going to be fairly jerky, not particularly smooth and overall rather basic. But still a lot of fun to play with.
KEYBOARD CONTROL - all games need keyboard control, and in BASIC there are two ways to read the keyboard. Both have their advantages, so check if you prefer to use INKEY$ or KEYROW to read the keyboard. KEYROW may be preferred, as you can read more than one key at a time e.g. to allow diagonal movement. KEYROW also has no type-ahead buffer, so you would not have the snags of keypresses repeating ahead of your game.
TIMING - QL systems vary in speed quite a lot, so you may write a simple program which works well on an unexpanded black box QL (BBQL), but runs blisteringly fast on an emulator on a fast computer, or to control the differences in running speed when running in compiled or uncompiled BASIC. So you need to decide how to control running speed of the program. Options are somewhat limited in unexpanded QL BASIC - you can use PAUSE statements, INKEY$(value) to read the keyboard and include a short pause, or you can use time wasting loops such as FOR/END FOR loops to control the running speed, different methods will all have their merits and disadvantages. For example, the delay using PAUSE n or INKEY$(n) is cut short by a keypress, so things speed up when you press a key. FOR?END FOR loops run at a different speed on different systems, so how do you cater for this? One way is to allow the user to enter a value for the delay loops so that they can have the choice of how fast it runs. This relieves you of the timing responsibilities in that you don't have to try to cater for all possible platform speeds as long as you allow a wide enough range to be chosen. Here is a very simple example:
Code: Select all
1000 INPUT"Slowdown factor (lower values=faster, higher values=slower) ";slowdown
1010 :
2000 FOR delay=1 TO slowdown
2010 REMark just waste some time
2020 END FOR delay
On a fast QPC2 system, for example, you may need a very high value for "slowdown", depending on what else your program is doing - just a short delay may need a value of many thousands, since SBASIC executes FOR loops very quickly.
Other ways of handling speed control is to look at the use of "SUSPEND" type extensions, which as the name implies stops a running job in its tracks for a few units of time (normally 20ms frames each) - there are examples in several extensions toolkits, usually taking the syntax SUSPEND number_of_frames. This has the advantage that during the short periods of "time wasting" the job is not actively running to tie up processor time. But on the other hand, other jobs may appear to speed up since the scheduler decides that they can have more time to themselves while your game is doing nothing for those short periods. Swings and roundabouts.
OBJECTS - the things which are moved around the screen. These might be simple characters redefined in a font ("user defined graphics" or UDG's is a commonly used term). There is an article called Fun With Fonts, about playing with character fonts on my site at
http://www.dilwyn.me.uk/docs/articles/funfonts.zip . Or they may be simple small graphical images if you have experience of handling graphics (fonts are simpler while you are tinkering with the other aspects of animation).
FRAMES - to make the graphic vary as it moves, for example, a figure walking. If we are using text characters, it might be convenient to define four characters with different leg positions as it walks left to right. Let us assume we used the letters A, B, C, and D to represent those. So, as it moved, every two pixels a different picture or 'frame' is shown to represent the movement. The figure as at co-ordinates x and y across the window, so by checking where it is in multiples of two pixels across we can decide which to display:
Code: Select all
CURSOR x,y : PRINT "ABCD"((x MOD 4)+1);
That simply converts the x value to a value from 0 to 3 (x modulo 4) and has 1 added to it to make it a range of values from 1 to 4, to pick one of the four letters.
Turning this into a simple example listing, to illustrate a figure walking left to right and back again, which continues until you press ESC, to show how you handle printing a different version in different positions along the path of travel, and how to make the figure reverse direction when it gets to the limits of its movement in either direction. I've included both x and y values, so you could tinker with it to try vertical movement if you wish, although to keep things simple the example uses only horizontal movement. The example uses INKEY$(1) which allowed it to work fairly well on QPC2, if too slow on your system just remove the brackets and the value.
Code: Select all
100 CLS
110 x = 0 : y = 100 : REMark starts here
120 CURSOR x,y : PRINT"ABCD"(1) : REMark show first 'frame'
130 direction = 1 : REMark -1=go left, +1=go right
140 REPeat loop
150 IF INKEY$(1) = CHR$(27) THEN EXIT loop : REMark ESC to finish
160 oldx = x : oldy = y : REMark where it was before moving
170 x = x + direction : REMark move to new position
180 IF x > 100 THEN x = 100 : direction=-direction : REMark reverse direction
190 IF x < 0 THEN x = 0 : direction = -direction : REMark reverse direction
200 IF oldx <> x OR oldy <> y THEN
210 REMark moved - erase and redraw
220 CURSOR oldx,oldy : PRINT " "; : REMark erase old position
230 CURSOR x,y : PRINT "ABCD"((x MOD 4)+1); : REMark show next frame at new position
240 END IF
250 END REPeat loop
Hopefully, the REM statements will be enough to explain how it works. The two main elements are (1) the use of the variable 'direction' to determine which way it moves (and you could add a third value of 0 if the character is to be stationary for a time) and (2) the use of current location co-ordinates to choose which 'frame' of the character to print at a given location.
To minimise flicker and jerkiness, have the "erase old position" and "draw at new position" routines (lines 220 and 230 in this case) as close together and with as little code between each other as possible to try to keep the animation as smooth as possible within the limits of what you can achieve in QL BASIC.
RELATIVE SPEEDS - if you need more than one object moving, and at different speeds, there are some options to consider. The easiest is to move the objects by a different number of pixels at a time. Obviously, chunkier movements will produce less smooth animation. Another way is to put the animation in a loop, with all objects moving by the same amount of pixels, but the fastest object moved in each round of the loop, and the slower ones only moving every other loop, or every third loop, for example. To do this, you would need a variable which clocks up counts of the loop, so the fastest object would be moved and redrawn every time round the loop. But the slower object movement was skipped every other time round the loop:
counter = 0
REPeat loop
REMark move fastest object here
IF (counter MOD 2) = 1 THEN
REMark only move the slower object here
END IF
counter = counter + 1
REMark counter will eventually go out of range, so reset to 0 when it reaches a limit
END REPeat loop
This starts to show how to handle more than one animated object at a time, by rapidly handling them in turn within a loop, with speed determined by often an object is handled in the loop compared to other objects in the same loop, moving fast enough to give the illusion of moving at the same time.
Turning this into a working example, here's the above listing modified to animate two objects at different speeds:
Code: Select all
100 CLS
110 x = 0 : y = 100 : REMark object 1 starting location
120 x2 = 0 : y2 = 150 : REMark object 2 starting location
130 CURSOR x,y : PRINT"ABCD"(1)
140 CURSOR x2,y2 : PRINT"ABCD"(1)
150 direction = 1 : REMark -1=left, 1=right
160 direction2 = 1 : REMark relative speeds timing counter
170 counter = 0
180 REPeat loop
190 IF INKEY$(1) = CHR$(27) THEN EXIT loop
200 oldx = x : oldy = y
210 x = x + direction
220 IF (counter MOD 2) = 1 THEN
230 REMark move the slower object only 50% of the time
240 oldx2 = x2 : oldy2 = y2
250 x2 = x2+direction2
260 END IF
270 :
280 REMark process object 1 - does it need reverse direction?
290 IF x > 100 THEN x = 100 : direction = -direction
300 IF x < 0 THEN x = 0 : direction = -direction
310 :
320 REMark process object 2 - does it need reverse direction?
330 IF x2 > 100 THEN x2 = 100 : direction2 = -direction2
340 IF x2 < 0 THEN x2 = 0 : direction2 = -direction2
350 :
360 IF oldx <> x OR oldy<>y THEN
370 CURSOR oldx,oldy : PRINT ' ';
380 CURSOR x,y : PRINT "ABCD"((x MOD 4)+1);
390 END IF
400 :
410 IF oldx2 <> x2 OR oldy2 <> y2 THEN
420 CURSOR oldx2,oldy2 : PRINT ' ';
430 CURSOR x2,y2 : PRINT "ABCD"((x2 MOD 4)+1);
440 END IF
450 :
460 counter = counter+1
470 END REPeat loop
Obviously, duplicating code for each object like this is going to get clumsy for a large number of objects, so we would start using arrays to hold lists of objects and so on, breaking things into easy lists of things to process in turn. You might have an entry for where the object starts, where it needs to end, whether it reverses direction when it reaches limits, and how fast it moves relative to other objects. Hopefully, you can start to see how handling lists of objects like this makes it easier to handle and process larger numbers of objects, using the same code to handle most things, but within a loops rather than individual code. This is the sort of approach which will lead to the approach to develope an animated sprite graphics system, but there is a lot more to be considered.
PLANES - You can have graphics at different 'depths', or 'planes', which regulate how they pass over each other. Which one is in the foreground, which one is behind it, and which ones can collide because they are on the same level. Think of them as different levels of a house if you like - people can only collide if they are on the same floor of the house. By drawing these from the furthest away to the front in turn, it's possible to create some degree of illusion of depth, with objects in different planes passing in front of and behind each other. The concept of 'planes' is important when it comes to programming animated graphics.
COLLISION DETECTION - this is as simple as spotting objects which occupy each other's space. Provided they are on the same plane or depth, all you need to do is check their x and y coordinates to see if they overlap and BANG, you have a collision (e.g. a missile hits a space invader). Simple games such as a basic shoot-em-up will only ever need the one plane, making collision detection easier.
Quite a long article I'm afraid, hope you find it useful to get started with and to provide ideas to get the discussion going on this thread. Maybe, just maybe, if someone is sufficiently interested after getting to grips with this, you might consider a complete article on the subject to publish on my website to help others get to grip with the sbject of programming moving graphics on the QL.