Copy Link
Add to Bookmark
Report

The Basix Fanzine issue 14

eZine's profile picture
Published in 
The Basix Fanzine
 · 28 Dec 2019

  

The BASIX Fanzine ISSUE #14 - MAY 1999
Text Issue Edited By David Groulx

------------------------------------------------------------------------
Contents
------------------------------------------------------------------------


Tutorials 3-D Graphics in Qbasic
Using EGA videopages in
QBasic
Masking sprites in
QBasic
Using sprites in QBasic
GETting and PUTting in
QBasic
Your
Programs Text Fire Program
Text Plasma
Misc &
Credits 2000 Website Awards
Credits
Contact Info

------------------------------------------------------------------------
Tutorials
------------------------------------------------------------------------

------------------------------------------------------------
3-D Graphics in QBasic
by J. Farrell
------------------------------------------------------------

This tutorial will teach you how to display 3-dimensional images on the
screen in QBASIC. A 3-dimensional object is an object which has depth as
well as length and width. To graph a 3-dimensional object, you need three
axes: the x-axis, which runs across your screen; the y-axis, which runs up
and down your screen; and finally, the z-axis, which represents depth.
Since we cannot enter all three coordinates into a graphics statement such
as LINE or PSET, we need an equation which converts 3-dimensional
coordinates into their 2-dimensional equivalents. The equation we will be
using is as follows:

x2 = x3 / z3 * screenwidth + (screenwidth / 2)
y2 = y3 / z3 * screenheight + (screenheight / 2)

In these equations, x2 and y2 represent the 2-dimensional coordinates.
These are the ones we will be setting on the screen. x3, y3, and z3
represent the 3-dimensional coordinates. The variable screenwidth
represents the width of the screen, in pixels. Thus, screenheight
represents the height of the screen, also in pixels. In screen mode 7,
screenheight would be 200 and screenwidth would be 320. For example, this
code would plot the 3-dimensional point (5, 5, 10) on the screen, using
screen mode 7:

SCREEN 7, 0, 0, 0
CLS
x3 = 5: y3 = 5: z3 = 10
x2 = x3 / z3 * 320 + 160
y2 = y3 / z3 * 200 + 100
PSET (x2, y2), 15
END

It's important to know just where the three axes are located on your screen
when using the equations above. The x-axis still runs across the screen,
but its center is in the middle of your screen (when referring to it by
3-dimensional coordinates). The y-axis runs up and down your screen, and
its center is also in the middle of the screen. So plotting the
3-dimensional point (0, 0, 30) would set a point right in the center of
your screen. The z-axis is harder to describe. Its center is as "close" as
you can get to the screen. As the z-values of points increase, they appear
farther away from the viewer. To get a feel for where the axes are, this
program will allow
you to enter 3-dimensional coordinate values as it places them on screen.
Entering a z-value of -1 will stop the program.

SCREEN 7, 0, 0, 0
CLS
DO
LOCATE 1,1: INPUT "Enter x, y, z: ", x3, y3, z3
x2 = x3 / z3 * 320 + 160
y2 = y3 / z3 * 200 + 100
PSET (x2, y2), 15
LOOP UNTIL z3 = -1

Now that we know enough to set points on the screen, let's take a look at
one problem you will probably run into sooner or later. If you look at the
equation, you'll see that entering a z-value of zero will cause the program
to error out (division by zero). Also, if a negative number is inserted for
z3, some unsightly results will occur! For this reason, it's best not to
display points which have a z-value of zero or less. Here are the changes
you'd have to make to the program so far:

SCREEN 7, 0, 0, 0
CLS
DO
LOCATE 1,1: INPUT "Enter x, y, z: ", x3, y3, z3
IF z3 > 0 THEN
x2 = x3 / z3 * 320 + 160
y2 = y3 / z3 * 200 + 100
PSET (x2, y2), 15
END IF
LOOP UNTIL z3 = -1

One way you can create a feeling of depth is to use darker colors for
points that are farther away from the screen (have larger z-values). For
instance, you could plot the points that are very close using color 15
(white), the points that are middle-range using color 7 (gray), the points
that are further away using color 8 (dark gray), and if a point is too far
away, don't plot it at all. Here is our program one more time, this time
adding some
color effects.

SCREEN 7, 0, 0, 0
CLS
DO
LOCATE 1,1: INPUT "Enter x, y, z: ", x3, y3, z3
IF z3 > 0 THEN
x2 = x3 / z3 * 320 + 160
y2 = y3 / z3 * 200 + 100
SELECT CASE z3
CASE 1 TO 35: clr = 15
CASE 36 TO 70: clr = 7
CASE 71 TO 105: clr = 8
CASE ELSE: clr = 0
END SELECT
PSET (x2, y2), clr
END IF
LOOP UNTIL z3 = -1

Now we're ready to take things one step further and add movement. The
program we will write will pick 100 stars at random 3-dimensional
coordinates, display them on the screen, and let the user move around. We
will be using the arrow keys to move up, down, left, and right. The 8 and 2
keys on the keypad will be used to zoom in or out, respectively. First,
we'll need to set up an array for holding all of these values, and fill it
with random numbers.

DIM stars(100, 3)
RANDOMIZE TIMER
FOR a = 1 TO 100
stars(a, 1) = INT(RND * 81) - 40
stars(a, 2) = INT(RND * 81) - 40
stars(a, 3) = INT(RND * 105) + 1
NEXT

