A Small Game Tutorial

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

A tutorial by Cyperium.

So I decided to make a tutorial of a space game, a sidescroller where you avoid obstacles and collect lives. All the graphics will be made internally so there are no need to download external files.

In this tutorial I will use a method that was common in the 90's for creating graphics, yes DATA, the image that will be created is easily seen by this method, so let's first create the graphics, I'll show you how to use the graphics later. For simplicity we'll make all the graphics 10*10 which will be sufficient for this kind of game.

SCREEN 13 DATA 000,000,000,000,000,000,000,000,000,000 DATA 000,000,015,015,015,015,015,000,000,000 DATA 000,015,015,015,015,007,007,015,000,000 DATA 000,015,015,015,015,015,015,015,015,000 DATA 000,015,015,015,015,015,015,015,015,015 DATA 000,015,015,015,015,015,015,015,015,000 DATA 000,000,015,015,015,015,015,015,000,000 DATA 000,000,000,000,000,000,000,000,000,000 DATA 000,000,000,000,000,000,000,000,000,000 DATA 000,000,000,000,000,000,000,000,000,000 FOR y = 1 TO 10 FOR x = 1 TO 10 READ c PSET (x, y), c NEXT NEXT SLEEP

Yes, that is a ship, I'm not the best at making graphics but I think we can all agree that this is a ship. 015 represent white, and 007 represent grey (the cockpit window). Now we can put the graphics into a image, we'll just name it "ship". After we have put the graphics into a image we'll erase the graphics using CLS to prepare it for making more graphics.

