Animation in SMSQE

Anything QL Software or Programming Related.
Tinyfpga
Gold Card
Posts: 252
Joined: Thu Sep 27, 2018 1:59 am

Animation in SMSQE

Post by Tinyfpga »

I have been trying to write some "crap" games for the 2021 competition using Liberated SBASIC in SMSQE and have found
animation rather difficult ,so when I noticed the Dynamic Sprite topic I posted a some questions relating to their use in
games programming.

My questions were quickly answered by dilwyn as follows:-
---
Short and simple answer: no they can't be used as moving sprites for games. The operating system has no support other than using them as 'moving' or animated on-screen pointers (mouse pointer), and writing them out as simple non-animated objects using commands such as SPRW in Easyptr and WSPRT in QPTR toolkit. Recent SMSQ/E can use them as cursors too instead of the standard flashing cursor block.
To make a game animation would require that some fairly substantial software is written for animation, collision detection, limit checks and so on. It's unfortunate that the name 'sprite' was used in some ways, as people think you can use them to write games easily, which you can't.
Very little by way of "beginner" documentation other than what's in the QPTR manual and a few articles from Marcel and Wolfgang which appeared in QL Today. Use the search box on my home page to see what info there is on my site, for example.
Basically, it's not much more than a small graphic or icon with a particular header. To make a dynamic sprite you basically stick a load of them together and put some timing values in to indicate how much of a pause between printing each one (somewhat simplified explanation). No support whatsoever for moving them around the screen in games or anything like that.
---

This set off a series of off-topic posts relating to animating displays.It seems that my difficulty in creating animated games in SMSQE might be of general interest and have thus started this topic.

Dilwyn posted a short SBASIC program (in the Dynamic Sprites topic) that I have modified to test the control of program flow using the keyboard. The program demonstrates a wide range of behaviors depending on my modifications which may help me to improve my programming skills. So far I have come up against keyboard buffering, limited control of program flow via the PAUSE keyword, disruptive effects on multitasking and jerky animation.

I am a recreational SBASIC programmer and can only guess that,as dilwyn posts, animation goes a bit beyond what the system is capable of doing. I suspect that, and remember that I know very little, the lack of, a real-time clock and an event-driven keyboard, amongst many other things, is part of a problem to be overcome


User avatar
BSJR
Trump Card
Posts: 182
Joined: Sun Oct 18, 2015 12:53 pm
Location: Amsterdam
Contact:

Re: Animation in SMSQE

Post by BSJR »

The QLE.win project by Urs Koening comes preinstalled with his QTop package which also includes some animation demos.
It's Turbo compiled so it's unlikely he used any QPRT or EasyPTR tools for that.
This may be worth a look, it's 4 or 8 colours but it should be looked at on slower system than my QPC2.
Anything from TrumpCard to Q68 will do.

BSJR


Martin_Head
Aurora
Posts: 847
Joined: Tue Dec 17, 2013 1:17 pm

Re: Animation in SMSQE

Post by Martin_Head »

There is Digital Precisions, Super Sprite Generator. See viewtopic.php?f=3&t=3467

I think some of it is SuperCharged BASIC. I think, that I decompiled some of it when I was working on the decompiler. If I remember, it uses some 'In line' compiled code to speed it up.

I don't know the copyright status, So I did not go any further. I'm not sure if I still have what I did on it. But it could be done again. The 'In line' bit needs to be decompiled by hand.
I think this is the only program I encountered that had used 'In line' code.


User avatar
dilwyn
Mr QL
Posts: 2753
Joined: Wed Dec 01, 2010 10:39 pm

Re: Animation in SMSQE

Post by dilwyn »

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.


Tinyfpga
Gold Card
Posts: 252
Joined: Thu Sep 27, 2018 1:59 am

Re: Animation in SMSQE

Post by Tinyfpga »

I wish to thank dilwyn et al. for the posts on this topic and the "Dynamic Sprites" topic. There is much to digest,so I am going to create a small manual consisting of edited extracts of these posts and then print them out and try and understand what is possible. I will document my attempts to create animated games and distractions and post the combined result in this topic.

As I mentioned in my first post I have already played around with dilwyn's test program,modified to run in my quasi SMS2 setup. I run the same programming environment on:-
Q68s (SMSQE)
PCs, QPC 1 and 2 (SMSQE), and SMS2 in an emulator
SMS2 in MIST
SMS2 in Atari ST PEROM
SMSQmulator (SMSQE) on a Raspberry PI400.
Apart from the matter of colours and display resolution I want my programs to work without modification on all the systems listed above, so I can't use code that bypasses the operating system or won't compile in Liberator.

I have a copy of Stella that runs on Atari STs but as no user interface or programming environment was ever (as far as I know) created for it, it is not really usable by a recreational user.


User avatar
dilwyn
Mr QL
Posts: 2753
Joined: Wed Dec 01, 2010 10:39 pm

Re: Animation in SMSQE

Post by dilwyn »

Moving on from the first article, I'll take a quick look at some useful aspects of QL graphics when creating moving graphics.

The examples I gave in the first articles printed a character, then overprinted it with a space, then printed it again at a new location. Nice and easy.

It is worth looking at the use of the OVER command when printing such graphics, as there are two useful features which may be worth considering. All variants can use a channel number to make it affect the chosen window only, if you don't specify a channel number, it will affect the default channel 1.

OVER 1 - this ensures that anything written to the screen with PRINT is printed in INK colour only, without background paper colour, e.g. transparent text, where only the shape of the letter itself is printed, any unset pixels simply assume the existing background colour. Useful if you wish to avoid changing PAPER colour a lot when printing assorted graphics, or simply wish to print some text transparently over a background without erasing the backing.

