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" with the subject "SUBSCRIBE". To unsubscribe send an email to "Basix Fanzine" 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