Jump to content
IGNORED

7800basic for Newbies: A Tutorial, Part 3


Atarius Maximus

Recommended Posts

Tutorial Contents:

 

Part 1:

Setting up your Development Environment

Part 2:

Creating Graphics

Creating a Title Screen

Sprite Creation & Displaying Them on the Screen

Part 3:

Getting Started with Writing the Code

Adding Player Sprite Movement and Gun firing

Moving Non-Player Sprites on screen

Collision Detection between gunfire and Non-Player Sprites

Adding a Status bar with the Score and Lives Remaining

Collision Detection between all Sprites, Speed Change on clearing screen

Animation, Sound Effects, and Performance Troubleshooting

Adding a High Score

Code Optimization, More Animation, and Holey DMA

AtariVox Speech

 

Welcome to Part 3 of the tutorial, where we'll actually start writing the game! :) I'll continue to use the Part 3 thread for the all the remaining code development work on Space Junk.

 

The Process

 

Before we start, I want to review how a typical 7800basic game is laid-out out in code. There are certain aspects of the code that you'll be adding in any given program. For the most part any game you write will have a similar flow. You can read Random Terrain's "Parts of a Program" here, which is a very good explanation of the flow of game code, but I'm going to review how I generally do it here.

 

This is how the general flow of code works in my 7800basic games:

 

Setup

1. Set Program Options

2. Variable Assignments

3. Graphics Import

4. Set Palette Colors

5. Initialize Variables (& Constants)

 

Title Screen

1. Display Title screen graphic

2. Display High Score, if appropriate

3. Display in-game options, if appropriate

4. Wait for input to start game then jump to main

 

Main

1. Set variables and other options used for this loop, I'd label it "main".

2. The "main" section does not loop, it flows directly into the next section.

 

Main Loop

1. The beginning of the actual game code. I'd label it "MainLoop", and loop back to it at the end with "goto MainLoop".

2. This loop will draw the screen, all sprites, and contain all the logic for game play.

3. The main loop will contain many gosubs to complete specific tasks outside of the loop.

4. When a game over condition is met, we'd jump to the "Game Over" section.

 

Game Over

1. Create a loop to display specific info when the game ends

2. Wait for input to restart game

 

Subroutines

This area will contain all of the subroutines that are called from the main loop.

 

Data

This area will contain any data tables or arrays used in the game

 

Naming and Code Formatting Conventions

 

There are a few standards to follow, but for the most part the naming standards you use are completely up to you. I will go over a few tips about how I do it.

 

First, the requirements. Where you have white space at the beginning of each line makes a difference. Labels are not indented, but all program statements need to be indented with at least once blank space. You may use labels with or without program statements after them. A label can have any combination of letters, numbers, or underscores, even as the first character. A label cannot match or start with a known keyword or any labels internal to 7800basic, for instance "next" or "end". As an example, you can't use 'nextitem' but you can use 'codenext'. Some programmers will always start a label or a variable name with one or two underscores to help identify them in code, and to prevent themselves from using a reserved word. It's a solid recommendation, but I personally don't do that because, well, I don't like how it looks. :)

 

Labels

 

Labels are used to mark an area of code, usually because you intend to either loop back to it or perform a goto or gosub to that section of code. I try to keep them short and descriptive, usually with one or two words. They are case sensitive, so if you choose to include Capital Letters you must be consistent and type it the same way whenver you call it. Here's a few examples:

 

FireGun

FireGunX=FireGunX+1

If FireGunX>5 then goto MainLoop

 

GameOver

GameOverFlag=0

GameOverLoop

; ...code...

goto GameOverLoop

 

Variable Names

 