This segment of code creates an array with 100 rows, one for each star; and
three columns. The first column is the x-value, the second is the y, and
the third is the z. It also picks random x-values between -40 and 40,
random y-values between -40 and 40, and random z-values between 1 and 105.
Next, we'll set up the main loop of the program and the segment which draws
the stars on the screen, which you should be familiar with. Note: This
segment of the program uses an animation technique called video pages. If
you are not familiar with video pages, you may want to read the separate
tutorial before continuing this one.

SCREEN 7, 0, 1, 0
DO
CLS
FOR a = 1 TO 100
IF stars(a, 3) > 0 THEN
x2 = stars(a, 1) / stars(a, 3) * 320 + 160
y2 = stars(a, 2) / stars(a, 3) * 200 + 100
SELECT CASE stars(a, 3)
CASE 1 TO 35: clr = 15
CASE 36 TO 70: clr = 7
CASE 71 TO 105: clr = 8
END SELECT
PSET (x2, y2), clr
END IF
NEXT
PCOPY 1, 0
LOOP

Finally, we will add the routine which allows movement. In order to move
the stars, we simply need to change the values of the stars along one of
the axes. For instance, to zoom in, you would subtract one from the z-value
of each star. To move up, you would add one to the y-value of each star,
and so on. To do this, we will be using a SUB procedure called Shift. Take
a look at the declaration statement for this subroutine.

DECLARE SUB shift (axis, value)

As you can see, we will be passing Shift two variables. Axis will represent
which axis is being changed: 1 for x, 2 for y, or 3 for z. Value will be
either 1 or -1, depending on whether we want to increase or decrease the
values. Here is the code for the Shift procedure:

SUB shift (axis, value)
FOR a = 1 TO 100
stars(a, axis) = stars(a, axis) + value
NEXT
END SUB

All that remains now is to give the user control of the Shift procedure. We
will be using three variables to track the movement of the stars: horiz,
for movement along the x-axis; vert, for movement along the y-axis; and
zoom, for the z-axis. When these variables are equal to zero, no movement
is taking place along that particular axis. If one of the variables is not
equal to zero, then the Shift procedure is called, and the variable is
brought one step closer to zero. That is, if vert was 3, Shift(2, 1) would
be called, and vert would be made 2. If vert was -4, Shift(2, -1) would be
called, and vert would be made -3. Let's take a look at the routine which
calls Shift if
necessary.

IF horiz <> 0 THEN
Shift 1, SGN(horiz)
horiz = horiz - SGN(horiz)
END IF
IF vert <> 0 THEN
Shift 2, SGN(vert)
vert = vert - SGN(vert)
END IF
IF zoom <> 0 THEN
Shift 3, SGN(zoom)
zoom = zoom - SGN(zoom)
END IF

Note the use of the SGN function. SGN(variable) returns -1 if the variable
is negative, 1 if the variable is positive, and 0 if the variable is zero.
This makes sure that the stars' values are only detracted from one pixel at
a time, allowing for fluid movement. Here is the final part of the program,
which allows the user use of the keyboard:

k$ = INKEY$
SELECT CASE k$
CASE CHR$(0) + CHR$(72): vert = vert + 15
CASE CHR$(0) + CHR$(75): horiz = horiz + 15
CASE CHR$(0) + CHR$(77): horiz = horiz - 15
CASE CHR$(0) + CHR$(80): vert = vert - 15
CASE "8": zoom = zoom - 15
CASE "2": zoom = zoom + 15
END SELECT

That's about it for our 3-D starfield program. Note that many improvements
can be made upon this program. For instance, you could alter it so that
once a star is off the screen, it reappears on the opposite side of the
screen, to give the effect that you cannot leave the starfield. You could
use the OUT procedure and the INP function to make many different shades of
color to enhance the color-depth effect. You could make the stars leave
trails behind them as they move for a Star Wars-like effect. Here is our
code, in its entirety:

' 3-D starfield
' Written by J. Farrell
' Use the arrow keys to shift viewpoint
' Use 8 and 2 to zoom in/out, respectively
DEFINT A-Z
DECLARE SUB Shift (axis, value)
DIM SHARED star(100, 3)
RANDOMIZE TIMER
FOR a = 1 TO 100
star(a, 1) = INT(RND * 81) - 40
star(a, 2) = INT(RND * 81) - 40
star(a, 3) = INT(RND * 105) + 1
NEXT
SCREEN 7, 0, 1, 0
DO
CLS
SELECT CASE INKEY$
CASE CHR$(0) + CHR$(72): vert = vert + 15
CASE CHR$(0) + CHR$(75): horiz = horiz + 15
CASE CHR$(0) + CHR$(77): horiz = horiz - 15
CASE CHR$(0) + CHR$(80): vert = vert - 15
CASE "8": zoom = zoom - 15
CASE "2": zoom = zoom + 15
END SELECT
IF horiz <> 0 THEN
Shift 1, SGN(horiz)
horiz = horiz - SGN(horiz)
END IF
IF vert <> 0 THEN
Shift 2, SGN(vert)
vert = vert - SGN(vert)
END IF
IF zoom <> 0 THEN
Shift 3, SGN(zoom)
zoom = zoom - SGN(zoom)
END IF
FOR a = 1 TO 100
x2 = star(a, 1) / star(a, 3) * 320 + 160
y2 = star(a, 2) / star(a, 3) * 200 + 100