SCREEN 13 DATA 000,000,000,000,000,000,000,000,000,000 DATA 000,000,015,015,015,015,015,000,000,000 DATA 000,015,015,015,015,007,007,015,000,000 DATA 000,015,015,015,015,015,015,015,015,000 DATA 000,015,015,015,015,015,015,015,015,015 DATA 000,015,015,015,015,015,015,015,015,000 DATA 000,000,015,015,015,015,015,015,000,000 DATA 000,000,000,000,000,000,000,000,000,000 DATA 000,000,000,000,000,000,000,000,000,000 DATA 000,000,000,000,000,000,000,000,000,000 FOR y = 1 TO 10 ' I use 1 TO 10 here because it is more intuitive (we don't normally count from 0 to 9), FOR x = 1 TO 10 ' in actuality you can PSET (0, 0), c as well which is the first valid pixel. READ c ' read the DATA and store it in c. PSET (x, y), c ' the values in the DATA are colors so we just PSET the colors. NEXT NEXT ' we use 11,11 since 0 to 10 is 11 pixels (in both width and height). ' / / ship = _NEWIMAGE(11, 11, 13) 'here we both create the new image and get the handle for it. ' \ ' we will operate in SCREEN 13 and thus create a SCREEN 13 compatible image. ' ' ' the source argument is empty and therefor assumes the current source (the screen). ' / ' destination v source _PUTIMAGE (1, 1)-(10, 10), , ship, (1, 1)-(10, 10) CLS ' Clear the screen \ ' destination argument (which is the ship imagehandle) ' 'We have now successfully stored the ship in the image, the handle for it is stored in the ship variable. 'We make the rest of the graphics exactly the same way (the only things that need to change are the graphics 'and the name of the variable to reference the image).

Now we have stored the graphics into the ship image. I have tried to explain each step by comments. If you are uncertain at how _PUTIMAGE works or how _NEWIMAGE works you can visit these pages:

I will briefly explain more about the DATA and READ before we go on. After each READ the DATA pointer moves to the next element, so in this DATA grid we have 10 elements * 10 elements (which represents a box of graphics), each element is used as a color for the pixel in the box. The box is then drawn using PSET with the information in the DATA. (FOR x = 1 TO 10 and FOR y = 1 TO 10 represents the 10*10 box). If you try to read more than is available in the DATA you will get "Out of DATA" error. If you want to reset the DATA pointer you use the keyword RESTORE, this is not needed in our game as we only need to read the DATA once. It doesn't matter where the DATA is in the program, as long as it isn't in any SUB or FUNCTION, I have decided to put it on top of the routines that use it (as it seems more intuitive) but all DATA can as well be collected at the bottom of the program (as many programmers chose to do, as they want to limit the code in the main body).

Ok, since the code I've done so far might seem more complicated than it is because of all the comments I've made a version without comments here:

SCREEN 13 DATA 000,000,000,000,000,000,000,000,000,000 DATA 000,000,015,015,015,015,015,000,000,000 DATA 000,015,015,015,015,007,007,015,000,000 DATA 000,015,015,015,015,015,015,015,015,000 DATA 000,015,015,015,015,015,015,015,015,015 DATA 000,015,015,015,015,015,015,015,015,000 DATA 000,000,015,015,015,015,015,015,000,000 DATA 000,000,000,000,000,000,000,000,000,000 DATA 000,000,000,000,000,000,000,000,000,000 DATA 000,000,000,000,000,000,000,000,000,000 FOR y = 1 TO 10 FOR x = 1 TO 10 READ c PSET (x, y), c NEXT NEXT ship = _NEWIMAGE(11, 11, 13) _PUTIMAGE (1, 1)-(10, 10), , ship, (1, 1)-(10, 10) CLS

Comments obviously have no impact on the program itself, I think it's cleaner without them but new concepts will always be commented anyway. Keep the comments in the first version if you are still uncertain how things are accomplished.

Now, let's create the other graphics as well, we want a obstacle that can destroy the ship and we want to collect lives. I've decided that progress will be to collect the numbers 1 to 9, when 9 is collected you have made the game and each number increases the speed at which the obstacles come at you (since this is such a simple game I don't want to complicate it by making more than one "level" but you are free to change it to your liking if you want to :), perhaps add some enemies? Ability to shoot things? Only your imagination sets the limit!

We only have to make the obstacle and the life graphics, 1 to 9 can be printed to the screen.

SCREEN 13 DATA 000,000,000,000,000,000,000,000,000,000 DATA 000,000,015,015,015,015,015,000,000,000 DATA 000,015,015,015,015,007,007,015,000,000 DATA 000,015,015,015,015,015,015,015,015,000 DATA 000,015,015,015,015,015,015,015,015,015 DATA 000,015,015,015,015,015,015,015,015,000 DATA 000,000,015,015,015,015,015,015,000,000 DATA 000,000,000,000,000,000,000,000,000,000 DATA 000,000,000,000,000,000,000,000,000,000 DATA 000,000,000,000,000,000,000,000,000,000 FOR y = 1 TO 10 FOR x = 1 TO 10 READ c PSET (x, y), c NEXT NEXT ship = _NEWIMAGE(11, 11, 13) _PUTIMAGE (1, 1)-(10, 10), , ship, (1, 1)-(10, 10) SLEEP CLS DATA 000,000,000,000,000,000,000,000,000,000 DATA 000,000,000,006,006,006,000,000,000,000 DATA 000,000,006,006,006,006,006,000,000,000 DATA 000,006,012,006,006,006,006,006,000,000 DATA 000,006,006,006,006,012,006,006,000,000 DATA 000,006,006,006,006,006,006,006,000,000 DATA 000,000,006,006,012,006,006,000,000,000 DATA 000,000,006,006,006,006,000,000,000,000 DATA 000,000,000,006,006,000,000,000,000,000 DATA 000,000,000,000,000,000,000,000,000,000 FOR y = 1 TO 10 FOR x = 1 TO 10 READ c PSET (x, y), c NEXT NEXT obstacle = _NEWIMAGE(11, 11, 13) _PUTIMAGE (1, 1)-(10, 10), , obstacle, (1, 1)-(10, 10) SLEEP CLS DATA 000,000,000,000,000,000,000,000,000,000 DATA 000,000,004,004,000,004,004,000,000,000 DATA 000,004,012,012,004,012,012,004,000,000 DATA 000,004,012,012,012,012,012,004,000,000 DATA 000,004,012,012,012,012,012,004,000,000 DATA 000,004,012,012,012,012,012,004,000,000 DATA 000,000,004,012,012,012,004,000,000,000 DATA 000,000,000,004,012,004,000,000,000,000 DATA 000,000,000,000,004,000,000,000,000,000 DATA 000,000,000,000,000,000,000,000,000,000 FOR y = 1 TO 10 FOR x = 1 TO 10 READ c PSET (x, y), c NEXT NEXT life = _NEWIMAGE(11, 11, 13) _PUTIMAGE (1, 1)-(10, 10), , life, (1, 1)-(10, 10) SLEEP CLS

As you can see I've only changed the actual DATA information and the variables that reference the new images, everything else works the same way. Here I've used SLEEP to enable you to view the graphics before it is cleared by CLS, just press any key to see all three graphics one by one.

If you are unsatisfied with the graphics you can change them and view the changes by running the program again (that is what I did).

The graphics part of the game is done now, so now we need to put the graphics on the screen and add a keyboard routine, and the game mechanics (collision detection, and moving the objects).

We'll store the obstacle x,y and speed information in arrays, as well as the life x,y and speed information. We can then check against the arrays to see if the ship has hit anything. There can only be one number on the screen at one time so that doesn't need a array, but simple x, y and speed variables.

We're getting ahead of ourselves though (which is a good thing, cause having the future concept firmly in your mind will help you know what to do later). First of all let's just put the ship on the screen and control it using the keyboard (I will repost all the code everytime, so each example can be run on its own):

SCREEN 13 DATA 000,000,000,000,000,000,000,000,000,000 DATA 000,000,015,015,015,015,015,000,000,000 DATA 000,015,015,015,015,007,007,015,000,000 DATA 000,015,015,015,015,015,015,015,015,000 DATA 000,015,015,015,015,015,015,015,015,015 DATA 000,015,015,015,015,015,015,015,015,000 DATA 000,000,015,015,015,015,015,015,000,000 DATA 000,000,000,000,000,000,000,000,000,000 DATA 000,000,000,000,000,000,000,000,000,000 DATA 000,000,000,000,000,000,000,000,000,000 FOR y = 1 TO 10 FOR x = 1 TO 10 READ c PSET (x, y), c NEXT NEXT ship = _NEWIMAGE(11, 11, 13) _PUTIMAGE (1, 1)-(10, 10), , ship, (1, 1)-(10, 10) CLS DATA 000,000,000,000,000,000,000,000,000,000 DATA 000,000,000,006,006,006,000,000,000,000 DATA 000,000,006,006,006,006,006,000,000,000 DATA 000,006,012,006,006,006,006,006,000,000 DATA 000,006,006,006,006,012,006,006,000,000 DATA 000,006,006,006,006,006,006,006,000,000 DATA 000,000,006,006,012,006,006,000,000,000 DATA 000,000,006,006,006,006,000,000,000,000 DATA 000,000,000,006,006,000,000,000,000,000 DATA 000,000,000,000,000,000,000,000,000,000 FOR y = 1 TO 10 FOR x = 1 TO 10 READ c PSET (x, y), c NEXT NEXT obstacle = _NEWIMAGE(11, 11, 13) _PUTIMAGE (1, 1)-(10, 10), , obstacle, (1, 1)-(10, 10) CLS DATA 000,000,000,000,000,000,000,000,000,000 DATA 000,000,004,004,000,004,004,000,000,000 DATA 000,004,012,012,004,012,012,004,000,000 DATA 000,004,012,012,012,012,012,004,000,000 DATA 000,004,012,012,012,012,012,004,000,000 DATA 000,004,012,012,012,012,012,004,000,000 DATA 000,000,004,012,012,012,004,000,000,000 DATA 000,000,000,004,012,004,000,000,000,000 DATA 000,000,000,000,004,000,000,000,000,000 DATA 000,000,000,000,000,000,000,000,000,000 FOR y = 1 TO 10 FOR x = 1 TO 10 READ c PSET (x, y), c NEXT NEXT life = _NEWIMAGE(11, 11, 13) _PUTIMAGE (1, 1)-(10, 10), , life, (1, 1)-(10, 10) CLS DO 'We'll put the entire game in this DO...LOOP. CLS 'clear the screen each loop _PUTIMAGE (shipx, shipy), ship IF _KEYDOWN(CVI(CHR\$(0) + "H")) THEN shipy = shipy - 1 'UP IF _KEYDOWN(CVI(CHR\$(0) + "P")) THEN shipy = shipy + 1 'DOWN IF _KEYDOWN(CVI(CHR\$(0) + "K")) THEN shipx = shipx - 1 'LEFT IF _KEYDOWN(CVI(CHR\$(0) + "M")) THEN shipx = shipx + 1 'RIGHT IF _KEYDOWN(27) THEN END 'ESC _DISPLAY 'display the contents drawn to the screen LOOP

We use _KEYDOWN for keyboard detection, it's good since you can detect many keys at once (to move diagonally for example). CHR\$(0) + "H", "P", "K", "M" is the codes for UP, DOWN, LEFT and RIGHT presses on the keyboard, I convert it to a integer value using CVI.

In _PUTIMAGE we only had to use the x and y destination coordinates and the ship variable as the reference for the source image the rest is assumed (if you omit the destination image then it will be drawn to the current destination, which is the screen).

You'll notice that the ship moves just a tad too fast, we don't have anything that limits the speed so the loop is run at the maximum speed your computer can offer. Another issue is that the ship easily gets lost outside the screen border, as we don't have any detection for that yet. This can easily be solved though.

We can use _LIMIT to limit the speed of the loop to a certain number of loops per second, I think 60 loops per second is ok for this game. I added a title using the _TITLE statement, decided to call it SPACE 64 (but you can give it any name you want of course). I also compared the ship x and y position to the screen borders to see that it doesn't go outside the screen. All of this is commented of course:

_TITLE "Space 64" 'let's call it SPACE 64 (will be displayed at the titlebar) SCREEN 13 DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 DATA 000, 000, 015, 015, 015, 015, 015, 000, 000, 000 DATA 000, 015, 015, 015, 015, 007, 007, 015, 000, 000 DATA 000, 015, 015, 015, 015, 015, 015, 015, 015, 000 DATA 000, 015, 015, 015, 015, 015, 015, 015, 015, 015 DATA 000, 015, 015, 015, 015, 015, 015, 015, 015, 000 DATA 000, 000, 015, 015, 015, 015, 015, 015, 000, 000 DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 FOR y = 1 TO 10 FOR x = 1 TO 10 READ c PSET (x, y), c NEXT NEXT ship = _NEWIMAGE(11, 11, 13) _PUTIMAGE (1, 1)-(10, 10), , ship, (1, 1)-(10, 10) CLS DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 DATA 000, 000, 000, 006, 006, 006, 000, 000, 000, 000 DATA 000, 000, 006, 006, 006, 006, 006, 000, 000, 000 DATA 000, 006, 012, 006, 006, 006, 006, 006, 000, 000 DATA 000, 006, 006, 006, 006, 012, 006, 006, 000, 000 DATA 000, 006, 006, 006, 006, 006, 006, 006, 000, 000 DATA 000, 000, 006, 006, 012, 006, 006, 000, 000, 000 DATA 000, 000, 006, 006, 006, 006, 000, 000, 000, 000 DATA 000, 000, 000, 006, 006, 000, 000, 000, 000, 000 DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 FOR y = 1 TO 10 FOR x = 1 TO 10 READ c PSET (x, y), c NEXT NEXT obstacle = _NEWIMAGE(11, 11, 13) _PUTIMAGE (1, 1)-(10, 10), , obstacle, (1, 1)-(10, 10) CLS DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 DATA 000, 000, 004, 004, 000, 004, 004, 000, 000, 000 DATA 000, 004, 012, 012, 004, 012, 012, 004, 000, 000 DATA 000, 004, 012, 012, 012, 012, 012, 004, 000, 000 DATA 000, 004, 012, 012, 012, 012, 012, 004, 000, 000 DATA 000, 004, 012, 012, 012, 012, 012, 004, 000, 000 DATA 000, 000, 004, 012, 012, 012, 004, 000, 000, 000 DATA 000, 000, 000, 004, 012, 004, 000, 000, 000, 000 DATA 000, 000, 000, 000, 004, 000, 000, 000, 000, 000 DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 FOR y = 1 TO 10 FOR x = 1 TO 10 READ c PSET (x, y), c NEXT NEXT life = _NEWIMAGE(11, 11, 13) _PUTIMAGE (1, 1)-(10, 10), , life, (1, 1)-(10, 10) CLS DO _LIMIT 60 'we limit the loop to 60 loops per second. CLS _PUTIMAGE (shipx, shipy), ship IF _KEYDOWN(CVI(CHR\$(0) + "H")) THEN shipy = shipy - 1 'UP IF _KEYDOWN(CVI(CHR\$(0) + "P")) THEN shipy = shipy + 1 'DOWN IF _KEYDOWN(CVI(CHR\$(0) + "K")) THEN shipx = shipx - 1 'LEFT IF _KEYDOWN(CVI(CHR\$(0) + "M")) THEN shipx = shipx + 1 'RIGHT IF _KEYDOWN(27) THEN END 'ESC 'let's check that it doesn't go beyond the borders of the screen: IF shipx + 10 > 319 THEN shipx = 309 '319 is the furthest x position, and the ship is 10 in width. IF shipx < 0 THEN shipx = 0 '0 is the lowest x position. IF shipy + 10 > 199 THEN shipy = 189 '199 is the furthest y position, and the ship is 10 in height. IF shipy < 0 THEN shipy = 0 '0 is the lowest y position. _DISPLAY 'display the contents drawn to the screen LOOP

Ok, so now we can start adding the "enemies" which are asteroids that can potentially hit the ship, we check if the asteroids hit the ship by the same logic as we do when we check the screen borders (in other words; if shipx + 10 >= asteroidx and shipx <= asteroidx + 10 then the asteroid has hit the ship (both the ship and the asteroid is 10 in size, hence the +10 in the checks), we do the same for the y positions of the ship and the asteroid to make a "bounding box" collision detection.

Now, there isn't only one asteroid in the game, so each asteroids x and y coordinates (and speed) will have to be stored in arrays. This isn't too difficult though, each element in the array represents one asteroid. So what we'll do is just DIM asteroidx(100), asteroidy(100), asteroidspeed(100), for the sake of simplicity we'll ignore any speed to the y direction and just let it go horizontally across the screen at different speeds.

We'll finally put all the asteroid graphics using the _PUTIMAGE statement the same way as we did with the ship:

_TITLE "Space 64" 'let's call it SPACE 64 (will be displayed at the titlebar) DIM asteroidx(100), asteroidy(100), asteroidspeed(100) SCREEN 13 DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 DATA 000, 000, 015, 015, 015, 015, 015, 000, 000, 000 DATA 000, 015, 015, 015, 015, 007, 007, 015, 000, 000 DATA 000, 015, 015, 015, 015, 015, 015, 015, 015, 000 DATA 000, 015, 015, 015, 015, 015, 015, 015, 015, 015 DATA 000, 015, 015, 015, 015, 015, 015, 015, 015, 000 DATA 000, 000, 015, 015, 015, 015, 015, 015, 000, 000 DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 FOR y = 1 TO 10 FOR x = 1 TO 10 READ c PSET (x, y), c NEXT NEXT ship = _NEWIMAGE(11, 11, 13) _PUTIMAGE (1, 1)-(10, 10), , ship, (1, 1)-(10, 10) CLS DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 DATA 000, 000, 000, 006, 006, 006, 000, 000, 000, 000 DATA 000, 000, 006, 006, 006, 006, 006, 000, 000, 000 DATA 000, 006, 012, 006, 006, 006, 006, 006, 000, 000 DATA 000, 006, 006, 006, 006, 012, 006, 006, 000, 000 DATA 000, 006, 006, 006, 006, 006, 006, 006, 000, 000 DATA 000, 000, 006, 006, 012, 006, 006, 000, 000, 000 DATA 000, 000, 006, 006, 006, 006, 000, 000, 000, 000 DATA 000, 000, 000, 006, 006, 000, 000, 000, 000, 000 DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 FOR y = 1 TO 10 FOR x = 1 TO 10 READ c PSET (x, y), c NEXT NEXT obstacle = _NEWIMAGE(11, 11, 13) _PUTIMAGE (1, 1)-(10, 10), , obstacle, (1, 1)-(10, 10) CLS DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 DATA 000, 000, 004, 004, 000, 004, 004, 000, 000, 000 DATA 000, 004, 012, 012, 004, 012, 012, 004, 000, 000 DATA 000, 004, 012, 012, 012, 012, 012, 004, 000, 000 DATA 000, 004, 012, 012, 012, 012, 012, 004, 000, 000 DATA 000, 004, 012, 012, 012, 012, 012, 004, 000, 000 DATA 000, 000, 004, 012, 012, 012, 004, 000, 000, 000 DATA 000, 000, 000, 004, 012, 004, 000, 000, 000, 000 DATA 000, 000, 000, 000, 004, 000, 000, 000, 000, 000 DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 FOR y = 1 TO 10 FOR x = 1 TO 10 READ c PSET (x, y), c NEXT NEXT life = _NEWIMAGE(11, 11, 13) _PUTIMAGE (1, 1)-(10, 10), , life, (1, 1)-(10, 10) CLS DO _LIMIT 60 CLS _PUTIMAGE (shipx, shipy), ship IF _KEYDOWN(CVI(CHR\$(0) + "H")) THEN shipy = shipy - 1 'UP IF _KEYDOWN(CVI(CHR\$(0) + "P")) THEN shipy = shipy + 1 'DOWN IF _KEYDOWN(CVI(CHR\$(0) + "K")) THEN shipx = shipx - 1 'LEFT IF _KEYDOWN(CVI(CHR\$(0) + "M")) THEN shipx = shipx + 1 'RIGHT IF _KEYDOWN(27) THEN END 'let's check that it doesn't go beyond the borders of the screen: IF shipx + 10 > 319 THEN shipx = 319 - 10 IF shipx < 0 THEN shipx = 0 IF shipy + 10 > 199 THEN shipy = 199 - 10 IF shipy < 0 THEN shipy = 0 'asteroids are here: IF numasteroids > 0 THEN FOR asteroid = 1 TO numasteroids 'we let the variable 'asteroid' represent each asteroid _PUTIMAGE (asteroidx(asteroid), asteroidy(asteroid)), obstacle 'obstacle is the image of the asteroid 'check if the asteroid is colliding with the ship: IF shipx + 10 >= asteroidx(asteroid) AND shipx <= asteroidx(asteroid) + 10 THEN IF shipy + 10 >= asteroidy(asteroid) AND shipy <= asteroidy(asteroid) + 10 THEN 'they have collided: 'we don't have any code here yet, since I'll go through that in the Wiki first... END IF END IF 'move the asteroid according to its speed: asteroidx(asteroid) = asteroidx(asteroid) - asteroidspeed(asteroid) NEXT END IF _DISPLAY 'display the contents drawn to the screen LOOP

This doesn't actually display any asteroids yet. Since there aren't any at the start.

My thinking is that they should start to arrive randomly at the far right at random speed towards the left. So we need to create the asteroids randomly, luckily QB64 has such a keyword and it's called RND which returns a random number between 0 and .999... (could be .4 for example or .8 or .2), in order to be sure it is random we use RANDOMIZE TIMER which uses the TIMER as the seed (is only the same number once per day).

Since RND gives a value between 0 and 1 we can use * to make a range of values that it should pick at random, for instance INT(RND * 50) + 1 will give a value between 1 and 50 (since .9999 * 50 is 49.999 and 0 * 50 is 0 the integer of the calculation (INT) becomes 0 to 49 and + 1 makes it 1 to 50).

We will also need to figure out what will happen when the asteroid actually hits the ship, we'll destroy it obviously and you'll lose a life, so what we'll do is create a life variable that holds the number of lives that the ship has and we create some simple pixel explosion to show that the ship has exploded.

We also need to remove the asteroid after it has gone outside the screen, this can't be done within the asteroid loop since the number of asteroids need to be decreased and the loop will always count to the number of asteroids even if they are decreased, try this for example:

tot = 10 FOR a = 1 TO tot tot = 5 PRINT a NEXT

It will go all the way to 10 even though tot was changed to 5 within the loop, so in order to remove the asteroid we simply set a removeasteroid variable to the asteroid which should be removed and then remove it outside the asteroid FOR...NEXT loop.

The code has got quite big but there are only a few things left in order to complete the game, already it is playable but you can't do any progress yet:

_TITLE "Space 64" DIM asteroidx(100), asteroidy(100), asteroidspeed(100) RANDOMIZE TIMER 'this makes sure the sequence of random numbers are different each run. SCREEN 13 DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 DATA 000, 000, 015, 015, 015, 015, 015, 000, 000, 000 DATA 000, 015, 015, 015, 015, 007, 007, 015, 000, 000 DATA 000, 015, 015, 015, 015, 015, 015, 015, 015, 000 DATA 000, 015, 015, 015, 015, 015, 015, 015, 015, 015 DATA 000, 015, 015, 015, 015, 015, 015, 015, 015, 000 DATA 000, 000, 015, 015, 015, 015, 015, 015, 000, 000 DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 FOR y = 1 TO 10 FOR x = 1 TO 10 READ c PSET (x, y), c NEXT NEXT ship = _NEWIMAGE(11, 11, 13) _PUTIMAGE (1, 1)-(10, 10), , ship, (1, 1)-(10, 10) CLS DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 DATA 000, 000, 000, 006, 006, 006, 000, 000, 000, 000 DATA 000, 000, 006, 006, 006, 006, 006, 000, 000, 000 DATA 000, 006, 012, 006, 006, 006, 006, 006, 000, 000 DATA 000, 006, 006, 006, 006, 012, 006, 006, 000, 000 DATA 000, 006, 006, 006, 006, 006, 006, 006, 000, 000 DATA 000, 000, 006, 006, 012, 006, 006, 000, 000, 000 DATA 000, 000, 006, 006, 006, 006, 000, 000, 000, 000 DATA 000, 000, 000, 006, 006, 000, 000, 000, 000, 000 DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 FOR y = 1 TO 10 FOR x = 1 TO 10 READ c PSET (x, y), c NEXT NEXT obstacle = _NEWIMAGE(11, 11, 13) _PUTIMAGE (1, 1)-(10, 10), , obstacle, (1, 1)-(10, 10) CLS DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 DATA 000, 000, 004, 004, 000, 004, 004, 000, 000, 000 DATA 000, 004, 012, 012, 004, 012, 012, 004, 000, 000 DATA 000, 004, 012, 012, 012, 012, 012, 004, 000, 000 DATA 000, 004, 012, 012, 012, 012, 012, 004, 000, 000 DATA 000, 004, 012, 012, 012, 012, 012, 004, 000, 000 DATA 000, 000, 004, 012, 012, 012, 004, 000, 000, 000 DATA 000, 000, 000, 004, 012, 004, 000, 000, 000, 000 DATA 000, 000, 000, 000, 004, 000, 000, 000, 000, 000 DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 FOR y = 1 TO 10 FOR x = 1 TO 10 READ c PSET (x, y), c NEXT NEXT life = _NEWIMAGE(11, 11, 13) _PUTIMAGE (1, 1)-(10, 10), , life, (1, 1)-(10, 10) CLS lives = 3 'let's start with 3 lives DO _LIMIT 60 CLS IF explosion = 0 THEN _PUTIMAGE (shipx, shipy), ship 'let's only display it when explosion = 0 IF _KEYDOWN(CVI(CHR\$(0) + "H")) THEN shipy = shipy - 1 IF _KEYDOWN(CVI(CHR\$(0) + "P")) THEN shipy = shipy + 1 IF _KEYDOWN(CVI(CHR\$(0) + "K")) THEN shipx = shipx - 1 IF _KEYDOWN(CVI(CHR\$(0) + "M")) THEN shipx = shipx + 1 IF _KEYDOWN(27) THEN END IF shipx + 10 > 319 THEN shipx = 319 - 10 IF shipx < 0 THEN shipx = 0 IF shipy + 10 > 199 THEN shipy = 199 - 10 IF shipy < 0 THEN shipy = 0 IF numasteroids > 0 THEN FOR asteroid = 1 TO numasteroids _PUTIMAGE (asteroidx(asteroid), asteroidy(asteroid)), obstacle IF explosion = 0 THEN 'this is added so that the ship doesn't explode twice (it is invulnerable for a while) IF shipx + 10 >= asteroidx(asteroid) AND shipx <= asteroidx(asteroid) + 10 THEN IF shipy + 10 >= asteroidy(asteroid) AND shipy <= asteroidy(asteroid) + 10 THEN 'ok now they have collided, let's start the explosion! explosion = 1 lives = lives - 1 END IF END IF END IF asteroidx(asteroid) = asteroidx(asteroid) - asteroidspeed(asteroid) 'if the asteroid has gone out of the screen to the left we need to remove it: IF asteroidx(asteroid) < 0 - 10 THEN removeasteroid = asteroid 'we'll remove it outside of the asteroid FOR...NEXT END IF NEXT END IF 'here we remove the asteroid that has left the screen IF removeasteroid <> 0 THEN FOR k = removeasteroid TO numasteroids asteroidx(k) = asteroidx(k + 1) 'we just let the next asteroid take its place asteroidy(k) = asteroidy(k + 1) asteroidspeed(k) = asteroidspeed(k + 1) NEXT numasteroids = numasteroids - 1 removeasteroid = 0 'set it to 0 again, otherwise the next asteroid will also be removed END IF 'here we handle the explosion: IF explosion = 1 THEN expl = expl + 1 'we use this variable to control the explosion FOR k = 1 TO expl PSET (shipx - INT(RND * k), shipy - INT(RND * k)), INT(RND * 255) 'plot random pixels PSET (shipx + INT(RND * k), shipy + INT(RND * k)), INT(RND * 255) 'at a increasing radius PSET (shipx - INT(RND * k), shipy + INT(RND * k)), INT(RND * 255) 'determined by k PSET (shipx + INT(RND * k), shipy - INT(RND * k)), INT(RND * 255) '(which is 1 to expl) NEXT IF expl > 30 THEN expl = 0: explosion = 0 'after 30 loops the explosion is done (.5 second). END IF 'let's randomly create new asteroids: rand = INT(RND * 30) + 1 'this will create a new asteroid every .5 seconds (approximately). IF rand = 1 THEN 'create asteroid! numasteroids = numasteroids + 1 asteroidx(numasteroids) = 319 + 10 '(we create it outside the screen) asteroidy(numasteroids) = INT(RND * 219) + 1 '(we create it at a random y location) asteroidspeed(numasteroids) = RND * 1.5 + .5 'create it at a random speed between .1 and 2 END IF _DISPLAY LOOP

I have commented the parts that are new, now all we need to add is a way to win and a way to loose (if life is < 0), to win we will have to collect all the numbers from 1 to 9, we'll also display the lives and the current number:

_TITLE "Space 64" DIM asteroidx(100), asteroidy(100), asteroidspeed(100) SCREEN 13 DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 DATA 000, 000, 015, 015, 015, 015, 015, 000, 000, 000 DATA 000, 015, 015, 015, 015, 007, 007, 015, 000, 000 DATA 000, 015, 015, 015, 015, 015, 015, 015, 015, 000 DATA 000, 015, 015, 015, 015, 015, 015, 015, 015, 015 DATA 000, 015, 015, 015, 015, 015, 015, 015, 015, 000 DATA 000, 000, 015, 015, 015, 015, 015, 015, 000, 000 DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 FOR y = 1 TO 10 FOR x = 1 TO 10 READ c PSET (x, y), c NEXT NEXT ship = _NEWIMAGE(11, 11, 13) _PUTIMAGE (1, 1)-(10, 10), , ship, (1, 1)-(10, 10) CLS DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 DATA 000, 000, 000, 006, 006, 006, 000, 000, 000, 000 DATA 000, 000, 006, 006, 006, 006, 006, 000, 000, 000 DATA 000, 006, 012, 006, 006, 006, 006, 006, 000, 000 DATA 000, 006, 006, 006, 006, 012, 006, 006, 000, 000 DATA 000, 006, 006, 006, 006, 006, 006, 006, 000, 000 DATA 000, 000, 006, 006, 012, 006, 006, 000, 000, 000 DATA 000, 000, 006, 006, 006, 006, 000, 000, 000, 000 DATA 000, 000, 000, 006, 006, 000, 000, 000, 000, 000 DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 FOR y = 1 TO 10 FOR x = 1 TO 10 READ c PSET (x, y), c NEXT NEXT obstacle = _NEWIMAGE(11, 11, 13) _PUTIMAGE (1, 1)-(10, 10), , obstacle, (1, 1)-(10, 10) CLS DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 DATA 000, 000, 004, 004, 000, 004, 004, 000, 000, 000 DATA 000, 004, 012, 012, 004, 012, 012, 004, 000, 000 DATA 000, 004, 012, 012, 012, 012, 012, 004, 000, 000 DATA 000, 004, 012, 012, 012, 012, 012, 004, 000, 000 DATA 000, 004, 012, 012, 012, 012, 012, 004, 000, 000 DATA 000, 000, 004, 012, 012, 012, 004, 000, 000, 000 DATA 000, 000, 000, 004, 012, 004, 000, 000, 000, 000 DATA 000, 000, 000, 000, 004, 000, 000, 000, 000, 000 DATA 000, 000, 000, 000, 000, 000, 000, 000, 000, 000 FOR y = 1 TO 10 FOR x = 1 TO 10 READ c PSET (x, y), c NEXT NEXT life = _NEWIMAGE(11, 11, 13) _PUTIMAGE (1, 1)-(10, 10), , life, (1, 1)-(10, 10) CLS lives = 3 shipx = 10 'we set the x position of the ship to 10 and the y position is set shipy = 95 'at the middle of the screen (middle of the ship is 5 hence 100-5=95). DO _LIMIT 60 CLS IF explosion = 0 THEN _PUTIMAGE (shipx, shipy), ship 'let's only display it when explosion = 0 'we can make some fire behind the ship by randomly setting some red pixels behind it. FOR k = 1 TO 10: PSET (shipx - INT(RND * 5), shipy + INT(RND * 6) + 2), 4: NEXT END IF 'display lives: IF lives > 0 THEN FOR k = 1 TO lives _PUTIMAGE (k * 10, 0), life NEXT END IF 'display number of numbers collected: _PRINTSTRING (0, 2), LTRIM\$(STR\$(collected)) IF _KEYDOWN(CVI(CHR\$(0) + "H")) THEN shipy = shipy - 1 IF _KEYDOWN(CVI(CHR\$(0) + "P")) THEN shipy = shipy + 1 IF _KEYDOWN(CVI(CHR\$(0) + "K")) THEN shipx = shipx - 1 IF _KEYDOWN(CVI(CHR\$(0) + "M")) THEN shipx = shipx + 1 IF _KEYDOWN(27) THEN END IF shipx + 10 > 319 THEN shipx = 319 - 10 IF shipx < 0 THEN shipx = 0 IF shipy + 10 > 199 THEN shipy = 199 - 10 IF shipy < 0 THEN shipy = 0 IF numasteroids > 0 THEN FOR asteroid = 1 TO numasteroids _PUTIMAGE (asteroidx(asteroid), asteroidy(asteroid)), obstacle IF explosion = 0 THEN 'this is added so that the ship doesn't explode twice (it is invulnerable for a while) IF shipx + 10 >= asteroidx(asteroid) AND shipx <= asteroidx(asteroid) + 10 THEN IF shipy + 10 >= asteroidy(asteroid) AND shipy <= asteroidy(asteroid) + 10 THEN explosion = 1 lives = lives - 1 END IF END IF END IF asteroidx(asteroid) = asteroidx(asteroid) - asteroidspeed(asteroid) IF asteroidx(asteroid) < 0 - 10 THEN removeasteroid = asteroid END IF NEXT END IF IF removeasteroid <> 0 THEN FOR k = removeasteroid TO numasteroids asteroidx(k) = asteroidx(k + 1) asteroidy(k) = asteroidy(k + 1) asteroidspeed(k) = asteroidspeed(k + 1) NEXT numasteroids = numasteroids - 1 removeasteroid = 0 END IF IF explosion = 1 THEN expl = expl + 1 FOR k = 1 TO expl PSET (shipx - INT(RND * k), shipy - INT(RND * k)), INT(RND * 255) PSET (shipx + INT(RND * k), shipy + INT(RND * k)), INT(RND * 255) PSET (shipx - INT(RND * k), shipy + INT(RND * k)), INT(RND * 255) PSET (shipx + INT(RND * k), shipy - INT(RND * k)), INT(RND * 255) NEXT IF expl > 30 THEN expl = 0: explosion = 0 END IF rand = INT(RND * (30 - (collected * 3))) + 1 '(collected*3) makes the asteroids more frequent IF rand = 1 THEN numasteroids = numasteroids + 1 asteroidx(numasteroids) = 319 + 10 asteroidy(numasteroids) = INT(RND * 219) + 1 asteroidspeed(numasteroids) = RND * 1.5 + .5 + ((collected + 1) / 50) '(collected+1)/50 makes them faster END IF IF heart = 0 THEN 'only if heart is 0 should we create one (it is already created otherwise) rand = INT(RND * 1000) + 1 'very seldom does a heart arrive IF rand = 1 THEN heart = 1 heartx = 319 + 10 hearty = INT(RND * 219) + 1 heartspeed = RND * 1.5 + .5 END IF END IF 'here we display the heart and handles the collision for the heart: IF heart = 1 THEN _PUTIMAGE (heartx, hearty), life heartx = heartx - heartspeed IF heartx < -10 THEN heart = 0 'remove it if it goes outside the screen. IF shipx + 10 >= heartx AND shipx <= heartx + 10 THEN IF shipy + 10 >= hearty AND shipy <= hearty + 10 THEN 'it collided with the heart! heart = 0 'set heart to 0 to remove it lives = lives + 1 'add 1 to lives END IF END IF END IF 'we do the same for the numbers as with the heart: IF number = 0 THEN rand = INT(RND * 500) + 1 'very seldom does a number arrive IF rand = 1 THEN number = 1 numberx = 319 + 10 numbery = INT(RND * 219) + 1 numberspeed = RND * 1.5 + .5 END IF END IF 'here we display the number and handle the collision for the number: IF number = 1 THEN _PRINTSTRING (numberx, numbery), STR\$(collected + 1) 'we use printstring to represent the number numberx = numberx - numberspeed IF numberx < -10 THEN number = 0 IF shipx + 10 >= numberx AND shipx <= numberx + 10 THEN IF shipy + 10 >= numbery AND shipy <= numbery + 10 THEN 'it collided with the number! collected = collected + 1 'add one to the collected numbers number = 0 'set number to 0 to remove it END IF END IF END IF 'loose: IF lives < 0 THEN LOCATE 10, 15: PRINT "GAME OVER" _DISPLAY: END END IF 'win: IF collected = 9 THEN LOCATE 10, 16: PRINT "YOU WIN" _DISPLAY: END END IF _DISPLAY LOOP

In this last version I also made the asteroids more frequent depending on the current number you've collected. They are also slightly faster depending on the current collected number.

When we use _PRINTSTRING we have to use STR\$ to turn the number into a string, since it can't handle pure numbers (unlike ordinary PRINT), LTRIM\$ takes away the leading space for numbers. With that said I think we're pretty much done with this game tutorial.

You can make black transparent on the images if you like. Just use _CLEARCOLOR, 0, image. To make the text transparent you can use _PRINTMODE _KEEPBACKGROUND just after SCREEN 13.

You can change anything any way you like!