// 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
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
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
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?
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.
// 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
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)
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.
OK, here's what we need to do next.
// 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!
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
// 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
OK, all we have to do here is..
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!
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)
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.
// 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
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.
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"?
EndGame?
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.