SELECT CASE star(a, 3)
CASE 1 TO 35: clr = 15
CASE 36 TO 70: clr = 7
CASE 71 TO 105: clr = 8
CASE ELSE: clr = 0
END SELECT
PSET (x2, y2), clr
NEXT
PCOPY 1, 0
LOOP
SUB shift (axis, value)
FOR a = 1 TO 100
star(a, axis) = star(a, axis) + value
NEXT
END SUB

Suggested reading: Using EGA videopages in QBasic


------------------------------------------------------------
Using EGA videopages in QBasic
by David Tordrup
------------------------------------------------------------

--------------------------------------
Introduction
--------------------------------------

This tutorial will teach you a technique allowing you to create smooth
sprites that move around without flickering. A -must- if you're planning to
write a game in QBasic. Throughout the tutorial it is assumed that screen
mode 7 is used. The advantage of mode 7 is, that you have 7 video-pages to
work with, and 16 colors at your disposal.

--------------------------------------
How the videopage technique works
--------------------------------------

When writing a program that uses videopages to animate graphics, it is
common practice to use a page as your 'clipboard'. For the actual animation
you only need two pages, the clipboard and the output page, but if you're
planning to add a background you should use three pages, the last one being
the page used to draw/animate the background. Let's start with using two
pages, e.g. no background. The first thing you do is of course to have your
sprites ready in an array, if you're not confident with this routine, see
Using Sprites in Qbasic. Next, decide which page you'll use for your output
page (the page that is viewed on screen), and which page will be your
clipboard (the page where you draw all your sprites). Say you've chosen
page 0 to be your output page, and page 1 for your clipboard, you would
initially set page 0 as your visual page, and 1as your active page:

SCREEN 7, , 1, 0
| |
| `------------ This is the number of your output page.
|
`--------------- This is the number of your clipboard page.

Everything you put on the screen from now on, will go onto the clipboard,
and will not be displayed on the screen - yet. To shift everything from the
clipboard onto the output page, we use the PCOPY statement - more on this
later. The great thing about this method is, that you can move sprites
around smoothly, so let's do just that. Say we have a sprite in the array
Sprite%, that we want to move from one end of the screen to the other. We
would create a loop counting 0 TO (320 - x), where x is the length of the
sprite.

SCREEN 7, , 1, 0 ' Set the pages
FOR a% = 1 TO 310 ' Assuming our sprite is 10 pixels wide
PUT (a%, 100), Sprite% ' Draw the sprite on the clipboard
PCOPY 1, 0 ' Copy the clipboard to the output page
NEXT a% ' Continue the movement.

The PCOPY statement overwrites the entire output page with the contents of
the clipboard, creating that smooth animation. That's it, smooth as a
cueball, no flickering. Getting back to the issue of a background, all you
have to do here is to assign a page that will be your background page,
we'll use 2, and draw your background on there before the sprite animation
starts:

SCREEN 7, , 2, 0 ' Set the active page to your background
page

[Background drawing routine goes here]
......
........
..........

SCREEN 7, , 1, 0 ' Change to the clipboard
FOR a% = 1 TO 310 ' Same length of the loop as above
PCOPY 2, 1 ' First copy the background to the
clipboard
PUT (a%, 100), Sprite% ' Then draw the sprite on the clipboard
PCOPY 1, 0 ' Copy the clipboard to the output page
NEXT a% ' Continue the movement.

All you have to do is to add an extra statement in your loop that copys the
background to the clipboard before you start drawing any sprites. It's as
simple as that, and the result: smooth, professional looking moving
sprites.


--------------------------------------
Afterword
--------------------------------------

If you're going to use a background in your program, or your sprites are
going to be drawn on top of each other, you should read Masking sprites in
QBasic. This document explains how to avoid the sprites being drawn in the
wrong colors when overlapped or drawn on a background.

------------------------------------------------------------
Masking sprites in QBasic
by David Tordrup
------------------------------------------------------------

--------------------------------------
Introduction
--------------------------------------
Ever wondered why your background shows through your sprites, and what to
do about it? Chances are, if you're PUTting sprites onto a background
that's anything else than plain black, you're sprites will be transparent.
This document will explain a technique to you that will let you get rid of
ghostly sprites once and for all: Masking The key to this routine lies in
simply using a certain keyword with your PUT statement, and having an extra
set of sprites. How? Read on...

--------------------------------------
How the masking technique works
--------------------------------------

This special technique works by using 'negatives' of all your sprites, that
are merged onto the background before your sprite is actually drawn. The
mask sprites simply contain black and white pixels. Each of these have
different effects on the background when merged. Black pixels will clear
the area they are put on, and white pixels will leave the area untouched.
Starting to get the picture? Say we have made a sprite from the following
bitmap:

DATA 0, 0, 1, 0, 0
DATA 0, 1, 1, 1, 0
DATA 1, 1, 1, 1, 1
DATA 0, 1, 1, 1, 0
DATA 0, 0, 1, 0, 0

Our mask bitmap would have to contain 15's (white) where our sprite bitmap
has 0's (black), and 0's where our sprite bitmap has a non-zero value (>0).
Thus, the mask for the above bitmap would look like this:

DATA 15,15, 0,15,15
DATA 15, 0, 0, 0,15
DATA 0, 0, 0, 0, 0
DATA 15, 0, 0, 0,15
DATA 15,15, 0,15,15

When PUT onto the background with the AND keyword, a clear area in the same
shape of your sprite is created. This lets you PUT your sprite onto a neat
clear space, obliterating transparancy.

