Under Attack

Our game, so far.

// My Game
// by Mr Green
// Created 2025/12/16

Symbol 0,"2__0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0P?0_0/P?0!P0,P0_0/P0,P0?P,00P0_0/P00P,0.P,00P0_0/P00P,0.P,00P0_0/P00P,0.P,00P!0!P!00P,0_0?P@0_0_0_0_0_0/P!0.P!0_0_0_0_0_0.P?0PP0P?0_0_0!PP0_0_0/P.0PP0P.0_0_0/PP0_0_0_0,PP0_0@P_P,0PP0P_P,0.P0_0_0@P0.P0_0_0@P0.P0_0_0@P0.P0@P0@P0@P0.PP0/P0@P0/PP0?P0/P0P!0P0/P0!P?0,P0@P0,P?0@P,0,P0@P0,P,0_0?P,0@P,";
Symbol 1,"2__0_0_0_0_0_0_0_0_0_0_0_0_0_0_00P,0_0_0@P_P,0_0?P_P,0?P0_0?P0?P,0@P0_0?P0_0/P0_0?P0_0/P?0!P?0_0_00P0!P0_0_0!P@0_0_0_0_0_0/P!0.P!0_0_0_0_0_0.P?0PP0P?0_0_0!PP0_0_0/P.0PP0P.0_0_0/PP0_0_0_0,PP0_0!P_P?0PP0P_P!0_0_0_0,PP0_0_0_0,PP0_0_0_0,PP0_0P0@P0_0PP0_0P0@P0_0PP0_0P0P!0P0_0PP0_0P0P0.P0P0_0PP0_0P0P0.P0P0_0P,0/P,0P!0P,0/P.0_0_0_0P.0_0_0_0PP";
Symbol 2,"2__0_0_0_0_0_0_0_0_0_0_0_0_0NN00NN0_0_0/NPQNNQPN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0?NPPQ.PPN0_0_0.NPPQ.PPN0_0_0.NPPQ.PPN0_0_0.NPPQ.PPN0_0_0.NPQ!PN0_0_0.NPQ!PN0_0_0.NPQ!PN0_0_0,NPPQ!PPN0_0_00NPPQ!PPN0_0_00NPPQ!PPN0_0_00NPPQ!PPN0_0,P_P_P_P0,PS_S_S_SP0PS_S_S_S,PPSSR,S,R,S,RRS,R,S,R,SSP0S_S_S_S,0,S_S_S_S";
Symbol 3,"2__P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P/";
Symbol 4,"2__0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_z0_0_0_0020,20_0_0_00;0_0_0_0z0;1;0z0_0_0_0;0_0_0_0020,20_0_0_00z";

AntiAlias Off // Turn off AntiAlias, for Crispy Invaders


// Arrays
Dim EnemyAlive(20)

// Main Loop
GameRun=0
Repeat
  If GameRun==0 then Gosub .StartScreen
  If GameRun==1 then Gosub .InGame
Flip
Forever


// This is our Titlescreen
.StartScreen
CLS
ResetDraw

  ShowInvader=1
  If Wrap(Frames,0,20)<10 Then ShowInvader=0
  DrawImg 320,160,ShowInvader

  SetFontSize 32
  Text 320,240,"Titlescreen!",1
  SetFontSize 16
  Text 320,360,"Press Fire to Play",1
  
  If GamePad(ButtonA)
    Gosub StartGame
    GameRun=1
  Endif
Return



// This resets the current level
.NextLevel
  DropDown=480
  For BadGuy=1 to 20
    EnemyAlive(BadGuy)=1
  Next

  // New variables to multiply the EnemyX and Y Angles
  WaveX=Rand(1,3)
  WaveY=Rand(1,3)
  Gap=Rand(3,15)
  
Return


// This prepares our game when the A Button is hit
.StartGame
  Score=0
  PlayerX=320 // Middle of the screen
  PlayerY=440 // Bottom of the screen
  
  BulletX=0  // Reset player's bullet
  BulletY=0
  
  Gosub NextLevel
Return