Code: Select all

PAPER 2 : CLS : OVER 1 : INK 7 : PAPER 4 : AT 0,0 : PRINT 'X'
So, despite the paper being green (4) when the X is printed, only the white letter A is printed over the background without disrupting the red background of the window.

OVER -1 - this ensures that anything printed over itself twice is erased, because the pixels drawn ar exclusive-ORed into the existing display. You can get an idea of how this works with a simple set of commands such as

Code: Select all

CLS:OVER -1
AT 0,0:PRINT'X':PAUSE 100:AT 0,0 :PRINT 'X'
OVER 0
OVER 0 just returns things to normal.

It has the advantage that you don't need to keep track of where an object was when it's moved - simply overprint it to erase it, change to the new position and print in the new position.

Incidentally, OVER -1 isn't just for text printing. It affects the BLOCK command too, so you can do something like this to draw a block for a second or so before it disappears:

Code: Select all

OVER -1 : BLOCK 100,100,0,0,7:PAUSE 50:BLOCK 100,100,0,0,7 : OVER 0
Because the colour is exclusive-ORed into the screen, some understanding of how the colour is exclusize-ORed into the screen is needed at a binary level to understand and predict how a colour will appear. Suppose the background is red (paper colour 2), which in binary is 010. We overprint the block using white ink (colour 7) which is 111 in binary. Exclusive OR 111 with 010 gives 101 (decimal 5) which is cyan. If both corresponding bits are 1, they become a 0 in the resultant colour, whereas if one bit is a zero and other a 1, the resultant colour bit becomes a 1, hence why printing white on red with OVER -1 becomes cyan, not white.

In many of my early QL programs, which worked with keyboard and text menus only (no mouse in those days to select things), I used to use OVER -1 to draw a block of colour over a line of text in the menu, as a highlighter block which was moved up and down to select an item from the menu. The block appeared around the text, and the text colour changed (inverted, with appropriate choice of colours).

If you haven't used OVER 1 and OVER -1 before, it can take a while to get used to. But it does have its uses, and some game sprite packages do use this to display and move the objects. It offers another way of doing things which may be advantageous in some programming circumstances.


Tinyfpga
Gold Card
Posts: 252
Joined: Thu Sep 27, 2018 1:59 am

Re: Animation in SMSQE

Post by Tinyfpga »

I have played around with dilwyns sample code and describe the effect on a 1024x768 QPC2 ancient laptop setup
as follows:- (I am compiling his code with liberator)

No matter what I try, if write to a window more than 50 times a second multitasking is seriously degraded.
At a certain speed the system ceases to be responsive. If I try to time-waste as a substitute for PAUSE the system response is significantly reduced.

This does not surprise me, but I would like to know why this happens. For animation, updating a screen 50 times a second
should be fast enough but only if one could create the frames in the background. I have no idea how one could do this.
I am guessing that one could write to a buffer and then write the buffer to a window but I am sure this can not be done
in SBASIC.

Dilwyns code has been useful for seeing how fast a particular function runs whilst multitasking. Is there any way of using the
clock to calculate and display the time taken for a program to run? At the moment I use a stop watch.


stevepoole
Super Gold Card
Posts: 712
Joined: Mon Nov 24, 2014 2:03 pm

Re: Animation in SMSQE

Post by stevepoole »

Hi Tiny,

Here is a stop-watch, accurately rounded down to the nearest second : (Line 110 is sample code to time).

100 start=DATE : if start<>DATE : ELSE GOTO 100
110 :
200 FOR n=1 to 1
210 FOR i=1 to 999999 : dud=rnd: REMark replace this line with your main program call here !
220 END FOR n
230 :
300 timer=(DATE-start)/n: PRINT timer

For precise timing, use the outer FOR loop with more 'n' loops. (On my QPC2, the line 210 code takes '2' seconds to terminate, with n=1).

Regards, Steve.


Tinyfpga
Gold Card
Posts: 252
Joined: Thu Sep 27, 2018 1:59 am

Re: Animation in SMSQE

Post by Tinyfpga »

Thank you, stevepoole. Your code works well and reveals that my ancient PC is 5 times slower than yours.


User avatar
TMD2003
Trump Card
Posts: 168
Joined: Sat Oct 10, 2020 12:18 pm

Re: Animation in SMSQE

Post by TMD2003 »

Tinyfpga wrote:I have been trying to write some "crap" games for the 2021 competition using Liberated SBASIC in SMSQE and have found
animation rather difficult ,so when I noticed the Dynamic Sprite topic I posted a some questions relating to their use in
games programming.
I'll be watching this thread with interest...

Incidentally, this is why I was asking (elsewhere) if the 68000 series has an equivalent of the Z80's LDIR and LDDR instructions - it means the Spectrum can load, say, an entire screen in about a second. Say we've got a screen stored at 56384 onwards, it can be recalled into screen memory this way:

LD HL,56384 ; HL is the start of the source
LD DE,16384 ; DE is the destination - 16384 is the first byte of the display file
LD BC,6912 ; BC is the number of bytes to copy
LDIR
RET

Alternatively you can start from the final address and work backwards - this loads the attributes first and then the screen from bottom to top:

LD HL,63295
LD DE,23295
LD BC,6912
LDDR
RET

So if the 68008 has an instruction where it can do the same thing, it could be useful for storing sprites in otherwise-free memory and dumping them into the required memory location. I was testing POKEs to the QL's display file earlier - though I still haven't worked it all out yet!


Spectribution: Dr. Jim's Sinclair computing pages.
Features my own programs, modified type-ins, RZXs, character sets & UDGs, and QL type-ins... so far!
Post Reply