Naming variables is extremely important, without it debugging code because an incredibly difficult task. Try to keep them short (I'd recommend 15 or less characters when possible) and very descriptive.

 

Commenting Code

 

Comments are also crucial to the debugging process. If a section of code is written in a way that it's not obvious what you're doing, add a sentence or two to describe it. You'll thank yourself later when you're adding or debugging your code and you can't remember what you did in a certain section. You can add comments with either the "rem" statement or the semicolon ";". Either can be used at the end of a line as well. Here's a few examples:

 

; This is an example of a commented section of code

for x=x+1: if x>10 then x=1 ; If X is greater than 10, reset it to 1

 

rem ** This is an example of a commented section of code

for x=x+1: if x>10 then x=1 : rem -- If X is greater than 10, reset it to 1

 

Stringing Commands on the same line of code

 

It is possible to enter multiple commands on the same line and separate them with the colon. In some cases this makes sense, as it makes it easier to see more of your code on the screen at the same time. I tend to use it more frequently for variable assignments, and it's also useful for if...then statements where you have multiple commands you want to run when a condition is met. As a whole, for code readability, it's generally good practice to limit your use of multiple commands on the same line.

 

Loops

 

For my game loops, I generally set them up with two labels. The first is to set variables, the second is where the loop returns to when it gets to the end. I’ll just add the word “Loop” to the end of the section’s label. For example, for the main code loop, this is how I do it:

 

Main

FrameCounter=0

MainLoop

FrameCounter=FrameCounter+1

if FrameCounter>10 then FrameCounter=0

goto MainLoop

 

In this example, I want the FrameCounter to be zero whenever the main loop is called, but I don't want it to actually be defined at the beginning of the loop, otherwise it would be set to zero every frame. I create the "MainLoop" label immediately after any variable assignments for the loop.

 

Set Program Options

 

Now that we know the normal flow of the game and have some guidlines on naming variables and labels, let's start writing it! The first order of business is to choose our program options. 7800basic offers a large number of options which are similar to the kernel options you can choose in batariBasic. First I'll show you the list of all the program options avaialble, then we'll discuss the general options and which ones we're going to set for the Space Junk game.

 

General Options:

set romsize [128k

set doublewide on

set screenheight [192|208|224]

set plotvalueonscreen [on|off]

set pauseroutine [on|off]

set debug color

set basepath [directory path]

set mcpdevcart [on|off]

 

Zone Options:

set zoneheight 8 [ 8 | 16]

set zoneprotection [on|off]

 

Voice and Sound Options:

set pokeysound [on|off]

set tiasfx mono

set avoxvoice on

 

Memory Options:

set extradlmemory on

set dlmemory $4000 $4fff

 

High Score Support Program Options:

set hssupport $####

set hsseconds #

set hsscoresize #

set hscolorbase #

set hsdifficultytext off

set hsdifficultytext 'diff str 1' 'string 2' 'string 3' 'string 4'

set hsgameranks # 'rank string 1' # 'rank name 2' # 'rank name 3' [...]

 

Romsize

 

For this demo game I'm going to go with the largest non-bankswitched format of 48k. If you don't set this option, the default is 32k. As a rule of thumb, I'd recommend starting games with bankswitching in mind just in case you get to a point where you run out of space and want to expand it. Converting a complete 48k game to bankswitching later is at best somewhat difficult, at worst impossible. I really don’t expect this game demo to be larger than 48k, so I’m going to break my own rule on this one to keep this code as simple as possible.

 

Doublewide

 

We discussed the doublewide option in a previous section, and we are using it for this game. All of our graphics will be doubled in width.

 

Screenheight

 

We're going to go with the default zone height of 192 for this tutorial. Valid height values are 192, 208, and 224, it can be used to increase the visible area of your game.

 

Plotvalueonscreen

 

This tells 7800basic that when the plotvalue command is used, it should update the screen immediately, instead of waiting for the screen to complete drawing. This will save precious off-screen cycles for plotting other objects, like sprites. I'm not going to turn this on initially, as I don’t expect we’ll need it.

 

Pauseroutine

 

Turning pauseroutine off tells 7800basic to not use its built-in pause routine in the game. I'm not going to be disabling it initially.

 

Debugcolor

 

This is a troubleshooting option that I won't be enabling initially. It 7800basic to change the color of the screen to red, until all of the screen plotting functions are called. This allows you to judge how much of the program calculations are happening during the visible screen, and allows you to see the relative efficiency of your program.

 

Basepath

 

This was discussed earlier, and we will be using it. It tells 7800basic to add the specified directory path to any relative paths used with the incgraphic, incmapfile, and plotmapfile commands.

 

We won't be setting any values for the more advanced program options involving zones, AtariVox/Pokey, Memory options, or high score support. Depending on how this game progresses and the ROM space we have available, I may add some of these options later in the development process.

 

For Space Junk, we're only going to set three options to start with. I generally also set the display mode alongside the "set" options, as they are all global program settings.

 

Here's our first four lines of code for Space Junk:

 

set doublewide on

set basepath gfx_spacejunk

set romsize 48k

displaymode 160A

 

Variable Assignments

 

The item in our game flow is to assign variables. I like to assign all of the variables I think I'm going to need with names from the very beginning, which is done with the 'dim' statement. I'd always recommend naming your variables, as it makes it much easier to understand what's going on in the code later on. Creating all of your variables up-front is not a requirement, they can be added anytime during the development process. I just like to create what I think I’m going to need from the very beginning.

 

Compared to batarBasic, we have a ton of available RAM we can assign and use. The range of letters A -> Z,

the range of 'var0' -> 'var99', and the RAM locations $2200 -> $27FF, well over 1500 locations are available. You don't have to use them in order, you can pick and choose. In Space Junk I’m going to start off using almost all of the “var” variable range.

 

Let's think about what variables we're going to need for this game. It may change as we go along, but there are some things I know we're going to need, and it would be easier to set them up before we get started on coding the game.

 

1. The X and Y position of all sprites will need a variable, as we'll want to be changing those values during the game.

2. Each sprite will probably need a flag to keep track of whether or not it's onscreen, or if it's been shot and destroyed. I'll add a "flag" for each one. If we want to keep track of multiple conditions for a sprite, we can always use bitwise operations, which would let us keep track of the true/false (or on/off) state up to eight times per variable assignment, as each variable uses a full byte (8 bits) of RAM.

3. The sprites that we'll end up animating will need a frame counter. For now I'm going to add a variable for that for the main spaceman character and the alien sprites. We can always add more later.

4. I always add a general frame counter in the main loop that increments up to 255 and then resets to 0. It can be used for many different things, including limiting sound effect lengths and slowing down sprite movement. For this game we'll probably end up with multiple frame counters to alter the speed of the various space junk sprites.

5. We'll need variables for the number of lives remaining, what level we're on, our score, etc.

 

With that said, I went ahead and created an initial list of variables that I'm going to assign below. This is the next section of code we’ll use for Space Junk. For the variable names that correspond with a sprite, I used the name of the sprite as the first part of the variable name. I’ve already assigned about three times the amount of variables you can use in the standard kernel of batariBasic, and we’ve barley started. ;)

 

dim debris1_xpos = var1 : rem Debris1 Sprite X Position

dim debris1_ypos = var2 : rem Debris1 Sprite Y Position

dim debris1_flag = var3 : rem Debris1 Flag

dim debris2_xpos = var4 : rem Debris2 Sprite X Position

dim debris2_ypos = var5 : rem Debris2 Sprite Y Position

dim debris2_flag = var6 : rem Debris2 Flag

dim debris3_xpos = var7 : rem Debris3 Sprite X Position

dim debris3_ypos = var8 : rem Debris3 Sprite Y Position

dim debris3_flag = var9 : rem Debris3 Flag

dim debris4_xpos = var10 : rem Debris4 Sprite X Position

dim debris4_ypos = var11 : rem Debris4 Sprite Y Position

dim debris4_flag = var12 : rem Debris4 Flag

dim debris5_xpos = var13 : rem Debris5 Sprite X Position

dim debris5_ypos = var14 : rem Debris5 Sprite Y Position

dim debris5_flag = var15 : rem Debris5 Flag

dim debris6_xpos = var16 : rem Debris6 Sprite X Position

dim debris6_ypos = var17 : rem Debris6 Sprite Y Position

dim debris6_flag = var18 : rem Debris6 Flag

dim debris7_xpos = var19 : rem Debris7 Sprite X Position

dim debris7_ypos = var20 : rem Debris7 Sprite Y Position

dim debris7_flag = var21 : rem Debris7 Flag

dim debris8_xpos = var22 : rem Debris8 Sprite X Position

dim debris8_ypos = var23 : rem Debris8 Sprite Y Position

dim debris8_flag = var24 : rem Debris8 Flag

dim debris9_xpos = var25 : rem Debris9 Sprite X Position

dim debris9_ypos = var26 : rem Debris9 Sprite Y Position

dim debris9_flag = var27 : rem Debris9 Flag

dim rocks1_xpos = var28 : rem Rocks1 Sprite X Position

dim rocks1_ypos = var29 : rem Rocks1 Sprite Y Position

dim rocks1_flag = var30 : rem Rocks1 Flag

dim rocks2_xpos = var31 : rem Rocks2 Sprite X Position

dim rocks2_ypos = var32 : rem Rocks2 Sprite Y Position

dim rocks2_flag = var33 : rem Rocks2 Flag

dim rocks3_xpos = var34 : rem Rocks3 Sprite X Position

dim rocks3_ypos = var35 : rem Rocks3 Sprite Y Position

dim rocks3_flag = var36 : rem Rocks3 Flag

dim rocks4_xpos = var37 : rem Rocks4 Sprite X Position

dim rocks4_ypos = var38 : rem Rocks4 Sprite Y Position

dim rocks4_flag = var39 : rem Rocks4 Flag

dim satellite1_xpos = var40 : rem Satellite1 Sprite X Position

dim satellite1_ypos = var41 : rem Satellite1 Sprite Y Position

dim satellite1_flag = var42 : rem Satellite1 Flag

dim satellite2_xpos = var43 : rem Satellite2 Sprite X Position

dim satellite2_ypos = var44 : rem Satellite2 Sprite Y Position

dim satellite2_flag = var45 : rem Satellite2 Flag

dim invader1_xpos = var46 : rem Invader1 Sprite X Position

dim invader1_ypos = var47 : rem Invader1 Sprite Y Position

dim invader1_flag = var48 : rem Invader1 Flag

dim alien_xpos = var49 : rem Alien Sprite X Position

dim alien_ypos = var50 : rem Alien Sprite Y Position

dim alien_flag = var51 : rem Alien Flag

dim alien_aniframe = var52 : rem Alien Animation Frame Counter

dim chip1_xpos = var53 : rem Chip1 Sprite X Position

dim chip1_ypos = var54 : rem Chip1 Sprite Y Position

dim cip1_flag = var55 : rem Chip1 Flag

dim rocket1_xpos = var56 : rem Rocket1 Sprite X Position

dim rocket1_ypos = var57 : rem Rocket1 Sprite Y Position

dim rocket1_flag = var58 : rem Rocket1 Flag

dim firstaid1_xpos = var59 : rem FirstAid1 Sprite X Position

dim firstaid1_ypos = var60 : rem FirstAid1 Sprite Y Position

dim firstaid1_flag = var61 : rem FirstAid1 Flag

dim bullet_xpos = var62 : rem Bullet Sprite X Position

dim bullet_ypos = var63 : rem Bullet Sprite Y Position

dim bullet_flag = var64 : rem Bullet Flag

dim spaceman_xpos = var65 : rem Space Man Sprite X Position

dim spaceman_ypos = var66 : rem Space Man Sprite Y Position

dim spaceman_flag = var67 : rem Space Man Flag

dim spaceman_dir = var68 : rem Space Man Direction Facing

dim spaceman_aniframe = var69 : rem Space Man Sprite Animation

dim junk_slowdown1 = var70 : rem Junk Slowdown Frame Counter

dim junk_slowdown2 = var71 : rem Junk Slowdown Frame Counter

dim junk_slowdown3 = var72 : rem Junk Slowdown Frame Counter

dim junk_slowdown4 = var73 : rem Junk Slowdown Frame Counter

dim framecounter = var74 : rem Global Frame Counter

dim level = var75 : rem Current Skill Level

dim lives = var76 : rem Current Lives Remaining

dim fire_debounce = var77 : rem Debounce, or cause action

dim gameover_flag = var78 : rem Flag for Game Over

 

Graphics Import

 

After assigning all of our variables, the next step is to import all of the graphics we're going to use in our game.

A few items to note:

  1. The sprites I’m plotting on the titlescreen that are larger than the default zone height of 16 pixels are imported as a “banner” rather than a “graphic”.
  2. The banner sprites need to have the display mode and palette index colors added to the end of the statement.
  3. When we get to sprite animations, the animated sprites must be imported in the order that you want them animated, and a specific block of animated sprites must be in the same graphics block. Currently, the graphics we’re defining use two separate graphics blocks, you can see how it’s split up below. If we notice that a series of animated sprites is split between two blocks, we can either rearrange the import order, or force a new block by using the command “newblock” immediately following the graphic that we want to be the last image in the graphics block. For now we don’t have to worry about that.

 

GFX Block #0 starts @ $C000, has 192 bytes left (12 x 16 bytes)

tileset_stars1 tileset_planet1 spacejunk_title00

spacejunk_title01 spacejunk_title02 spacejunk_title03

spacejunk_title04 spacejunk_title05

GFX Block #1 starts @ $E000, has 2096 bytes left (131 x 16 bytes)

scoredigits_8_wide sprite_7800basic200 sprite_7800basic201

sprite_firetobegin sprite_satellite1 sprite_satellite2

sprite_invader1 sprite_alien sprite_chip1 sprite_debris1

sprite_debris2 sprite_debris3 sprite_debris4 sprite_debris5

sprite_debris6 sprite_debris7 sprite_debris8 sprite_debris9

sprite_rocket1 sprite_rocks1 sprite_rocks2 sprite_rocks3

sprite_rocks4 sprite_spaceman_left sprite_spaceman_right

sprite_firstaid1 sprite_bullet

 

  1. If we have a tmx map file from the Tiled Map editor to import, I generally add that import statement at the very end of the block of graphic imports. In this case I’m adding “incmapfile spacejunk.tmx” to the very end.

 

Here is the code we’ll use in Space Junk for importing the graphics:

 

incgraphic tileset_stars1.png

incgraphic tileset_planet1.png

incbanner spacejunk_title.png 160A 0 1 2 3

incgraphic scoredigits_8_wide.png

incbanner sprite_7800basic2.png 160A 0 1 1 1

incgraphic sprite_firetobegin.png

incgraphic sprite_satellite1.png

incgraphic sprite_satellite2.png

incgraphic sprite_invader1.png

incgraphic sprite_alien.png

incgraphic sprite_chip1.png

incgraphic sprite_debris1.png

incgraphic sprite_debris2.png

incgraphic sprite_debris3.png

incgraphic sprite_debris4.png

incgraphic sprite_debris5.png

incgraphic sprite_debris6.png

incgraphic sprite_debris7.png

incgraphic sprite_debris8.png

incgraphic sprite_debris9.png

incgraphic sprite_rocket1.png

incgraphic sprite_rocks1.png

incgraphic sprite_rocks2.png

incgraphic sprite_rocks3.png

incgraphic sprite_rocks4.png

incgraphic sprite_spaceman_left.png

incgraphic sprite_spaceman_right.png

incgraphic sprite_firstaid1.png

incgraphic sprite_bullet.png

incmapfile spacejunk.tmx

 

Palette Colors

 

MARIA has eight 3 color palettes to choose from when drawing sprites and characters. Setting the actual color these palettes use is just a matter of setting some special variables. To keep this section concise, I decided to use the color separator and keep each palette on it's own line. The color choices are already defined from the demo code that I presented at the end of Part 2 formatted differently. We’ll add this section to our Space Junk code as well. The "P" in the variable represents "Palette", and the "C" represents the color. Therefore, setting P0C1 sets Pallete 0 Color 1.

 

P0C1=$26: P0C2=$24: P0C3=$04

P1C1=$0E: P1C2=$38: P1C3=$FC

P2C1=$CE: P2C2=$C2: P2C3=$C6

P3C1=$32: P3C2=$36: P3C3=$3C

P4C1=$22: P4C2=$26: P4C3=$2C

P5C1=$82: P5C2=$86: P5C3=$0A

P6C1=$32: P6C2=$36: P6C3=$3C

P7C1=$92: P7C2=$96: P7C3=$9F

 

Initialize Variables

 

Before we start the game, we need to initialize all the variables that we defined to the correct values before we start. Some of these will need to be changed as we progress with development, but they all need an initial value. If we don't set a value, the default value will be 0. When the game is over, this is typically the section that I will loop back to so everything is reset to the default when the game starts over.

 

This section is a good example of where it’s appropriate and helpful to string commands on the same line, as you’ll be able to see all of your variable assignments on the same screen at the same time.

 

debris1_xpos = 4 : debris1_ypos = 0 : debris1_flag = 0

debris2_xpos = 4 : debris2_ypos = 20 : debris2_flag = 0

debris3_xpos = 4 : debris3_ypos = 40 : debris3_flag = 0

debris4_xpos = 4 : debris4_ypos = 60 : debris4_flag = 0

debris5_xpos = 4 : debris5_ypos = 80 : debris5_flag = 0

debris6_xpos = 4 : debris6_ypos = 100 : debris6_flag = 0

debris7_xpos = 4 : debris7_ypos = 120 : debris7_flag = 0

debris8_xpos = 4 : debris8_ypos = 140 : debris8_flag = 0

debris9_xpos = 18 : debris9_ypos = 0 : debris9_flag = 0

rocks1_xpos = 18 : rocks1_ypos = 20 : rocks1_flag = 0

rocks2_xpos = 18 : rocks2_ypos = 40 : rocks2_flag = 0

rocks3_xpos = 18 : rocks3_ypos = 60 : rocks3_flag = 0

rocks4_xpos = 18 : rocks4_ypos = 80 : rocks4_flag = 0

satellite1_xpos = 18 : satellite1_ypos = 100 : satellite1_flag = 0

satellite2_xpos = 18 : satellite2_ypos = 120 : satellite2_flag = 0

invader1_xpos = 18 : invader1_ypos = 140 : invader1_flag = 0

chip1_xpos = 32 : chip1_ypos = 0 : chip1_flag = 0

rocket1_xpos = 32 : rocket1_ypos = 20 : rocket1_flag = 0

firstaid1_xpos = 32 : firstaid1_ypos = 40 : firstaid1_flag = 0

bullet_xpos = 32 : bullet_ypos = 60 : bullet_flag = 0

alien_xpos = 32 : alien_ypos = 140 : alien_flag = 0

spaceman_xpos = 120 : spaceman_ypos = 80 : spaceman_flag = 0

junk_slowdown1 = 0 : junk_slowdown2 = 0 : junk_slowdown3 = 0

framecounter = 0 : level = 0 : lives = 3

fire_debounce = 0 : gameover_flag = 0 : junk_slowdown4 = 0

alien_aniframe = 0 : spaceman_aniframe = 0 : spaceman_dir = 0

 

Choosing Sprite Locations

 

So, how did I choose the X/Y locations for the sprites, and how did I know where to place them? Honestly, it’s a little bit of trial and error to get it perfect. I routinely enter in the X/Y location that I think is about right, then compile and test, making small changes to get it just right. Below is a chart that explains the visible area of the 7800’s screen. With the default screenheight option, you get 160 pixels horizontally and 192 pixels vertically. The X/Y location is the top left pixel of the sprite, so you need to keep that in mind when you’re placing them. If I want to place one of the 8x8 sprites in the top right corner and keep it completely visible on-screen, you’d use the X location of 152, not 160, as you need it to be 8 pixels to the left of the edge. For the Y location, you’d use 0, which is the very top line.

 

post-2143-0-86279400-1506120882_thumb.png

 

Main Loop

 

Now that all of our variables have initial values assigned, it's time to move on to the main loop of the game. For starters, let's note the commands to plot the sprites. We use the "plotsprite" command to draw the sprite on the screen. Below is the syntax.

 

plotsprite sprite_graphic palette_# x y [frame]

 

[frame] is an optional parameter that we'll be adding in later on, it's designed for sprite animation. If you have a series of sprites you wish to animate, you can do so by using a variable here. If the value is 0, the first sprite will be displayed, if the value is 1 the second sprite will be displayed, and so on. This feature requires all of the sprites to be the same width. The display order of displayed sprites and characters depends on the order they were drawn in. The very last sprite will be the one that appears to pass over top of other sprites.

 

For the plotsprite command, we're going to enter in the name of the sprite, using the same name we used when it was imported, but without the png extension. After that, we type in the palette number we want to display the sprite in, and then the X and Y variables that we assigned earlier. This is the last large section of code I’ll be adding for now. There are a few more labels and commands I’m going to have to put in to tie all of this together, and that’s how we’ll wrap this up. The next time around I’ll be adding some actual game play functions into the code, so we’ll have something to do on the screen after we fire it up.

 

plotsprite sprite_invader1 5 invader1_xpos invader1_ypos

plotsprite sprite_alien 6 alien_xpos alien_ypos

plotsprite sprite_chip1 5 chip1_xpos chip1_ypos

plotsprite sprite_debris1 1 debris1_xpos debris1_ypos

plotsprite sprite_debris2 1 debris2_xpos debris2_ypos

plotsprite sprite_debris3 0 debris3_xpos debris3_ypos

plotsprite sprite_debris4 1 debris4_xpos debris4_ypos

plotsprite sprite_debris5 1 debris5_xpos debris5_ypos

plotsprite sprite_debris6 1 debris6_xpos debris6_ypos

plotsprite sprite_debris7 1 debris7_xpos debris7_ypos

plotsprite sprite_debris8 1 debris8_xpos debris8_ypos

plotsprite sprite_debris9 0 debris9_xpos debris9_ypos

plotsprite sprite_rocket1 5 rocket1_xpos rocket1_ypos

plotsprite sprite_rocks1 3 rocks1_xpos rocks1_ypos

plotsprite sprite_rocks2 2 rocks2_xpos rocks2_ypos

plotsprite sprite_rocks3 3 rocks3_xpos rocks3_ypos

plotsprite sprite_rocks4 4 rocks4_xpos rocks4_ypos

plotsprite sprite_satellite1 4 satellite1_xpos satellite1_ypos

plotsprite sprite_satellite2 3 satellite2_xpos satellite2_ypos

plotsprite sprite_spaceman_left 5 spaceman_xpos spaceman_ypos

 

Technical Tip: You can plot the exact same sprite multiple times on the screen. Rather than creating 20+ different “junk” sprites, for this demo I could have simply plotted the same sprite 20+ times.

 

Now is a good time to use all of the information we just learned and write the code. I'll make a few more notes in the code below as well.

 

rem ** Space Junk

set doublewide on

set basepath gfx_spacejunk

set romsize 48k

displaymode 160A

 

; Variable Assignments

dim debris1_xpos=var1 :rem Debris1 Sprite X Position

dim debris1_ypos=var2 :rem Debris1 Sprite Y Position

dim debris1_flag=var3 :rem Debris1 Flag

dim debris2_xpos=var4 :rem Debris2 Sprite X Position

dim debris2_ypos=var5 :rem Debris2 Sprite Y Position

dim debris2_flag=var6 :rem Debris2 Flag

dim debris3_xpos=var7 :rem Debris3 Sprite X Position

dim debris3_ypos=var8 :rem Debris3 Sprite Y Position

dim debris3_flag=var9 :rem Debris3 Flag

dim debris4_xpos=var10 :rem Debris4 Sprite X Position

dim debris4_ypos=var11 :rem Debris4 Sprite Y Position

dim debris4_flag=var12 :rem Debris4 Flag

dim debris5_xpos=var13 :rem Debris5 Sprite X Position

dim debris5_ypos=var14 :rem Debris5 Sprite Y Position

dim debris5_flag=var15 :rem Debris5 Flag

dim debris6_xpos=var16 :rem Debris6 Sprite X Position

dim debris6_ypos=var17 :rem Debris6 Sprite Y Position

dim debris6_flag=var18 :rem Debris6 Flag

dim debris7_xpos=var19 :rem Debris7 Sprite X Position

dim debris7_ypos=var20 :rem Debris7 Sprite Y Position

dim debris7_flag=var21 :rem Debris7 Flag

dim debris8_xpos=var22 :rem Debris8 Sprite X Position

dim debris8_ypos=var23 :rem Debris8 Sprite Y Position

dim debris8_flag=var24 :rem Debris8 Flag

dim debris9_xpos=var25 :rem Debris9 Sprite X Position

dim debris9_ypos=var26 :rem Debris9 Sprite Y Position

dim debris9_flag=var27 :rem Debris9 Flag

dim rocks1_xpos=var28 :rem Rocks1 Sprite X Position

dim rocks1_ypos=var29 :rem Rocks1 Sprite Y Position

dim rocks1_flag=var30 :rem Rocks1 Flag

dim rocks2_xpos=var31 :rem Rocks2 Sprite X Position

dim rocks2_ypos=var32 :rem Rocks2 Sprite Y Position

dim rocks2_flag=var33 :rem Rocks2 Flag

dim rocks3_xpos=var34 :rem Rocks3 Sprite X Position

dim rocks3_ypos=var35 :rem Rocks3 Sprite Y Position

dim rocks3_flag=var36 :rem Rocks3 Flag

dim rocks4_xpos=var37 :rem Rocks4 Sprite X Position

dim rocks4_ypos=var38 :rem Rocks4 Sprite Y Position

dim rocks4_flag=var39 :rem Rocks4 Flag

dim satellite1_xpos=var40 :rem Satellite1 Sprite X Position

dim satellite1_ypos=var41 :rem Satellite1 Sprite Y Position

dim satellite1_flag=var42 :rem Satellite1 Flag

dim satellite2_xpos=var43 :rem Satellite2 Sprite X Position

dim satellite2_ypos=var44 :rem Satellite2 Sprite Y Position

dim satellite2_flag=var45 :rem Satellite2 Flag

dim invader1_xpos=var46 :rem Invader1 Sprite X Position

dim invader1_ypos=var47 :rem Invader1 Sprite Y Position

dim invader1_flag=var48 :rem Invader1 Flag

dim alien_xpos=var49 :rem Alien Sprite X Position

dim alien_ypos=var50 :rem Alien Sprite Y Position

dim alien_flag=var51 :rem Alien Flag

dim alien_aniframe=var52 :rem Alien Animation Frame Counter

dim chip1_xpos=var53 :rem Chip1 Sprite X Position

dim chip1_ypos=var54 :rem Chip1 Sprite Y Position

dim chip1_flag=var55 :rem Chip1 Flag

dim rocket1_xpos=var56 :rem Rocket1 Sprite X Position

dim rocket1_ypos=var57 :rem Rocket1 Sprite Y Position

dim rocket1_flag=var58 :rem Rocket1 Flag

dim firstaid1_xpos=var59 :rem FirstAid1 Sprite X Position

dim firstaid1_ypos=var60 :rem FirstAid1 Sprite Y Position

dim firstaid1_flag=var61 :rem FirstAid1 Flag

dim bullet_xpos=var62 :rem Bullet Sprite X Position

dim bullet_ypos=var63 :rem Bullet Sprite Y Position

dim bullet_flag=var64 :rem Bullet Flag

dim spaceman_xpos=var65 :rem Space Man Sprite X Position

dim spaceman_ypos=var66 :rem Space Man Sprite Y Position

dim spaceman_flag=var67 :rem Space Man Flag

dim spaceman_dir=var68 :rem Space Man Direction Facing

dim spaceman_aniframe=var69 :rem Space Man Sprite Animation

dim junk_slowdown1=var70 :rem Junk Slowdown Frame Counter

dim junk_slowdown2=var71 :rem Junk Slowdown Frame Counter

dim junk_slowdown3=var72 :rem Junk Slowdown Frame Counter

dim junk_slowdown4=var73 :rem Junk Slowdown Frame Counter

dim framecount=var74 :rem Global Frame Counter

dim level=var75 :rem Current Skill Level

dim lives=var76 :rem Current Lives Remaining

dim fire_debounce=var77 :rem Debounce, or cause action

dim gameover_flag=var78 :rem Flag for Game Over

 

; Color Palettes

P0C1=$26: P0C2=$24: P0C3=$04

P1C1=$0E: P1C2=$38: P1C3=$FC

P2C1=$CE: P2C2=$C2: P2C3=$C6

P3C1=$32: P3C2=$36: P3C3=$3C

P4C1=$22: P4C2=$26: P4C3=$2C

P5C1=$82: P5C2=$86: P5C3=$0A

P6C1=$32: P6C2=$36: P6C3=$3C

P7C1=$92: P7C2=$96: P7C3=$9F

 

; Graphics Import

incgraphic tileset_stars1.png

incgraphic tileset_planet1.png

incbanner spacejunk_title.png 160A 0 1 2 3

incgraphic scoredigits_8_wide.png

incbanner sprite_7800basic2.png 160A 0 1 1 1

incgraphic sprite_firetobegin.png

incgraphic sprite_satellite1.png

incgraphic sprite_satellite2.png

incgraphic sprite_invader1.png

incgraphic sprite_alien.png

incgraphic sprite_chip1.png

incgraphic sprite_debris1.png

incgraphic sprite_debris2.png

incgraphic sprite_debris3.png

incgraphic sprite_debris4.png

incgraphic sprite_debris5.png

incgraphic sprite_debris6.png

incgraphic sprite_debris7.png

incgraphic sprite_debris8.png

incgraphic sprite_debris9.png

incgraphic sprite_rocket1.png

incgraphic sprite_rocks1.png

incgraphic sprite_rocks2.png

incgraphic sprite_rocks3.png

incgraphic sprite_rocks4.png

incgraphic sprite_spaceman_left.png

incgraphic sprite_spaceman_right.png

incgraphic sprite_firstaid1.png

incgraphic sprite_bullet.png

incmapfile spacejunk.tmx

characterset tileset_stars1

 

; Plot the Titlescreen Images

; Note that the order matters for which one is on top.

; The last one plotted will appear on top of the others.

clearscreen ; Clear the Visible Screen

plotmap spacejunk 0 0 0 20 12 ; Plot the "Space Junk" playfield map in the background

plotbanner spacejunk_title 0 18 10 ; Plot the "Space Junk" title screen image

plotsprite sprite_firetobegin 6 44 115 ; Plot the "Press Fire to Begin" text image

plotbanner sprite_7800basic2 1 58 152 ; Plot the "7800Basic" logo image

savescreen ; Save the plotted images to memory

drawscreen ; Display the plotted images on the screen

 

; Initialize Variables

debris1_xpos = 4 : debris1_ypos = 0 : debris1_flag = 0

debris2_xpos = 4 : debris2_ypos = 20 : debris2_flag = 0

debris3_xpos = 4 : debris3_ypos = 40 : debris3_flag = 0

debris4_xpos = 4 : debris4_ypos = 60 : debris4_flag = 0

debris5_xpos = 4 : debris5_ypos = 80 : debris5_flag = 0

debris6_xpos = 4 : debris6_ypos = 100 : debris6_flag = 0

debris7_xpos = 4 : debris7_ypos = 120 : debris7_flag = 0

debris8_xpos = 4 : debris8_ypos = 140 : debris8_flag = 0

debris9_xpos = 18 : debris9_ypos = 0 : debris9_flag = 0

rocks1_xpos = 18 : rocks1_ypos = 20 : rocks1_flag = 0

rocks2_xpos = 18 : rocks2_ypos = 40 : rocks2_flag = 0

rocks3_xpos = 18 : rocks3_ypos = 60 : rocks3_flag = 0

rocks4_xpos = 18 : rocks4_ypos = 80 : rocks4_flag = 0

satellite1_xpos = 18 : satellite1_ypos = 100 : satellite1_flag = 0

satellite2_xpos = 18 : satellite2_ypos = 120 : satellite2_flag = 0

invader1_xpos = 18 : invader1_ypos = 140 : invader1_flag = 0

chip1_xpos = 32 : chip1_ypos = 0 : chip1_flag = 0

rocket1_xpos = 32 : rocket1_ypos = 20 : rocket1_flag = 0

firstaid1_xpos = 32 : firstaid1_ypos = 40 : firstaid1_flag = 0

bullet_xpos = 32 : bullet_ypos = 60 : bullet_flag = 0

alien_xpos = 32 : alien_ypos = 140 : alien_flag = 0

spaceman_xpos = 120 : spaceman_ypos = 80 : spaceman_flag = 0

junk_slowdown1 = 0 : junk_slowdown2 = 0 : junk_slowdown3 = 0

framecounter = 0 : level = 0 : lives = 3

fire_debounce = 0 : gameover_flag = 0 : junk_slowdown4 = 0

alien_aniframe = 0 : spaceman_aniframe = 0 : spaceman_dir = 0

 

titlescreenloop

restorescreen ; Restore the plotted images saved with the previous savescreen

drawscreen ; Display the plotted images on the screen

if joy0fire then clearscreen:goto init ; Check for a joystick button press. If yes, clear the screen and goto init label

goto titlescreenloop ; Repeat this loop until the jobstick button is pressed

 

; Here we clear the screen, plot only the background playfield map, save it, then move on to the main game loop.

init

clearscreen ; Clear the Visible Screen

plotmap spacejunk 0 0 0 20 12 ; Plot the "Space Junk" playfield map in the background

savescreen ; Save the plotted images to memory

drawscreen ; Display the plotted images on the screen

 

mainloop

BACKGRND=$00 ; Set the background color to Black ($00)

restorescreen ; Restore the plotted images saved with the previous savescreen

 

; Here we plot all of the sprites on the screen, using the variables we defined for the X and Y locations.

; Spaces were added for readability, only one space is required between option

; Name of the Sprite Palette X Position Y Position

; ------------------- ------- ------------- -------------

plotsprite sprite_invader1 5 invader1_xpos invader1_ypos

plotsprite sprite_alien 6 alien_xpos alien_ypos

plotsprite sprite_chip1 5 chip1_xpos chip1_ypos

plotsprite sprite_debris1 1 debris1_xpos debris1_ypos

plotsprite sprite_debris2 1 debris2_xpos debris2_ypos

plotsprite sprite_debris3 0 debris3_xpos debris3_ypos

plotsprite sprite_debris4 1 debris4_xpos debris4_ypos

plotsprite sprite_debris5 1 debris5_xpos debris5_ypos

plotsprite sprite_debris6 1 debris6_xpos debris6_ypos

plotsprite sprite_debris7 1 debris7_xpos debris7_ypos

plotsprite sprite_debris8 1 debris8_xpos debris8_ypos

plotsprite sprite_debris9 0 debris9_xpos debris9_ypos

plotsprite sprite_rocket1 5 rocket1_xpos rocket1_ypos

plotsprite sprite_rocks1 3 rocks1_xpos rocks1_ypos

plotsprite sprite_rocks2 2 rocks2_xpos rocks2_ypos

plotsprite sprite_rocks3 3 rocks3_xpos rocks3_ypos

plotsprite sprite_rocks4 4 rocks4_xpos rocks4_ypos

plotsprite sprite_satellite1 4 satellite1_xpos satellite1_ypos

plotsprite sprite_satellite2 3 satellite2_xpos satellite2_ypos

plotsprite sprite_spaceman_left 5 spaceman_xpos spaceman_ypos

 

drawscreen ; Display the plotted images on the screen

goto mainloop ; Jump back to the beginning of the main game loop

 

 

Here is the code, graphics, and compiled files for this section: SpaceJunk_092317_1a.zip

 

Note: I added two new graphics files to the title screen. Once that says “Press Fire to Begin” and one with the 7800Basic logo.

 

Next I’ll work on adding some gameplay to the game, we’ll move the sprites around and put some ammo in our space gun so we can start shooting!

post-2143-0-28346700-1506120986.pngpost-2143-0-88328000-1506120986.png

  • Like 4
Link to comment
Share on other sites

In this update, you can move your Space Man around the screen and fire your gun in four directions.

 

The following variable changes were made in the Variable Assignment section of the code:

 

dim bullet_fire_xpos =var76 :rem Bullet Sprite X Position

dim bullet_fire_ypos =var77 :rem Bullet Sprite Y Position

dim joyposup =var78 :rem Track last position the joystick was moved

dim joyposdown =var79 :rem Track last position the joystick was moved

dim joyposleft =var80 :rem Track last position the joystick was moved

dim joyposright =var81 :rem Track last position the joystick was moved

 

The variables were also added to the section that initializes the starting values for all of the variables. They were all set to 0 with the exception of joyposleft, which is a flag that sets the initial firing direction of your gun to left. The following changes were made:

 

bullet_fire_xpos= 0 : bullet_fire_ypos = 0 : joyposleft = 1

joyposright = 0 : joyposup = 0 : joyposdown = 0

 

The code below was added to the main loop. I did add the code to move the Space Man around the screen, but the majority of the changes were directly related to firing your gun. The tricky part of this code, and the reason it seems to be so bloated, is that we need the gun to stop firing when you change directions. Without that feature, and if we simply set the bullet to move in the direction you were pushing the joystick, your bullet would change direction mid-flight whenever you changed direction.

 

We also needed to set the "home" location of the bullet itself. The bullet is reset to the home position whenever you change directions with the joystick, you're not pressing the fire button, or if the bullet you've just fired reaches one of the edges of the screen. If we set the home location of the bullet to the same X/Y location of the Space Man, it would appear just above and to the left of his head, as the X/Y location doesn't match up. Because of that, I did a little trial and error testing to get the bullet to always appear on the very tip of his gun. That position turned out to be exactly 2 pixels to the left and 8 pixels down from the current X/Y position of the Space Man. I know that when you fire to the right the bullet travels through your body. Eventually we'll get to sprite animation and sprite flipping, I'll have him facing right so it looks more polished.

 

Moving the Space Man was a bit easier, as we simply add and subtract from the X/Y value based on joystick movement. I also added a section of code to check the X/Y position relative to the edges of the screen, as we don't want our hero to be able to walk off the visible area of the screen. In the Space Man movement code is where I set four new variables for joystick direction tracking. When you press the joystick in any given direction, a flag is set which then determines the current trajectory of your bullet.

 

; This plots the bullet on the screen.

plotsprite sprite_bullet 5 bullet_fire_xpos bullet_fire_ypos

 

; This block of code checks to see if you've changed joystick directions. If so, the bullet is stopped and reset.

; First Line: If Joystick is being pushed up, and the last push was not up, then reset the bullet to the home location.

if joy0up && joyposup=0 then bullet_fire_xpos=spaceman_xpos-2:bullet_fire_ypos=spaceman_ypos+8

if joy0down && joyposdown=0 then bullet_fire_xpos=spaceman_xpos-2:bullet_fire_ypos=spaceman_ypos+8

if joy0left && joyposleft=0 then bullet_fire_xpos=spaceman_xpos-2:bullet_fire_ypos=spaceman_ypos+8

if joy0right && joyposright=0 then bullet_fire_xpos=spaceman_xpos-2:bullet_fire_ypos=spaceman_ypos+8

 

; This block of code moves your Space Man, and sets a flag for the currently pressed joystick direction

; The direction flag is used to determine the direction the bullet is fired later on

if joy0up1 then spaceman_ypos=spaceman_ypos-1:joyposup=1:joyposdown=0:joyposleft=0:joyposright=0

if joy0down1 then spaceman_ypos=spaceman_ypos+1:joyposup=0:joyposdown=1:joyposleft=0:joyposright=0

if joy0left1 then spaceman_xpos=spaceman_xpos-1:joyposup=0:joyposdown=0:joyposleft=1:joyposright=0

if joy0right1 then spaceman_xpos=spaceman_xpos+1:joyposup=0:joyposdown=0:joyposleft=0:joyposright=1

 

; Based on the most recent direction the joystick was pushed, fire the bullet in that direction

if joy0fire && joyposup=1 then bullet_fire_ypos=bullet_fire_ypos-4

if joy0fire && joyposdown=1 then bullet_fire_ypos=bullet_fire_ypos+4

if joy0fire && joyposleft=1 then bullet_fire_xpos=bullet_fire_xpos-4

if joy0fire && joyposright=1 then bullet_fire_xpos=bullet_fire_xpos+4

if !joy0fire then bullet_fire_xpos=spaceman_xpos-2:bullet_fire_ypos=spaceman_ypos+8

 

; Limit the Space Man's movement to keep him on the visible screen

if spaceman_xpos>146 then spaceman_xpos=spaceman_xpos-1 ; Don't move off the right side of the screen

if spaceman_xpos<1 then spaceman_xpos=spaceman_xpos+1 ; Don't move off the left side of the screen

if spaceman_ypos>176 then spaceman_ypos=spaceman_ypos-1 ; Don't move off the bottom of the screen

if spaceman_ypos<1 then spaceman_ypos=spaceman_ypos+1 ; Don't move off the top of the screen

 

; Don't let bullet fire off of the visible screen

if bullet_fire_xpos>158 then bullet_fire_xpos=spaceman_xpos-2:bullet_fire_ypos=spaceman_ypos+8 ; Stop bullet at right edge of screen

if bullet_fire_xpos<2 then bullet_fire_xpos=spaceman_xpos-2:bullet_fire_ypos=spaceman_ypos+8 ; Stop bullet at left edge of screen

if bullet_fire_ypos>188 then bullet_fire_xpos=spaceman_xpos-2:bullet_fire_ypos=spaceman_ypos+8 ; Stop bullet at bottom edge of screen

if bullet_fire_ypos<2 then bullet_fire_xpos=spaceman_xpos-2:bullet_fire_ypos=spaceman_ypos+8 ; Stop bullet at top edge of screen

 

That's it for this update. No screenshots of the game this time because they'd look exactly the same, this update only affects the movement of the Space Man and gun firing. The code is getting a little long to copy and paste the entire thing into a post. Please download the file and open the file to review it. If anyone has any questions, please post 'em up and I'd be happy to explain any of this code in more detail.

 

The zip file contains the updated code, graphics files, and compiled files: SpaceJunk_092317_1e.zip

  • Like 2
Link to comment
Share on other sites

I’ve been working on the game this morning and made some additional changes. I must say, the most difficult part of writing this game for a tutorial is self restraint. It’s difficult to pick a task and focus ONLY on that, I want to just throw all my ideas in there at once. J

 

I’m of course writing this game on the fly, so I don’t really know what the ultimate game play is going to be like. I decided to make the space junk randomly move on the screen, but it purposely makes slow, jerky movements. I don’t want all of the sprites to keep moving in one direction until they hit an edge and turn around – the game would be much too difficult. Adding intelligence to the “junk” movement is a little much for this demo game, I’m trying to keep this simple, and it's also a bit counterintuitive for floating space junk. For now, the junk will aimlessly float around the screen, the alien will be indestructible and you’ll have to avoid him (it’ll keep you on your toes, and require constant movement). We can change it all up later if this doesn’t work well in practice.

 

I’m also aware that the code I wrote for sprite movement is not very efficient, it could be condensed. I think for the purposes of this demo, how it’s written now makes it much clearer as to what’s going on in the code. I can work on cleanup and optimization later on, especially if we run low on ROM space.

 

This update introduces the following changes:

  1. Random movement for our space junk. I spread the sprites out a little bit around the sides and top of the screen.
  2. Alien Pursuit. The alien will now relentlessly pursue you
  3. First Aid. For now it’s statically placed at the bottom right of the screen, in the future it will be a randomly placed powerup/health recharge.

I think for the next module I’ll work on collision detection. I’ll make it possible to shoot the junk and make it disappear from the screen (explosion animation will come later). I’ll also work on adding a score and life meter.

 

Variable Updates

 

Below are all of the new variables that I added to the code. They are all used for the random movement of the space junk sprites on the screen.

 

dim RMovement1 = var82 :rem Used for Random Movement for the Junk

dim RMovement2 = var83 :rem Used for Random Movement for the Junk

dim RMovement3 = var84 :rem Used for Random Movement for the Junk

dim RMovement4 = var85 :rem Used for Random Movement for the Junk

dim RMovement5 = var86 :rem Used for Random Movement for the Junk

dim RMovement6 = var87 :rem Used for Random Movement for the Junk

dim RDirection = var88 :rem Used for Random Directions for the Junk

 

Variable Assignment Updates

 

I added the new variables to the assignments section as well. They are all seeded with 1. I also changed the X/Y values for the “junk” sprites in the assignment table, they are a little more spread out around the screen now for their starting locations.

 

RMovement1 = 1 : RMovement2 = 1 : RMovement3 = 1

RMovement4 = 1 : RMovement5 = 1 : RMovement6 = 1

RDirection = 1

 

Main Loop Updates

 

This was added near the beginning of the main loop:

 

RMovement1=(rand&3) ; Generate Random number between 0-3 (for Movement)

RMovement2=(rand&1) ; Generate Random number between 0-1 (for Movement)

RMovement3=(rand&7)-1 ; Generate Random number between 0-6 (for Movement)

RMovement4=(rand&3)+1 ; Generate Random number between 1-4 (for Movement)

RMovement5=(rand&7)-2 ; Generate Random number between 1-4 (for Movement)

RDirection=(rand&3) ; Generate RAndom number between 0-3 (for Direction)

 

junk_slowdown1=junk_slowdown1+1 ; This is a "Slowdown" frame counter

if junk_slowdown1>8 then junk_slowdown1=0 ; We'll apply it to space junk movement to slow it down

junk_slowdown2=junk_slowdown2+1 ; This is a "Slowdown" frame counter

if junk_slowdown2>6 then junk_slowdown2=0 ; We'll apply it to space junk movement to slow it down

 

I added the first aid sprite to the plotsprite list:

 

plotsprite sprite_firstaid1 5 firstaid1_xpos firstaid1_ypos

 

This was added after we plot the sprites:

 

This is a lot of code for sprite movement, but of course we have a lot of sprites to move. This could be condensed at the expense of readability by putting all of the movement into a subroutines for each RDirection check, and all of the location checks could put placed in a separate gosub subroutine. There are many ways to accomplish the same thing in code. I’ll probably work on some optimization tips later on in the development process, for now I want to make sure the code is clear and understandable.

 

; Frame Counters for slowing down sprite movement

junk_slowdown1=junk_slowdown1+1 ; This is a "Slowdown" frame counter

if junk_slowdown1>8 then junk_slowdown1=0 ; We'll apply it to space junk movement to slow it down

junk_slowdown2=junk_slowdown2+1 ; This is a "Slowdown" frame counter

if junk_slowdown2>6 then junk_slowdown2=0 ; We'll apply it to space junk movement to slow it down

 

; The junk_slowdown1 variable counts from 0-8, if the value is 2-8 we skip sprite movement

; To speed up the movement, we can simply reduce the range that it counts from 0-8 to 0-7, etc.

if junk_slowdown1>1 then goto skipjunkmovement1

 

I created 6 different "RMovement" variables that will generate different ranges of random numbers between 0-8. I randomly picked which one to use in each section of sprite movement. To change up how the sprites move, we could change the ranges of the RMovement variables, or change which of the six variables is used on each line of code.

 

; "Debris1" Sprite Movement

; This checks the "RDirection" random variable (0-3), which randomizes which direction the sprite travels.

; The "RMovement" Variables add a random number to the X/Y movement

if RDirection=0 then debris1_xpos=debris1_xpos+RMovement1:debris1_ypos=debris1_ypos+RMovement3

if RDirection=1 then debris1_xpos=debris1_xpos-RMovement5:debris1_ypos=debris1_ypos-RMovement3

if RDirection=2 then debris1_xpos=debris1_xpos+RMovement1:debris1_ypos=debris1_ypos-RMovement3

if RDirection=3 then debris1_xpos=debris1_xpos-RMovement3:debris1_ypos=debris1_ypos+RMovement2

 

This code does not completely prevent the sprites from moving off the edge of the screen, but it makes it happen less frequently. For example, if a sprite is currently at X6, Y6, and it's random movement reduces X by 8, the code below won't stop it from wrapping over. It's actually how I'd prefer it to be, occasionally having some junk wrap off the screen will help balance the difficulty of having so many sprites to avoid, and visually it looks better because most of the time they stay on the visible screen.

 

; This checks the sprite location, and bounces them back 5 pixels if they are on the edge of the screen.

if debris1_xpos>141 then debris1_xpos=debris1_xpos-5 ; Don't move off the right side of the screen

if debris1_xpos<5 then debris1_xpos=debris1_xpos+5 ; Don't move off the left side of the screen

if debris1_ypos>171 then debris1_ypos=debris1_ypos-5 ; Don't move off the bottom of the screen

if debris1_ypos<5 then debris1_ypos=debris1_ypos+5 ; Don't move off the top of the screen

 

The remainder of the code updates are the same block of code duplicated for each individual sprite.

 

; "Debris2" Sprite Movement

; This checks the "RDirection" random variable (0-3), which randomizes which direction the sprite travels.

; The "RMovement" Variables add a random number to the X/Y movement

if RDirection=0 then debris2_xpos=debris2_xpos+RMovement2:debris2_ypos=debris2_ypos+RMovement6

if RDirection=1 then debris2_xpos=debris2_xpos-RMovement2:debris2_ypos=debris2_ypos-RMovement1

if RDirection=2 then debris2_xpos=debris2_xpos+RMovement3:debris2_ypos=debris2_ypos-RMovement1

if RDirection=3 then debris2_xpos=debris2_xpos-RMovement2:debris2_ypos=debris2_ypos+RMovement1

; This checks the sprite location, and bounces them back 5 pixels if they are on the edge of the screen.

if debris2_xpos>141 then debris2_xpos=debris2_xpos-5 ; Don't move off the right side of the screen

if debris2_xpos<5 then debris2_xpos=debris2_xpos+5 ; Don't move off the left side of the screen

if debris2_ypos>171 then debris2_ypos=debris2_ypos-5 ; Don't move off the bottom of the screen

if debris2_ypos<5 then debris2_ypos=debris2_ypos+5 ; Don't move off the top of the screen

 

; "Debris3" Sprite Movement

; This checks the "RDirection" random variable (0-3), which randomizes which direction the sprite travels.

; The "RMovement" Variables add a random number to the X/Y movement

if RDirection=0 then debris3_xpos=debris3_xpos+RMovement5:debris3_ypos=debris3_ypos+RMovement4

if RDirection=1 then debris3_xpos=debris3_xpos-RMovement2:debris3_ypos=debris3_ypos-RMovement2

if RDirection=2 then debris3_xpos=debris3_xpos+RMovement4:debris3_ypos=debris3_ypos-RMovement3

if RDirection=3 then debris3_xpos=debris3_xpos-RMovement1:debris3_ypos=debris3_ypos+RMovement2

; This checks the sprite location, and bounces them back 5 pixels if they are on the edge of the screen.

if debris3_xpos>141 then debris3_xpos=debris3_xpos-5 ; Don't move off the right side of the screen

if debris3_xpos<5 then debris3_xpos=debris3_xpos+5 ; Don't move off the left side of the screen

if debris3_ypos>171 then debris3_ypos=debris3_ypos-5 ; Don't move off the bottom of the screen

if debris3_ypos<5 then debris3_ypos=debris3_ypos+5 ; Don't move off the top of the screen

 

; "Debris4" Sprite Movement

; This checks the "RDirection" random variable (0-3), which randomizes which direction the sprite travels.

; The "RMovement" Variables add a random number to the X/Y movement

if RDirection=0 then debris4_xpos=debris4_xpos+RMovement2:debris4_ypos=debris4_ypos+RMovement4

if RDirection=1 then debris4_xpos=debris4_xpos-RMovement3:debris4_ypos=debris4_ypos-RMovement4

if RDirection=2 then debris4_xpos=debris4_xpos+RMovement3:debris4_ypos=debris4_ypos-RMovement4

if RDirection=3 then debris4_xpos=debris4_xpos-RMovement3:debris4_ypos=debris4_ypos+RMovement1

; This checks the sprite location, and bounces them back 5 pixels if they are on the edge of the screen.

if debris4_xpos>141 then debris4_xpos=debris4_xpos-5 ; Don't move off the right side of the screen

if debris4_xpos<5 then debris4_xpos=debris4_xpos+5 ; Don't move off the left side of the screen

if debris4_ypos>171 then debris4_ypos=debris4_ypos-5 ; Don't move off the bottom of the screen

if debris4_ypos<5 then debris4_ypos=debris4_ypos+5 ; Don't move off the top of the screen

 

; "Debris5" Sprite Movement

; This checks the "RDirection" random variable (0-3), which randomizes which direction the sprite travels.

; The "RMovement" Variables add a random number to the X/Y movement

if RDirection=0 then debris5_xpos=debris5_xpos+RMovement5:debris5_ypos=debris5_ypos+RMovement2

if RDirection=1 then debris5_xpos=debris5_xpos-RMovement5:debris5_ypos=debris5_ypos-RMovement3

if RDirection=2 then debris5_xpos=debris5_xpos+RMovement5:debris5_ypos=debris5_ypos-RMovement2

if RDirection=3 then debris5_xpos=debris5_xpos-RMovement1:debris5_ypos=debris5_ypos+RMovement2

; This checks the sprite location, and bounces them back 5 pixels if they are on the edge of the screen.

if debris5_xpos>141 then debris5_xpos=debris5_xpos-5 ; Don't move off the right side of the screen

if debris5_xpos<5 then debris5_xpos=debris5_xpos+5 ; Don't move off the left side of the screen

if debris5_ypos>171 then debris5_ypos=debris5_ypos-5 ; Don't move off the bottom of the screen

if debris5_ypos<5 then debris5_ypos=debris5_ypos+5 ; Don't move off the top of the screen

 

; "Debris6" Sprite Movement

; This checks the "RDirection" random variable (0-3), which randomizes which direction the sprite travels.

; The "RMovement" Variables add a random number to the X/Y movement

if RDirection=0 then debris6_xpos=debris6_xpos+RMovement1:debris6_ypos=debris6_ypos+RMovement4

if RDirection=1 then debris6_xpos=debris6_xpos-RMovement4:debris6_ypos=debris6_ypos-RMovement5

if RDirection=2 then debris6_xpos=debris6_xpos+RMovement5:debris6_ypos=debris6_ypos-RMovement5

if RDirection=3 then debris6_xpos=debris6_xpos-RMovement4:debris6_ypos=debris6_ypos+RMovement5

; This checks the sprite location, and bounces them back 5 pixels if they are on the edge of the screen.

if debris6_xpos>141 then debris6_xpos=debris6_xpos-5 ; Don't move off the right side of the screen

if debris6_xpos<5 then debris6_xpos=debris6_xpos+5 ; Don't move off the left side of the screen

if debris6_ypos>171 then debris6_ypos=debris6_ypos-5 ; Don't move off the bottom of the screen

if debris6_ypos<5 then debris6_ypos=debris6_ypos+5 ; Don't move off the top of the screen

 

; "Debris7" Sprite Movement

; This checks the "RDirection" random variable (0-3), which randomizes which direction the sprite travels.

; The "RMovement" Variables add a random number to the X/Y movement

if RDirection=0 then debris7_xpos=debris7_xpos+RMovement1:debris7_ypos=debris7_ypos+RMovement4

if RDirection=1 then debris7_xpos=debris7_xpos-RMovement1:debris7_ypos=debris7_ypos-RMovement4

if RDirection=2 then debris7_xpos=debris7_xpos+RMovement1:debris7_ypos=debris7_ypos-RMovement4

if RDirection=3 then debris7_xpos=debris7_xpos-RMovement1:debris7_ypos=debris7_ypos+RMovement4

; This checks the sprite location, and bounces them back 5 pixels if they are on the edge of the screen.

if debris7_xpos>141 then debris7_xpos=debris7_xpos-5 ; Don't move off the right side of the screen

if debris7_xpos<5 then debris7_xpos=debris7_xpos+5 ; Don't move off the left side of the screen

if debris7_ypos>171 then debris7_ypos=debris7_ypos-5 ; Don't move off the bottom of the screen

if debris7_ypos<5 then debris7_ypos=debris7_ypos+5 ; Don't move off the top of the screen

 

; "Debris8" Sprite Movement

; This checks the "RDirection" random variable (0-3), which randomizes which direction the sprite travels.

; The "RMovement" Variables add a random number to the X/Y movement

if RDirection=0 then debris8_xpos=debris8_xpos+RMovement5:debris8_ypos=debris8_ypos+RMovement1

if RDirection=1 then debris8_xpos=debris8_xpos-RMovement3:debris8_ypos=debris8_ypos-RMovement2

if RDirection=2 then debris8_xpos=debris8_xpos+RMovement2:debris8_ypos=debris8_ypos-RMovement5

if RDirection=3 then debris8_xpos=debris8_xpos-RMovement4:debris8_ypos=debris8_ypos+RMovement3

; This checks the sprite location, and bounces them back 5 pixels if they are on the edge of the screen.

if debris8_xpos>141 then debris8_xpos=debris8_xpos-5 ; Don't move off the right side of the screen

if debris8_xpos<5 then debris8_xpos=debris8_xpos+5 ; Don't move off the left side of the screen

if debris8_ypos>171 then debris8_ypos=debris8_ypos-5 ; Don't move off the bottom of the screen

if debris8_ypos<5 then debris8_ypos=debris8_ypos+5 ; Don't move off the top of the screen

 

; "Debris9" Sprite Movement

; This checks the "RDirection" random variable (0-3), which randomizes which direction the sprite travels.

; The "RMovement" Variables add a random number to the X/Y movement

if RDirection=0 then debris9_xpos=debris9_xpos+RMovement5:debris9_ypos=debris9_ypos+RMovement1

if RDirection=1 then debris9_xpos=debris9_xpos-RMovement5:debris9_ypos=debris9_ypos-RMovement1

if RDirection=2 then debris9_xpos=debris9_xpos+RMovement5:debris9_ypos=debris9_ypos-RMovement1

if RDirection=3 then debris9_xpos=debris9_xpos-RMovement5:debris9_ypos=debris9_ypos+RMovement1

; This checks the sprite location, and bounces them back 5 pixels if they are on the edge of the screen.

if debris9_xpos>141 then debris9_xpos=debris9_xpos-5 ; Don't move off the right side of the screen

if debris9_xpos<5 then debris9_xpos=debris9_xpos+5 ; Don't move off the left side of the screen

if debris9_ypos>171 then debris9_ypos=debris9_ypos-5 ; Don't move off the bottom of the screen

if debris9_ypos<5 then debris9_ypos=debris9_ypos+5 ; Don't move off the top of the screen

 

; "Rocks1" Sprite Movement

; This checks the "RDirection" random variable (0-3), which randomizes which direction the sprite travels.

; The "RMovement" Variables add a random number to the X/Y movement

if RDirection=0 then rocks1_xpos=rocks1_xpos+RMovement2:rocks1_ypos=rocks1_ypos+RMovement4

if RDirection=1 then rocks1_xpos=rocks1_xpos-RMovement2:rocks1_ypos=rocks1_ypos-RMovement4

if RDirection=2 then rocks1_xpos=rocks1_xpos+RMovement2:rocks1_ypos=rocks1_ypos-RMovement4

if RDirection=3 then rocks1_xpos=rocks1_xpos-RMovement2:rocks1_ypos=rocks1_ypos+RMovement4

; This checks the sprite location, and bounces them back 5 pixels if they are on the edge of the screen.

if rocks1_xpos>141 then rocks1_xpos=rocks1_xpos-5 ; Don't move off the right side of the screen

if rocks1_xpos<5 then rocks1_xpos=rocks1_xpos+5 ; Don't move off the left side of the screen

if rocks1_ypos>171 then rocks1_ypos=rocks1_ypos-5 ; Don't move off the bottom of the screen

if rocks1_ypos<5 then rocks1_ypos=rocks1_ypos+5 ; Don't move off the top of the screen

 

; "Rocks2" Sprite Movement

; This checks the "RDirection" random variable (0-3), which randomizes which direction the sprite travels.

; The "RMovement" Variables add a random number to the X/Y movement

if RDirection=0 then rocks2_xpos=rocks2_xpos+RMovement4:rocks2_ypos=rocks2_ypos+RMovement2

if RDirection=1 then rocks2_xpos=rocks2_xpos-RMovement4:rocks2_ypos=rocks2_ypos-RMovement2

if RDirection=2 then rocks2_xpos=rocks2_xpos+RMovement4:rocks2_ypos=rocks2_ypos-RMovement2

if RDirection=3 then rocks2_xpos=rocks2_xpos-RMovement4:rocks2_ypos=rocks2_ypos+RMovement2

; This checks the sprite location, and bounces them back 5 pixels if they are on the edge of the screen.

if rocks2_xpos>141 then rocks2_xpos=rocks2_xpos-5 ; Don't move off the right side of the screen

if rocks2_xpos<5 then rocks2_xpos=rocks2_xpos+5 ; Don't move off the left side of the screen

if rocks2_ypos>171 then rocks2_ypos=rocks2_ypos-5 ; Don't move off the bottom of the screen

if rocks2_ypos<5 then rocks2_ypos=rocks2_ypos+5 ; Don't move off the top of the screen

 

; "Rocks3" Sprite Movement

; This checks the "RDirection" random variable (0-3), which randomizes which direction the sprite travels.

; The "RMovement" Variables add a random number to the X/Y movement

if RDirection=0 then rocks3_xpos=rocks3_xpos+RMovement1:rocks3_ypos=rocks3_ypos+RMovement4

if RDirection=1 then rocks3_xpos=rocks3_xpos-RMovement2:rocks3_ypos=rocks3_ypos-RMovement3

if RDirection=2 then rocks3_xpos=rocks3_xpos+RMovement3:rocks3_ypos=rocks3_ypos-RMovement2

if RDirection=3 then rocks3_xpos=rocks3_xpos-RMovement4:rocks3_ypos=rocks3_ypos+RMovement1

; This checks the sprite location, and bounces them back 5 pixels if they are on the edge of the screen.

if rocks3_xpos>141 then rocks3_xpos=rocks3_xpos-5 ; Don't move off the right side of the screen

if rocks3_xpos<5 then rocks3_xpos=rocks3_xpos+5 ; Don't move off the left side of the screen

if rocks3_ypos>171 then rocks3_ypos=rocks3_ypos-5 ; Don't move off the bottom of the screen

if rocks3_ypos<5 then rocks3_ypos=rocks3_ypos+5 ; Don't move off the top of the screen

 

; "Rocks4" Sprite Movement

; This checks the "RDirection" random variable (0-3), which randomizes which direction the sprite travels.

; The "RMovement" Variables add a random number to the X/Y movement

if RDirection=0 then rocks4_xpos=rocks4_xpos+RMovement5:rocks4_ypos=rocks4_ypos+RMovement3

if RDirection=1 then rocks4_xpos=rocks4_xpos-RMovement5:rocks4_ypos=rocks4_ypos-RMovement3

if RDirection=2 then rocks4_xpos=rocks4_xpos+RMovement4:rocks4_ypos=rocks4_ypos-RMovement2

if RDirection=3 then rocks4_xpos=rocks4_xpos-RMovement4:rocks4_ypos=rocks4_ypos+RMovement2

; This checks the sprite location, and bounces them back 5 pixels if they are on the edge of the screen.

if rocks4_xpos>141 then rocks4_xpos=rocks4_xpos-5 ; Don't move off the right side of the screen

if rocks4_xpos<5 then rocks4_xpos=rocks4_xpos+5 ; Don't move off the left side of the screen

if rocks4_ypos>171 then rocks4_ypos=rocks4_ypos-5 ; Don't move off the bottom of the screen

if rocks4_ypos<5 then rocks4_ypos=rocks4_ypos+5 ; Don't move off the top of the screen

 

; "Satellite1" Sprite Movement

; This checks the "RDirection" random variable (0-3), which randomizes which direction the sprite travels.

; The "RMovement" Variables add a random number to the X/Y movement

if RDirection=0 then satellite1_xpos=satellite1_xpos+RMovement5:satellite1_ypos=satellite1_ypos+RMovement2

if RDirection=1 then satellite1_xpos=satellite1_xpos-RMovement3:satellite1_ypos=satellite1_ypos-RMovement5

if RDirection=2 then satellite1_xpos=satellite1_xpos+RMovement2:satellite1_ypos=satellite1_ypos-RMovement4

if RDirection=3 then satellite1_xpos=satellite1_xpos-RMovement1:satellite1_ypos=satellite1_ypos+RMovement3

; This checks the sprite location, and bounces them back 5 pixels if they are on the edge of the screen.

if satellite1_xpos>141 then satellite1_xpos=satellite1_xpos-5 ; Don't move off the right side of the screen

if satellite1_xpos<5 then satellite1_xpos=satellite1_xpos+5 ; Don't move off the left side of the screen

if satellite1_ypos>171 then satellite1_ypos=satellite1_ypos-5 ; Don't move off the bottom of the screen

if satellite1_ypos<5 then satellite1_ypos=satellite1_ypos+5 ; Don't move off the top of the screen

 

; "Satellite2" Sprite Movement

; This checks the "RDirection" random variable (0-3), which randomizes which direction the sprite travels.

; The "RMovement" Variables add a random number to the X/Y movement

if RDirection=0 then satellite2_xpos=satellite2_xpos+RMovement3:satellite2_ypos=satellite2_ypos+RMovement5

if RDirection=1 then satellite2_xpos=satellite2_xpos-RMovement5:satellite2_ypos=satellite2_ypos-RMovement1

if RDirection=2 then satellite2_xpos=satellite2_xpos+RMovement1:satellite2_ypos=satellite2_ypos-RMovement2

if RDirection=3 then satellite2_xpos=satellite2_xpos-RMovement2:satellite2_ypos=satellite2_ypos+RMovement3

; This checks the sprite location, and bounces them back 5 pixels if they are on the edge of the screen.

if satellite2_xpos>141 then satellite2_xpos=satellite2_xpos-5 ; Don't move off the right side of the screen

if satellite2_xpos<5 then satellite2_xpos=satellite2_xpos+5 ; Don't move off the left side of the screen

if satellite2_ypos>171 then satellite2_ypos=satellite2_ypos-5 ; Don't move off the bottom of the screen

if satellite2_ypos<5 then satellite2_ypos=satellite2_ypos+5 ; Don't move off the top of the screen

 

; "Chip1" Sprite Movement

; This checks the "RDirection" random variable (0-3), which randomizes which direction the sprite travels.

; The "RMovement" Variables add a random number to the X/Y movement

if RDirection=0 then chip1_xpos=chip1_xpos+RMovement4:chip1_ypos=chip1_ypos+RMovement2

if RDirection=1 then chip1_xpos=chip1_xpos-RMovement2:chip1_ypos=chip1_ypos-RMovement5

if RDirection=2 then chip1_xpos=chip1_xpos+RMovement3:chip1_ypos=chip1_ypos-RMovement4

if RDirection=3 then chip1_xpos=chip1_xpos-RMovement1:chip1_ypos=chip1_ypos+RMovement4

; This checks the sprite location, and bounces them back 5 pixels if they are on the edge of the screen.

if chip1_xpos>141 then chip1_xpos=chip1_xpos-5 ; Don't move off the right side of the screen

if chip1_xpos<5 then chip1_xpos=chip1_xpos+5 ; Don't move off the left side of the screen

if chip1_ypos>171 then chip1_ypos=chip1_ypos-5 ; Don't move off the bottom of the screen

if chip1_ypos<5 then chip1_ypos=chip1_ypos+5 ; Don't move off the top of the screen

 

; "Invader1" Sprite Movement

; This checks the "RDirection" random variable (0-3), which randomizes which direction the sprite travels.

; The "RMovement" Variables add a random number to the X/Y movement

if RDirection=0 then invader1_xpos=invader1_xpos+RMovement4:invader1_ypos=invader1_ypos+RMovement3

if RDirection=1 then invader1_xpos=invader1_xpos-RMovement5:invader1_ypos=invader1_ypos-RMovement2

if RDirection=2 then invader1_xpos=invader1_xpos+RMovement5:invader1_ypos=invader1_ypos-RMovement2

if RDirection=3 then invader1_xpos=invader1_xpos-RMovement4:invader1_ypos=invader1_ypos+RMovement3

; This checks the sprite location, and bounces them back 5 pixels if they are on the edge of the screen.

if invader1_xpos>141 then invader1_xpos=invader1_xpos-5 ; Don't move off the right side of the screen

if invader1_xpos<5 then invader1_xpos=invader1_xpos+5 ; Don't move off the left side of the screen

if invader1_ypos>171 then invader1_ypos=invader1_ypos-5 ; Don't move off the bottom of the screen

if invader1_ypos<5 then invader1_ypos=invader1_ypos+5 ; Don't move off the top of the screen

 

; "Rocket1" Sprite Movement

; This checks the "RDirection" random variable (0-3), which randomizes which direction the sprite travels.

; The "RMovement" Variables add a random number to the X/Y movement

if RDirection=0 then rocket1_xpos=rocket1_xpos+RMovement1:rocket1_ypos=rocket1_ypos+RMovement3

if RDirection=1 then rocket1_xpos=rocket1_xpos-RMovement5:rocket1_ypos=rocket1_ypos-RMovement4

if RDirection=2 then rocket1_xpos=rocket1_xpos+RMovement2:rocket1_ypos=rocket1_ypos-RMovement5

if RDirection=3 then rocket1_xpos=rocket1_xpos-RMovement4:rocket1_ypos=rocket1_ypos+RMovement1

; This checks the sprite location, and bounces them back 5 pixels if they are on the edge of the screen.

if rocket1_xpos>141 then rocket1_xpos=rocket1_xpos-5 ; Don't move off the right side of the screen

if rocket1_xpos<5 then rocket1_xpos=rocket1_xpos+5 ; Don't move off the left side of the screen

if rocket1_ypos>171 then rocket1_ypos=rocket1_ypos-5 ; Don't move off the bottom of the screen

if rocket1_ypos<5 then rocket1_ypos=rocket1_ypos+5 ; Don't move off the top of the screen

 

; The skipjunkmovement1 label is used as a target to skip to with the junk_slowdown1 frame counter/timer

skipjunkmovement1

 

; This section causes the alien sprite to constantly pursue our Space Man

; The X/Y value of the alien is compared to the Space Man, and incremented toward the Space Man.

; The slowdown counter makes it happen more slowly, every frame would be too fast.

if junk_slowdown2>1 then goto skipalienmovmement1

if alien_xpos > spaceman_xpos then alien_xpos=alien_xpos-1

if alien_xpos < spaceman_xpos then alien_xpos=alien_xpos+1

if alien_ypos > spaceman_ypos then alien_ypos=alien_ypos-1

if alien_ypos < spaceman_ypos then alien_ypos=alien_ypos+1

 

;The skipalienmovmement1 label is used as a target to skip to with the junk_slowdown1 frame counter/timer

skipalienmovmement1

 

post-2143-0-37165600-1506186699.png

 

Here's the zip file with all the code, graphics, and compiled files:

SpaceJunk_092317_1f.zip

Link to comment
Share on other sites

I added some simple collision detection to this update. We can now shoot the space junk and it will disappear from the screen.

 

The 7800 handles collision detection differently than the 2600, it doesn't have hardware collision registers, so any collision detection needs to happen through software. In order to detect a collision between two sprites, we need to draw a virtual box around it using the “boxcollision” command. This means that we won’t have precise pixel-perfect collisions as there may be a small amount of blank space in the sprite that will still trigger a collision. I’ve illustrated this in the image below. One of the 8x16 junk sprites is shaped like a cylinder, and it has empty pixels at the four corners. If we fire a bullet at the top left corner, setting an 8x16 box around it for collision detection will in fact trigger a collsion when it hits the empty top left portion. To get around this we can reduce the size of the box, if desired.

 

post-2143-0-57788700-1506197453.png

 

To use boxcollision to detect a collision between the bullet and the junk sprite, we’d use the command below. The command illustrates drawing an 8x16 virtual box around the junk sprite, and a 2x2 box around the bullet sprite, which actually is solid 2x2 block. The width and height information must be numerical values (not variables).

 

if boxcollision(debris1_xpos,debris1_ypos,8,16, bullet_fire_xpos,bullet_fire_ypos,2,2) then … <do something>

I was just reading the manual for 7800 basic, and saw that If your game needs collision detection with sprites that are partially on-screen partially off-screen, you'll need to set collision-wrapping on, and that setting collision-wrapping on makes the collision detection take a bit of extra time. I may need to do that for this game.

 

set collisionwrap on

 

Checking the overlap of too many sprites with boxcollision() may cause the game to use too many cycles, which will result in slowdown and graphic artifacts. It’s also noted in the manual that checking if the main character collided with 10 enemies should not be an issue, but checking if those enemies have collided with each other will likely be a problem. Because we have so many sprites on screen, I decided to approach collisions a different way, but I’m going to demonstrate how to do it either way.

 

Rather than using boxcollision for detecting collisions between the junk sprites and the bullet, I’m going to collision detect for the two sprites being in the same area. We will probably still use boxcollision in other parts of the game, but this is a perfect use-case for doing it that way, and it’s a faster alternative to boot.

 

This section of code will detect collisions between the bullet and the junk sprites using area detection

 

if bullet_fire_xpos > debris1_xpos && bullet_fire_xpos <(debris1_xpos+16) && bullet_fire_ypos > debris1_ypos && bullet_fire_ypos < (debris1_ypos+16) then debris1_flag=1

if bullet_fire_xpos > debris2_xpos && bullet_fire_xpos <(debris2_xpos+16) && bullet_fire_ypos > debris2_ypos && bullet_fire_ypos < (debris2_ypos+16) then debris2_flag=1

if bullet_fire_xpos > debris3_xpos && bullet_fire_xpos <(debris3_xpos+16) && bullet_fire_ypos > debris3_ypos && bullet_fire_ypos < (debris3_ypos+16) then debris3_flag=1

if bullet_fire_xpos > debris4_xpos && bullet_fire_xpos <(debris4_xpos+16) && bullet_fire_ypos > debris4_ypos && bullet_fire_ypos < (debris4_ypos+16) then debris4_flag=1

if bullet_fire_xpos > debris5_xpos && bullet_fire_xpos <(debris5_xpos+16) && bullet_fire_ypos > debris5_ypos && bullet_fire_ypos < (debris5_ypos+16) then debris5_flag=1

if bullet_fire_xpos > debris6_xpos && bullet_fire_xpos <(debris6_xpos+16) && bullet_fire_ypos > debris6_ypos && bullet_fire_ypos < (debris6_ypos+16) then debris6_flag=1

if bullet_fire_xpos > debris7_xpos && bullet_fire_xpos <(debris7_xpos+16) && bullet_fire_ypos > debris7_ypos && bullet_fire_ypos < (debris7_ypos+16) then debris7_flag=1

if bullet_fire_xpos > debris8_xpos && bullet_fire_xpos <(debris8_xpos+16) && bullet_fire_ypos > debris8_ypos && bullet_fire_ypos < (debris8_ypos+16) then debris8_flag=1

if bullet_fire_xpos > debris9_xpos && bullet_fire_xpos <(debris9_xpos+16) && bullet_fire_ypos > debris9_ypos && bullet_fire_ypos < (debris9_ypos+16) then debris9_flag=1

if bullet_fire_xpos > rocks1_xpos && bullet_fire_xpos <(rocks1_xpos+16) && bullet_fire_ypos > rocks1_ypos && bullet_fire_ypos < (rocks1_ypos+16) then rocks1_flag=1

if bullet_fire_xpos > rocks2_xpos && bullet_fire_xpos <(rocks2_xpos+16) && bullet_fire_ypos > rocks2_ypos && bullet_fire_ypos < (rocks2_ypos+16) then rocks2_flag=1

if bullet_fire_xpos > rocks3_xpos && bullet_fire_xpos <(rocks3_xpos+16) && bullet_fire_ypos > rocks3_ypos && bullet_fire_ypos < (rocks3_ypos+16) then rocks3_flag=1

if bullet_fire_xpos > rocks4_xpos && bullet_fire_xpos <(rocks4_xpos+16) && bullet_fire_ypos > rocks4_ypos && bullet_fire_ypos < (rocks4_ypos+16) then rocks4_flag=1

if bullet_fire_xpos > invader1_xpos && bullet_fire_xpos <(invader1_xpos+16) && bullet_fire_ypos > invader1_ypos && bullet_fire_ypos < (invader1_ypos+16) then invader1_flag=1

if bullet_fire_xpos > chip1_xpos && bullet_fire_xpos <(chip1_xpos+16) && bullet_fire_ypos > chip1_ypos && bullet_fire_ypos < (chip1_ypos+16) then chip1_flag=1

if bullet_fire_xpos > rocket1_xpos && bullet_fire_xpos <(rocket1_xpos+16) && bullet_fire_ypos > rocket1_ypos && bullet_fire_ypos < (rocket1_ypos+16) then rocket1_flag=1

if bullet_fire_xpos > satellite1_xpos && bullet_fire_xpos <(satellite1_xpos+16) && bullet_fire_ypos > satellite1_ypos && bullet_fire_ypos < (satellite1_ypos+16) then satellite1_flag=1

if bullet_fire_xpos > satellite2_xpos && bullet_fire_xpos <(satellite2_xpos+16) && bullet_fire_ypos > satellite1_ypos && bullet_fire_ypos < (satellite2_ypos+16) then satellite2_flag=1

 

This section of code does the exact same thing as the code block above using boxcollision instead of area detection. You could cut and paste this section into the game code and it would function exactly the same way. I'm including this as an example, I'm not actually using this block of code.

 

; This section of code detects collisions between the bullet and all of the space junk sprites

; When shot, a flag is set for each sprite and it is placed off screen

if boxcollision(debris1_xpos,debris1_ypos, 8,16, bullet_fire_xpos,bullet_fire_ypos, 2,2) then debris1_xpos=200 :debris1_ypos=200 :debris1_flag=1

if boxcollision(debris2_xpos,debris2_ypos, 8,16, bullet_fire_xpos,bullet_fire_ypos, 2,2) then debris2_xpos=200 :debris2_ypos=200 :debris2_flag=1

if boxcollision(debris3_xpos,debris3_ypos, 8,16, bullet_fire_xpos,bullet_fire_ypos, 2,2) then debris3_xpos=200 :debris3_ypos=200 :debris3_flag=1

if boxcollision(debris4_xpos,debris4_ypos, 8,16, bullet_fire_xpos,bullet_fire_ypos, 2,2) then debris4_xpos=200 :debris4_ypos=200 :debris4_flag=1

if boxcollision(debris5_xpos,debris5_ypos, 8,16, bullet_fire_xpos,bullet_fire_ypos, 2,2) then debris5_xpos=200 :debris5_ypos=200 :debris5_flag=1

if boxcollision(debris6_xpos,debris6_ypos, 8,16, bullet_fire_xpos,bullet_fire_ypos, 2,2) then debris6_xpos=200 :debris6_ypos=200 :debris6_flag=1

if boxcollision(debris7_xpos,debris7_ypos, 8,16, bullet_fire_xpos,bullet_fire_ypos, 2,2) then debris7_xpos=200 :debris7_ypos=200 :debris7_flag=1

if boxcollision(debris8_xpos,debris8_ypos, 8,16, bullet_fire_xpos,bullet_fire_ypos, 2,2) then debris8_xpos=200 :debris8_ypos=200 :debris8_flag=1

if boxcollision(debris9_xpos,debris9_ypos, 8,16, bullet_fire_xpos,bullet_fire_ypos, 2,2) then debris9_xpos=200 :debris9_ypos=200 :debris9_flag=1

if boxcollision(rocks1_xpos,rocks1_ypos, 8,16, bullet_fire_xpos,bullet_fire_ypos, 2,2) then rocks1_xpos=200 :rocks1_ypos=200 :rocks1_flag=1

if boxcollision(rocks2_xpos,rocks2_ypos, 8,16, bullet_fire_xpos,bullet_fire_ypos, 2,2) then rocks2_xpos=200 :rocks2_ypos=200 :rocks2_flag=1

if boxcollision(rocks3_xpos,rocks3_ypos, 8,16, bullet_fire_xpos,bullet_fire_ypos, 2,2) then rocks3_xpos=200 :rocks3_ypos=200 :rocks3_flag=1

if boxcollision(rocks4_xpos,rocks4_ypos, 8,16, bullet_fire_xpos,bullet_fire_ypos, 2,2) then rocks4_xpos=200 :rocks4_ypos=200 :rocks4_flag=1

if boxcollision(invader1_xpos,invader1_ypos,8,16, bullet_fire_xpos,bullet_fire_ypos, 2,2) then invader1_xpos=200:invader1_ypos=200:invader1_flag=1

if boxcollision(chip1_xpos,chip1_ypos, 8,16, bullet_fire_xpos,bullet_fire_ypos, 2,2) then chip1_xpos=200 :chip1_ypos=200 :chip1_flag=1

if boxcollision(rocket1_xpos,rocket1_ypos, 8,16, bullet_fire_xpos,bullet_fire_ypos, 2,2) then rocket1_xpos=200 :rocket1_ypos=200 :rocket1_flag=1

if boxcollision(satellite1_xpos,satellite1_ypos,8,16,bullet_fire_xpos,bullet_fire_ypos,2,2) then satellite1_xpos=200:satellite1_ypos=200:satellite1_flag=1

if boxcollision(satellite2_xpos,satellite2_ypos,8,16,bullet_fire_xpos,bullet_fire_ypos,2,2) then satellite2_xpos=200:satellite2_ypos=200:satellite2_flag=1

 

When the collision detection has a positive result, we set the sprite to be off the visible area of the screen, and set the flag variable to mark it as being hit. We already set up the flag variable for all of the sprites at the very beginning, as I knew I was going to eventually need them.

 

The other change I made to the code was to stop movement of the sprites once they’d been shot. Even though they were placed off screen, the code would still randomly move them and they could possibly inch their way back to the visible area.

 

I labeled each section of the movement code, and then added an “if…then” statement that skips the section if the flag variable has been set. The new code is highlighted in red. I didn’t paste in all of the code, but each section was updated in exactly the same way.

 

debris1_move

; "Debris1" Sprite Movement

; This checks the "RDirection" random variable (0-3), which randomizes which direction the sprite travels.

; The "RMovement" Variables add a random number to the X/Y movement

if debris1_flag=1 then goto debris2_move

if RDirection=0 then debris1_xpos=debris1_xpos+RMovement1:debris1_ypos=debris1_ypos+RMovement3

if RDirection=1 then debris1_xpos=debris1_xpos-RMovement5:debris1_ypos=debris1_ypos-RMovement3

if RDirection=2 then debris1_xpos=debris1_xpos+RMovement1:debris1_ypos=debris1_ypos-RMovement3

if RDirection=3 then debris1_xpos=debris1_xpos-RMovement3:debris1_ypos=debris1_ypos+RMovement2

; This checks the sprite location, and bounces them back 5 pixels if they are on the edge of the screen.

if debris1_xpos>141 then debris1_xpos=debris1_xpos-5 ; Don't move off the right side of the screen

if debris1_xpos<5 then debris1_xpos=debris1_xpos+5 ; Don't move off the left side of the screen

if debris1_ypos>171 then debris1_ypos=debris1_ypos-5 ; Don't move off the bottom of the screen

if debris1_ypos<5 then debris1_ypos=debris1_ypos+5 ; Don't move off the top of the screen

 

debris2_move

; "Debris2" Sprite Movement

; This checks the "RDirection" random variable (0-3), which randomizes which direction the sprite travels.

; The "RMovement" Variables add a random number to the X/Y movement

 

Now after you’ve shot all of the junk, there really isn’t anything else to do. J I’m done working on it today. Next I’m going to work on adding the score and life counter.

 

Here’s the zip file with the updated code, graphics, and compiled files: SpaceJunk_092317_1h.zip

  • Like 1
Link to comment
Share on other sites

In this section I added two score variables and a status bar to the top of the screen. I did try out creating a status bar below the planet on the bottom of the screen along with increasing the visible screen with “set screenheight 208” (we’re using the default of 192), however it caused problems. I assume we were overrunning the CPU, as sprite movement dramatically slowed down when I set it to 208. We’re definitely sticking with 192 for this game.

 

Tiled playfield changes

 

I made some changes to the playfield map images specifically for the score and lives display on the screen. Because we arere already at the maximum number of sprites we can use on the screen, I decided to add the "score" and "lives" text directly to the background tiled image. It works well, however because we only have one background image it will also display on the titlescreen, and must share one of the colors of the background palette. When you first load up the game the score and lives will appear as zeros, after you complete a game and bounce back to the title screen the plan is for it to then show the score you've most recently recorded. In a future update we can add a third score variable to display your high score on the title screen.

 

Because I was editing the tileset images anyway, I expanded both the planet and starts tilesets to be wider. That gave me the space to add a few more detail changes to the planet's surface and add a horizontal bar underneath the score and lives remaining text, further defining it on the screen as a status bar. I think it turned out looking pretty cool.

 

Here's a screenshot of the new tileset images:

 

post-2143-0-41972900-1506275174_thumb.png

 

Variable Assignments

 

I defined score1 as "lives" in the variable assignments area. I'm going to use the built in "score0" for the actual score in the game as it's already descriptive enough to know what we're using it for. I don't think we need to assign a different name.

 

This line of code was added to the variable assignments section:

 

dim lives=score1

 

Title Screen Changes

 

In the "plot" block of code, which plots the background tiles and images for the title screen, I added the score and lives counter with the plotvalue command. I also adjusted the vertical location of the title screen and "press fire to begin" sprites to lower them a little bit. That was needed to make room for the new status bar at the top.

 

plotvalue scoredigits_8_wide 3 score0 6 25 0 ; Plot the score on the title screen

plotvalue scoredigits_8_wide 3 score1 1 153 0 ; Plot the lives remaining on the title screen

 

The Y values were modified to lower the location of these sprites:

 

plotbanner spacejunk_title 0 18 20 ; Plot the "Space Junk" title screen image

plotsprite sprite_firetobegin 6 44 124 ; Plot the "Press Fire to Begin" text image

 

In the variable initialization block of code, I added an entry for the number of lives. For now the lives counter doesn't do anything, it will always display "3" until we add addiitonal code later to do that.

 

framecounter = 0 : level = 0 : lives = $03

 

Main Loop Changes

 

I also added the score variables to the display in the main loop. I used a little bit of trial and error testing to get the X/Y location just right. For the score digits, I used the "scoredigits_8_wide" graphic that's included in the 7800basic samples directory, but I modified the image a little bit to change up the default look of the digits. In the plotvalue command, the "5" is the pallete number, the "6" in score0 and the "1" in score1 are the number of digits of the 24bit score variables to display, and the last two digits in each are the X and Y values respectively.

 

; Plot score values

; Image File Palette Score Variable # Digits X Pos Y Pos

; ------------------- ------- --------------- -------- ------ -------

plotvalue scoredigits_8_wide 5 score0 6 26 0

plotvalue scoredigits_8_wide 5 score1 1 153 0

 

In order to have the score actually do something, I updated the block of code that detects collisions and added "score0=score0+1" to the very end of the line. Now when you destroy a piece of space junk you will score one point. Right now your maximum score is 18, as that's the number of space junk sprites on the screen.

 

; This section of code detects collisions between the bullet and all of the space junk sprites

; When shot, a flag is set for each sprite and it is placed off screen

if bullet_fire_xpos > debris1_xpos && bullet_fire_xpos <(debris1_xpos+16) && bullet_fire_ypos > debris1_ypos && bullet_fire_ypos < (debris1_ypos+16) then debris1_xpos=200:debris1_ypos=200:debris1_flag=1:score0=score0+1

if bullet_fire_xpos > debris2_xpos && bullet_fire_xpos <(debris2_xpos+16) && bullet_fire_ypos > debris2_ypos && bullet_fire_ypos < (debris2_ypos+16) then debris2_xpos=200:debris2_ypos=200:debris2_flag=1:score0=score0+1

if bullet_fire_xpos > debris2_xpos && bullet_fire_xpos <(debris2_xpos+16) && bullet_fire_ypos > debris2_ypos && bullet_fire_ypos < (debris2_ypos+16) then debris2_xpos=200:debris2_ypos=200:debris2_flag=1:score0=score0+1

if bullet_fire_xpos > debris3_xpos && bullet_fire_xpos <(debris3_xpos+16) && bullet_fire_ypos > debris3_ypos && bullet_fire_ypos < (debris3_ypos+16) then debris3_xpos=200:debris3_ypos=200:debris3_flag=1:score0=score0+1

if bullet_fire_xpos > debris4_xpos && bullet_fire_xpos <(debris4_xpos+16) && bullet_fire_ypos > debris4_ypos && bullet_fire_ypos < (debris4_ypos+16) then debris4_xpos=200:debris4_ypos=200:debris4_flag=1:score0=score0+1

if bullet_fire_xpos > debris5_xpos && bullet_fire_xpos <(debris5_xpos+16) && bullet_fire_ypos > debris5_ypos && bullet_fire_ypos < (debris5_ypos+16) then debris5_xpos=200:debris5_ypos=200:debris5_flag=1:score0=score0+1

if bullet_fire_xpos > debris6_xpos && bullet_fire_xpos <(debris6_xpos+16) && bullet_fire_ypos > debris6_ypos && bullet_fire_ypos < (debris6_ypos+16) then debris6_xpos=200:debris6_ypos=200:debris6_flag=1:score0=score0+1

if bullet_fire_xpos > debris7_xpos && bullet_fire_xpos <(debris7_xpos+16) && bullet_fire_ypos > debris7_ypos && bullet_fire_ypos < (debris7_ypos+16) then debris7_xpos=200:debris7_ypos=200:debris7_flag=1:score0=score0+1

if bullet_fire_xpos > debris8_xpos && bullet_fire_xpos <(debris8_xpos+16) && bullet_fire_ypos > debris8_ypos && bullet_fire_ypos < (debris8_ypos+16) then debris8_xpos=200:debris8_ypos=200:debris8_flag=1:score0=score0+1

if bullet_fire_xpos > debris9_xpos && bullet_fire_xpos <(debris9_xpos+16) && bullet_fire_ypos > debris9_ypos && bullet_fire_ypos < (debris9_ypos+16) then debris9_xpos=200:debris9_ypos=200:debris9_flag=1:score0=score0+1

if bullet_fire_xpos > rocks1_xpos && bullet_fire_xpos <(rocks1_xpos+16) && bullet_fire_ypos > rocks1_ypos && bullet_fire_ypos < (rocks1_ypos+16) then rocks1_xpos=200:rocks1_ypos=200:rocks1_flag=1:score0=score0+1

if bullet_fire_xpos > rocks2_xpos && bullet_fire_xpos <(rocks2_xpos+16) && bullet_fire_ypos > rocks2_ypos && bullet_fire_ypos < (rocks2_ypos+16) then rocks2_xpos=200:rocks2_ypos=200:rocks2_flag=1:score0=score0+1

if bullet_fire_xpos > rocks3_xpos && bullet_fire_xpos <(rocks3_xpos+16) && bullet_fire_ypos > rocks3_ypos && bullet_fire_ypos < (rocks3_ypos+16) then rocks3_xpos=200:rocks3_ypos=200:rocks3_flag=1:score0=score0+1

if bullet_fire_xpos > rocks4_xpos && bullet_fire_xpos <(rocks4_xpos+16) && bullet_fire_ypos > rocks4_ypos && bullet_fire_ypos < (rocks4_ypos+16) then rocks4_xpos=200:rocks4_ypos=200:rocks4_flag=1:score0=score0+1

if bullet_fire_xpos > invader1_xpos && bullet_fire_xpos <(invader1_xpos+16) && bullet_fire_ypos > invader1_ypos && bullet_fire_ypos < (invader1_ypos+16) then invader1_xpos=200:invader1_ypos=200:invader1_flag=1:score0=score0+1

if bullet_fire_xpos > chip1_xpos && bullet_fire_xpos <(chip1_xpos+16) && bullet_fire_ypos > chip1_ypos && bullet_fire_ypos < (chip1_ypos+16) then chip1_xpos=200:chip1_ypos=200:chip1_flag=1:score0=score0+1

if bullet_fire_xpos > rocket1_xpos && bullet_fire_xpos <(rocket1_xpos+16) && bullet_fire_ypos > rocket1_ypos && bullet_fire_ypos < (rocket1_ypos+16) then rocket1_xpos=200:rocket1_ypos=200:rocket1_flag=1:score0=score0+1

if bullet_fire_xpos > satellite1_xpos && bullet_fire_xpos <(satellite1_xpos+16) && bullet_fire_ypos > satellite1_ypos && bullet_fire_ypos < (satellite1_ypos+16) then satellite1_xpos=200:satellite1_ypos=200:satellite1_flag=1:score0=score0+1

if bullet_fire_xpos > satellite2_xpos && bullet_fire_xpos <(satellite2_xpos+16) && bullet_fire_ypos > satellite2_ypos && bullet_fire_ypos < (satellite2_ypos+16) then satellite2_xpos=200:satellite2_ypos=200:satellite2_flag=1:score0=score0+1

 

I made a few other changes to accomodate the addition of the status bar at the top of the screen. I didn't want the space man, the bullet, or the space junk to travel over the top of the status bar, so I needed to update the code that was already in place to prevent moving off the top of the screen.

 

For the Space Man and the bullet, I updated the highlited areas below. I changed the "1" on the bottom line of each line to "11" instead. I used a little bit of trial and error testing to figure out exactly which Y value to use.

 

; Limit the Space Man's movement to keep him on the visible screen

if spaceman_xpos>146 then spaceman_xpos=spaceman_xpos-1 ; Don't move off the right side of the screen

if spaceman_xpos<1 then spaceman_xpos=spaceman_xpos+1 ; Don't move off the left side of the screen

if spaceman_ypos>176 then spaceman_ypos=spaceman_ypos-1 ; Don't move off the bottom of the screen

if spaceman_ypos<11 then spaceman_ypos=spaceman_ypos+1 ; Don't move off the top of the screen

 

; Don't let bullet fire off of the visible screen

if bullet_fire_xpos>158 then bullet_fire_xpos=spaceman_xpos-1:bullet_fire_ypos=spaceman_ypos+8 ; Stop bullet at right edge of screen

if bullet_fire_xpos<2 then bullet_fire_xpos=spaceman_xpos-1:bullet_fire_ypos=spaceman_ypos+8 ; Stop bullet at left edge of screen

if bullet_fire_ypos>188 then bullet_fire_xpos=spaceman_xpos-1:bullet_fire_ypos=spaceman_ypos+8 ; Stop bullet at bottom edge of screen

if bullet_fire_ypos<11 then bullet_fire_xpos=spaceman_xpos-1:bullet_fire_ypos=spaceman_ypos+8 ; Stop bullet at top edge of screen

 

For the space junk sprites, I had to update the same line of code for each one of them. Below is the block of code for the rocket1 sprite, and I made a similar change. The bottom line was changed to a value of "11". The space junk will now never scroll off the top of the screen or into the status bar area.

 

; This checks the sprite location, and bounces them back 5 pixels if they are on the edge of the screen.

if rocket1_xpos>141 then rocket1_xpos=rocket1_xpos-5 ; Don't move off the right side of the screen

if rocket1_xpos<5 then rocket1_xpos=rocket1_xpos+5 ; Don't move off the left side of the screen

if rocket1_ypos>171 then rocket1_ypos=rocket1_ypos-5 ; Don't move off the bottom of the screen

if rocket1_ypos<11 then rocket1_ypos=rocket1_ypos+5 ; Don't move off the top of the screen

 

Bug Fixes

 

I noticed and fixed a couple of bugs in the previous version of the code I posted. Because there is so much duplication of code between the different sprites I did a lot of copying and pasting. I made a few mistakes.

 

I changed the highlighted area in this line. I had typed in "satellite1" instead of "satellite2".

 

if bullet_fire_xpos > satellite2_xpos && bullet_fire_xpos <(satellite2_xpos+16) && bullet_fire_ypos > satellite1_ypos && bullet_fire_ypos < (satellite2_ypos+16) then satellite2_xpos=200:satellite2_ypos=200:satellite2_flag=1:score0=score0+1

 

In the "rocket1" sprite movement block, I didn't update the line of code that checks the flag correctly. It was checking the invader1 flag instead of the rocket1 flag. I corrected it.

 

rocket1_move

; "Rocket1" Sprite Movement

; This checks the "RDirection" random variable (0-3), which randomizes which direction the sprite travels.

; The "RMovement" Variables add a random number to the X/Y movement

if invader1_flag=1 then goto skipjunkmovement1

 

After all the changes were made and I was playing the game, I noticed that enemies were disappearing just by touching them. It was because the bullet is positioned right at the tip of your gun, so you could just ram enemies and it would register a collision based on the area detection. I moved the home position of the bullet back by one pixel, but ultimately I don't think it's going to be a big issue when all the pieces of this game are complete. There's no collision detection between the space man and the junk yet, when that's in place that scenario is much less likely to be a concern.

 

In the next update I'll work on some additional collision detection.

 

Here's what the changes look like:

post-2143-0-91489700-1506275173.pngpost-2143-0-34183300-1506275173.png

 

The zip file includes the updated code, updated graphics, and all compiled files: SpaceJunk_092417_1b.zip

  • Like 2
Link to comment
Share on other sites

At this point we now have an actual playable game! I've added collision detection between the space junk & alien and the space man, added 3 lives (which you'll lose if you touch a piece of space junk or the alien), and when you clear the screen it will reset everything on screen at a slightly faster speed. There is still a whole lot of work to be done to make this a polished game, but the core gameplay is complete.

 

Graphics Updates

 

I spent a little bit of time tweaking some of the graphics and the colors. The status bar now has a solid strip of color behind it (done by editing the tileset image again), and I went with more of a gray and blue theme for the titlescreen and playfield map colors overall. It's a bit more of a "moon" theme now.

 

The code below was added to the Variable assignment section. After all of the junk is cleared, it now reappears, but moves a little more quickly.

 

dim JunkSpeed =var88 :rem To vary the speed of the junk sprites

dim AlienSpeed =var89 :rem To vary the speed of the Alien sprite

 

I made a minor correction on the title screen in the "plot" area of the code. I changed the X position of the score from 25 to 26 to make it the same on both the titlescreen and the main game.

 

plotvalue scoredigits_8_wide 2 score0 6 26 0 ; Plot the score on the title screen

plotvalue scoredigits_8_wide 2 score1 1 153 0 ; Plot the lives remaining on the title screen

 

Variable initialization changes

 

The alien sprite now starts off-screen. That gives you a little pause at the beginning of a level before it appears. I also added the defaults for the speed variables.

 

alien_xpos = 200 : alien_ypos = 200 : alien_flag = 0

framecounter = 0 : level = 0 ;(removed the lives assignment to set it elsewhere)

RDirection = 1 : JunkSpeed = 8 : AlienSpeed = 6 ;(added JunkSpeed and AlienSpeed)

 

Titlescreen loop changes

 

I changeed the titlescreen loop so that your lives are set to three and the score is reset when the game is started.

 

if joy0fire then clearscreen:lives=$03:score0=0:goto main

 

Main Loop Changes

 

The first thing I added was a line that checks to see if all of the space junk flags are set to 1, meaning they've all been destroyed. If that's true, we gosub to a new subroutine that slightly increases the speed of the junk and the alien, and brings back all of the junk in the default locations.

 

; If all flags are set, we increase speed and put all of the junk back on the screen

if debris1_flag=1 && debris2_flag=1 && debris3_flag=1 && debris4_flag=1 && debris5_flag=1 && debris6_flag=1 && debris7_flag=1 && debris8_flag=1 && debris9_flag=1 && rocks1_flag=1 && rocks2_flag=1 && rocks3_flag=1 && rocks4_flag=1 && satellite1_flag=1 && satellite2_flag=1 && invader1_flag=1 && chip1_flag=1 && rocket1_flag=1 then gosub nextlevel

 

I also added a new block of code that detects collisions between the space man and all of the junk sprites. For now, the screen will pause when a collision is detected so you can see what triggered it. Pressing the fire button will then continue the game where you left off. In the future we'll trigger an explosion animation.

 

While the collision detection does work, it's not pixel perfect (as I explained in an earlier section) and will probably need some tweaking. Because not all of our sprites are perfect squares, there will be collisions triggered when the corners of sprites collide even if they appear to be blank. To accomodate the issue I've slightly reduced the box around the sprites, so there will be a small overlap on a collision before it is registered.

 

; Detect collision between spaceman and alien

if boxcollision(spaceman_xpos,spaceman_ypos,13,13, alien_xpos,alien_ypos, 15,15) then alien_xpos=200 :alien_ypos=200:lives=lives-1:goto losealife

 

; This section of code detects collisions between the spaceman and all of the space junk sprites

if boxcollision(spaceman_xpos,spaceman_ypos,13,13, alien_xpos,alien_ypos, 15,15) then alien_xpos=200 :alien_ypos=200:lives=lives-1:goto losealife

if boxcollision(debris1_xpos,debris1_ypos, 6,14, spaceman_xpos,spaceman_ypos, 13,13) then debris1_xpos=200 :debris1_ypos=200 :debris1_flag=1:lives=lives-1:goto losealife

if boxcollision(debris2_xpos,debris2_ypos, 6,14, spaceman_xpos,spaceman_ypos, 13,13) then debris2_xpos=200 :debris2_ypos=200 :debris2_flag=1:lives=lives-1:goto losealife

if boxcollision(debris3_xpos,debris3_ypos, 6,14, spaceman_xpos,spaceman_ypos, 13,13) then debris3_xpos=200 :debris3_ypos=200 :debris3_flag=1:lives=lives-1:goto losealife

if boxcollision(debris4_xpos,debris4_ypos, 6,14, spaceman_xpos,spaceman_ypos, 13,13) then debris4_xpos=200 :debris4_ypos=200 :debris4_flag=1:lives=lives-1:goto losealife

if boxcollision(debris5_xpos,debris5_ypos, 6,14, spaceman_xpos,spaceman_ypos, 13,13) then debris5_xpos=200 :debris5_ypos=200 :debris5_flag=1:lives=lives-1:goto losealife

if boxcollision(debris6_xpos,debris6_ypos, 6,14, spaceman_xpos,spaceman_ypos, 13,13) then debris6_xpos=200 :debris6_ypos=200 :debris6_flag=1:lives=lives-1:goto losealife

if boxcollision(debris7_xpos,debris7_ypos, 6,14, spaceman_xpos,spaceman_ypos, 13,13) then debris7_xpos=200 :debris7_ypos=200 :debris7_flag=1:lives=lives-1:goto losealife

if boxcollision(debris8_xpos,debris8_ypos, 6,14, spaceman_xpos,spaceman_ypos, 13,13) then debris8_xpos=200 :debris8_ypos=200 :debris8_flag=1:lives=lives-1:goto losealife

if boxcollision(debris9_xpos,debris9_ypos, 6,14, spaceman_xpos,spaceman_ypos, 13,13) then debris9_xpos=200 :debris9_ypos=200 :debris9_flag=1:lives=lives-1:goto losealife

if boxcollision(rocks1_xpos,rocks1_ypos, 6,14, spaceman_xpos,spaceman_ypos, 13,13) then rocks1_xpos=200 :rocks1_ypos=200 :rocks1_flag=1:lives=lives-1:goto losealife

if boxcollision(rocks2_xpos,rocks2_ypos, 6,14, spaceman_xpos,spaceman_ypos, 13,13) then rocks2_xpos=200 :rocks2_ypos=200 :rocks2_flag=1:lives=lives-1:goto losealife

if boxcollision(rocks3_xpos,rocks3_ypos, 6,14, spaceman_xpos,spaceman_ypos, 13,13) then rocks3_xpos=200 :rocks3_ypos=200 :rocks3_flag=1:lives=lives-1:goto losealife

if boxcollision(rocks4_xpos,rocks4_ypos, 6,14, spaceman_xpos,spaceman_ypos, 13,13) then rocks4_xpos=200 :rocks4_ypos=200 :rocks4_flag=1:lives=lives-1:goto losealife

if boxcollision(invader1_xpos,invader1_ypos,6,14, spaceman_xpos,spaceman_ypos, 13,13) then invader1_xpos=200:invader1_ypos=200:invader1_flag=1:lives=lives-1:goto losealife

if boxcollision(chip1_xpos,chip1_ypos, 6,14, spaceman_xpos,spaceman_ypos, 13,13) then chip1_xpos=200 :chip1_ypos=200 :chip1_flag=1:lives=lives-1:goto losealife

if boxcollision(rocket1_xpos,rocket1_ypos, 6,14, spaceman_xpos,spaceman_ypos, 13,13) then rocket1_xpos=200 :rocket1_ypos=200 :rocket1_flag=1:lives=lives-1:goto losealife

if boxcollision(satellite1_xpos,satellite1_ypos,6,14,spaceman_xpos,spaceman_ypos,13,13) then satellite1_xpos=200:satellite1_ypos=200:satellite1_flag=1:lives=lives-1:goto losealife

if boxcollision(satellite2_xpos,satellite2_ypos,6,14,spaceman_xpos,spaceman_ypos,13,13) then satellite2_xpos=200:satellite2_ypos=200:satellite2_flag=1:lives=lives-1:goto losealife

 

I changed the block of code to use variable checks for the frame counters that slow down the movement of the space junk and the alien. Now we can adjust the speed, which I've implemented as an increase in difficulty when you clear the screen. The JunkSpeed and AlienSpeed variables are reduced by 1, which reduces the length of the delay, thereby increasing the speed of the sprites.

 

; Frame Counters for slowing down sprite movement

junk_slowdown1=junk_slowdown1+1 ; This is a "Slowdown" frame counter

if junk_slowdown1>JunkSpeed then junk_slowdown1=0 ; We'll apply it to space junk movement to slow it down (1C)

junk_slowdown2=junk_slowdown2+1 ; This is a "Slowdown" frame counter

if junk_slowdown2>AlienSpeed then junk_slowdown2=0 ; We'll apply it to Alien movement to slow it down (1C)

 

New Subroutines

 

Two new subroutines were added at the very end of the code, labeled "losealife" and "nextlevel". The "losealife" subroutine is called whenever a collision is detected between the spaceman and the junk or alien sprites.

 

losealife

 

; Here we reset some variable values before returning to the game

; We reset the location of the junk to the default if they're still active

if debris1_flag <>1 then debris1_xpos = 52 : debris1_ypos = 5

if debris2_flag <>1 then debris2_xpos = 4 : debris2_ypos = 20

if debris3_flag <>1 then debris3_xpos = 72 : debris3_ypos = 12

if debris4_flag <>1 then debris4_xpos = 4 : debris4_ypos = 60

if debris5_flag <>1 then debris5_xpos = 92 : debris5_ypos = 18

if debris6_flag <>1 then debris6_xpos = 122 : debris6_ypos = 23

if debris7_flag <>1 then debris7_xpos = 4 : debris7_ypos = 120

if debris8_flag <>1 then debris8_xpos = 4 : debris8_ypos = 140

if debris9_flag <>1 then debris9_xpos = 38 : debris9_ypos = 12

if rocks1_flag <>1 then rocks1_xpos = 18 : rocks1_ypos = 20

if rocks2_flag <>1 then rocks2_xpos = 140 : rocks2_ypos = 48

if rocks3_flag <>1 then rocks3_xpos = 18 : rocks3_ypos = 60

if rocks4_flag <>1 then rocks4_xpos = 140 : rocks4_ypos = 88

if satellite1_flag <>1 then satellite1_xpos = 18 : satellite1_ypos = 100

if satellite2_flag <>1 then satellite2_xpos = 18 : satellite2_ypos = 120

if invader1_flag <>1 then invader1_xpos = 140 : invader1_ypos = 126

if chip1_flag <>1 then chip1_xpos = 152 : chip1_ypos = 90

if rocket1_flag <>1 then rocket1_xpos = 152 : rocket1_ypos = 130

; Reset location of alien to offscreen

alien_xpos = 200 : alien_ypos = 200 : alien_flag = 0

; Reset location of spaceman to the default

spaceman_xpos = 80 : spaceman_ypos = 80 : spaceman_flag = 0

; Reset other variables

fire_debounce = 0 : gameover_flag = 0 : junk_slowdown4 = 0

alien_aniframe = 0 : spaceman_aniframe = 0 : spaceman_dir = 0

bullet_fire_xpos= 0 : bullet_fire_ypos = 0 : joyposleft = 1

joyposright = 0 : joyposup = 0 : joyposdown = 0

savescreen ; We'll save the screen so it shows the collision on screen

 

losealifeloop

; This is the losealife Loop. We will loop continuously until the fire button is pressed.

restorescreen ; Restore the plotted images saved with the previous savescreen

; Plot score values

; Image File Palette Score Variable # Digits X Pos Y Pos

; ------------------- ------- --------------- -------- ------ -------

plotvalue scoredigits_8_wide 2 score0 6 26 0

plotvalue scoredigits_8_wide 2 score1 1 153 0

drawscreen ; Display the plotted images on the screen

 

; Here we "debounce" the fire button, and activate it when you release the button, not when you press it.

; If we don't do this, the button press registers on the next screen, and we don't want that.

if joy0fire then fire_debounce=2

if !joy0fire && fire_debounce=2 then fire_debounce=1

 

if fire_debounce=1 && lives>0 then clearscreen:goto main ; Check for a joystick button press. If yes, clear the screen and goto init label

if fire_debounce=1 && lives<1 then clearscreen:goto plot

goto losealifeloop ; Repeat this loop until the jobstick button is pressed

 

The "nextlevel" subroutine is called when you've cleared the entire screen of junk. It increases the speed of the junk and the alien, resets their flags to 0, and resets all of them to their default locations on the screen.

 

nextlevel

 

; Increase the movement speed of the junk and the alien

JunkSpeed=JunkSpeed-1

AlienSpeed=AlienSpeed-1

 

; Reset the junk sprites, spaceman, and alien to their default locations

debris1_xpos = 52 : debris1_ypos = 5 : debris1_flag = 0

debris2_xpos = 4 : debris2_ypos = 20 : debris2_flag = 0

debris3_xpos = 72 : debris3_ypos = 12 : debris3_flag = 0

debris4_xpos = 4 : debris4_ypos = 60 : debris4_flag = 0

debris5_xpos = 92 : debris5_ypos = 18 : debris5_flag = 0

debris6_xpos = 122 : debris6_ypos = 23 : debris6_flag = 0

debris7_xpos = 4 : debris7_ypos = 120 : debris7_flag = 0

debris8_xpos = 4 : debris8_ypos = 140 : debris8_flag = 0

debris9_xpos = 38 : debris9_ypos = 12 : debris9_flag = 0

rocks1_xpos = 18 : rocks1_ypos = 20 : rocks1_flag = 0

rocks2_xpos = 140 : rocks2_ypos = 48 : rocks2_flag = 0

rocks3_xpos = 18 : rocks3_ypos = 60 : rocks3_flag = 0

rocks4_xpos = 140 : rocks4_ypos = 88 : rocks4_flag = 0

satellite1_xpos = 18 : satellite1_ypos = 100 : satellite1_flag = 0

satellite2_xpos = 18 : satellite2_ypos = 120 : satellite2_flag = 0

invader1_xpos = 140 : invader1_ypos = 126 : invader1_flag = 0

chip1_xpos = 152 : chip1_ypos = 90 : chip1_flag = 0

rocket1_xpos = 152 : rocket1_ypos = 130 : rocket1_flag = 0

alien_xpos = 200 : alien_ypos = 200 : alien_flag = 0

spaceman_xpos = 80 : spaceman_ypos = 80 : spaceman_flag = 0

 

return

 

Joystick Debounce

 

In the “losealife” subroutine below I implemented a “debounce” routine for the joystick button. Because we are checking the fire button to jump back to the titlescreen AND we are checking the fire button to start the game once we’re on the title screen, this would have been a problem. One fire button press would have registered both times and we would have simply skipped completely over the title screen. To get around this, we set a debounce variable when the button is pushed, and then reset that variable to a different number when the button is NOT being pushed. We then check the variable for when the button is not being pushed as an indication of the button having been pressed.

 

if joy0fire then fire_debounce=2

if !joy0fire && fire_debounce=2 then fire_debounce=1

 

if fire_debounce=1 && lives>0 then clearscreen:goto main ; Check for a joystick button press. If yes, clear the screen and goto init label

if fire_debounce=1 && lives<1 then clearscreen:goto plot

 

This will certainly be the last update for today. I’ve come a long way from nothing to a playable game in a pretty short number of days, but I really have spent a long time on this. I’ve put in probably about 30 hours or so between the coding and the writing of these posts, so it’s not as quick as it may seem. The hard part is the last 10% of the game and making it polished and professional, what we’ve accomplished here so far is the easy part. J Also note that I’ve spent very little time with any gameplay testing, and it’s certainly possible that there may be a mistake or two made that I’ll catch later.

 

This zip includes the updated graphics, code, and compiled files: SpaceJunk_092417_1d.zip

 

post-2143-0-03760300-1506291038.pngpost-2143-0-43478800-1506291037.png

  • Like 2
Link to comment
Share on other sites

I have a new build ready to share. This version includes the addition of a death explosion animation for our space man and a few sound effects for firing your gun and during the explosion. I also made some performance related changes.

 

As I was testing the previous build and making some changes, I noticed our first performance related issue pop up, and I had to do a little troubleshooting. I was noticing a little bit of graphic artifacting at the bottom of the screen, and after I added the animation for the death of the space man the normal graphic on-screen for the space man was no longer displaying correctly. Knowing that I was already pushing the limit of sprites on screen, first I removed the first aid sprite from the screen. It didn't completely resolve the problem, but since I hadn't implemented it yet I decided to leave it out altogether. The graphic import and plotsprite commands for sprite_firstaid1.png as well as the variable assignments for it were removed from the code.

 

I remembered from past experience that how graphics blocks are organized is important. I know for a fact that animated sprite images must all be in the same graphics block. As a test, I used the "newblock" command to divide up the sprites into three separate graphics blocks - one for title screen graphics, one for the junk sprites, and one for the spaceman and explosion images. Once I added the newblock command in, the display issues for the spaceman disappeared. That took care of the problem. Below is how our images are now laid out in the graphics blocks.

 

INFO, GFX Block #0 starts @ $8000, has 448 bytes left (28 x 16 bytes)

scoredigits_8_wide tileset_stars1 tileset_planet1

spacejunk_title00 spacejunk_title01 spacejunk_title02

INFO, GFX Block #1 starts @ $A000, has 1856 bytes left (116 x 16 bytes)

spacejunk_title03 spacejunk_title04 spacejunk_title05

sprite_7800basic200 sprite_7800basic201 sprite_firetobegin

INFO, GFX Block #2 starts @ $C000, has 3440 bytes left (215 x 16 bytes)

sprite_satellite1 sprite_satellite2 sprite_invader1

sprite_alien sprite_chip1 sprite_rocket1 sprite_rocks1

sprite_rocks2 sprite_rocks3 sprite_rocks4 sprite_debris1

sprite_debris2 sprite_debris3 sprite_debris4 sprite_debris5

sprite_debris6 sprite_debris7 sprite_debris8 sprite_debris9

sprite_bullet

INFO, GFX Block #3 starts @ $E000, has 3456 bytes left (216 x 16 bytes)

sprite_spaceman_left sprite_spaceman_right sprite_explode1

sprite_explode2 sprite_explode3 sprite_explode4 sprite_explode5

sprite_explode6 sprite_explode7 sprite_explode8

 

Unfortunately, the display glitch was still evident in the game. I wasn't sure of the cause, so I ran the issue by RevEng. He reminded me that for performance issues, the best practice you can use to avoid performance issues is to place all of your general game logic right after the drawscreen, which is the start of the visible screen. This means game logic runs during the visible screen, which represents a large percentage of the CPU time. With that in mind, I relocated all of the any plot* commands until right before the drawscreen command (at the end of the code). If the screen is being drawn, a plot* command wastes time until the screen isn't being drawn. You need try and minimize any other game logic once you have started with the plot* commands. Once I relocated all of the plot* commands to the end of the code the graphical glitch on the screen went away. Problem solved!

 

As a side note, especially if the above tip doesn't resolve your problem, you can alternatively explore the use of double-buffering, which frees you from locking the game to the framerate, but if you have a lot of game objects it pretty much requires on-cart or XM RAM. I'm not planning on using double-buffering unless it becomes a necessity, but you can review how it's used in the doublebuffer sample that RevEng includes in the samples directory of the official 7800basic distribution.

 

Now, on to the code changes I made in this revision.

 

Variable Assignments

 

Because my goal was to add a death animation to the space man, I knew I'd have to assign a variable that would be used to count the frames in the explosion. I assigned "exploldeanimframe" to var90 for this purpose.

 

dim explodeaniframe =var90 :rem frame counter for explosion animation

 

Image Imports

 

Several changes were made in the "Import" section of the code. I explained earlier that I added two newblock statements to manually divide the graphics blocks how I wanted them. In addition, I imported 8 new image files that are used for the explosion animation for the spaceman, sprite_explode1 - sprite_explode8. Obviously, it's an 8 frame animation. It's important to note that the animation will run in the order that you import the sprites.

 

import

; Here we import all of the indexed image files

; Banner images require that you specify the display mode and index colors

incgraphic scoredigits_8_wide.png

incgraphic tileset_stars1.png

incgraphic tileset_planet1.png

incbanner spacejunk_title.png 160A 0 1 2 3

incbanner sprite_7800basic2.png 160A 0 1 1 1

incgraphic sprite_firetobegin.png

newblock

incgraphic sprite_satellite1.png

incgraphic sprite_satellite2.png

incgraphic sprite_invader1.png

incgraphic sprite_alien.png

incgraphic sprite_chip1.png

incgraphic sprite_rocket1.png

incgraphic sprite_rocks1.png

incgraphic sprite_rocks2.png

incgraphic sprite_rocks3.png

incgraphic sprite_rocks4.png

incgraphic sprite_debris1.png

incgraphic sprite_debris2.png

incgraphic sprite_debris3.png

incgraphic sprite_debris4.png

incgraphic sprite_debris5.png

incgraphic sprite_debris6.png

incgraphic sprite_debris7.png

incgraphic sprite_debris8.png

incgraphic sprite_debris9.png

incgraphic sprite_bullet.png

newblock

incgraphic sprite_spaceman_left.png

incgraphic sprite_spaceman_right.png

incgraphic sprite_explode1.png

incgraphic sprite_explode2.png

incgraphic sprite_explode3.png

incgraphic sprite_explode4.png

incgraphic sprite_explode5.png

incgraphic sprite_explode6.png

incgraphic sprite_explode7.png

incgraphic sprite_explode8.png

incmapfile spacejunk.tmx

characterset tileset_stars1

 

Main Loop Changes

 

While I didn't change a whole lot in the gameplay, there were numerous changes to the code in the main loop, some of which were repetitive changes where I'd change one simple thing in many different places. With that in mind, I'll describe the change with one line of code rather than pasting in everything.

 

Collision Detection

 

In the collision detection block of code, I removed the "goto losealife" that was at the end of each line. I changed that subroutine completely, as the prior code was more intended to pause the game and show how a collision was registered and I don't want that in the actual finished game. That subroutine now simply resets variables. Lives are now decremented in a single line of code when the death animation is triggered later on.

 

; Detect collision between spaceman and alien

if boxcollision(spaceman_xpos,spaceman_ypos,13,13, alien_xpos,alien_ypos, 15,15) then alien_xpos=200 :alien_ypos=200:lives=lives-1:goto losealife <removed>

 

; This section of code detects collisions between the spaceman and all of the space junk sprites

if boxcollision(debris1_xpos,debris1_ypos, 6,14, spaceman_xpos,spaceman_ypos, 13,13) then debris1_xpos=200 :debris1_ypos=200 :debris1_flag=1:lives=lives-1:goto losealife <removed>

 

Plotsprite location

 

The main difference in the plotsprite section is that it's been moved. I cut and paste the entire block of code with all of the plot* commands and pasted it in at the very end of the main loop, immediately before the drawscreen.

 

Changes related to explosion animation

 

I made a few other changes near the end of the code, all related to accommodating the inclusion of the new death animation, starting at line 571. First, I added a line to skip over the code that controls the spaceman movement, the joystick direction detection, and bullet firing while the death animation is active. We don't want the player to be able to move around the screen and fire when the sprite is in the middle of exploding. In the same first line, I also reset the location of the bullet off the screen, it otherwise would remain at the home location and be visible behind the explosion animation.

 

Here’s an image of what all 8 of the death animation frames look like:

 

post-2143-0-09096800-1506446685.jpg

 

Next you'll see I added some sound effects to the code that fires your gun. This is accomplished with the "playsfx" command, followed by the name of the subroutine that contains the audio data. I also added a random number to the end of the sound effect to vary the pitch when it's played. I'll get to more detail on sound effects a little later.

 

; If the explosion animation is running for the space man, we don't want him to be able to move around or fire.

; We also temporarily reset the location of the bullet, or we'll see it behind the explosion animation.

if spaceman_flag=1 then bullet_fire_xpos=200:bullet_fire_ypos=200:goto skip_spaceman_move

 

; This block of code checks to see if you've changed joystick directions. If so, the bullet is stopped and reset.

; First Line: If Joystick is being pushed up, and the last push was not up, then reset the bullet to the home location.

if joy0up && joyposup=0 then bullet_fire_xpos=spaceman_xpos-1:bullet_fire_ypos=spaceman_ypos+8

if joy0down && joyposdown=0 then bullet_fire_xpos=spaceman_xpos-1:bullet_fire_ypos=spaceman_ypos+8

if joy0left && joyposleft=0 then bullet_fire_xpos=spaceman_xpos-1:bullet_fire_ypos=spaceman_ypos+8

if joy0right && joyposright=0 then bullet_fire_xpos=spaceman_xpos-1:bullet_fire_ypos=spaceman_ypos+8

 

; This block of code moves your Space Man, and sets a flag for the currently pressed joystick direction

; The direction flag is used to determine the direction the bullet is fired later on

if joy0up1 then spaceman_ypos=spaceman_ypos-1:joyposup=1:joyposdown=0:joyposleft=0:joyposright=0

if joy0down1 then spaceman_ypos=spaceman_ypos+1:joyposup=0:joyposdown=1:joyposleft=0:joyposright=0

if joy0left1 then spaceman_xpos=spaceman_xpos-1:joyposup=0:joyposdown=0:joyposleft=1:joyposright=0

if joy0right1 then spaceman_xpos=spaceman_xpos+1:joyposup=0:joyposdown=0:joyposleft=0:joyposright=1

 

; Here we set a temporary variable to fluctuate the pitch of the gun firing sound.

temp7=rand&3 : rem set temp7 to a random number from 0-3

 

; Based on the most recent direction the joystick was pushed, fire the bullet in that direction

if joy0fire && joyposup=1 then bullet_fire_ypos=bullet_fire_ypos-4:playsfx sfx_gunfire temp7

if joy0fire && joyposdown=1 then bullet_fire_ypos=bullet_fire_ypos+4:playsfx sfx_gunfire temp7

if joy0fire && joyposleft=1 then bullet_fire_xpos=bullet_fire_xpos-4:playsfx sfx_gunfire temp7

if joy0fire && joyposright=1 then bullet_fire_xpos=bullet_fire_xpos+4:playsfx sfx_gunfire temp7

if !joy0fire then bullet_fire_xpos=spaceman_xpos-1:bullet_fire_ypos=spaceman_ypos+8

 

skip_spaceman_move

 

; Limit the Space Man's movement to keep him on the visible screen

if spaceman_xpos>146 then spaceman_xpos=spaceman_xpos-1 ; Don't move off the right side of the screen

if spaceman_xpos<1 then spaceman_xpos=spaceman_xpos+1 ; Don't move off the left side of the screen

if spaceman_ypos>176 then spaceman_ypos=spaceman_ypos-1 ; Don't move off the bottom of the screen

if spaceman_ypos<11 then spaceman_ypos=spaceman_ypos+1 ; Don't move off the top of the screen

 

; Don't let bullet fire off of the visible screen

if bullet_fire_xpos>158 then bullet_fire_xpos=spaceman_xpos-1:bullet_fire_ypos=spaceman_ypos+8 ; Stop bullet at right edge of screen

if bullet_fire_xpos<2 then bullet_fire_xpos=spaceman_xpos-1:bullet_fire_ypos=spaceman_ypos+8 ; Stop bullet at left edge of screen

if bullet_fire_ypos>188 then bullet_fire_xpos=spaceman_xpos-1:bullet_fire_ypos=spaceman_ypos+8 ; Stop bullet at bottom edge of screen

if bullet_fire_ypos<11 then bullet_fire_xpos=spaceman_xpos-1:bullet_fire_ypos=spaceman_ypos+8 ; Stop bullet at top edge of screen

 

Explosion Animation Code

 

Here's the code that actually makes the explosion animation work. Turning on the animation itself is trivial, as all you need to do is plot the sprite and add a frame counter variable to the end. This code by itself is what actually puts it on the screen:

 

plotsprite sprite_explode1 5 spaceman_xpos spaceman_ypos explodeaniframe

 

There's a little more to it than that, though, as we need to control the speed of the animation, set flags for the sprite that's exploding so we can trigger other changes in the code, and we need to set a flag to know when the explosion animation is completed so we can move on.

 

The first thing I did was to add a few frame counters. I could have used just the one new counter "explodeaniframe" and counted from 1-8, but doing that alone causes the entire animation to run extremely fast - so fast that you can't really see it on the screen. We need a way to run an additional counter that increments the animation counter a little more slowly. To accomplish that, I used one of the "junk_Slowdown" frame counters that I'd already assigned at the very beginning but hadn't used yet. It will increment by 1 each frame, and when it gets to 8 it then resets itself and increments the explosion animation frame variable by 1. This effectively makes our animation run every 8th frame, slowing it down to an acceptable level. You'll also notice that all of the if...then statements are precluded by "if spaceman_flag=1", which is because we want to verify that the spaceman has just been killed before we run the code for the explosion. Finally, notice that we also trigger the explosion sound effect in this section of code.

 

; This section starts two frame counters to control the animation speed of the space man's explosion

; The junk_slowdown3 frame counter is used to slow down the animation frame counter.

; When the animation is complete, the animation frame variable (explodeaniframe) is set to 0

if spaceman_flag=1 && explodeaniframe=1 then playsfx sfx_death

if spaceman_flag=1 then junk_slowdown3=junk_slowdown3+1

if spaceman_flag=1 && junk_slowdown3=8 then junk_slowdown3=0

if spaceman_flag=1 && junk_slowdown3=2 then explodeaniframe=explodeaniframe+1

if spaceman_flag=1 && explodeaniframe>9 then explodeaniframe=200

 

I mentioned earlier that I changed the "losealife" subroutine code. The next few lines perform a few of the functions that were formally in that subroutine. The first line checks to see if the death flag is set for the space man (spaceman_flag), the explosion animation is complete, and you have at least one life left, reset but stay in the game

 

if spaceman_flag=1 && explodeaniframe=200 && lives>0 then spaceman_xpos=80:spaceman_ypos=80:spaceman_flag=0:explodeaniframe=0:gosub losealife:goto main

 

This line checks to see if the death flag is set for the space man, the explosion animation is complete, and you have no lives left, reset to the title screen. The Game is over. Note that the score is not reset prior to the titlescreen, so the score you see when you jump back is the score you just achieved in the game. It resets to zero when the main loop is called from the titlescreen at the beginning of a new game.

 

if spaceman_flag=1 && explodeaniframe=200 && lives<1 then explodeaniframe=0:spaceman_flag=0:goto plot

 

Next is the area for all of the plot values, at the end of the code but prior to the drawscreen. The first few lines are all-new, they will plot either the spaceman sprite or the explosion depending on the value of the spaceman_flag variable. That flag is set when a collision is detected, and then reset with the lines of code just above that take you back to the main loop or the titlescreen if the game is over.

 

; If the death flag is off, plot the normal space man sprite. If the death flag is on, plot the explosion animation sequence.

if spaceman_flag=0 then plotsprite sprite_spaceman_left 5 spaceman_xpos spaceman_ypos

if spaceman_flag=1 then plotsprite sprite_explode1 5 spaceman_xpos spaceman_ypos explodeaniframe

 

The remaining plotsprite commands are the same as before, just moved to a new location.

 

; Plot score values

; Image File Palette Score Variable # Digits X Pos Y Pos

; ------------------- ------- --------------- -------- ------ -------

plotvalue scoredigits_8_wide 2 score0 6 26 0

plotvalue scoredigits_8_wide 2 score1 1 153 0

 

; Here we plot all of the sprites on the screen, using the variables we defined for the X and Y locations.

; Spaces were added for readability, only one space is required between each option

 

; Name of the Sprite Palette X Position Y Position

; ------------------- ------- --------------- -------------

 

plotsprite sprite_invader1 5 invader1_xpos invader1_ypos

plotsprite sprite_alien 6 alien_xpos alien_ypos

plotsprite sprite_chip1 5 chip1_xpos chip1_ypos

plotsprite sprite_debris1 1 debris1_xpos debris1_ypos

plotsprite sprite_debris2 1 debris2_xpos debris2_ypos

plotsprite sprite_debris3 0 debris3_xpos debris3_ypos

plotsprite sprite_debris4 1 debris4_xpos debris4_ypos

plotsprite sprite_debris5 1 debris5_xpos debris5_ypos

plotsprite sprite_debris6 1 debris6_xpos debris6_ypos

plotsprite sprite_debris7 1 debris7_xpos debris7_ypos

plotsprite sprite_debris8 1 debris8_xpos debris8_ypos

plotsprite sprite_debris9 0 debris9_xpos debris9_ypos

plotsprite sprite_rocket1 5 rocket1_xpos rocket1_ypos

plotsprite sprite_rocks1 3 rocks1_xpos rocks1_ypos

plotsprite sprite_rocks2 2 rocks2_xpos rocks2_ypos

plotsprite sprite_rocks3 3 rocks3_xpos rocks3_ypos

plotsprite sprite_rocks4 4 rocks4_xpos rocks4_ypos

plotsprite sprite_satellite1 4 satellite1_xpos satellite1_ypos

plotsprite sprite_satellite2 3 satellite2_xpos satellite2_ypos

plotsprite sprite_bullet 5 bullet_fire_xpos bullet_fire_ypos

 

drawscreen ; Display the plotted images on the screen

goto mainloop ; Jump back to the beginning of the main game loop

 

The losealife subroutine

 

This subroutine was changed to simply reset variables and decrement the lives counter.

 

losealife

 

lives=lives-1

 

; Here we reset some variable values before returning to the game

; We reset the location of the junk to the default if they're still active

if debris1_flag <>1 then debris1_xpos = 52 : debris1_ypos = 5

if debris2_flag <>1 then debris2_xpos = 4 : debris2_ypos = 20

if debris3_flag <>1 then debris3_xpos = 72 : debris3_ypos = 12

if debris4_flag <>1 then debris4_xpos = 4 : debris4_ypos = 60

if debris5_flag <>1 then debris5_xpos = 92 : debris5_ypos = 18

if debris6_flag <>1 then debris6_xpos = 122 : debris6_ypos = 23

if debris7_flag <>1 then debris7_xpos = 4 : debris7_ypos = 120

if debris8_flag <>1 then debris8_xpos = 4 : debris8_ypos = 140

if debris9_flag <>1 then debris9_xpos = 38 : debris9_ypos = 12

if rocks1_flag <>1 then rocks1_xpos = 18 : rocks1_ypos = 20

if rocks2_flag <>1 then rocks2_xpos = 140 : rocks2_ypos = 48

if rocks3_flag <>1 then rocks3_xpos = 18 : rocks3_ypos = 60

if rocks4_flag <>1 then rocks4_xpos = 140 : rocks4_ypos = 88

if satellite1_flag <>1 then satellite1_xpos = 18 : satellite1_ypos = 100

if satellite2_flag <>1 then satellite2_xpos = 18 : satellite2_ypos = 120

if invader1_flag <>1 then invader1_xpos = 140 : invader1_ypos = 126

if chip1_flag <>1 then chip1_xpos = 152 : chip1_ypos = 90

if rocket1_flag <>1 then rocket1_xpos = 152 : rocket1_ypos = 130

 

fire_debounce = 0 : gameover_flag = 0 : junk_slowdown4 = 0

alien_aniframe = 0 : spaceman_aniframe = 0 : spaceman_dir = 0

bullet_fire_xpos= 0 : bullet_fire_ypos = 0 : joyposleft = 1

joyposright = 0 : joyposup = 0 : joyposdown = 0

 

; Reset location of alien to offscreen

alien_xpos = 200 : alien_ypos = 200 : alien_flag = 0

 

return

 

Sound Effects Data

 

I'm only going to use TIA sound effects in this game, I won't be exploring the use of POKEY. Sound effects are craeted by playing TIA data with the playsfx command, or by manipulating the TIA registers directly. I'm going to foucs on the playsfx command, as it greatly simplifies adding TIA sound effects in the game.

 

Sound effect are created by adding the following line in the main game loop:

 

playsfx soundata [offset]

 

The optional offset parameter allows you to raise or lower the pitch of the played sound. If you have a sound which is often repeated, it's recommended that you vary its pitch a bit randomly. I decided to use the optional parameter in Space Junk for the gun firing sound, as I noted earlier.

 

When you play a sound, the sound driver will automatically choose between the two available sound channels based on which channels are already being used by the driver, and if both channels are being used it will interrupt one of the playing sounds based on a priority system.

 

Before playing a sound with playsfx, you'll need to define the sound effect data in the expected format. The data format 7800basic works with is composed of three parts, a header, the sound data itself, and an end-of-sound marker.

 

The header consists of 3 bytes:

1. The format version. Use "16" here for TIA ("32" for POKEY, but we're not using POKEY)

2. The sound priority. The suggested usage here is to enter the number of chunks of data. This will have the effect that longer sounds will be less likely to be interrupted early on compared to short sounds. If you have a background sound that should only be played if a channel is free, use a 0 here.

3. The number of frames each chunk of data represents, less one.

 

The sound data then consists of 3 byte chunks, each of which represents the Sound Frequency, Control/Waveform, and Volume to play. The sound driver will continue to play the sound data until it reaches an end of sound marker, which consists of 3 zero bytes.

 

I'm no expert on sound effects, and when I've created them in the past it's generally involved a lot of trial and error testing by changing bytes in the data and seeing what happens. The sound data included in Space Junk was pulled directly from RevEng's soundfx sample program, although I did modify them a bit to make them shorter in length.

 

data sfx_gunfire

$10,$10,$01 ; version, priority, frames per chunk

$16,$04,$04 ; first chunk of freq,channel,volume

$16,$04,$07

$16,$04,$0f

$16,$04,$0e

$04,$0c,$07

$0d,$04,$07

$03,$0c,$04

$03,$0c,$04

$04,$0c,$02

$05,$0c,$04

$12,$04,$04

$12,$04,$02

$05,$0c,$04

$05,$0c,$02

$05,$0c,$02

$10,$04,$02

$00,$06,$01

$00,$00,$00

end

 

data sfx_death

$10,$10,$02 ; version, priority, frames per chunk

$1F,$03,$0F ; first chunk of freq,channel,volume data

$1F,$08,$0E

$1F,$03,$0D

$1F,$08,$0C

$1F,$03,$0B

$1F,$08,$0A

$1F,$03,$09

$1F,$08,$08

$1F,$03,$07

$1F,$08,$06

$1F,$03,$05

$1F,$08,$04

$1F,$03,$03

$1F,$08,$02

$1F,$03,$01

$00,$00,$00

End

 

That’s it for this revision. I’m not sure what’s coming next, I’ll have to give it some thought. There’s plenty of ROM space left (about 6k, and I haven't optimized anything) so I may add some optional features like the high score table.

 

Here’s the latest version of code with the updated graphics files and all compiled files: SpaceJunk_092617_1a.zip

  • Like 3
Link to comment
Share on other sites

I looked into adding RevEng's built in highscore table, and while I got it to work, I had to comment out all of the collision detection code to squeeze it in. It's pretty big. I could free up more space with dmaholes, but I think I'm going to leave that particular feature out of this game. As an alternative, I added in a custom high score routine that saves one high score entry to the title screen. I'd like to save the remaining ROM space for other things. I didn't add any atarivox or savekey support, so you will lose the high score when you exit the game.

 

I created a graphic image to visually explain how it works below, but I'll also paste in the code below it with more detailed info.

 

post-2143-0-69865500-1506467821_thumb.png

 

I added variable assignments to the score variables, as I need to address them by byte as they need to be addressed with BCD compliant values. I’ll check each byte individually when comparing the current score to the high score when we decide whether or not to update it.

 

rem ** Variable Assignments

 

dim GameScoreHi=score0

dim GameScoreMd=score0+1

dim GameScoreLo=score0+2

 

; Assign scoreA to High Score

dim scoreA=var94

dim hiscoreHi=scoreA

dim hiscoreMd=scoreA+1

dim hiscoreLo=scoreA+2

 

I added a new text graphic with the words “High Score” to plot on the title screen. It’s plotted just below the “press fire to begin” image.

 

import

; Here we import all of the indexed image files

; Banner images require that you specify the display mode and index colors

incgraphic scoredigits_8_wide.png

incgraphic tileset_stars1.png

incgraphic tileset_planet1.png

incbanner spacejunk_title.png 160A 0 1 2 3

incbanner sprite_7800basic2.png 160A 0 1 1 1

incgraphic sprite_firetobegin.png

newblock

incgraphic sprite_highscore.png

 

I added the high score text image to the plot section, so it’s added to screen RAM before the title screen is loaded up along with the playfield map and title screen logo.

 

plot

; Here we Plot the Titlescreen Images

; Note that the order matters for which image appears on top of the others.

; The last one plotted will appear on top.

clearscreen ; Clear the Visible Screen

plotmap spacejunk 0 0 0 20 12 ; Plot the "Space Junk" playfield map in the background

plotbanner spacejunk_title 0 18 20 ; Plot the "Space Junk" title screen image

plotsprite sprite_firetobegin 6 41 118 ; Plot the "Press Fire to Begin" text image

plotsprite sprite_highscore 5 49 138 ; Plot the High Score text image

 

I added a gosub statement to the titlescreen loop that checks the current score against the high score value, and updates it if the high score that was just achieved is higher. If it is higher, the value is updated. I also added a plotvalue statement that plots the 6 digit high score value to the screen.

 

titlescreenloop

; Calculate the High Score Value

; This is the main Title Screen Loop. We will loop continuously until the fire button is pressed.

restorescreen ; Restore the plotted images saved with the previous savescreen

if joy0fire then clearscreen:lives=$03:score0=0:goto main ; Check for a joystick button press. If yes, clear the screen and goto init label

gosub HighScoreCalc ; Calculate current high score

plotvalue scoredigits_8_wide 5 scoreA 6 58 8 ; Plot the High Score Value

 

Below is the subroutine that actually does the high score calculation. It starts off checking the high score byte, then the middle byte, and finally the lowest byte.

 

Here’s how it works. Let’s say your score is 876,543, and the current high score is 123,456.

 

The first section checks the high byte, so it will compare the GameScoreHi value (your current score) of 87 to the current high score value of 12. 87 is larger than 12, so it will skip the remaining subroutine, and update the first two bytes to match the new, higher score, and copy the values from the lower two bytes. There’s no need to compare to see if they are higher or lower, because we already know that the entire value as a whole is larger, so we just copy the rest. The remainder of the subroutine continues with the same logic.

 

HighScoreCalc

; Checks for a new high score.

if GameScoreHi > hiscoreHi then goto New_High_Score_Check

if GameScoreHi < hiscoreHi then goto Skip_High_Score_Check

; First byte equal. Do the next test.

if GameScoreMd > hiscoreMd then goto New_High_Score_Check

if GameScoreMd < hiscoreMd then goto Skip_High_Score_Check

; Second byte equal. Do the next test.

if GameScoreLo > hiscoreLo then goto New_High_Score_Check

if GameScoreLo < hiscoreLo then goto Skip_High_Score_Check

; All bytes equal. Current score is the same as the high score.

goto Skip_High_Score_Check

New_High_Score_Check

; Save new high score.

hiscoreHi=GameScoreHi : hiscoreMd=GameScoreMd : hiscoreLo=GameScoreLo

Skip_High_Score_Check

return

 

That’s it for this update. Next I may work a little bit on refining the collision detection, and maybe adding an explosion for the space junk sprites.

 

Here’s the updated code, graphics, and compiled files: SpaceJunk_092617_1b.zip

  • Like 2
Link to comment
Share on other sites

I thought I'd start off by demonstrating one way we can do a little code optimization. We're starting with 6214 bytes free in this build (and we still have 4096 bytes free in each of the 3 DMA holes). In general, I look for any sections of code that repeatedly used the same line of code. If so, it can save ROM space to place that code into a subroutine rather than repeating it over and over within the main loop. The first thing I noticed was that the "bullet_fire_xpos=spaceman_xpos-:bullet_fire_ypos=spaceman_ypos+8" line that sets the bullet back to its home location was repeated 9 times in the main loop. Changing each of those entries to make a call to a subroutine saved 114 bytes.

 

I created this subroutine to replace each entry:

 

set_bullet_home

;This code sets the bullet location back to it's home position

bullet_fire_xpos=spaceman_xpos+4:bullet_fire_ypos=spaceman_ypos+8

return

 

Anywhere in the code where that line was used, I changed it to make a call to the set_bullet_home subroutine instead:

 

; Don't let bullet fire off of the visible screen

if bullet_fire_xpos>158 then gosub set_bullet_home ; Stop bullet at right edge of screen

if bullet_fire_xpos<2 then gosub set_bullet_home ; Stop bullet at left edge of screen

if bullet_fire_ypos>188 then gosub set_bullet_home ; Stop bullet at bottom edge of screen

if bullet_fire_ypos<11 then gosub set_bullet_home ; Stop bullet at top edge of screen

 

I also modified the line from "-1" to "+4", to place the home location of the bullet closer to the center of the spaceman's body. This was done because we'll be working on animating the space man next so he faces left and right, and keeping it the same would have the bullet firing from his back when facing right.

 

Before gosub set_bullet_home, we had 6214 bytes of ROM space left in the main area, after adding set_bullet_home we have 6328 bytes of ROM space left in the main area.

 

Spaceman Animation

 

I had made two spaceman sprites at the very beginning, one facing left and one facing right. I could add additional frames that show him walking or moving his body in some way, but I decided to keep this simple. The code was updated so that when you push left he will face left, and when you push right he will face right. Below are the two sprites were already located in the Import Graphics section of code that we'll be using.

 

incgraphic sprite_spaceman_left.png

incgraphic sprite_spaceman_right.png

 

We need a variable for the animation, however we already had one defined that was simply not used yet. We'll assign "spaceman_aniframe" to keep track of whether or not to use the left or right facing frame. By default we'll keep it set it to 0 (the first frame). Remember that the animation runs in the order that the sprites are imported, so the spaceman will start the game facing left.

 

alien_aniframe = 0 : spaceman_aniframe = 0 : spaceman_dir = 0

 

Because there are only two sprites and two directions, we don't need to use a counter for this animation, just set it to either 0 or 1 at the appropriate time. In the section below, I added "spaceman_aniframe" to the end of the command that plots the spaceman sprite.

 

; If the death flag is off, plot the normal space man sprite. If the death flag is on, plot the explosion animation sequence.

if spaceman_flag=0 then plotsprite sprite_spaceman_left 5 spaceman_xpos spaceman_ypos spaceman_aniframe

if spaceman_flag=1 then plotsprite sprite_explode1 5 spaceman_xpos spaceman_ypos explodeaniframe

 

Now that the animation is enabled, we need to change the variable at the appropriate time. In the block of code that moves the spaceman, I added a statement that sets the variable to 0 if you push left on the joystick, and 1 if you push right on the joystick. That's all we need to do, we're done.

 

; This block of code moves your Space Man, and sets a flag for the currently pressed joystick direction

; The direction flag is used to determine the direction the bullet is fired later on

if joy0up1 then spaceman_ypos=spaceman_ypos-1:joyposup=1:joyposdown=0:joyposleft=0:joyposright=0

if joy0down1 then spaceman_ypos=spaceman_ypos+1:joyposup=0:joyposdown=1:joyposleft=0:joyposright=0

if joy0left1 then spaceman_xpos=spaceman_xpos-1:joyposup=0:joyposdown=0:joyposleft=1:joyposright=0:spaceman_aniframe=0

if joy0right1 then spaceman_xpos=spaceman_xpos+1:joyposup=0:joyposdown=0:joyposleft=0:joyposright=1:spaceman_aniframe=1

 

DMA Holes

 

Getting back to code optimization, I thought I'd discuss the use of DMA holes next.

 

The 7800 requires its graphics to be padded with zeroes, which is a huge waste of ROM space. To make use of this extra space, 7800basic uses a 7800 feature called "holey DMA". This feature allows you to stick program code in these areas between the graphics blocks that would otherwise be wasted with zeroes. This is generally something you do when you've already used a fair amount of the ROM space avaialble and you need to free up some space. At the beginning of the development process there's no real need to stuff data into DMA holes when you have plenty of space left in the main ROM area anyway.

 

When compiling, you'll notice that the DMA hole free space is noted just below the total ROM space left in the main area. We currently have 12,288 bytes of DMA hole ROM space we can potentially reclaim.

 

6311 bytes of ROM space left in the main area.

4096 bytes of ROM space left in DMA hole 0.

4096 bytes of ROM space left in DMA hole 1.

4096 bytes of ROM space left in DMA hole 2.

$1880 to $1fff used as zone memory, allowing 31

2464 bytes left in the 7800basic reserved area.

 

To direct 7800basic to store any code that follows in these areas, use the dmahole command followed by the number of the DMA hole that you want to use. If you have multiple dmahole areas you can use multiple dmahole commands. In this case, we can specify "dmahole 0", "dmahole 1" and "dmahole 2" within our code. 7800basic will stop directing code into the current dmahole when another dmahole command is encountered, or if a new bank is declared. As we define our DMA Holes and code is stuffed into them, we'll see the free space in our main ROM area increase.

 

dmahole 0

 

7800basic will automatically add assembly code to allow your BASIC program to flow across a DMA hole. While it's not relevant to the Space Junk tutorial game, it's important to note that if you're creating a bankswitched ROM and intend to fill the last bank entirely with graphics you'll need to disable that extra generated assembly code. You can do that by adding the noflow keyword to the dmahole command.

 

dmahole 0 noflow

 

So, how do we know where to place the commands? It involves a bit of trial and error. We'll start from the very bottom of the code and work our way backwards with each command, starting with dmahole 2. We'll pick a place to add it, compile, and see how much code was stuffed in to it. If there's still a fair amount of space remaining, we can reposition the dmahole command farther up in the code so more is included within it. You can place the dma hole command pretty much anywhere.

 

To get started, let's place the dmahole 2 command immediately after the end of the main loop and see how much data gets stuffed in to it.

 

drawscreen ; Display the plotted images on the screen

goto mainloop ; Jump back to the beginning of the main game loop

 

dmahole 2

 

After adding the command and recompiling, you can see from the output below that we stuffed 911 bytes of data into DMA hole 2. We can move it farther up in the code to stuff a little more in to it. I'd recommend not trying to get it to just a few bytes free, as then it will require you to relocate the command again each time you add code to that particular area.

 

7219 bytes of ROM space left in the main area.

4096 bytes of ROM space left in DMA hole 0.

4096 bytes of ROM space left in DMA hole 1.

3185 bytes of ROM space left in DMA hole 2.

 

 

I moved it up to just after the "skipjunkmovement1" label and tried again. it now shows 1630 bytes remaining in DMA Hole 2, and we're now up to 8774 bytes of space left in the main area. That's good enough for now, let's try adding the dmahole 1 statement next a little farther up.

 

8774 bytes of ROM space left in the main area.

4096 bytes of ROM space left in DMA hole 0.

4096 bytes of ROM space left in DMA hole 1.

1630 bytes of ROM space left in DMA hole 2.

 

For this test, I added the dmahole 1 command immediately before the "debris1_move" label. This block of code cotains all of the game logic that moves the space junk sprites around. After adding it in and recompiling, we can see we now have 817 bytes of free space in DMA hole 1, and 12,050 bytes free in the main ROM area.

 

12050 bytes of ROM space left in the main area.

4096 bytes of ROM space left in DMA hole 0.

817 bytes of ROM space left in DMA hole 1.

1630 bytes of ROM space left in DMA hole 2.

 

For completeness, I'll go ahead and add the final statement, dmahole 0, immediately prior to the "main" label. After adding it and recompiling, you'll see we now have 1009 bytes of free space in DMA hole 0, and 15,134 bytes of free space left in the main ROM area.

 

15134 bytes of ROM space left in the main area.

1009 bytes of ROM space left in DMA hole 0.

817 bytes of ROM space left in DMA hole 1.

1630 bytes of ROM space left in DMA hole 2.

 

Holey DMA is a great way to free up some last minute space, but it can also be cumbersome if you make a whole lot of code changes after adding it in. Adding a large block of code will likely fill up the DMA hole it's in and then require relocating and re-testing the location of all of them again to get it just right. I'll go ahead and leave them in this build, but it's likely their location will change in the future.

 

This zip contains the compiled code, graphics, and compiled files: SpaceJunk_092717_1a.zip

  • Like 1
Link to comment
Share on other sites

In this update I added some AtariVox speech to the game. At the beginning of Part 1 of the tutorial I included some information on testing AtariVox speech, I'll include it here again.

 

The AtariVox is an incredibly fun addition to the 2600 and the 7800, providing in-game voice as well as high score saving. 7800basic makes it relatively easy to add AtariVox speech to your code, but testing it is a whole different story. Unfortunately there is no way to emulate 7800 AtariVox speech on your PC with an emulator, you need an actual AtariVox to hear it. There is a workaround to hear speech from your AtariVox & PC outside of a real Atari 7800 or a 7800 emulator for development purposes, but the process is admittedly a little painful. Despite that, I'll go ahead and explain how it's done in case anyone would like to give it a shot.

 

If you hook up your AtariVox to your PC via a USB/Serial connection there is a way to hear speech using the Phrase-a-lator software tool. It works, but I didn't use it for creating speech for Dungeon Stalker, I used my Cuttle Cart 2 and an AtariVox plugged in to a real 7800. You will need to connect your AtariVox to your PC with a USB to serial adapter. I have an official AtariVox USB to serial adapter I got from goldenaxe and I don't think AtariAge sells them, but I assume a generic USB to serial port adapter would work. If you examine the asm output file of your 7800 code once you've compiled the program, you can enter the specific numbers from the output in the .byte commands to listen to speech on your PC without a 7800. If you want to take the time to figure it out, it's the only way to listen to speech on a PC with an AtariVox.

 

Here's a sample of what you'd be looking for in the asm output file for use with the Phrase-a-lator software. The labels you use in 7800basic will also appear in the asm output file, so it's easy enough to find them with a text string search. In this example it's the 'watch out' speech from Dungeon Stalker, however the asm code is pulled from my AtariVox Speech Utility (phrase 31).

 

.L0270 ; speechdata speech31

JMP .skipL0270

speech31

.byte 31, 31 ; reset

.byte 21, 100 ; speed

.byte 22, 80 ; pitch

.byte 147,14,135,182 ; WATCH (dictionary)

.byte 21, 100 ; speed

.byte 22, 78 ; pitch

.byte 14,161,191 ; OUT (dictionary)

.byte 255 ; end of avox data

.skipL0270

 

When you lauch Phrase-a-lator, go to the "SpeakJet Phrase Editor", and add in the .byte commands from the asm output file like this: "\31 \31 \21 \100 \22 \80", those are the phoneme codes. I'd include some screenshots, but I don't have the software installed on my current laptop.

 

I created an AtariVox speech test utility a few years ago that had some phrases I created that mimic some of the voice from Berzerk. I decided to add those in. While I do have all the hardware I need to test this, I'd have to pull everything out and hook it all up to test, and as of right now I haven't done that. This speech code is completely untested, but should work based on my experience. At some point I will test it on real hardware and make changes to the code if needed. If anyone else reading this tutorial has one and would like to test it and report the results back to me, I'd appreciate the feedback. The only actual speech phrase that isn't tested is "Space Junk", the Berzerk phrases are previously tested and I know they sound ok. “Space” may have to be phonetically spelled out like “Spase” or something, I won’t know until it’s tested.

 

The code has been updated to use AtariVox speech in multiple locations. When you first turn the game off, there will be a random speech phrase when the title screen loads. You will hear one of four phrases: "Space Junk", "The Humanoid must die", "Intruder Alert, Intruder Alert", or "Chicken Fight like a robot". You will hear one of those four same phrases when you press the fire button to start the game.

 

Once you're in the game, you will hear the speech "Got Him" whenever you die, and "Game Over" (speech taken from Dungeon Stalker) when the game is over. Below are all of the code changes I made to implement the AtariVox speech.

 

Code Changes

 

Game Options Section

 

At the very beginning of the code, we must use the "set avoxvoice" command to enable AtariVox speech.

 

set avoxvoice on

 

Variable Assignments

 

I assigned another variable to be used as a random number generator, which chooses one of four speech phrases that are called two different times in the code.

 

dim ChooseSpeech = var91 ; Randomly choose one of four speech phrases

 

Just before "plot"

 

The command below is what calls the random speech subroutine once just as you turn the game on for the first time. It will play at the same time as the short intro sound effect.

 

; AtariVox speech prhase when the game loads for the first time

gosub spacejunk_speech

 

In the "Main" section

 

One line was added to speak one of the four random phrases right after you press the fire button and the main loop starts.

 

main

; Here we initialize the main game loop before jumping in to it.

; We clear the screen, plot only the background playfield map, save it, then move on to the main game loop.

clearscreen ; Clear the Visible Screen

plotmap spacejunk 0 0 0 20 12 ; Plot the "Space Junk" playfield map in the background

savescreen ; Save the plotted images to memory

drawscreen ; Display the plotted images on the screen

gosub spacejunk_speech ; AtariVox Random Speech Phrase: "Space Junk", "The Humanoid must die", "Intruder Alert", or "Chicken Fight like a robot"

 

In the "mainloop" section

 

The phrase "game over" is spoken when the conditions are met. It was added to the line below.

 

if spaceman_flag=1 && explodeaniframe=200 && lives<1 then explodeaniframe=0:spaceman_flag=0:speak gameover:goto plot ; Added AtariVox speech phrase: "Game Over"

 

In the "losealife" subroutine

 

The phrase "Got Him" was added to the losealife subroutine. It will play each time you lose a life.

 

losealife

 

speak gothim ; AtariVox Speech phrase: "Got Him"

lives=lives-1

 

Speech Data

 

So far we've looked at the commands that call the speech data, but we haven't actually looked at the speech data yet. "Speechdata" is the command used to generate voice data for use with the speak command, which sends data to the AtariVox speech module. Like other data commands, speechdata must be terminated by the end keyword. The data itself contains several keywords, each of which represent the different types of data that AtariVox will accept.

 

Here's the list of valid data types for use in the speechdata command.

 

reset: Causes AtariVox to reset current speech and parameters.

pitch #: Any future speech data will use the provided pitch.

speed #: Any future speech data will use the provided speed.

raw #,#,#,etc.: The raw data provided is sent to the AtariVox. This is useful if you wish to send more complex commands to the AtariVox. See the SpeakJet manual for more information.

dictionary 'word phrase': Each word is converted to AtariVox data via 7800basic's dictionary. Any words that aren't in the dictionary are run through phonetic translation.

phonetic 'word phrase': Each word is converted to AtariVox data via 7800basic's phonetic translation. You will likely need to misspell complex words to get the pronounciation you desire, though first give the correct spelling a try before you try tweaking it.

 

For both dictionary and phonetic data, if you use a period at the end of a word, it will drop the pitch partway through the word and invoke a pause. Similarly, a question mark will cause a rise in pitch and similar pause. Spaces and commas will provide moments of silence.

 

The actual speech data I added to the code is below. As I've mentioned before with other things, there is a ton of trial and error testing involved with speech phrase creation. Every time I'd want to test, I'd need to compile, copy the new binary to the Cuttle Cart MMC card, re-generate the Cuttle Cart menu, put it back in the 7800, select the new binary from the menu, and run the game until it got to the point where the text I wanted to hear was spoken. That entire process was simplified with the AtariVox Speech Test Utility I created, you can create a whole lot of test speech phrases with up to 48 menu slots to choose from, which made the testing process much faster for me.

 

Each word can be spoken phonetically or as a dictionary word. You'll have to experiment to see which method sounds better for a specific word. Some words sound terrible either way and need to be purposely misspelled to get the correct pronunciation. The "Dungeon Stalker" speech phrase was a good example. As a regular word, the AtariVox pronounced it as something like 'Dun-ghee-on', I had to change it to 'Dunjun' to get it to sound right. Adusting the speed and pitch are important, and doing it in the middle of a word can make the speech sound a bit more natural.

 

spacejunk_speech

ChooseSpeech=rand&3

on ChooseSpeech goto spkg0 spkg1 spkg2 spkg3

spkg0

speak spacejunkspeech:return

spkg1

speak humanoid:return

spkg2

speak intruder:return

spkg3

speak chicken:return

 

speechdata spacejunkspeech

reset

speed 110

pitch 74

phonetic 'space junk'

end

 

speechdata humanoid

reset

speed 110

pitch 82

dictionary 'the'

speed 120

pitch 80

phonetic 'hyu'

speed 110

pitch 82

phonetic 'manoyd'

pitch 82

phonetic 'must die'

end

 

speechdata intruder

reset

speed 110

pitch 82

dictionary 'intruder'

pitch 78

dictionary 'a'

pitch 82

phonetic 'lert'

pitch 82

dictionary 'intruder'

pitch 78

dictionary 'a'

pitch 82

phonetic 'lert'

end

 

speechdata gameover

reset

speed 100

pitch 86

phonetic 'gayme'

pitch 82

speed 110

dictionary 'owe'

speed 95

pitch 80

phonetic 'vur'

end

 

speechdata chicken

reset

speed 110

pitch 82

phonetic 'chickun'

speed 110

pitch 82

dictionary 'fight like a robot'

end

 

speechdata gothim

reset

speed 110

pitch 80

dictionary 'gaht the intruder'

end

 

Only because I forgot to do it earlier, I also added the ability to reset the game with the "reset" switch on real hardware from within the titlescreen or in the main game loop.

 

if switchreset then reboot ; Reset the game when the reset hardware switch is pressed

 

Here's the latest update: SpaceJunk_092717_1b.zip

  • Like 2
Link to comment
Share on other sites

I've decided to call the tutorial complete as the main goal has been accomplished. I may make a new unique game based on the Space Junk code, but it will be a different style game. Thanks to everyone who's gotten this far, and I hope it will continue to be helpful to those who'd like to get started.

 

I spent several hours today compiling all of the content from this tutorial into a single PDF file, complete with links to all of the code that was attached in the various posts in the tutorials. It also includes a clickable table of contents to make it easier to find specific areas of the tutorial you're looking for, as it's 137 pages long. I may make a few updates to it in the future, as I'm not 100% positive I caught all of the spelling errors, and I'll of course update it again if any glaring content errors are found.

 

7800Basic Tutorial Document: 7800basic Tutorial.pdf

  • Like 4
Link to comment
Share on other sites

Here's a quick 39 second video that lets you hear some of the AtariVox speech in the game. I just propped up my iPhone in front of my monitor, so the perspective isn't perfect, and it's running through a cheap VGA / S-Video converter box, so the colors aren't perfect.

 

SpaceJunk.mov

  • Like 2
Link to comment
Share on other sites

Hi AM

 

great tutorial....Space Junk is little bit

like Robotron.....

I love it.

greetings Walter

 

Thanks Walter. It was a game I came up with and wrote in about a week specifically for the tutorial, so it could certainly be improved. I'd encourage anyone who wants to make changes and make it a better game to please do, the code is free for anyone to use and make into their own game.

Link to comment
Share on other sites

  • 1 year later...

Hi, thanks for making this tutorial, hopefully it'll get more people into 7800 devel. I'm currently trying to make a simple platformer, but I'm having trouble implementing platform collision, every solution I see online assumes the existence of classes. Are there classes in 7800basic or some workaround? If not, would you have any suggestions on how to implement platform collision?

 

Thanks (sorry for sending the tweet earlier, I had just sent it when my AtariAge validation came through).

Link to comment
Share on other sites

Hi Dalta,

Classes aren't really a thing in 7800BASIC. Classes in other languages are really just a group of variables saying what values an object holds and where in memory to find code to run. You'll inevitably end up doing something similar to a class, just in a more 7800 focused way.

 

Saying that, how you handle your platform collision really depends on how you handle your platforms. If your platforms are floating objects that aren't really on a grid you can use the boxcollision routine to see if the player is touching one and then stop them falling if they are.

 

If your platforms are part of a big grid map you can use peekchar to see if the tile below the player is a number representing a solid tile.

 

When you're confident with that you can scale it up and check which part of the player collided with the platform. If it's their feet stop them falling, if it's their head stop them jumping, etc.

Link to comment
Share on other sites

Thanks for the advice SmittyB, I went the first route and after a few days I have a very unpolished single platform. Making a big tiled map seems like a better way to go, easier to implement levels. I just have to figure out how to stop an object falling when it hits a certain tile.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...