--------------------------------------
Afterword
--------------------------------------

The technique explained will only work if your background is redrawn with
every frame of the animation sequence, as it deletes the part of the back-
ground it draws on. This is, however, not a problem if you use screen mode
7 (320x200/16 colors/7 pages), as you can draw your background on one
videopage, and copy it to your active page with every frame. If you need
more info on this, read Using EGA videopages in QBasic. Also, if you're not
quite confident in using GET/PUT, you also check out GETting and PUTting in
QBasic. Any comments or suggestions can be e-mailed to David Tordrup.

------------------------------------------------------------

Using sprites in QBasic
by David Tordrup
------------------------------------------------------------

--------------------------------------
Introduction
--------------------------------------

This tutorial will teach you some vital sprite techniques you can implement
in games or other programs. Topics like creating sprites, storing sprites
and animating sprites will be covered in depth.

--------------------------------------
Creating a sprite
--------------------------------------

The first thing we need to do when working with sprites, is of course to
have a sprite to work with :) This can be achieved in a number of ways.
Assuming we're going to make a small sprite, say 5x5 pixels for a game, we
would use the DATA/PSET method: This method works by having a double loop
routine read some data from a bitmap, whilst plotting the pixels from the
bitmap on the screen. In our case we will have two loops both counting 5,
totalling at 25, because our sprite is going to be 5x5 pixels. A sprite can
be any size, as long as it doesn't exceed the bounds of the screen mode,
but we'll use a small one because it only serves as an example. The bitmap
is simply a number of DATA statements, containing the color-values of the
sprite. Consider this 5x5 bitmap:

DATA 0, 0, 1, 0, 0
DATA 0, 1, 1, 1, 0
DATA 1, 1, 1, 1, 1
DATA 0, 1, 1, 1, 0
DATA 0, 0, 1, 0, 0

This would give us a blue, tilted square, because color number 1 is blue.
If we had used 2 instead of 1 we would have had a green square etc. You can
use any amount of colors in your bitmap, as long as you don't exceed the
amount available in the screen-mode used. Now to discuss how we actually
get the DATA put on to the screen. As mentioned, we make a loop, and inside
that loop we make another loop:

FOR a% = 1 TO 5
FOR b% = 1 TO 5
READ Pixel%
PSET(b%, a%), Pixel%
NEXT b%
NEXT a%

What this routine does is, that it reads every element of data from our
DATA statements, and draws the pixels on the screen one at a time, starting
with the top row, second row, third row etc. If your sprite is bigger that
5x5 pixels, you will have to change the bounds of the loop to the size of
your sprite. After we've got our sprite on the screen, it's time to capture
it in an array using the GET statement. This step could be left out, and we
could simply put the sprite-drawing into a SUB routine that could be called
whenever the sprite needed to be drawn, but this would slow down operation
considerably, and since we're working with QBasic we should avoid this.
We'll call our array Sprite%. A size of 15 integers should be sufficient
for storing this particular sprite.

DIM Sprite%(15)
GET(1, 1)-(5, 5), Sprite%

The above will define the array, and grab the sprite into the array. The
coordinates in the GET statement must match the bounds of the bitmapreading
loops. We now have a sprite ready to be used in our program. Looking back,
we have done the following:


* Made a routine to read our bitmap and put it on to the screen
* Made a bitmap in the shape of a tilted square
* Prepared an array, and saved our bitmap in it

A complete ready-to-run code example you can try and modify:

SCREEN 7 ' 320x200 pixels, 16 colors
DIM Sprite%(15) ' Prepare the array for our sprite
RESTORE Square ' Start reading data at label 'Square'
FOR a% = 1 TO 5 ' Start the row count
FOR b% = 1 TO 5 ' Start the column count
READ Pixel% ' Read a data-element from the bitmap
PSET(b%, a%), Pixel% ' Plot the element on the screen
NEXT b% ' Increase column position
NEXT a% ' Increase row position
GET(1, 1)-(5, 5), Sprite% ' Store the sprite in our array
PUT(160, 100), Sprite% ' Draw our sprite on the screen
END ' End the program

Square: ' Here comes the bitmap
DATA 0, 0, 1, 0, 0
DATA 0, 1, 1, 1, 0
DATA 1, 1, 1, 1, 1
DATA 0, 1, 1, 1, 0
DATA 0, 0, 1, 0, 0

In the above example, an extra statement has been added: RESTORE This makes
the following READ statement read data from the label 'Square' which we
have inserted just before our bitmap. Strictly, this isn't necessary in the
example, but if you're writing a program that uses more than one sprite
it's a good idea to add labels and RESTORE statements to avoid messups.

Troubleshooting


Problem I get a Syntax Error at the READ statement, although I've
typed it in correctly, what gives?
This will happen if you've added comments after your DATA
Solution statements. The READ statement will also try to read the
comments, and it won't work at all. Remove the comments.
Problem My sprite doesn't look like it should on the screen.
Try swapping the variables in the PSET statement. If these
Solution variables have been incorrectly placed, your sprite will be
drawn 'lying down'. Also look for missing commas between your
data elements.
Problem I get an Illegal Function Call at the GET statement.

Solution The array used to hold the sprite is too small. Try
increasing the size until it works properly.

--------------------------------------
Storing multiple sprites in an array
--------------------------------------

