█▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀█ ▌ISS 2 VOL 1 October 1997 ▐ ▌ █ █ ███ ███ █ ███ █ █ ██ █ ▐ ▌ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ▐ ▌ █ █ █ █ ███ █ █ █▄█ █ █ █ █ █ █ ▐ ▌ █ █ █ █ █ █ █▀▀▀▀▀█ █ █ █ █▀▀▀▀▀█ █ █ █ ▐ ▌ █ ███ ███ █ █ █ █ █ █ █ █ ██ ▐ ▌ ▐ ▌ N E W S L E T T E R ▐ █▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█ \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ CONTENTS: Article: Description: ------------------------------------------------------------------------ GETting and PUTting in QBasic efficient, fast graphics EGA Page Flipping for no-flicker animation Sprites advanced sprite operations ▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀ ############################################################################## ########### GETting PUTting IN QBASIC ########### ############################################################################## 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 receive 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 obsolete, 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. David Tordrup _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- ############################################################################## ########### EGA PAGE FLIPPING ########### ############################################################################## Just about every programmer knows how to use graphic statements like circle, line, pset, etc., but not many programmers are using page flipping in their programs! Use page flipping! It makes QB look good! Just what is page flipping? It's the best way to do animation, for sure. Page flipping is just like a flip book. Each page is a frame, and by flipping through a page at a time you get perceived motion. This is EGA, so let's review what EGA means for QBasic. Okay, EGA is screen mode 7, so remember that. Like mode 13, we get 320x200 resolution and like mode 12, we get 16 colors. But what's different is that we get 8 graphic pages (numbered 0-7). With these pages, we can write our graphics to any page, line, circle, PUT, pset, paint, etc., but we can only SEE one page at a time on our monitor. That's the trick. We write our graphics to the pages, then flip through them. Since graphic statements are slow, we can write graphics while another page is showing, and since the turning of a page happens almost instantly, we get virtually no flicker! To change what page we write to, we need to use the SCREEN statement. Just type: SCREEN 7, , 3, 0 If you don't remember the SCREEN statement syntax, it's: SCREEN [mode], [colorswitch], [activepage], [visualpage] Mode is the mode, colorswitch changes between B&W and color, activepage is the page our graphics statements are going on, and visualpage is the graphic page that is displayed on the monitor. There are 2 ways to do the actual flipping. One way is to copy the contents of one page to the visualpage. For this, we use PCOPY. PCOPY [sourcepage], [destinationpage] For example, let's say the visual page was 3, and we wanted the user to see the contents of page 5. We just type: PCOPY 5, 3 To copy page 5 to page 3. Another way to do it is to change the visual page. For example, if we are currently looking at page 4 and we want to see page 5, we type: SCREEN 7, , , 5 That will keep us in mode 7, ignore colorswitch, ignore activepage, and change the visual page to 5. That's all there is to it! Try putting some page flipping in your programs. Maybe add some effects to your main menu. I don't know what you will use it for, but it's a great technique to play with and makes spicy programs! Here's some sample code. It moves a sprite across the screen. Play around with it. Maybe put in your own sprite and see if you can do some other neat effects. You might also consider having the sprite remain stationary but change slightly so it looks like motion, so you get the same effect as using PUT with the XOR actionverb. Anyway, enjoy! '-----------CODE BEGINS HERE-------------- SCREEN 7, , 0, 0 'switch to mode 7 DIM sprite%(10) 'set up an array of 10 integers FOR x% = 0 TO 3 'this just loads the sprite up FOR y% = 0 TO 3 READ n% PSET (y%, x%), n% NEXT y% NEXT x% DATA 0,1,1,1 DATA 1,0,0,0 DATA 1,0,0,0 DATA 0,1,1,1 GET (0, 0)-(3, 3), sprite% FOR x% = 0 TO 300 'here's the action. this moves the sprite. SCREEN 7, , 1, 2 'we change the active and visual pages... PUT (x%, 100), sprite%, PSET 'this put the sprite on the ACTIVE page PCOPY 1, 2 'this copies the active page to the VISUAL page CLS 'this clears the act. page, leaving the vis. ok NEXT x% '--------------END OF CODE-------------- NOTE: To get this code into QBasic, highlight the code by pressing SHIFT while moving the cursor with the arrow keys. Then select Copy from the Edit menu. Then select New from the File menu, and the select Paste from the Edit menu. Then save the new file with a .BAS extension, load it into QB, and play with the effects! _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- ############################################################################## ########### SPRITES ########### ############################################################################## ┌────────────────────────────────────────────────────────────────────────────┐ │ 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, totaling 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 bitmap reading 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? Solution: This will happen if you've added comments after your DATA 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. Solution: Try swapping the variables in the PSET statement. If these 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 hard drive │ └────────────────────────────────────────────────────────────────────────────┘ 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 hard drive, 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 separate 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 hard drive, 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 hard drive 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 enemies 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 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, 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. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- EOF