// This is our ingame loop
.InGame
CLS
ResetDraw

  // Starfield using Symbol 4
  Starfield 1,0,4

  // Player
  DrawImg PlayerX,PlayerY,2
  Speed=4
  If GamePad(ButtonLeft)>0.5 then PlayerX=PlayerX-Speed
  If GamePad(ButtonRight)>0.5 then PlayerX=PlayerX+Speed
  // Using >0.5 allows players to use a thumbstick, too!
  
  PlayerX=Limit(PlayerX,32,640-32)
  
  
  // Player's Bullet
  // Keep the Bullet going upwards
  BulletSpeed=16
  BulletY=Limit(BulletY-BulletSpeed, -100,480)

  // Draw the Bullet
  SetScale 0.2,0.5

  BulletCollide=DrawImg(BulletX,BulletY,3)

  // And reset things afterwards
  ResetDraw
  
  If BulletY<0 And GamePad(ButtonA)
    BulletX=PlayerX
    BulletY=PlayerY
    PlaySFX("Lazer_2")
  EndIf

  
  // Enemy Section
  
  // Reuse this to keep the invaders animated
  ShowInvader=1
  if Wrap(Frames,0,20)<10 then ShowInvader=0
  
  CircleSize=160
  // New Variable for Circle Position
  CircleX=320+Sin(Frames)*160
  CircleY=200-DropDown
  DropDown=Limit( DropDown-5 ,0,480)
    // Why not try changing this value, too.
      // The higher the number the faster the drop.
      // Make it slower to have the invaders creep into view!
  


  BaddyCount=0
  // A loop for 20 Bad Guys
  For BadGuy=1 to 20
    // If Alive
    If EnemyAlive(BadGuy)==1
    
      BaddyCount=BaddyCount+1

      // Each enemy should be further around the loop.
      PlusAngle=BadGuy*Gap
      Angle=Wrap(Frames + PlusAngle,0,360)
      EnemyX=CircleX+Cos(Angle*WaveX)*CircleSize
      EnemyY=CircleY+Sin(Angle*WaveY)*CircleSize
      EnemyColour=Wrap(BadGuy,0,7)
      
      // New collision code
      EnemyCollide=DrawImg(EnemyX,EnemyY,ShowInvader,EnemyColour)
      If Collide(BulletCollide,EnemyCollide)
        // If the bullet hits the Enemy

        // Turn off the Enemy
        EnemyAlive(BadGuy)=0
        // Make a Bang sound
        PlaySFX("Explode_Low_2")  
        // Gimme 5 points!
        Gimme 5,EnemyX,EnemyY      
        
        // Move the Bullet off the screen
        BulletY=-100
      EndIf // Collision
      
    EndIf // Alive
    
  Next // Each BadGuy

  If BaddyCount<1 then Gosub NextLevel

  
  // Quit button (Return)
  If GamePad(ButtonStart) then GameRun=0
    // We can hit the Return key to quit
    
  SetFontSize 16
  Text 320,16,Score,1
Return

Fixing the Bullet

I'm not sure if you've noticed this, but a single bullet can kill loads of baddies!

During our collision test we only do a quick check of whether the bullet is touching a bad guy.

If Collide(BulletCollide,EnemyCollide)
...
  // Move the Bullet off the screen
  BulletY=-100
EndIf

We move BulletY off the screen after a colision, but at no point do we take that position into account when checking collisions.

This means (and no doubt you've noticed) that sometimes "no bullet" can hit enemies before they're even in the level, since the bullet and enemy are both around -100 pixels above the screen.

Offscreen Panic for the poor Invader

Offscreen Panic for the poor Invader

This is remarkably easy to fix. All we have to do is double-check that the BulletY is greater than 0 (or, that it's still on-screen.)

Simply add a "And BulletY>0" check onto the If.

If Collide(BulletCollide,EnemyCollide) And BulletY>0
Now each bullet only hits once!
That was a quick fix.

Weapons Array

For our own bullet all we really use are BulletX and BulletY.

For the enemy bullets we want more than one at a time, so we'll need an array with enough slots for .. Let's go with 50 bullets on the screen? That sounds ok, right?

And a name. What should we call our Enemy Bullet Array?

AlienDeathBlastsFromAbove()?

 

Dim EnemyBullet(50,2)

Let's go with EnemyBullet() for our Array name.

As usual, place the Array towards the top of our Program, so that GotoJSE knows how to handle it.

Inside the .StartGame subroutine, let's prepare our array.

  XPosition=1
  YPosition=2
  For b=0 to 50
    EnemyBullet(b,XPosition)=0
    EnemyBullet(b,YPosition)=1000
  Next

  EnemyBulletTo=0  // We increase this when an enemy fires a bullet.
  EnemyBulletDelay=0 // We'll have a delay between every bullet drop.

We've created a EnemyBulletTo variable.

Every time a baddy fires a bullet, we'll position the bullet at slot "EnemyBulletTo", and then tick EnemyBulletTo onwards, wrapping between 0 and 50.

We've also set the Y Position to be 1,000 pixels down the screen. We can treat anything that's off the bottom of the screen, as if it no longer exists.

This works like when our own bullets fly off above the top of the screen, except the enemy bullets will be flying down the screen, instead.

Our game, so far.

// My Game
// by Mr Green
// Created 2025/12/16

Symbol 0,"2__0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0P?0_0/P?0!P0,P0_0/P0,P0?P,00P0_0/P00P,0.P,00P0_0/P00P,0.P,00P0_0/P00P,0.P,00P!0!P!00P,0_0?P@0_0_0_0_0_0/P!0.P!0_0_0_0_0_0.P?0PP0P?0_0_0!PP0_0_0/P.0PP0P.0_0_0/PP0_0_0_0,PP0_0@P_P,0PP0P_P,0.P0_0_0@P0.P0_0_0@P0.P0_0_0@P0.P0@P0@P0@P0.PP0/P0@P0/PP0?P0/P0P!0P0/P0!P?0,P0@P0,P?0@P,0,P0@P0,P,0_0?P,0@P,";
Symbol 1,"2__0_0_0_0_0_0_0_0_0_0_0_0_0_0_00P,0_0_0@P_P,0_0?P_P,0?P0_0?P0?P,0@P0_0?P0_0/P0_0?P0_0/P?0!P?0_0_00P0!P0_0_0!P@0_0_0_0_0_0/P!0.P!0_0_0_0_0_0.P?0PP0P?0_0_0!PP0_0_0/P.0PP0P.0_0_0/PP0_0_0_0,PP0_0!P_P?0PP0P_P!0_0_0_0,PP0_0_0_0,PP0_0_0_0,PP0_0P0@P0_0PP0_0P0@P0_0PP0_0P0P!0P0_0PP0_0P0P0.P0P0_0PP0_0P0P0.P0P0_0P,0/P,0P!0P,0/P.0_0_0_0P.0_0_0_0PP";
Symbol 2,"2__0_0_0_0_0_0_0_0_0_0_0_0_0NN00NN0_0_0/NPQNNQPN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0?NPPQ.PPN0_0_0.NPPQ.PPN0_0_0.NPPQ.PPN0_0_0.NPPQ.PPN0_0_0.NPQ!PN0_0_0.NPQ!PN0_0_0.NPQ!PN0_0_0,NPPQ!PPN0_0_00NPPQ!PPN0_0_00NPPQ!PPN0_0_00NPPQ!PPN0_0,P_P_P_P0,PS_S_S_SP0PS_S_S_S,PPSSR,S,R,S,RRS,R,S,R,SSP0S_S_S_S,0,S_S_S_S";
Symbol 3,"2__P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P/";
Symbol 4,"2__0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_z0_0_0_0020,20_0_0_00;0_0_0_0z0;1;0z0_0_0_0;0_0_0_0020,20_0_0_00z";

AntiAlias Off // Turn off AntiAlias, for Crispy Invaders


// Arrays
Dim EnemyAlive(20)
Dim EnemyBullet(50,2)


// Main Loop
GameRun=0
Repeat
  If GameRun==0 then Gosub .StartScreen
  If GameRun==1 then Gosub .InGame
Flip
Forever


// This is our Titlescreen
.StartScreen
CLS
ResetDraw

  ShowInvader=1
  If Wrap(Frames,0,20)<10 Then ShowInvader=0
  DrawImg 320,160,ShowInvader

  SetFontSize 32
  Text 320,240,"Titlescreen!",1
  SetFontSize 16
  Text 320,360,"Press Fire to Play",1
  
  If GamePad(ButtonA)
    Gosub StartGame
    GameRun=1
  Endif
Return



// This resets the current level
.NextLevel
  DropDown=480
  For BadGuy=1 to 20
    EnemyAlive(BadGuy)=1
  Next

  // New variables to multiply the EnemyX and Y Angles
  WaveX=Rand(1,3)
  WaveY=Rand(1,3)
  Gap=Rand(3,15)
  
Return


// This prepares our game when the A Button is hit
.StartGame
  Score=0
  PlayerX=320 // Middle of the screen
  PlayerY=440 // Bottom of the screen
  
  BulletX=0  // Reset player's bullet
  BulletY=0

  XPosition=1
  YPosition=2
  For b=0 to 50
    EnemyBullet(b,XPosition)=0
    EnemyBullet(b,YPosition)=1000
  Next
  
  EnemyBulletTo=0  // We increase this when an enemy fires a bullet.
  EnemyBulletDelay=0 // We'll have a delay between every bullet drop.

  Gosub NextLevel
Return



// This is our ingame loop
.InGame
CLS
ResetDraw

  // Starfield using Symbol 4
  Starfield 1,0,4

  // Player
  DrawImg PlayerX,PlayerY,2
  Speed=4
  If GamePad(ButtonLeft)>0.5 then PlayerX=PlayerX-Speed
  If GamePad(ButtonRight)>0.5 then PlayerX=PlayerX+Speed
  // Using >0.5 allows players to use a thumbstick, too!
  
  PlayerX=Limit(PlayerX,32,640-32)
  
  
  // Player's Bullet
  // Keep the Bullet going upwards
  BulletSpeed=16
  BulletY=Limit(BulletY-BulletSpeed, -100,480)

  // Draw the Bullet
  SetScale 0.2,0.5

  BulletCollide=DrawImg(BulletX,BulletY,3)

  // And reset things afterwards
  ResetDraw
  
  If BulletY<0 And GamePad(ButtonA)
    BulletX=PlayerX
    BulletY=PlayerY
    PlaySFX("Lazer_2")
  EndIf

  
  // Enemy Section
  
  // Reuse this to keep the invaders animated
  ShowInvader=1
  if Wrap(Frames,0,20)<10 then ShowInvader=0
  
  CircleSize=160
  // New Variable for Circle Position
  CircleX=320+Sin(Frames)*160
  CircleY=200-DropDown
  DropDown=Limit( DropDown-5 ,0,480)
    // Why not try changing this value, too.
      // The higher the number the faster the drop.
      // Make it slower to have the invaders creep into view!
  


  BaddyCount=0
  // A loop for 20 Bad Guys
  For BadGuy=1 to 20
    // If Alive
    If EnemyAlive(BadGuy)==1
    
      BaddyCount=BaddyCount+1

      // Each enemy should be further around the loop.
      PlusAngle=BadGuy*Gap
      Angle=Wrap(Frames + PlusAngle,0,360)
      EnemyX=CircleX+Cos(Angle*WaveX)*CircleSize
      EnemyY=CircleY+Sin(Angle*WaveY)*CircleSize
      EnemyColour=Wrap(BadGuy,0,7)
      
      // New collision code
      EnemyCollide=DrawImg(EnemyX,EnemyY,ShowInvader,EnemyColour)
      If Collide(BulletCollide,EnemyCollide) And BulletY>0
        // If the bullet hits the Enemy

        // Turn off the Enemy
        EnemyAlive(BadGuy)=0
        // Make a Bang sound
        PlaySFX("Explode_Low_2")  
        // Gimme 5 points!
        Gimme 5,EnemyX,EnemyY      
        
        // Move the Bullet off the screen
        BulletY=-100
      EndIf // Collision
      
    EndIf // Alive
    
  Next // Each BadGuy

  If BaddyCount<1 then Gosub NextLevel

  
  // Quit button (Return)
  If GamePad(ButtonStart) then GameRun=0
    // We can hit the Return key to quit
    
  SetFontSize 16
  Text 320,16,Score,1
Return

Bullet Delay

First thing's first. A delay to ensure the screen doesn't get flooded with enemy bullets, as fun as that might sound!

Any time an enemy drops a bullet, we'll reset this the delay to a high value. We won't drop any more bullets until this value is back to 0 again.

At the top of the // Enemy Section of our InGame area, we have a number of "per frame" things that keep ticking away. We'll place our delay's workings in there.

  // Bullet Delay workings
  EnemyBulletDelay=Limit(EnemyBulletDelay-1, 0,200)
Subtract one each frame, but stop at 0.

Readying Bullets

OK, we're heading inside our enemy loop, now. We'll create a new Bullet Dropping section, underneath the Endif // Collision, and before the Endif // Alive

  // Dropping Bullets
  If Rand(0,10)==1 and EnemyBulletDelay==0
    
  EndIf

We ensure that EnemyBulletDelay is back to 0, and also add a little randomness so it isn't always the same invader dropping bullets all the time.

There's a one in 11 chance that the invader will drop a bullet.

Feel free to play with this random number a little, to see how it impacts the bullet dropping.

Positioning Bullets

OK, here's what we need to do next.

Wrap EnemyBulletTo, increase by one and wrap between 1 and 50
Move the Bullet in slot "EnemyBulletTo" to the X and Y position of the current Enemy
  // Dropping Bullets
  If Rand(0,10)==1 and EnemyBulletDelay==0
    EnemyBulletTo=Wrap(EnemyBulletTo+1,1,50)
    // Wrap the value between 1 and 50

    // Then position the Bullet
    EnemyBullet(EnemyBulletTo, XPosition)=EnemyX
    EnemyBullet(EnemyBulletTo, YPosition)=EnemyY

    // Reset the Delay
    EnemyBulletDelay=100
    
    // And go "peow!"
    PlaySFX("Lazer_1")
        
  EndIf

From here, all the bullets will simply drop down the screen, just like our bullet "falls" up the screen.

But now we need to draw them!

Bullet Loop

We already have a place where we draw our player's bullet, and we've already set the size to be 0.2, 0,5 so that the bullet is smaller.

Let's reuse that area for drawing enemy bullets, too

In this area, we'll create a new For-Next loop.

  BulletCollide=DrawImg(BulletX,BulletY,3)
  
  // ^^ this line is already here, drawing the player bullet.
  // vv  Add this new loop below it.

  // Handle Enemy Bullets
  For b=1 to 50
    
  Next

  // vv  then this is already in our code.
  
  // And reset things afterwards
  ResetDraw
It's inside this For-Next loop that we'll code the drawing and collision of our enemy bullets!

Our game, so far.

// My Game
// by Mr Green
// Created 2025/12/16

Symbol 0,"2__0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0P?0_0/P?0!P0,P0_0/P0,P0?P,00P0_0/P00P,0.P,00P0_0/P00P,0.P,00P0_0/P00P,0.P,00P!0!P!00P,0_0?P@0_0_0_0_0_0/P!0.P!0_0_0_0_0_0.P?0PP0P?0_0_0!PP0_0_0/P.0PP0P.0_0_0/PP0_0_0_0,PP0_0@P_P,0PP0P_P,0.P0_0_0@P0.P0_0_0@P0.P0_0_0@P0.P0@P0@P0@P0.PP0/P0@P0/PP0?P0/P0P!0P0/P0!P?0,P0@P0,P?0@P,0,P0@P0,P,0_0?P,0@P,";
Symbol 1,"2__0_0_0_0_0_0_0_0_0_0_0_0_0_0_00P,0_0_0@P_P,0_0?P_P,0?P0_0?P0?P,0@P0_0?P0_0/P0_0?P0_0/P?0!P?0_0_00P0!P0_0_0!P@0_0_0_0_0_0/P!0.P!0_0_0_0_0_0.P?0PP0P?0_0_0!PP0_0_0/P.0PP0P.0_0_0/PP0_0_0_0,PP0_0!P_P?0PP0P_P!0_0_0_0,PP0_0_0_0,PP0_0_0_0,PP0_0P0@P0_0PP0_0P0@P0_0PP0_0P0P!0P0_0PP0_0P0P0.P0P0_0PP0_0P0P0.P0P0_0P,0/P,0P!0P,0/P.0_0_0_0P.0_0_0_0PP";
Symbol 2,"2__0_0_0_0_0_0_0_0_0_0_0_0_0NN00NN0_0_0/NPQNNQPN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0?NPPQ.PPN0_0_0.NPPQ.PPN0_0_0.NPPQ.PPN0_0_0.NPPQ.PPN0_0_0.NPQ!PN0_0_0.NPQ!PN0_0_0.NPQ!PN0_0_0,NPPQ!PPN0_0_00NPPQ!PPN0_0_00NPPQ!PPN0_0_00NPPQ!PPN0_0,P_P_P_P0,PS_S_S_SP0PS_S_S_S,PPSSR,S,R,S,RRS,R,S,R,SSP0S_S_S_S,0,S_S_S_S";
Symbol 3,"2__P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P/";
Symbol 4,"2__0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_z0_0_0_0020,20_0_0_00;0_0_0_0z0;1;0z0_0_0_0;0_0_0_0020,20_0_0_00z";

AntiAlias Off // Turn off AntiAlias, for Crispy Invaders


// Arrays
Dim EnemyAlive(20)
Dim EnemyBullet(50,2)


// Main Loop
GameRun=0
Repeat
  If GameRun==0 then Gosub .StartScreen
  If GameRun==1 then Gosub .InGame
Flip
Forever


// This is our Titlescreen
.StartScreen
CLS
ResetDraw

  ShowInvader=1
  If Wrap(Frames,0,20)<10 Then ShowInvader=0
  DrawImg 320,160,ShowInvader

  SetFontSize 32
  Text 320,240,"Titlescreen!",1
  SetFontSize 16
  Text 320,360,"Press Fire to Play",1
  
  If GamePad(ButtonA)
    Gosub StartGame
    GameRun=1
  Endif
Return



// This resets the current level
.NextLevel
  DropDown=480
  For BadGuy=1 to 20
    EnemyAlive(BadGuy)=1
  Next

  // New variables to multiply the EnemyX and Y Angles
  WaveX=Rand(1,3)
  WaveY=Rand(1,3)
  Gap=Rand(3,15)
  
Return


// This prepares our game when the A Button is hit
.StartGame
  Score=0
  PlayerX=320 // Middle of the screen
  PlayerY=440 // Bottom of the screen
  
  BulletX=0  // Reset player's bullet
  BulletY=0


  XPosition=1
  YPosition=2

  For b=0 to 50
    EnemyBullet(b,XPosition)=0
    EnemyBullet(b,YPosition)=1000
  Next

  EnemyBulletTo=0  // We increase this when an enemy fires a bullet.
  EnemyBulletDelay=0 // We'll have a delay between every bullet drop.
  
  Gosub NextLevel
Return



// This is our ingame loop
.InGame
CLS
ResetDraw

  // Starfield using Symbol 4
  Starfield 1,0,4

  // Player
  DrawImg PlayerX,PlayerY,2
  Speed=4
  If GamePad(ButtonLeft)>0.5 then PlayerX=PlayerX-Speed
  If GamePad(ButtonRight)>0.5 then PlayerX=PlayerX+Speed
  // Using >0.5 allows players to use a thumbstick, too!
  
  PlayerX=Limit(PlayerX,32,640-32)
  
  
  // Player's Bullet
  // Keep the Bullet going upwards
  BulletSpeed=16
  BulletY=Limit(BulletY-BulletSpeed, -100,480)

  // Draw the Bullet
  SetScale 0.2,0.5

  BulletCollide=DrawImg(BulletX,BulletY,3)

  // Handle Enemy Bullets
  For b=1 to 50
    
  Next

  // And reset things afterwards
  ResetDraw
  
  If BulletY<0 And GamePad(ButtonA)
    BulletX=PlayerX
    BulletY=PlayerY
    PlaySFX("Lazer_2")
  EndIf

  
  // Enemy Section

  // Reuse this to keep the invaders animated
  ShowInvader=1
  if Wrap(Frames,0,20)<10 then ShowInvader=0
  
  CircleSize=160
  // New Variable for Circle Position
  CircleX=320+Sin(Frames)*160
  CircleY=200-DropDown
  DropDown=Limit( DropDown-5 ,0,480)
    // Why not try changing this value, too.
      // The higher the number the faster the drop.
      // Make it slower to have the invaders creep into view!
  
  // Bullet Delay workings
  EnemyBulletDelay=Limit(EnemyBulletDelay-1, 0,200)


  BaddyCount=0
  // A loop for 20 Bad Guys
  For BadGuy=1 to 20
    // If Alive
    If EnemyAlive(BadGuy)==1
    
      BaddyCount=BaddyCount+1

      // Each enemy should be further around the loop.
      PlusAngle=BadGuy*Gap
      Angle=Wrap(Frames + PlusAngle,0,360)
      EnemyX=CircleX+Cos(Angle*WaveX)*CircleSize
      EnemyY=CircleY+Sin(Angle*WaveY)*CircleSize
      EnemyColour=Wrap(BadGuy,0,7)
      
      // New collision code
      EnemyCollide=DrawImg(EnemyX,EnemyY,ShowInvader,EnemyColour)
      If Collide(BulletCollide,EnemyCollide) And BulletY>0
        // If the bullet hits the Enemy

        // Turn off the Enemy
        EnemyAlive(BadGuy)=0
        // Make a Bang sound
        PlaySFX("Explode_Low_2")  
        // Gimme 5 points!
        Gimme 5,EnemyX,EnemyY      
        
        // Move the Bullet off the screen
        BulletY=-100
      EndIf // Collision
      
      // Dropping Bullets
      If Rand(0,10)==1 and EnemyBulletDelay==0
        EnemyBulletTo=Wrap(EnemyBulletTo+1,1,50)
        // Wrap the value between 1 and 50
    
        // Then position the Bullet
        EnemyBullet(EnemyBulletTo, XPosition)=EnemyX
        EnemyBullet(EnemyBulletTo, YPosition)=EnemyY
    
        // Reset the Delay
        EnemyBulletDelay=100
        
        // And go "peow!"
        PlaySFX("Lazer_1")

      EndIf
      
    EndIf // Alive
    
  Next // Each BadGuy

  If BaddyCount<1 then Gosub NextLevel

  
  // Quit button (Return)
  If GamePad(ButtonStart) then GameRun=0
    // We can hit the Return key to quit
    
  SetFontSize 16
  Text 320,16,Score,1
Return

Drawing Bullets

OK, all we have to do here is..

If a bullet has a Y position less than the height of the screen ( <480 )
Move it down a bit
Draw it
Check if it hits the player
  For b=1 to 50
    If EnemyBullet(b, YPosition)<480
      EnemyBullet(b, YPosition)=EnemyBullet(b, YPosition)+8
      ThisCollide=DrawImg(EnemyBullet(b, XPosition),EnemyBullet(b, YPosition),3)
      If Collide(ThisCollide,PlayerCollide) then Dead=1
    EndIf
  Next

There we go!

Except we haven't created a PlayerCollide.

 

Oh, good call, Mr Green.

Scroll up a bit to find

  // Player
  DrawImg PlayerX,PlayerY,2

And change the DrawImg so that it returns a PlayerCollide value that we can use with the Collide command.

  // Player
  PlayerCollide=DrawImg(PlayerX,PlayerY,2)

Game Over

We've set a new Dead variable. We should probably do something with that.

During the .StartGame subroutine, it's a good idea to reset Dead to 0. .. So we're not Dead!

You can throw it next to where we reset the Score to 0, too.

  Score=0
  Dead=0

Those are two fairly common things that we need to remember to reset in a StartGame subroutine. Score and Death!

It's probably good to set a number of Lives here, at the start of a game, too, but for now we'll stick to a one-life game.

Next we need to deal with the Dead!

  If Dead>0
    If Dead==1 then PlaySFX("Explode_High_4")
    Dead=Dead+1
    EndGame "Too Bad"
    If Dead>120 Then GameRun=0
  EndIf

We can place this down near the bottom of our .InGame subroutine, since it handles things quite a lot like how the Quit button code works.

If we're dead, keep Dead ticking away, and once it's over 120 (a nice long delay) then exit our loop by setting GameRun back to 0 again.

Remember our GameRun loop? Chapter 16 seems a whole world away, now!

Our game, so far.

// My Game
// by Mr Green
// Created 2025/12/16

Symbol 0,"2__0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0P?0_0/P?0!P0,P0_0/P0,P0?P,00P0_0/P00P,0.P,00P0_0/P00P,0.P,00P0_0/P00P,0.P,00P!0!P!00P,0_0?P@0_0_0_0_0_0/P!0.P!0_0_0_0_0_0.P?0PP0P?0_0_0!PP0_0_0/P.0PP0P.0_0_0/PP0_0_0_0,PP0_0@P_P,0PP0P_P,0.P0_0_0@P0.P0_0_0@P0.P0_0_0@P0.P0@P0@P0@P0.PP0/P0@P0/PP0?P0/P0P!0P0/P0!P?0,P0@P0,P?0@P,0,P0@P0,P,0_0?P,0@P,";
Symbol 1,"2__0_0_0_0_0_0_0_0_0_0_0_0_0_0_00P,0_0_0@P_P,0_0?P_P,0?P0_0?P0?P,0@P0_0?P0_0/P0_0?P0_0/P?0!P?0_0_00P0!P0_0_0!P@0_0_0_0_0_0/P!0.P!0_0_0_0_0_0.P?0PP0P?0_0_0!PP0_0_0/P.0PP0P.0_0_0/PP0_0_0_0,PP0_0!P_P?0PP0P_P!0_0_0_0,PP0_0_0_0,PP0_0_0_0,PP0_0P0@P0_0PP0_0P0@P0_0PP0_0P0P!0P0_0PP0_0P0P0.P0P0_0PP0_0P0P0.P0P0_0P,0/P,0P!0P,0/P.0_0_0_0P.0_0_0_0PP";
Symbol 2,"2__0_0_0_0_0_0_0_0_0_0_0_0_0NN00NN0_0_0/NPQNNQPN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0!NPQ.PN0_0_0?NPPQ.PPN0_0_0.NPPQ.PPN0_0_0.NPPQ.PPN0_0_0.NPPQ.PPN0_0_0.NPQ!PN0_0_0.NPQ!PN0_0_0.NPQ!PN0_0_0,NPPQ!PPN0_0_00NPPQ!PPN0_0_00NPPQ!PPN0_0_00NPPQ!PPN0_0,P_P_P_P0,PS_S_S_SP0PS_S_S_S,PPSSR,S,R,S,RRS,R,S,R,SSP0S_S_S_S,0,S_S_S_S";
Symbol 3,"2__P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P_P/";
Symbol 4,"2__0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_z0_0_0_0020,20_0_0_00;0_0_0_0z0;1;0z0_0_0_0;0_0_0_0020,20_0_0_00z";

AntiAlias Off // Turn off AntiAlias, for Crispy Invaders


// Arrays
Dim EnemyAlive(20)
Dim EnemyBullet(50,2)


// Main Loop
GameRun=0
Repeat
  If GameRun==0 then Gosub .StartScreen
  If GameRun==1 then Gosub .InGame
Flip
Forever


// This is our Titlescreen
.StartScreen
CLS
ResetDraw

  ShowInvader=1
  If Wrap(Frames,0,20)<10 Then ShowInvader=0
  DrawImg 320,160,ShowInvader

  SetFontSize 32
  Text 320,240,"Titlescreen!",1
  SetFontSize 16
  Text 320,360,"Press Fire to Play",1
  
  If GamePad(ButtonA)
    Gosub StartGame
    GameRun=1
  Endif
Return



// This resets the current level
.NextLevel
  DropDown=480
  For BadGuy=1 to 20
    EnemyAlive(BadGuy)=1
  Next

  // New variables to multiply the EnemyX and Y Angles
  WaveX=Rand(1,3)
  WaveY=Rand(1,3)
  Gap=Rand(3,15)
  
Return


// This prepares our game when the A Button is hit
.StartGame
  Score=0
  Dead=0

  PlayerX=320 // Middle of the screen
  PlayerY=440 // Bottom of the screen
  
  BulletX=0  // Reset player's bullet
  BulletY=0


  XPosition=1
  YPosition=2

  For b=0 to 50
    EnemyBullet(b,XPosition)=0
    EnemyBullet(b,YPosition)=1000
  Next

  EnemyBulletTo=0  // We increase this when an enemy fires a bullet.
  EnemyBulletDelay=0 // We'll have a delay between every bullet drop.
  
  Gosub NextLevel
Return



// This is our ingame loop
.InGame
CLS
ResetDraw

  // Starfield using Symbol 4
  Starfield 1,0,4

  // Player
  PlayerCollide=DrawImg(PlayerX,PlayerY,2)
  Speed=4
  If GamePad(ButtonLeft)>0.5 then PlayerX=PlayerX-Speed
  If GamePad(ButtonRight)>0.5 then PlayerX=PlayerX+Speed
  // Using >0.5 allows players to use a thumbstick, too!
  
  PlayerX=Limit(PlayerX,32,640-32)
  
  
  // Player's Bullet
  // Keep the Bullet going upwards
  BulletSpeed=16
  BulletY=Limit(BulletY-BulletSpeed, -100,480)

  // Draw the Bullet
  SetScale 0.2,0.5

  BulletCollide=DrawImg(BulletX,BulletY,3)

  // Handle Enemy Bullets
  For b=1 to 50
    If EnemyBullet(b, YPosition)<480
      EnemyBullet(b, YPosition)=EnemyBullet(b, YPosition)+8
      ThisCollide=DrawImg(EnemyBullet(b, XPosition),EnemyBullet(b, YPosition),3)
      If Collide(ThisCollide,PlayerCollide) then Dead=1
    EndIf
  Next

  // And reset things afterwards
  ResetDraw
  
  If BulletY<0 And GamePad(ButtonA)
    BulletX=PlayerX
    BulletY=PlayerY
    PlaySFX("Lazer_2")
  EndIf

  
  // Enemy Section

  // Reuse this to keep the invaders animated
  ShowInvader=1
  if Wrap(Frames,0,20)<10 then ShowInvader=0
  
  CircleSize=160
  // New Variable for Circle Position
  CircleX=320+Sin(Frames)*160
  CircleY=200-DropDown
  DropDown=Limit( DropDown-5 ,0,480)
    // Why not try changing this value, too.
      // The higher the number the faster the drop.
      // Make it slower to have the invaders creep into view!
  
  // Bullet Delay workings
  EnemyBulletDelay=Limit(EnemyBulletDelay-1, 0,200)


  BaddyCount=0
  // A loop for 20 Bad Guys
  For BadGuy=1 to 20
    // If Alive
    If EnemyAlive(BadGuy)==1
    
      BaddyCount=BaddyCount+1

      // Each enemy should be further around the loop.
      PlusAngle=BadGuy*Gap
      Angle=Wrap(Frames + PlusAngle,0,360)
      EnemyX=CircleX+Cos(Angle*WaveX)*CircleSize
      EnemyY=CircleY+Sin(Angle*WaveY)*CircleSize
      EnemyColour=Wrap(BadGuy,0,7)
      
      // New collision code
      EnemyCollide=DrawImg(EnemyX,EnemyY,ShowInvader,EnemyColour)
      If Collide(BulletCollide,EnemyCollide) And BulletY>0
        // If the bullet hits the Enemy

        // Turn off the Enemy
        EnemyAlive(BadGuy)=0
        // Make a Bang sound
        PlaySFX("Explode_Low_2")  
        // Gimme 5 points!
        Gimme 5,EnemyX,EnemyY      
        
        // Move the Bullet off the screen
        BulletY=-100
      EndIf // Collision
      
      // Dropping Bullets
      If Rand(0,10)==1 and EnemyBulletDelay==0
        EnemyBulletTo=Wrap(EnemyBulletTo+1,1,50)
        // Wrap the value between 1 and 50
    
        // Then position the Bullet
        EnemyBullet(EnemyBulletTo, XPosition)=EnemyX
        EnemyBullet(EnemyBulletTo, YPosition)=EnemyY
    
        // Reset the Delay
        EnemyBulletDelay=100
        
        // And go "peow!"
        PlaySFX("Lazer_1")

      EndIf
      
    EndIf // Alive
    
  Next // Each BadGuy

  If BaddyCount<1 then Gosub NextLevel

  
  // Quit button (Return)
  If GamePad(ButtonStart) then GameRun=0
    // We can hit the Return key to quit


  If Dead>0
    If Dead==1 then PlaySFX("Explode_High_4")
    Dead=Dead+1
    EndGame "Too Bad"
    If Dead>120 Then GameRun=0
  EndIf
    
  SetFontSize 16
  Text 320,16,Score,1
Return
1

Little Tweaks

There's lots of little tweaks we can do to these bullets.

For starters, they're all drawn in "Colour 0", so they're all green like our own bullets.

Maybe it would be nice to have them be different colours?

You can play with the bullet speeds, and more, as well.

2

Be Fair

Sometimes a bullet can be launched by a bad guy that's right down near the bottom, making it all but impossible to avoid.

Maybe you could tweak it so that bullets only launch when an enemy is above the middle of the screen?

"EnemyY<240"?

3

EndGame?

Where did EndGame come from, in that last code box?
That kinda snuck in, didn't it?
What does it do?
Why is it there?

Hmm. I guess we're going to have to find out.

But it'll have to wait until Chapter 25, because first, we're going to have to blow stuff up.