You can have more than one sprite in an array. To do this, you simply need
to make sure that the array is large enough to hold all your sprites, and
use an index number in your GET and PUT statements. The index number is a
pointer, that tells GET where in the array to store the sprite, and PUT
which sprite to display.

Let's say we have an array called Sprites%, which has the size of 200
Integers. If we have two images on the screen we want to store, we would do
the following:

GET (1, 1)-(10, 10), Sprites%(0)
GET (40, 40)-(50, 50), Sprites%(100)

(Assuming the images are on the locations used in the GET statements, and
that the images both take up 100 Integers of the array)

We now have an array with to sprites. To PUT them onto the screen again, we
use the same index numbers as in the GET statements:

PUT (50, 50), Sprites%(0)
PUT (100, 100), Sprites%(100)

It's as simple as that. You might want to define constants with the index
numbers of your sprites, for example you could type:

CONST Alien = 0
CONST Player = 100

Then you could get away with simply typing the name of the sprite instead
of having to remember loads of index numbers. It's really very easy and
straightforward.

--------------------------------------
Storing sprites on the harddrive
--------------------------------------

If we use a lot of sprites in our program, or they're just really large, it
can take some time to read in all the bitmaps. This problem can be overcome
by storing the sprites on the harddrive, and reading them directly into a
program instead of having to create them again every time the program is
run.

The easiest way to do this is by using the BLOAD/BSAVE statements. You
would write a seperate program to create the sprites and the sprite-file,
and add a routine to your main program to read the file into memory.

For example, using the sprite array from the 'Multiple sprites in one
array' topic, we would add the following to save it to a file:

DEF SEG = VARSEG(Sprites%(0))
BSAVE "SPRITES.GFX", 0, 200

The first statement tells the BSAVE statement where in memory to start
saving the data from. The actual BSAVE statement first needs a filename,
which can be anything with any extension as long as it's a legal DOS name.
It then requires an offset which will nearly always be 0 (this tells BSAVE
how many bytes from the beginning of the memory-segment to start saving. To
store the entire array, use 0). Finally you type the length of the array
you're saving, in our case 200.

We have now stored our sprite array on the harddrive, ready to be read by
our main program. This is done with the BLOAD statement:

DIM Sprites%(200)
DEF SEG = VARSEG(Sprites%(0))
BLOAD "SPRITES.GFX", 0

Naturally, we have to define the array which the sprites are read into
again, and we set the memory segment to the beginning of the Sprites%
array. The BLOAD statement reads the file into the array, starting at
position 0 (the beginning of the array).

That's all there is to it. And it makes your code more efficient, so use
this method if you've got a lot of sprites in your program. Only problem
is, that you have to make sure the file with the sprite data is in the same
directory as the program, but this shouldn't give you too much trouble if
you create a setup program for your program.

--------------------------------------
Animating and moving sprites
--------------------------------------

We've learnt how to create sprites, store them on the harddrive and read
them back into memory, but what do we do with them now?

A game isn't much fun if you can't control the character on the screen, or
the enemys don't move. We're going to take a look at animating and moving
sprites, probably the easiest topic so far as it utilizes basic QBasic
routines. Let's take a look at animating sprites first. This process is
fairly easy, but takes up more time in the graphics designing department.
The minimum number of sprites needed for an animation is of course 2
different frames. Unless you're looking for really primitive animation,
like a blinking light, you should use at least 4-5 frames in your
animation. All you need to do, is to create as many sprites as you'll use
in your animation in the different stages of movement. Once you've created
all your sprites, you need to figure out a way to swap between them in the
actual program. Easiest thing to do is to have a variable count the number
of times your program has looped (yes, it does have to loop to be a game
:), and use the counter to determine which frame to display. Consider the
following example:

DIM Sprites%(500) ' Prepare the sprites array
DEF SEG = VARSEG(Sprites%(0)) ' Jump to memory location of array
BLOAD "SPRITES.GFX", 0 ' Load the ready-made sprites

Counter% = 0 ' Counter at 0

DO ' Start the loop

IF Counter%<100 THEN ' If counter is less than 100
Counter% = Counter% + 1 ' increase counter
ELSE ' otherwise
Counter% = 0 ' reset the counter
END IF ' to avoid assigning too large
value

Frame% = Counter% MOD 5 ' Determine the frame number
' assuming we have 5 frames.
SELECT CASE Frame%
CASE 0: PUT(X, Y), Sprites%(0) ' Put first frame
CASE 1: PUT(X, Y), Sprites%(100) ' Put second frame
........
........
........
END SELECT

LOOP ' Jump to beginning of loop

The example assumes we've already created our sprite file SPRITES.GFX, and
that we're using an animation with 5 frames. To change the number of
frames, change the number after the MOD keyword.

The above isn't the only way to control animation, there are other ways.
Consider using two-dimensional arrays for your sprites, and you can use the
'Counter% MOD 5' function directly in a single PUT statement, drastically
shortening your code [ PUT (X, Y), Sprites%(0, Counter%...) ]

Moving sprites is also a good idea in game-development. This is simply
achieved by using variables instead of numbers in the PUT statements. For
example, if a player is controlling a sprite with the keyboard, you would
create a routine to gather the input from the keyboard, and modify the X
and Y variables accordingly. Consider the following example:

PlayerX = 1 ' Start at first column
PlayerY = 1 ' Start at first row
DO ' Start loop
SELECT CASE INKEY$ ' Start keyboard reading
CASE CHR$(0) + "H" ' Up arrow:
PlayerY = PlayerY - 1 ' player moves up
CASE CHR$(0) + "P" ' Down arrow:
PlayerY = PlayerY + 1 ' player moves down
CASE CHR$(0) + "K" ' Left arrow:
PlayerX = PlayerX - 1 ' player moves left
CASE CHR$(0) + "M" ' Right arrow:
PlayerX = PlayerX + 1 ' player moves right
END SELECT ' Stop keyboard reading
PUT(PlayerX, PlayerY), Player% ' Draw the player on the screen
LOOP ' Continue loop

The example would let the player move the character around the screen one
pixel at a time. This could be used in some cases (RPG's and such), but in
some games a constant movement would be required. This can be achieved by
assigning a 'direction variable' a new value instead of actually moving the
character in the SELECT CASE routine. You must then add an extra SELECT
CASE routine, that moves the player according to the direction variable in
every loop:

SELECT CASE PlayerDirection ' Select the players direction
CASE 1 ' Up:
PlayerY = PlayerY - 1 ' player moves up
CASE 2 ' Down:
PlayerY = PlayerY + 1 ' player moves down
CASE 3 ' Left:
PlayerX = PlayerX - 1 ' player moves left
CASE 4 ' Right:
PlayerX = PlayerX + 1 ' player moves right
END SELECT ' End movement
PUT(PlayerX, PlayerY), Player% ' Draw the player on the screen

The above code assumes we have assigned the variable PlayerDirection a
value between 1 and 4 in a previous case; it also assumes that we have
determined 1 to be up, 2 to be down etc. You can of course change all
of this, as long as it corresponds with the keyboard reading routine.


--------------------------------------
Afterword
--------------------------------------

I hope you have benefited from reading this tutorial, I myself have learnt
a thing or two writing it. If there's something you don't quite understand
or something you'd like explained in more detail, feel free to contact me
e-mail address below. Any comments, complaints and criticism are very
welcome, also I'd like to know if you find any inaccuracies in the text,
but please don't modify it yourself.

You can contact me on the following e-mail address:

dtordrup@mail1.stofanet.dk

Thank you for reading this tutorial!

Suggested reading: Masking sprites in QBasic, Using EGA videopages in
QBasic

---------------------------------------------------------
GETting and PUTting in QBasic
By David Tordrup
---------------------------------------------------------

--------------------------------------
Introduction
--------------------------------------

This document is made for anyone who wants to use graphics easily in their
programs. The QBasic routines GET and PUT are powerful and fast tools for
managing graphics from within QBasic, and after reading this document you
should be confident in using them.

Enjoy!

--------------------------------------
The GET Statement
--------------------------------------

GET is the statement used to capture an area of the screen, and save it to
an array of any type. In order to use GET, we first need to define the
array we'll save our picture in, this can be done using a long formula, but
for now we'll just use a rough estimate. If we're going to capture a 10x10
pixel area (which we are), an array of 50 INTEGERs should be sufficient. So
first we DIMension our array (you can use any arrayname, we'll use
Picture%):

DIM Picture%(50)

Our array is now ready to recieve the picture data. Next step is to
actually create the picture we want to store on the screen. After switching
to a graphical screen mode (we'll use SCREEN 7), we'll draw a white box in
the upper left corner of the screen:

LINE(1,1)-(10,10), 15, BF

We can now use the GET statement to store the white box in our array
Picture%. This is done by using the following statement:

GET(1,1)-(10,10), Picture%

The first set of coordinates is the upper-left corner of our image, and the
second set is the lower-right corner. If you get an error message at this
line, try increasing the size of the array Picture%. The picture doesn't
have to be a white box, it can be anything you want, at any size you want.
But if you make something bigger than 10x10 you should also make the array
bigger, otherwise it won't fit into the array.

So, we now have a picture stuffed into the Picture% array, ready for
PUTting.

--------------------------------------
PUTting the picture on the screen
--------------------------------------
Next step is to actually put our picture on to the screen. This is done
with the PUT statement:

PUT(X, Y), Picture% [,method]

X and Y are the points on the screen where the upper-left corner of the
picture is put. PUT(100,100), Picture% would put the contents of our
array 100 pixels from the left, 100 pixels down. The [,method] is an
optional keyword you can add to influence the way the picture is drawn. If
you're just PUTting on a black background,
this is obselete, but if you're PUTting on top of another picture, you may
want to use this option. The keywords and their meanings are as follows:

Keyword Description

AND Merges the image with the background
OR Superimposes the image on the background
PSET Overwrites the background with the image
PRESET Draws the image in reverse colors,
erasing the background
XOR Draws the image or erases a previously
drawn image without erasing the background.
This can be used for animation.

For example:

PUT(100,100), Picture%, PSET

will draw our picture on the screen overwriting anything on the background.
If we want to preserve the background, we can use the XOR keyword, but then
we have to use two PUT statements - one to draw the image, and one to erase
it again:

PUT(100,100), Picture%, XOR ' Draw the picture on screen
PUT(100,100), Picture%, XOR ' Erase the picture again

Play around with the different keywords, and learn their effects
first-hand. It's the best way. Also try using several keywords, by first
putting the picture with one keyword, and then putting it again with
another, you'll be surprised how many effects you can achieve.

------------------------------------------------------------------------
Your Programs
------------------------------------------------------------------------

--------------------------------------
Text Fire
By Buzz
--------------------------------------


DECLARE SUB updatescreen ()
DECLARE FUNCTION txtfirepoint! (y!, x!)
DECLARE SUB txtfirepset (y, x, firecol)

DIM SHARED scrbuf(80, 25, 2)

WIDTH 80, 25

' Initialize txtfire data

RANDOMIZE TIMER

x = VAL(COMMAND$)
IF x = 0 THEN x = INT(RND * 6) + 1

SELECT CASE x
CASE 1
RESTORE 1
CASE 2
RESTORE 2
CASE 3
RESTORE 3
CASE 4
RESTORE 4
CASE 5
RESTORE 5
CASE 6
RESTORE 6
END SELECT

'wood fire
1 DATA 9,219,15,1,219,14,4,178,14,4,177,14,4,176,14,4,219,4,0,178,4,0,177,4,0,176,4,0

'blue fire
2 DATA 8,219,9,1,178,9,1,177,9,1,176,9,1,219,1,0,178,1,0,177,1,0,176,1,0

'slime fire
3 DATA 8,219,10,0,178,10,2,177,10,2,176,10,2,219,2,0,178,2,0,177,2,0,176,2,0

'water fire
4 DATA 8,219,3,0,178,3,1,177,3,1,176,3,1,219,1,0,178,1,0,177,1,0,176,1,0

'plasma fire (as in the lamps)
5 DATA 8,219,5,0,178,5,4,177,5,4,176,5,4,219,4,0,178,4,0,177,4,0,176,4,0

'b/w fire
6 DATA 10,219,15,0,178,15,7,177,15,7,219,7,0,178,7,0,177,7,0,219,8,0,178,8,0,177,8,0,176,8,0

DIM SHARED txtfirecols
READ txtfirecols
DIM SHARED txtfirecol(txtfirecols, 2)
DIM SHARED txtfire$(txtfirecols)
DIM willekeurig(50)

FOR i = 1 TO 50
willekeurig(i) = RND
NEXT i

FOR i = 1 TO txtfirecols
READ a
txtfire$(i) = CHR$(a)
READ a
txtfirecol(i, 1) = a
READ a
txtfirecol(i, 2) = a
NEXT i

DEF SEG = &HB800

' demonstrate

RANDOMIZE TIMER

CLS
COLOR 14, 0
LOCATE 12, 36
PRINT "fire!"

FOR i = 21 TO 59
a = INT(RND * 3) + 1
txtfirepset 23, i, a
NEXT i
txtfirepset 23, 20, INT(txtfirecols / 2) + 1
txtfirepset 23, 60, INT(txtfirecols / 2) + 1
q = 1
meuh:


FOR y = 17 TO 23
FOR x = 20 TO 60
a = txtfirepoint(y, x)
IF a + 1 < txtfirecols THEN
IF a = 0 THEN
txtfirepset y - 1, x, 0
END IF
IF a > 0 THEN
txtfirepset y - 1, x, a + 1 + (willekeurig(q) * 2): q = q + 1
IF q > 50 THEN q = 1
END IF
ELSE
txtfirepset y - 1, x, 0
END IF

NEXT
NEXT
i$ = INKEY$
IF i$ = CHR$(27) THEN GOTO flierups:

updatescreen

GOTO meuh:

flierups:

CLS
CLS
WIDTH 80, 25: COLOR 7, 0: CLS : COLOR 4, 0
LOCATE 1, 1
PRINT "bwsb 1.2 used here"
COLOR 4, 0: PRINT "a part of the meuh! demonstration": COLOR 7, 0: END

FUNCTION txtfirepoint (y, x)

a = scrbuf(x, y, 1)
col = scrbuf(x, y, 2)
txt$ = CHR$(a)

c = col MOD 16
col = col - c
col = col / 16

FOR i = 1 TO txtfirecols
IF col = txtfirecol(i, 2) THEN
IF c = txtfirecol(i, 1) THEN
IF txt$ = txtfire$(i) THEN
check = i
END IF
END IF
END IF
NEXT i

txtfirepoint = check

END FUNCTION

SUB txtfirepset (y, x, firecol)

IF firecol > 0 THEN
a = ASC(txtfire$(INT(firecol)))
col = (txtfirecol(INT(firecol), 2) * 16) + txtfirecol(INT(firecol), 1)
ELSE
a = 32
col = 7
END IF

scrbuf(x, y, 1) = a
scrbuf(x, y, 2) = col

END SUB

SUB updatescreen

DEF SEG = &HB800
c = 2360
FOR y = 16 TO 23 STEP 1
c = c + 78
FOR x = 20 TO 60 STEP 1

a = scrbuf(x, y, 1)
b = scrbuf(x, y, 2)
c = c + 2


POKE c, a
POKE c + 1, b

NEXT x
NEXT y

DEF SEG

END SUB


--------------------------------------
Text Plasma
By Buzz
--------------------------------------

DECLARE SUB txtpalget (col%, r%, g%, b%)
DECLARE SUB txtpalset (col%, r%, g%, b%)
'DEFINT A-Z
RANDOMIZE -TIMER
CONST PI = 3.14159265358#
DIM COSINUS(160) AS INTEGER
DIM RAND(255) AS INTEGER
DIM oldpal(16, 3)

'FOR i = 1 TO 15
' txtpalget i, oldpal(i, 1), oldpal(i, 2), oldpal(i, 3)
'NEXT i

'made by :

'omega , omega@inorbit.com
' buzz , buzz@ddsw.nl

FOR c = 0 TO 160
COSINUS(c) = COS(c * 2 * PI / 80) * 16 + 16
NEXT

FOR c = 0 TO 255
RAND(c) = INT(RND * 4) + 1
NEXT

WIDTH 80, 50
LOCATE , , 0

r = 3
g = 5
b = 5

'FOR i = 0 TO 6
' txtpalset i, i * r, i * g, i * b
'NEXT i

'FOR i = 7 TO 15
' txtpalset i, (15 - i) * r, (15 - i) * g, (15 - i) * b
'NEXT i

WAVESIDE1 = 1
WAVESIDE2 = 3
WAVESIDE3 = 2
R1 = 1
R2 = 10
R3 = 20
DEF SEG = &HB800
a$ = "ºúþ based þú"
l = LEN(a$)
'FOR i = 0 TO 8000 STEP 2
' POKE i, RND * 256
'NEXT i
position = 8000
position = position - (l * 2)
FOR i = 1 TO l
POKE position, ASC(MID$(a$, i, 1))
position = position + 2
NEXT i
position = 7840
a$ = "ÉÍÍÍÍÍÍÍÍÍÍÍ"
l = LEN(a$)
position = position - (l * 2)
FOR i = 1 TO l
POKE position, ASC(MID$(a$, i, 1))
position = position + 2
NEXT i

xwindow1 = 5
ywindow1 = 5
xwindow2 = 70
ywindow2 = 18
DO
WAVE1 = WAVE1 + WAVESIDE1
IF WAVE1 >= 80 THEN
WAVE1 = 0
R1 = (R1 + 1) AND 255
WAVESIDE1 = RAND(R1)
END IF
WAVE2 = WAVE2 + WAVESIDE2
IF WAVE2 >= 80 THEN
WAVE2 = 0
R2 = (R2 + 2) AND 255
WAVESIDE2 = RAND(R2)
END IF
WAVE3 = WAVE3 + WAVESIDE3
IF WAVE3 >= 80 THEN
WAVE3 = 0
R3 = (R3 + 2) AND 255
WAVESIDE3 = RAND(R3)
END IF
position = 1
FOR i = 1 TO 2
WAIT &H3DA, 8, 8
WAIT &H3DA, 8, 0: NEXT i
FOR y = 0 TO 49
E = COSINUS(y + WAVE1)
FOR x = 0 TO 79
IF x > xwindow1 AND x < xwindow2 THEN
IF y > ywindow1 AND y < ywindow2 THEN
col = COSINUS(x + WAVE2) + E + COSINUS(x + WAVE3) + COSINUS(x + y)
IF col > 127 THEN col = 127
POKE position, col
END IF
END IF
position = position + 2
NEXT
NEXT
IF INP(96) = 1 THEN EXIT DO
LOOP
DEF SEG

'FOR i = 1 TO 15
' txtpalset i, oldpal(i, 1), oldpal(i, 2), oldpal(i, 3)
'NEXT i

DEFINT A-Z
SUB txtpalget (col, r, g, b)
c = col

SELECT CASE c
CASE 6
c = 20
CASE 8 TO 15
c = c + 48
END SELECT

OUT &H3C7, c

r = INP(&H3C9)
g = INP(&H3C9)
b = INP(&H3C9)

END SUB

SUB txtpalset (col, r, g, b)

c = col

SELECT CASE c
CASE 6
c = 20
CASE 8 TO 15
c = c + 48
END SELECT

OUT &H3C8, c
OUT &H3C9, r
OUT &H3C9, g
OUT &H3C9, b

END SUB


------------------------------------------------------------------------
Miscellaneous & Credits
------------------------------------------------------------------------

------------------------------------------------------------
2000 BASIC Website Award
------------------------------------------------------------

Nominated your favourite BASIC site

You can vote for your favourite on the Basix Fanzine homepage.

---------------------------------------------------------
Credits
---------------------------------------------------------

Thank you to those people who took time to send in articles for this issue.
I wasn't able to fit them all in , but they will appear in the next issue.
Those people who contributed are:

Joe Farrel tsarkon@aol.com
David Tordrup dtordrup@mail1.stofanet.dk http://www.cybernet.dk/users/dtordrup/qbzone/
Buzz buzz@ddsw.nl http://huizen.ddsw.nl/bewoners/buzz/

---------------------------------------------------------
Contact Info
---------------------------------------------------------


ARTICLES Basix_Fanzine@yahoo.com
OTHER INQUIRIES ETC Basix_Fanzine@yahoo.com
WWW ADDRESS http://www.come.to/basixfanzine

--------------------------------------
Mailing List
--------------------------------------

I assume no responsibility for any message that you receive or don't
receive from being on the list.

To join send an email to "Basix Fanzine" <ListProcessor@mindspring.com>
with the subject "SUBSCRIBE".

To unsubscribe send an email to "Basix Fanzine"
<ListProcessor@mindspring.com> with the subject "UNSUBSCRIBE".

---------------------------------------------------------
Next Issue
---------------------------------------------------------

Two more tutorials from David Tordrup of the The QBasic Team on modular and
structured programming. Plus I have two more great text programs written by
"Buzz".

Also I am always looking for articles, tutorials, comments, newsgroup
articles, etc to put in the Basix Fanzine. Please send them in.


------------------------------------------------------------------------

Edited By David Groulx
Copyright © 1999 The Basix Fanzine

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT