LiveCode Game Tutorial: Side Scrolling Shoot'em'up

Creating Games? Developing something for fun?

Moderators: FourthWorld, heatherlaine, Klaus, kevinmiller, robinmiller

xAction
Posts: 86
Joined: Sun Oct 03, 2021 4:14 am

Re: LiveCode Game Tutorial: Side Scrolling Shoot'em'up

Post by xAction » Mon Oct 04, 2021 12:12 pm

Part 11.Enemy Factory
EnemyFactoryv1.png
Version 1 of Enemy Factory
EnemyFactoryv1.png (96.64 KiB) Viewed 9275 times
So far we have:
1. a title screen with music
2. player graphic that moves about
3. a scrolling landscape
4. missiles

Now we need something to shoot, once again instead of dumping everything directly into our game I'll make a small stack for developing some enemies and testing their scripts first.

So the Enemy Factory stack works nearly exactly like the Missile Factory:
We generate graphics with "Regular" style, this time with a bunch of random numbers for their colors, linesize, occasional dashes, outerGlow and sometims their opaque property with an innerShadow, we keep their script handy in a button and then copy that script to the objects.
Here'se the basic code
MakeEnemies

Code: Select all

on MakeEnemies
   put 0 into Rows
   put 0 into Cols
   put the script of button "EnemyScript" into EnemyBehavior
   repeat with N = 1 to 20
      create graphic
      set the script of the last graphic to EnemyBehavior
      set the name of the last graphic to "Enemy"&N
      set the style of the last graphic to "regular"
      set the polySides of the last graphic to 3+random(10)
      set the height of the last graphic to 15+random(30)
      set the width of the last graphic to 15+random(30)
      set the loc of the last graphic to 50+(50*Cols), 50+(Rows*50)
      put item N of colorList into tColor
      set the foregroundColor of the last graphic to tColor
      put random(10) into dashrand
      if dashrand > 7 then
         set the dashes of the last graphic to random(3)
      end if
      set the lineSize of the last graphic to random(4)
      Glamour
      add 1 to Cols
      if Cols > 4 then
         put 0 into Cols
         add 1 to Rows
      end if
   end repeat
end MakeEnemies
Glamour

Code: Select all

on Glamour
   put item random(20) of colorList into tColor
   put RGBFromColorName(tColor) into fgColor
   add random(100) to item 1 of fgColor
   add random(100) to item 2 of fgColor
   add random(100) to item 2 of fgColor
   set outerGlow of the last graphic to true
   set the outerGlow["color"] of the last graphic to fgColor
   set the outerGlow["spread"] of the last graphic to 24+random(40)
   set the outerGlow["size"] of the last graphic to 24+random(40)
   set colorOverlay of the last graphic to false
   subtract random(100) from item 1 of fgColor
   subtract random(100) from item 2 of fgColor
   subtract random(100) from item 2 of fgColor
   put random(10)into OpaRand
   if OpaRand > 8 then
      set the opaque of the last graphic to true
      set the backgroundcolor of the last graphic to "White"
      set innerShadow of the last graphic to true
      set the innerShadow["color"] of the last graphic to fgColor
      set the innerShadow["spread"] of the last graphic to 24+random(40)
      set the innerShadow["size"] of the last graphic to 24+random(40)
   end if
end Glamour
I borrowed a function from the Livecode website to covert color names to RGB value so I could add some random values to the base colorList I've set up (in a local Variable): RGBFromColorName.

The enemy scripts call ActivateBullets from the main stack script so I added the enemy bullets in the stack along with their scripts and everything works mostly just like the Missile Factory, only we don't test/adjust bullet speeds since they are randomized and rely on the player to target...hmm, might have to make the Enemy Factory bigger to test against a "Player" object. But for now these are the elements:

button "Make Enemies" -- calls MakeEnemies handler
button "Get Enemies" -- calls GetEnemies handler
button "Stage Enemies" -- calls StageEnemies handler
button "Activate Enemies" -- calls ActivateEnemies and continues to do so while the mouse is the button rect
button "Edit Enemy Script"[i/] -- opens script editor to the script of button "EnemyScript"[/i]
button "EnemyScript" -- contains the script that is copied to the "Enemy" graphic objects

There's another column of buttons for the missiles that do all the same stuff for them, with the addition of:
button "Style" -- sets the graphic style of the bullets to Regular, Rectangle, Oval or Line

The GetEnemies, StageEnemies, GetBullets, StageBullets scripts work like their counterparts do in the Missile Factory stack, ie, they either present the objects on the stack or hide them away off screen.
The "Get..." buttons and their handlers also act to update the scripts of the objects, use those if you've modified the script of their respetcive script "*Script" buttons.

Okay next to explain the script of button "EnemyScript the ActivateEnemies, InactiveEnemies, and EnemyExplosions handlers and then we'll get into some actual math with the bullets.

Enemy_Factory.zip
Enemy Factory version 4 feature complete for tutorial parts 11 through 14.
(13.67 KiB) Downloaded 168 times
Last edited by xAction on Tue Oct 05, 2021 1:07 pm, edited 5 times in total.

xAction
Posts: 86
Joined: Sun Oct 03, 2021 4:14 am

Re: LiveCode Game Tutorial: Side Scrolling Shoot'em'up

Post by xAction » Mon Oct 04, 2021 10:00 pm

Part 11b. Enemy Factory: Enemy Handlers

-------- BEGIN Script of the button "EnemyScript -----
This is copied into every enemy object, I'll break it down into parts

Local Variables:

Code: Select all

local enemyActivated=false
local speedX=0, speedY=0
local targetLoc = "0,0"
local targetRect = "0,0,0,0"
local bulletDelay=0
local enemyXY
local hMod=2,wMod=2
If this enemy is activated via the stack ActivateEnemies handler then enemyActivated is set to true and the rest of the enemy scripts within the object can be carried out, else the object sits idle waiting for activation.
The targetLoc is a point in 2D space that the enemy will want to travel to
The targetrect is a virtual rectangle surroudning the targetLoc , if the enemy loc xy is within that rect then the enemy will deactivate
bulletDelay limits the enemy firing at the player to every tenth run of the script
enemyXY tracks the enemy's 2D position across handlers in the object script

hMod & wMod are part of a disabled ShapeShiftFunk handler that we'll leave out for now. It's basically runtime animation by modifing height and width, but has some side effects in its current state.

ActivateEnemy

Code: Select all

on ActivateEnemy
  if enemyActivated is true then exit ActivateEnemy
   put true into enemyActivated
   send EnemyTargetLoc to me in 1 milliseconds
   --// fly enemy fly!
   send EnemyTravel to me in 60 milliseconds
end ActivateEnemy
If the enemy is already activated, exit the ActiveEnemy handler so the enemy doesn't teleport off screen.
First the enemy teleports to a random position, then picks a random position to travel to via EnemyTargetLoc handler then gets the go head to travel with EnemyTravel handler

EnemyTargetLoc

Code: Select all

on EnemyTargetLoc
   put the loc of me into enemyXY
   put random (10) into rX
   -- set up a destination for the enemy
   if rX > 5 then 
      put width of this stack+20 into newX
      put -30 into  item 1 of enemyXY
   else
      put -20 into newX
      put width of this stack+20 into item 1 of enemyXY
   end if
   -- target location Y coordinate
   put random(height of this stack)-random(400) into item 2 of enemyXY
   --if the origin Y is low then go high
   if item 2 of enemyXY > 200 then 
      put -20 into newY
   else
      put random(height of this stack+250) into newY
   end if
   put  newX-10, newY-10, newX+10, newY+10 into targetRect
   set the loc of me to enemyXY
   put   newX & comma & newY into targetLoc
   put random(10)+6 into speedX
   put random(10)+6 into speedY
end EnemyTargetLoc
If the enemy spawns or rather teleports offscreen at top left of the window, then it should be given a targetLoc that is offscreen near the bottom right. In theory. Sometimes they stop midscreen and don't deactivate until the player scrolls them offscreen, which shouldn't be happening since the player movement shouldn't effect the enemy movement, but somewhere, somehow the enemies inherit player movement, as do the bullets. We'll troubelshoot that issue later.

EnemyTravel

Code: Select all

on EnemyTravel
   if the tool is not "browse tool" then put false into enemyActivated
   if enemyActivated is false  then
      DeactivateEnemy
      exit enemyTravel
   end if
   -- travel to and check for arrivel to destination
   put  targetLoc  into xy
   put "0,0," & stackDepth() into sRect
   if targetLoc is within sRect then EnemyTargetLoc
   
   --//destination is a virtual rectangle
   put the loc of me into enemyXY
   
   --// enemy is on screen, shoot at the player
   if enemyXY is within sRect then 
      if bulletDelay < 10 then
         add 1 to bulletDelay
      else
         send ActivateBullets &&  (the short Name of me) to stack (the mainStack of this stack) in 2 milliseconds
         put 0 into bulletDelay
      end if
   end if
   if item  1 of enemyXY < item 1 of targetLoc then add speedX to item 1 of enemyXY
   if item 1 of enemyXY > item 1 of targetLoc then subtract speedX from item 1 of enemyXY
   if item 2 of enemyXY < item 2 of targetLoc then add  speedY to item 2 of enemyXY
   if item 2 of enemyXY > item 2 of targetLoc then subtract speedY from item 2 of enemyXY
   if enemyXY is within targetREct then 
      DeactivateEnemy  
      exit enemyTravel
   end if
   set the loc of me to enemyXY
--- ShapeShiftFunk
   if enemyActivated is true then send enemyTravel to me in 2 milliseconds
end EnemyTravel
Pretty straight forward, if the enemy is not at the targetLoc, move toward the targetLoc, if the enemy is within the targetRect, deactivate. If the enemy is on screen shoot at the player.

DeactivateEnemy

Code: Select all

on DeactivateEnemy
   put false into enemyActivated 
   set the loc of me to -1200,-1200
   InactiveEnemies (the short name of me)
end DeactivateEnemy
Set the enemyActivated bool to false so that the EnemyTravel handler stops calling itself every 2 milliseconds.
Teleport to the offscreen hidey hole.
Send InactiveEnemies to the stack script, whiche removes the enemy name from the activeEnemies list used by the ActivateEnemies handler of the stack.


MissileHit

Code: Select all

on MissileHit
   put (speedX+speedY)*10 into mVal
   UpdateScore mVal
   DeactivateEnemy
end MissileHit
The CheckCollisions script of the main game stack will send MissileHit to the enemy whose rectangle coordinates have been penetrated by a player Missile object. The game score is then updated based on the enemy's (speedX plus SpeedY) multiplied by ten, then DeactivateEnemy is called.

--------- END Script of button "EnemyScript" ---------


-------- BEGIN Enemy Script of the Game Stack -------

ActivateEnemies

Code: Select all

--// -- randomly activate the enemies, probably could set the random number high at start for easier levels, then decrease
on ActivateEnemies
   --set the label of button "status" to timeLevel
   if playerCrash is true or playerCrash is "null" then exit ActivateEnemies
   put random(166) into rEnemy
   if rEnemy < 20 and the number of lines of activeEnemies  < timeLevel then 
      if "Enemy"&rEnemy is not among the lines of activeEnemies then  
         put quote &  "Enemy"&rEnemy& quote into NextEnemy
         do "send activateEnemy to graphic" && NextEnemy && "in 0 milliseconds"
         if activeEnemies is empty or activeEnemies is "empty" then 
            put "Enemy"&rEnemy  into activeEnemies
         else
            put cr & "Enemy"&rEnemy  after activeEnemies
         end if
         filter activeEnemies without empty
      end if
   end if
end ActivateEnemies
First of all check if the player has crashed, don't activate any enemies during that time. The "null" state of playerCrash is the crashing animation period.

Then we check a random number designated rEnemy, when this number is less than the number of enemies, AND the number of enemies on the current activeEnemies list is less than the timeLevel then activate an enemy whose name is "Enemy" &rEnemy.

timeLevel a variable which is currently determined as (timePassed /60)+2 , ie, seconds the game has been running, divided by 60., with a minium of 2, or 2 enemies to start, then 3 after a minute, 4 after two minutes etc. (For the purpose of the Enemy Factory I just timeLevel to 20 in the stack script, woohoo.

Hmm just notice a flaw in this script, it can activate an enemy who is already activated, which would explain why some of them just pop off the screen. Easy fix, added "if enemyActivated is true then exit ActivateEnemy" to the ActivateEnemy handler of button "EnemyScript", reflected in the material above. The enemy can just ignore being activated twice and the main stack can keep digging around it's random number generator for a loitering enemy to put to work.

InactiveEnemies

Code: Select all

on InactiveEnemies eName
   replace "Enemy" with empty in eName
   filter activeEnemies without "*"&eName  
end InactiveEnemies
The active enemies are tracked in stack variable list activeEnemies, InactiveEnemies simply removes inactive enemies from that list.

EnemyExplosions

Code: Select all

on EnemyExplosions XY
   put SoundDirectory() &"/" & "Explode0" & random(3) & ".wav" into tSound
   repeat with i = 1 to 3
      put i*10 into msW
      put "crash" & i into tGrph
      do "set the loc of graphic" && tGrph && "to" &&  XY
      do "send Explode &&" && XY &&"to graphic" && tGrph &&"in" && msw && "milliseconds"
      play tSound
   end repeat
end EnemyExplosions
When an enemy is encounters a player missile, or the player object play 1 of 3 explosion sounds contained in the Sound directory randomly. Set the 3 "explosion" graphic objects at the enemy location, then send the Explode message to them so they can animate an explosion effect.

StageEnemies

Code: Select all

on StageEnemies
   repeat with n = 1 to 20
      put  "Enemy" & n into nEnemy
      send   DeactivateEnemy to graphic nEnemy in  0 milliseconds
      put "" into activeEnemies
   end repeat
end StageEnemies
Teleport the enemies offscreen.

GetEnemies

Code: Select all

-- set the script of enemy graphics according to script stored in button
on GetEnemies
   put 0 into rows
   put 0 into cols
   put the script of button "EnemyScript" into eScript
   repeat with n = 1 to 20
      put  "Enemy" & n  into nEnemy
      do "show graphic" && nEnemy
      -- storing button script in stack for passing to objects without the button later
      set the EnemyCode of stack (the mainStack of this stack) to eScript
      do "set the script of graphic" && nEnemy && "to eScript"
      do "set the activated of graphic" && nEnemy && "to false"
      set the loc of graphic nENemy to 50+(50*Cols), 50+(Rows*50)
      add 1 to Cols
      if Cols > 4 then
         put 0 into Cols
         add 1 to Rows
      end if
   end repeat
   put empty into activeEnemies
end GetEnemies
Present the enemies on screen in a neat grid and update their scripts to the script held in button "EnemyScript"
Copy that script to the mainStack in a property called EnemyCode...just in case we accidentally delete the EnemyScript button someday, or maybe we want to add a Scripts folder to the game directory and update the enemy scripts via a text file after the game binary is built.
-------- END Enemy Script of the Game Stack -------
Last edited by xAction on Tue Oct 05, 2021 1:13 pm, edited 4 times in total.

xAction
Posts: 86
Joined: Sun Oct 03, 2021 4:14 am

Re: LiveCode Game Tutorial: Side Scrolling Shoot'em'up

Post by xAction » Mon Oct 04, 2021 11:14 pm

Part 11c. Enemy Script and Enemy Factory stack script in 2 code boxes, for quick copy/paste

Script of button "EnemyScript" <--- This evolves constantly as develop moves forward.

Code: Select all

local enemyActivated=false
local speedX=0, speedY=0
local targetLoc = "0,0"
local targetRect = "0,0,0,0"
local hMod=2,wMod=2
local bulletDelay=0
local enemyXY

on ActivateEnemy
   if enemyActivated is true then exit ActivateEnemy
   put true into enemyActivated
   send EnemyTargetLoc to me in 1 milliseconds
   -- fly enemy fly!
   send EnemyTravel to me in 60 milliseconds
end ActivateEnemy

on EnemyTargetLoc
   put the loc of me into enemyXY
   put random (10) into rX
   -- set up a destination for the enemy
   if rX > 5 then 
      put width of this stack+20 into newX
      put -30 into  item 1 of enemyXY
   else
      put -20 into newX
      put width of this stack+20 into item 1 of enemyXY
   end if
   -- target location Y coordinate
   put random(height of this stack)-random(400) into item 2 of enemyXY
   --if the origin Y is low then go high
   if item 2 of enemyXY > 200 then 
      put -20 into newY
   else
      put random(height of this stack+250) into newY
   end if
   put  newX-10, newY-10, newX+10, newY+10 into targetRect
   set the loc of me to enemyXY
   put   newX & comma & newY into targetLoc
   put random(10)+6 into speedX
   put random(10)+6 into speedY
end EnemyTargetLoc
   
   
on EnemyTravel
   if the tool is not "browse tool" then put false into enemyActivated
   if enemyActivated is false  then
      DeactivateEnemy
      exit enemyTravel
   end if
   -- travel to and check for arrivel to destination
   put  targetLoc  into xy
   put "0,0," & stackDepth() into sRect
   if targetLoc is within sRect then EnemyTargetLoc
   
   --//destination is a virtual rectangle
   put the loc of me into enemyXY
   
   --// enemy is on screen, shoot at the player
   if enemyXY is within sRect then 
      if bulletDelay < 10 then
         add 1 to bulletDelay
      else
         send ActivateBullets &&  (the short Name of me) to stack (the mainStack of this stack) in 2 milliseconds
         put 0 into bulletDelay
      end if
   end if
   if item  1 of enemyXY < item 1 of targetLoc then add speedX to item 1 of enemyXY
   if item 1 of enemyXY > item 1 of targetLoc then subtract speedX from item 1 of enemyXY
   if item 2 of enemyXY < item 2 of targetLoc then add  speedY to item 2 of enemyXY
   if item 2 of enemyXY > item 2 of targetLoc then subtract speedY from item 2 of enemyXY
   if enemyXY is within targetREct then 
      DeactivateEnemy  
      exit enemyTravel
   end if
   set the loc of me to enemyXY
--- ShapeShiftFunk
   if enemyActivated is true then send enemyTravel to me in 2 milliseconds
end EnemyTravel

on DeactivateEnemy
   put false into enemyActivated 
   --hide me
   set the loc of me to -1200,-1200
   InactiveEnemies (the short name of me)
end DeactivateEnemy

on MissileHit
   put (speedX+speedY)*10 into mVal
   UpdateScore mVal
   DeactivateEnemy
end MissileHit
   
on ShapeShiftFunk
   -- some funky shape shifting stuff
   --   put hMod*-1 into hMod
   --   put wMod*-1 into wMod
   --   set the height of me to (the height of me)+hMod
   --   set the width of me to (the width of me)+wMod
   --   put the properties of me into myProps
   --   put the keys of myProps into myKeys
   --   filter myKeys without "Arc*"
   --   if angle is among the lines of myKeys then
   --      put the angle of me into myAngle
   --      if myAngle is not empty then
   --         add 2 to myAngle
   --         if myAngle >356 then put 0 into myAngle
   --         set the angle of me to myAngle
   --      end if
   --   end if
end ShapeShiftFunk

Stack script of stack "Enemy Factory" most of which is duplicated in the game stack

Code: Select all

local lastEnemy=0
Local bulletDelay=0
local playerCrash = false
-- string lists
local activeEnemies=""
local activeBullets=""
local colorList="BlueViolet,Violet,PaleVioletRed,Red,OrangeRed,Orange,Yellow,LimeGreen,Green,Cyan,CornflowerBlue,Blue,Purple,Magenta,SpringGreen,Pink, HotPink,Lavender,SkyBlue,White"
local timeLevel =20
local crashTime=0

-- set some directory globals
global gStackDirectory
global gSoundDirectory

function SoundDirectory
   put word 2 the long name of stack (the mainStack of this stack) into myStack
   replace quote with empty in myStack
   set itemDel to "/" 
   put item 1 to -2 of myStack & "/" & "Sounds" into gSoundDirectory
   return gSoundDirectory
end SoundDirectory

function StackDirectory
   put word 2 the long name of stack (the mainStack of this stack) into myStack
   replace quote with empty in myStack
   set itemDel to "/" 
   put item 1 to -2 of myStack into gStackDirectory
   return gStackDirectory
end StackDirectory

on preOpenStack
   put stackDirectory() into gStackDirectory
   put SoundDirectory() into gSoundDirectory
   go card "Title"
end preOpenStack

function StackDepth
   put the width of stack (the mainStack of this stack) into H
   put the height of stack (the mainStack of this stack) into W
   put H & comma & W into stackSize
   return stackSize
end StackDepth

function CenterScreen XY
   put StackDepth() into HW
   put floor(item 1 of HW/2) & comma & floor(item 2 of HW/2) into screenCenter
   if XY is "x" then return floor(item 1 of HW/2)
   if XY is "y" then return floor(item 2 of HW/2)
   return screenCenter
end CenterScreen


on EnemyExplosions XY
   put SoundDirectory() &"/" & "Explode0" & random(3) & ".wav" into tSound
   repeat with i = 1 to 3
      put i*10 into msW
      put "Explosion" & i into tGrph
      do "set the loc of graphic" && tGrph && "to" &&  XY
      do "send explode &&" && XY &&"to graphic" && tGrph &&"in" && msw && "milliseconds"
      play tSound
   end repeat
end EnemyExplosions

-- randomly activate the enemies, probably could set the random number high at start for easier levels, then decrease
on ActivateEnemies
   --set the label of button "status" to timeLevel
   if playerCrash is true or playerCrash is "null" then exit ActivateEnemies
   put random(166) into rEnemy
   
   if rEnemy < 21 and the number of lines of activeEnemies  < timeLevel then 
      if "Enemy"&rEnemy is not among the lines of activeEnemies then  
         put quote &  "Enemy"&rEnemy& quote into NextEnemy
         do "send activateEnemy to graphic" && NextEnemy && "in 0 milliseconds"
         if activeEnemies is empty or activeEnemies is "empty" then 
            put "Enemy"&rEnemy  into activeEnemies
         else
            put cr & "Enemy"&rEnemy  after activeEnemies
         end if
         filter activeEnemies without empty
      end if
   end if
end ActivateEnemies

on MouseAcitvateEnemies
   if playerCrash is true or playerCrash is "null"then 
      TestDummyCrash
   else
      ActivateEnemies
      set the label of button "activeEnemies" to activeEnemies
      set the label of button "activeBullets" to activeBullets
      CheckFactoryCollisions
   end if
   put "0,0,"& stackDepth() into stackRect
   if the mouseLoc is within stackRect and the tool is "browse tool" then 
      send MouseAcitvateEnemies  to stack (the mainStack of this stack)in 2 milliseconds
   end if
   
end MouseAcitvateEnemies

on TestDummyCrash
   if crashTime < 10   then
      put "null" into playerCrash
      put the lineSize of graphic "PlayerHitBox" into LinSiz
      add 2 to LinSiz
      set the lineSize of graphic "PlayerHitBox" to LinSiz
      add 1 to crashTime
      --send TestDummyPlayer to stack (the mainStack of this stack) in 2 milliseconds
   else
      put 0 into crashTime
      put false into playerCrash
      set the lineSize of graphic "PlayerHitBox" to 2
   end if
end TestDummyCrash
   
   
   on ActivateBullets enemyName
   if enemyName is empty then exit ActivateBullets
   --if the short name of this card is not "Play" then exit ActivateBullets
   if playerCrash is true or playerCrash is "null" then exit ActivateBullets
   if activeBullets is "empty" or activeBullets is "activeBullets" then put "" into activeBullets
   put the number of lines of activeBullets into N
   
   if N+1 > 20 then put 0 into N
   add 1 to N
   
   put "Bullet"&N into tActiveBullet
   if tActiveBullet is not among the lines of activeBullets then
      if activeBullets is empty then
         put tActiveBullet into activeBullets
      else
         put cr& tActiveBullet after activeBullets
      end if
   end if
   
   send PrepareToFire && enemyName to graphic tActiveBullet
end ActivateBullets

on RecycleBullets bulletName
   put bulletName into bulletNum
   replace "Bullet" with empty in bulletNum
   filter activeBullets without "*"&bulletNum
   set the loc of graphic bulletName to -300,300
   send DeactivateBullet to graphic bulletName in 1 milliseconds
end RecycleBullets

on InactiveEnemies eName
   replace "Enemy" with empty in eName
   filter activeEnemies without "*"&eName  
end InactiveEnemies

-- set the script of enemy graphics according to script stored in button
on GetEnemies
   put 0 into rows
   put 0 into cols
   put the script of button "EnemyScript" into eScript
   repeat with n = 1 to 20
      put  "Enemy" & n  into nEnemy
      do "show graphic" && nEnemy
      -- storing button script in stack for passing to objects without the button later
      set the EnemyCode of stack (the mainStack of this stack) to eScript
      do "set the script of graphic" && nEnemy && "to eScript"
      do "set the activated of graphic" && nEnemy && "to false"
      set the loc of graphic nENemy to 50+(50*Cols), 50+(Rows*50)
      
      add 1 to Cols
      if Cols > 4 then
         put 0 into Cols
         add 1 to Rows
      end if
   end repeat
   put empty into activeEnemies
end GetEnemies


on StageBullets
   put "" into activeBullets
   repeat with N = 1 to 20
      put  "Bullet"&N  into bName
      --put the script of button "BulletScript" into tScript
      set the loc of graphic bName to -3000,-3000
      send DeactivateBullet to graphic bName in 2 milliseconds
   end repeat
end StageBullets

on StageEnemies
   repeat with n = 1 to 20
      put  "Enemy" & n into nEnemy
      send   DeactivateEnemy to graphic nEnemy in  0 milliseconds
      put "" into activeEnemies
   end repeat
end StageEnemies


on GetBullets
   repeat with N = 1 to 20
      put quote & "Bullet"&N & quote into tName
      put the script of button "BulletScript" into tScript
      do "set the script of graphic" && tName && "to tScript"
      -- storing button script in stack for passing to objects without the button later
      set the BulletCode of stack (the mainStack of this stack) to tScript
      put 20+(n*12) into x
      put "set the loc of the graphic" && tName && "to x,250" into tCom
      put tCom & cr after alCom
      do tCom
   end repeat
end GetBullets


on MakeEnemies
   put 0 into Rows
   put 0 into Cols
   put the script of button "EnemyScript" into EnemyBehavior
   repeat with N = 1 to 20
      create graphic
      set the script of the last graphic to EnemyBehavior
      set the name of the last graphic to "Enemy"&N
      set the style of the last graphic to "regular"
      set the polySides of the last graphic to 3+random(10)
      set the height of the last graphic to 15+random(30)
      set the width of the last graphic to 15+random(30)
      set the loc of the last graphic to 50+(50*Cols), 50+(Rows*50)
      put item N of colorList into tColor
      set the foregroundColor of the last graphic to tColor
      put random(10) into dashrand
      if dashrand > 7 then
         set the dashes of the last graphic to random(3)
      end if
      set the lineSize of the last graphic to random(4)
      Glamour
      add 1 to Cols
      if Cols > 4 then
         put 0 into Cols
         add 1 to Rows
      end if
   end repeat
end MakeEnemies



--// convert color name to RGB --// 
--// from https://lessons.livecode.com/m/2592/l/125746-translating-a-color-name-to-an-rgb-numeric-triplet
function RGBFromColorName pColorName
   local tRGBColor
   if pColorName is not a color then
      return "Error: not a color"
   end if
   
   ## create a temporary object for the color transformation:
   create invisible button
   if the result is not empty then 
      return "Error"
   end if
   
   set the backgroundColor of last button to pColorName
   
   ## transform the color using the backgroundPixel trick:
   set the backgroundPixel of last button to the backgroundPixel of last button
   
   ## the button's backgroundColor is now RGB:
   put the backgroundColor of last button into tRGBColor
   delete last button
   
   ## the create command automatically chose the pointer tool
   ## change it back:
   send "choose browse tool" to me in 10 milliseconds
   return tRGBColor
end RGBFromColorName


on MakeMissiles aName
   repeat with N = 1 to 20
      create graphic
      set  the loc of the last graphic to 20+(N*12),250
      set the style of the last graphic to "Rectangle"
      set the lineSize of the last graphic to 2
      set the width of the last graphic to 8
      set the height of the last graphic to 8
      put aName&"Script" into codeButtonName
      put the script of button codeButtonName into tScript
      set the script of the last graphic to tScript
      put item N of colorList into RGB
      set the foregroundColor of the last graphic to RGB
      set the outerGlow["color"] of the last graphic to RGB
      set the outerGlow["size"] of the last graphic to random(28)+20
      set the outerGlow["spread"] of the last graphic to random(28)+20
      put  aName &N into tName
      set the name of the last graphic to tName
   end repeat
   put 0 into lastMissile 
end MakeMissiles

on Glamour
   put item random(20) of colorList into tColor
   put RGBFromColorName(tColor) into fgColor
   add random(100) to item 1 of fgColor
   add random(100) to item 2 of fgColor
   add random(100) to item 2 of fgColor
   set outerGlow of the last graphic to true
   set the outerGlow["color"] of the last graphic to fgColor
   set the outerGlow["spread"] of the last graphic to 24+random(40)
   set the outerGlow["size"] of the last graphic to 24+random(40)
   set colorOverlay of the last graphic to false
   subtract random(100) from item 1 of fgColor
   subtract random(100) from item 2 of fgColor
   subtract random(100) from item 2 of fgColor
   put random(10)into OpaRand
   if OpaRand > 8 then
      set the opaque of the last graphic to true
      set the backgroundcolor of the last graphic to "White"
      set innerShadow of the last graphic to true
      set the innerShadow["color"] of the last graphic to fgColor
      set the innerShadow["spread"] of the last graphic to 24+random(40)
      set the innerShadow["size"] of the last graphic to 24+random(40)
   end if
end Glamour

on MakeExplosions
   put the script of button "ExplosionScript" into ExplosionScript
   repeat with N = 1 to 3
      create graphic
      set the script of the last graphic to ExplosionScript
      set the name of the last graphic to "Explosion"&N
      set the style of the last graphic to "regular"
      set the polySides of the last graphic to 4+random(10)
      set the height of the last graphic to 20+random(20)
      set the width of the last graphic to 20+random(20)
      set the lineSize of the last graphic to 7+random(7)
      set the loc of the last graphic to 50+(50*N), 300
      set itemDel to comma
      put random(number of items of colorList) into rColor
      put item rColor  of colorList into tColor
      
      set the foregroundColor of the last graphic to tColor
      set the colorOverlay of the last graphic to false  ---///why is this true by default?
      put random(10) into dashrand
      if dashrand > 7 then
         set the dashes of the last graphic to random(3)
      end if
      --Glamour
   end repeat
end MakeExplosions

on GetExplosions
   put the script of button "ExplosionScript" into ExplosionScript
   repeat with N = 1 to 3
      put "Explosion"&N into exName
      set the loc of graphic exName to 20+(50*N), 300
      set the script of graphic exName to ExplosionScript
      set the blendLevel of graphic exName to 0
   end repeat
end GetExplosions


on StageExplosions
   put the script of button "ExplosionScript" into ExplosionScript
   repeat with N = 1 to 3
      put "Explosion"&N into exName
      set the loc of graphic exName to -1200,-1200
      set the script of graphic exName to ExplosionScript
   end repeat
end StageExplosions


function pathToTarget x1,y1,x2,y2
   repeat with i = 0 to 1 step 0.01
      put  x1 +(x2 - x1) *i into newX
      put y1 +(y2 - y1) *i into newY
      put newX & comma & newY & cr after tPathToTarget
   end repeat
   return tPathToTarget
end pathToTarget

function DeltaToTarget x1,y1,x2,y2
   repeat with i = 0 to 1 step 0.01
      put  x1 +(x2 - x1) *i into newX
      put y1 +(y2 - y1) *i into newY
      put newX & comma & newY & cr after tPathToTarget
   end repeat
   --// use the first and last line of the point list to determine the x,y delta
   put line 1 of tPathToTarget into axy
   put line -1 of tPathToTarget into bxy
   put (item 1 of bxy - item 1 of axy)/100 into dx
   put (item 2 of bxy - item 2 of axy)/100 into dy
   return dx,dy
end DeltaToTarget

--// get a  point along the path (progress)  between an origin and a destination
Function LERPVector origin,destination,progress
   --//progress is percentage from 0 to 1
   return source +(destination- origin) *progress 
end LERPVector

--// get the difference between point b and point a of a vector
Function deltaVal a,b
   return b-a
end deltaVal

on CheckFactoryCollisions
   if the tool is not "browse tool" then exit CheckFactoryCollisions
   if playerCrash is true or playerCrash is "null" then exit CheckFactoryCollisions
   put the number of lines of activeEnemies  into nLines
   if nLines > 0 then 
      if lastEnemy+1 > nLines then put 0 into lastEnemy
      put 0 into lastEnemy
      repeat while lastEnemy < nLines
         add 1 to lastEnemy
         put line lastEnemy of activeEnemies into cEnemyName
         if cEnemyName is not empty and cEnemyName is not "empty" and cEnemyName is not "activeEnemies" then 
            put the loc of graphic cEnemyName into eXY
            
            --// enemy versus player missiles 
            --            put 0 into lastMissileCheck
            --            repeat while lastMissileCheck <20
            --               add 1 to lastMissileCheck
            --               put "Missile,"&lastMissileCheck into nMissile 
            --               put the loc of graphic nMissile into mXY 
            --               if mXY is within the rect of graphic cEnemyName then
            --                  send MissileHit to graphic cEnemyName 
            --                  EnemyExplosions (mXY)
            --                  replace "Enemy" with empty in cEnemyName
            --                  filter activeEnemies without "*"&cEnemyName  
            --                  set the loc of graphic nMissile to -1000,-1000
            --                  exit repeat
            --               end if
            --            end repeat
            --// 
            
            --// Enemy vs Player: check for player collision and reset
            if eXY  is within the rect of graphic "playerHitBox" then 
               send DeactivateEnemy to graphic cEnemyName in 0 milliseconds
               replace "Enemy" with empty in cEnemyName
               filter activeEnemies without "*"&cEnemyName  
               put true into playerCrash
               play soundDirectory() & "/PlayerCrash.wav"
               EnemyExplosions (eXY) 
            end if
         end if
      end repeat
   end if
   
   --//  enemy bullets vs. player
   repeat with B = 1 to 20
      put "Bullet"& B into tBullet
      put the loc of graphic tBullet into bXY
      if bXY  is within the rect of graphic "playerHitBox" then 
         put tBullet into cBulletName
         replace "Bullet" with empty in cBulletName
         filter activeBullets without "*"&cBulletName  
         put true into playerCrash
         play soundDirectory() & "/PlayerCrash.wav"
         EnemyExplosions (loc of graphic "Player") 
         send DeactivateBullet to graphic tBullet in 4 milliseconds
         send StageEnemies to stack (the mainStack of this stack) in 0 milliseconds
         --exit repeat
         exit CheckFactoryCollisions
      end if
   end repeat
   
   send CheckFactoryCollisions to stack (the mainStack of this stack) in 2 milliseconds
end CheckFactoryCollisions


Okay on to the bullets.
Last edited by xAction on Sat Oct 09, 2021 11:58 am, edited 5 times in total.

xAction
Posts: 86
Joined: Sun Oct 03, 2021 4:14 am

Re: LiveCode Game Tutorial: Side Scrolling Shoot'em'up

Post by xAction » Tue Oct 05, 2021 12:25 am

Part 12. Enemy Factory: Enemy Bullets
EnemyFactoryv2.png
Enemy Factory version 2, with enemy Bullets
------The script of of button "BulletScript" in 3 parts.------

Local Variables:

Code: Select all

local dx,dy
local bulletXY
local activatedBullet=false
local bulletSpeed
local stackRect
dx and dy represent delta x and delta y position , ie, the difference between a point of origin and the destination point along a 2D trajectory, divided by 100. These value are used to send a bullet toward a target.
bulletXY is the current location of the bullet
activatedBullet is used to determine if the script should continue running or not
bulletSpeed is the speed multiplied by the delta x or delta y (dx,dy) that the bullet travels toward the target

PrepareToFire

Code: Select all

on PrepareToFire enemyName
   if activatedBullet is true then exit PrepareToFire
   if enemyName is empty then exit PrepareToFire
   put the loc of graphic enemyName into enemyXY
   set the loc of me to enemyXY
   put the loc of graphic "player" into playerXY
   put "0,0," & stackDepth() into stackRect
   
   --// plot the trajectory to target as a series of x,y coordinates
   repeat with i = 0 to 1 step 0.01
      put item 1 of enemyXY+(item 1 of playerXY- item 1 of enemyXY) *i into newX
      put item 2 of enemyXY+(item 2 of playerXY- item 2 of enemyXY) *i into newY
      --set the loc of graphic "Bullet" to newX,newY
      put newX & comma & newY & cr after pathToTarget
   end repeat
   
   --// use the first and last line of the point list to determine the x,y delta
   put line 1 of pathToTarget into axy
   put line -1 of pathToTarget into bxy
   put deltaVal(item 1 of axy, item 1 of bxy)/100 into dx
   put deltaVal(item 2 of axy, item 2 of bxy)/100 into dy
   
   put true into activatedBullet
   put the loc of me into bulletXY
   put random(2)+2 into bSpeed
   BulletFly
end PrepareToFire
if the bullet is already activated, exit the script. If enemyName is empty then exit the script.
put the bullet at the location of the given enemy.
Plot a line from the enemy to the player along a 2D plane.

Let's see if I can explain the math:
Assume there are 100 steps (or 1% of the total distance per step) between the enemy:A and the player:B.
For each of the x and y coordinates:
Add the origin point value to the valued derived by the distance between A and B
Mutllpy the sum by each increasing step of 0.01 from 0 to 1
Each incrementing result represents distance traveled from point A to point B over 100 steps.

That gives us the pathToTarget as a list of 100 points.
If you want to Google this stuff it's generally called "LERP vectors"
Here's a function i made for future use:

Code: Select all

Function LERPVector origin,destination,progress
   --//progress is percentage from 0 to 1
   return source +(destination- origin) *progress 
end LERPVector
Then we use the first (axy) and last points (bxy) on that list to determine the delta values dx and dy via the deltaVal function, which is so simple we probably don't need it, but I made it in case I forget how this works.

Code: Select all

Function deltaVal a,b
   return b-a
end deltaVal
This gives us a direction to move the bullet without having to actually stop at the target location, when the dx and dy are added to the current position of an object, it can travel in a line of any arbitrary angle indefinately.

BulletFly

Code: Select all

on BulletFly 
   if the tool is not "browse tool" then exit BulletFly
   if activatedBullet is false then 
   send RecycleBullets && the short name of me to stack (the mainStack of this stack) in 2 milliseconds
   exit BulletFly
   end if
   add dx*bulletSpeed to item 1 of bulletXY
   add dy*bulletSpeed to item 2 of bulletXY
   set the loc of me to bulletXY
   if bulletXY is within stackRect then 
      send BulletFly  to me in 10 milliseconds
   else
      send RecycleBullets && the short name of me to stack (the mainStack of this stack) in 2 milliseconds
         put false into activatedBullet
   end if
end BulletFly
Once the delta x (dx) and delta y (dy) are determined the bullet is aimed and ready to fire.
The bullet will travel along it's given trajectory at bulletSpeed until it exits the rectangle area of the stack window. Then it sends RecycleBullets to the main game stack (or Enemy Factory), which removes it's name from the activeBullets list variable.

DeactivateBullet

Code: Select all

on DeactivateBullet
   put false into activatedBullet
end DeactivateBullet
Sets activatedBullet variable to false so the BulletFly handler knows to stop looping and to send RecycleBullets to the main stack

Okay, that's pretty much it for bullets.
I've added the LERPVector and deltaVal functions to the previous post's stack script listing.

Since the enemy script wants to call EnemyExplosions handler , I'll make the explosion graphics and explain the scripts next and include them with the Enemy Factory stack before we copy them into the game stack.

Oh here's Script of button "BulletScript" in one easy block to copy/paste.

Code: Select all

local dx,dy
local bulletXY
local activatedBullet=false
local bulletSpeed
local stackRect="0,0,900,900"
on PrepareToFire enemyName
   if activatedBullet is true then exit PrepareToFire
   if enemyName is empty then exit PrepareToFire
   if disabledBullet is true then exit PrepareToFire
   put the loc of graphic enemyName into enemyXY
   set the loc of me to enemyXY
   put the loc of graphic "player" into playerXY
   put "0,0," & stackDepth() into stackRect
   --// plot the trajectory to target as a series of x,y coordinates
   repeat with i = 0 to 1 step 0.01
      put item 1 of enemyXY +(item 1 of playerXY - item 1 of enemyXY) *i into newX
      put item 2 of enemyXY +(item 2 of playerXY - item 2 of enemyXY) *i into newY
      put newX & comma & newY & cr after pathToTarget
   end repeat
   filter pathToTarget without empty
   --// use the first and last line of the point list to determine the x,y delta
   put line 1 of pathToTarget into axy
   put line -1 of pathToTarget into bxy
   
   put deltaVal(item 1 of axy, item 1 of bxy)/100 into dx
   put deltaVal(item 2 of axy, item 2 of bxy)/100 into dy
   
   put true into activatedBullet
   put the loc of me into bulletXY
   put random(2)+2 into bulletSpeed
   BulletFly
end PrepareToFire

on BulletFly 
   if the tool is not "browse tool" then exit BulletFly
   if activatedBullet is false then 
      send RecycleBullets && the short name of me to stack (the mainStack of this stack) in 2 milliseconds
      exit BulletFly
   end if
   add dx*bulletSpeed to item 1 of bulletXY
   add dy*bulletSpeed to item 2 of bulletXY
   set the loc of me to bulletXY
   
   if bulletXY is within stackRect then 
      send BulletFly  to me in 10 milliseconds
   else
      send RecycleBullets && the short name of me to stack (the mainStack of this stack) in 2 milliseconds
      put false into activatedBullet
   end if
end BulletFly

on DeactivateBullet
   put false into activatedBullet
end DeactivateBullet
Last edited by xAction on Tue Oct 05, 2021 12:41 pm, edited 4 times in total.

xAction
Posts: 86
Joined: Sun Oct 03, 2021 4:14 am

Re: LiveCode Game Tutorial: Side Scrolling Shoot'em'up

Post by xAction » Tue Oct 05, 2021 7:36 am

Part 13. Enemy Factory: Explosions.
EnemyFactoryv3.png
Enemy Factory version 3, with explosions
Our explosions are easy, they are 3 simple graphics called "Explosion1','Explosion2',and 'Explosion3'
But whoa, their script is huge. Who knew?
I'll store the script in a button called "ExplosionScript" on the Enemy Factory stack and make some functions that match the Enemy, Bullets handlers to include in the stack.

Okay lets get to it then.
The Explosion elements of the Enemy Factory stack are as follows:
button "Make Explosions" -- calls MakeExplosions from the stack script
button "Get Explosions"-- calls GetExplosions
button "Stage Explosions" -- calls StageExplosions
button "Activate Explosions" -- sends EnemyExplosions and a point above the button column to each of the three explosion objects, after 2 seconds it will reset the objects back in the ...gallery where they live.
button "Edit Explosion Script" -- opens the script editor to the script of button "ExplosionScript"
button "ExplosionScript" -- contains the script that is copied into each explosion object.

I copied the effective functionality of bullet handler PrepareToFire to a function called DeltaToTarget to which I pass the explosion location and two random points based on stack width and height. It provides the variables to send the explosion bits zooming in random directions like I wanted. That function is now included in the stack.

--------Script of button "Explosion Script" broken into explainable bits:

Local Variables:

Code: Select all

local exploded=false
local exDeltaX=0
local exDeltaY=0
local explodeLoc="0,0"
local exBlend=0
local fadeDelay=0
Explode

Code: Select all

-- set up explosion object for action
on explode eX,eY
   resetExplosion
   set the loc of me to eX,eY
   put true into exploded
   --// choose a virtual destination to explode to
   put stackDepth() into scrXY
   put random(item 1 of scrXY) into destX 
   put random(item 2 of scrXY) into destY
First I resetExplosion because I was pressing the Activate Explosion button too quick and reactivating them before they were finished which resulted in changes made in Exploding to be carried over to the next instance of Explode which was apparent in things like massively large outerGlow and/or invisibhilty due to blendLevel approaching 100.
Then we set the explosion objects to the location provided by the eX,eY values.
The exploded flag is set to true, which tells Exploding to continue activating itself or not.
We then get a destination x,y coordinate (destX,destY) based on the screen size

Code: Select all

   --// get delta to the target location
   put DeltaToTarget(eX,eY,destX,destY) into DxDy
   
   --// delta values modify  object location each loop
   put item 1 of DxDy  into exDeltaX
   put item 2 of DxDy  into exDeltaY
We pass the location of the explosion (eX,eY) and the destination coordinate (destX,destY) to the DeltaToTarget which we borg-like assimilated from the bullet scripts. This gives us exDeltaX and exDeltaY which will guide our explosions away from the point of origin.

Code: Select all

   put 0 into exBlend
   put 0 into fadeDelay
   --// explode!
   Exploding
end explode
exBlend tracks our ojects blendLevel , which will be used to determine the life of our script, no sense running if it isn't visible, right?
fadeDelay increases by 1 for 20 cycles of the script before exBlend starts to increase, this keeps our explosions looking fresh a little bit before fading.


Exploding ---- In Multiple Parts ---

Code: Select all

on Exploding
   if exploded is false then exit exploding
   --// BLEND LEVEL determines life of explosion
   add 1 to fadeDelay
   if fadeDelay > 20 then
      add 3 to exBlend
      set the blendLEvel of me to exBlend
      if exBlend > 40 then put false into exploded 
   end if
As mentioned above, when the object blendLevel reaches 40 or above, we set exploded to false which will cause Explodingto stop calling itself up. "
if exploded is false then exit exploding" is probably not needed, at the start but I probably had caused an infinite loop at some point, so... there it is.

Exploding -- position

Code: Select all

   --// POSITION
   put the loc of me into explodeLoc
   put item 1 of explodeLoc+ exDeltaX into item 1 of explodeLoc
   put item 2 of explodeLoc+ exDeltaY into item 2 of explodeLoc
   set the loc of me to explodeLoc
As mentioned above, the x,y position of the explode object is sent careening through space by adding the exDeltaX or exDeltaY values respectively to the current position.

Exploding -- dashes

Code: Select all

   --// DASHES
   put  the dashes of me into exDash
   put item -1 of exDash into DS
   if DS is empty or DS is 0 then put 0 into DS
   if DS > 5 then put 0 into DS
   add 1 to DS
   put DS & comma & DS into exDash
   set the dashes of me to exDash
I modify the dashes of the graphic to make it look like bits of disintegrating debris stuff.

Exploding -- angles

Code: Select all

   --// ANGLES
   put the angle of me into ang
   add 5 to ang
   if ang > 359 then put 0 into ang
   set the angle of me to ang
Spin the object a bit...

Exploding -- linesize

Code: Select all

   --// LINE SIZE
   put the lineSize of me into mLin
   if mLin > 1 then subtract 1 from mLin
   set the lineSize of me to mLin
Decrease the linesize.

Exploding -- height and width

Code: Select all

   --// HEIGHT and WIDTH
   put the height of me into mH
   put the width of me into mW
   add random(10)-random(10) to mH
   set the height of me to mH
   add random(10)-random(10) to mW
   set the width of me to mW
Wiggles the size up and/or down...

Exploding -- glow

Code: Select all

 --// glow
   put the outerGlow["spread"] of me into oSpr
   put the outerGlow["size"] of me into oSiz
   add random(2) to oSpr
   add random(2) to oSiz
   set the  outerGlow["spread"] of me to oSpr
   set the  outerGlow["size"] of me to oSiz
Self explanatory, I think.

Exploding -- end... whew!

Code: Select all

   if  exploded  is false then 
      send resetExplosion to me in 0 milliseconds
   else
      send exploding to me in 2 milliseconds
   end if
end Exploding
if the exploded flag is false, send resetExplosion, else resend Exploding

ResetExplosion -- clear enough to read as one box, I think?

Code: Select all

--// reset the explosion object 
on resetExplosion
   put false into exploded 
   put char -1 of the short name of me into N
   set the loc of me to -1200,-1200
   set the BlendLevel of me to 0
   set the angle of me to 0
   set the dashes of me to 0
   set the polySides of me to random(10)*3
   set the width of me to random(10)+10
   set the height of me to random(10)+10
   set the lineSize of me to random(20)+20
   put random(100)+155 into R
   put random(100)+155 into G
   put random(100)+155 into B
   set the  outerGlow["color"] of me to R,G,B
   set the  outerGlow["spread"] of me to random(4)+4
   set the  outerGlow["size"] of me to random(4)+4
   set the foregroundColor of me to R,G,B
end resetExplosion
Okay, so we've got enemies, bullets, explosions, and if my Enemy Factory script tests are to be believed, everything works enough to put it into the game BUT we need collisions, so we'll work that out next since its a convulted mess. I'll start with a testing handler in the Enemy Factory.
Last edited by xAction on Sat Oct 09, 2021 12:02 pm, edited 6 times in total.

xAction
Posts: 86
Joined: Sun Oct 03, 2021 4:14 am

Re: LiveCode Game Tutorial: Side Scrolling Shoot'em'up

Post by xAction » Tue Oct 05, 2021 7:46 am

Part 13b. Explosions : The Script of button "ExplosionScript" in one big code block.

Code: Select all

local exploded=false
local exDeltaX=0
local exDeltaY=0
local explodeLoc="0,0"
local exBlend=0
local fadeDelay=0

-- set up explosion object for action
on explode eX,eY
   resetExplosion
   set the loc of me to eX,eY
   put true into exploded
   --// choose a virtual destination to explode to
   put stackDepth() into scrXY
   put random(item 1 of scrXY) into destX 
   put random(item 2 of scrXY) into destY
   
   --// get delta to the target location
   put DeltaToTarget(eX,eY,destX,destY) into DxDy
   
   --// delta values modify  object location each loop
   put item 1 of DxDy  into exDeltaX
   put item 2 of DxDy  into exDeltaY
   
   put 0 into exBlend
   put 0 into fadeDelay
   --// explode!
   Exploding
end explode

on Exploding
   if exploded is false then exit exploding
   --// BLEND LEVEL determines life of explosion
   add 1 to fadeDelay
   if fadeDelay > 20 then
      add 3 to exBlend
      set the blendLEvel of me to exBlend
      if exBlend > 40 then put false into exploded 
   end if
   
   --// POSITION
   put the loc of me into explodeLoc
   put item 1 of explodeLoc+ exDeltaX into item 1 of explodeLoc
   put item 2 of explodeLoc+ exDeltaY into item 2 of explodeLoc
   set the loc of me to explodeLoc
   
   --// DASHES
   put  the dashes of me into exDash
   put item -1 of exDash into DS
   if DS is empty or DS is 0 then put 0 into DS
   if DS > 5 then put 0 into DS
   add 1 to DS
   put DS & comma & DS into exDash
   set the dashes of me to exDash
   
   --// ANGLES
   put the angle of me into ang
   add 5 to ang
   if ang > 359 then put 0 into ang
   set the angle of me to ang
   
   --// LINE SIZE
   put the lineSize of me into mLin
   if mLin > 1 then subtract 1 from mLin
   set the lineSize of me to mLin
   
   --// HEIGHT and WIDTH
   put the height of me into mH
   put the width of me into mW
   add random(10)-random(10) to mH
   set the height of me to mH
   add random(10)-random(10) to mW
   set the width of me to mW
   
   --// glow
   put the outerGlow["spread"] of me into oSpr
   put the outerGlow["size"] of me into oSiz
   add random(2) to oSpr
   add random(2) to oSiz
   set the  outerGlow["spread"] of me to oSpr
   set the  outerGlow["size"] of me to oSiz
   
   if  exploded  is false then 
      send resetExplosion to me in 0 milliseconds
   else
      send exploding to me in 2 milliseconds
   end if
end Exploding

--/// rest the explosion object 
on resetExplosion
   put false into exploded 
   put char -1 of the short name of me into N
   set the loc of me to -1200,-1200
   set the BlendLevel of me to 0
   set the angle of me to 0
   set the dashes of me to 0
   set the polySides of me to random(10)*3
   set the width of me to random(10)+10
   set the Height of me to random(10)+10
   set the lineSize of me to random(20)+20
   put random(100)+155 into R
   put random(100)+155 into G
   put random(100)+155 into B
   set the  outerGlow["color"] of me to R,G,B
   set the  outerGlow["spread"] of me to random(4)+4
   set the  outerGlow["size"] of me to random(4)+4
   set the foregroundColor of me to R,G,B
end resetExplosion
Last edited by xAction on Tue Oct 05, 2021 10:03 am, edited 1 time in total.

xAction
Posts: 86
Joined: Sun Oct 03, 2021 4:14 am

Re: LiveCode Game Tutorial: Side Scrolling Shoot'em'up

Post by xAction » Tue Oct 05, 2021 9:13 am

Part 14. Collisions

Okay this stack script is a thick one. I'll break it into parts. For the Enemy Factory I called it:

CheckFactoryCollisions

Code: Select all

on CheckFactoryCollisions
   if the tool is not "browse tool" then exit CheckFactoryCollisions
   if playerCrash is true or playerCrash is "null" then exit CheckFactoryCollisions
   
It's important to stop checking collisions if the player has already been hit and is in crashing animation state( playerCrash is true), or when the player is respawning (playeCrash is "null") or the player will lose all their lives and the sounds effects will wreck your eardrums as all the bullets/enemies on screen crash into the poor paralyzed player.

Code: Select all

   
put the number of lines of activeEnemies  into nLines
if nLines > 0 then 
      if lastEnemy+1 > nLines then put 0 into lastEnemy
      put 0 into lastEnemy
    repeat while lastEnemy < nLines
         add 1 to lastEnemy
         put line lastEnemy of activeEnemies into cEnemyName
         if cEnemyName is not empty and cEnemyName is not "empty" and cEnemyName is not "activeEnemies" then 
            put the loc of graphic cEnemyName into eXY
If there are activeEnemies, then repeat for each line of the activeEnemies list, double check that there is not some bad data in the active enemy chosen from the list: cEnemyName .
Livecode does this weird thing now where it returns some string in the form of the variable name for an empty variable? What's up with that anyway?

CheckFactoryCollisions -- enemy versus player missiles

Code: Select all

   --// enemy versus player missiles 
            put 0 into lastMissileCheck
            repeat while lastMissileCheck <20
               add 1 to lastMissileCheck
               put "Missile,"&lastMissileCheck into nMissile 
               put the loc of graphic nMissile into mXY 
               if mXY is within the rect of graphic cEnemyName then
                  send MissileHit to graphic cEnemyName 
                  EnemyExplosions (mXY)
                  replace "Enemy" with empty in cEnemyName
                  filter activeEnemies without "*"&cEnemyName  
                  set the loc of graphic nMissile to -1000,-1000
                  exit repeat
               end if
            end repeat
The missiles are actually commented out at this part until our next stack.
There are 20 missiles and gamers are blood thirsty missile spraying maniacs, so we loop through all of them and see if their x,y location is within the rectangle of an active enemy. In theory we should probably only loop through the activeMissiles? I had such a hard time wrangling control over the activeEnemies and activeBullet lists so I just went for the whole armory here.

CheckFactoryCollisions -- enemy versus player

Code: Select all

 --// Enemy vs Player: check for player collision and reset
            if eXY  is within the rect of graphic "playerHitBox" then 
               send DeactivateEnemy to graphic cEnemyName in 0 milliseconds
               replace "Enemy" with empty in cEnemyName
               filter activeEnemies without "*"&cEnemyName  
               put true into playerCrash
               play soundDirectory() & "/PlayerCrash.wav"
               EnemyExplosions (eXY) 
            end if
If the enemy collides with the player, everyone crashes and explodes!

Code: Select all

         end if
      end repeat
   end if
End actions taken if cEnemyName is not some junk data
End looping through the activeEnemies list
End actions taken if activeEnemies list isn't empty to begin with
Whew!

CheckFactoryCollisions -- enemy bullets versus player

Code: Select all

   --//  enemy bullets vs. player
   repeat with B = 1 to 20
      put "Bullet"& B into tBullet
      put the loc of graphic tBullet into bXY
      if bXY  is within the rect of graphic "playerHitBox" then 
         put tBullet into cBulletName
         replace "Bullet" with empty in cBulletName
         filter activeBullets without "*"&cBulletName  
         put true into playerCrash
         play soundDirectory() & "/PlayerCrash.wav"
         EnemyExplosions (loc of graphic "Player") 
         send DeactivateBullet to graphic tBullet in 4 milliseconds
         send StageEnemies to stack (the mainStack of this stack) in 0 milliseconds
         --exit repeat
         exit CheckFactoryCollisions
      end if
   end repeat
There are 20 enemy bullets, loop through them and see if they have entered the rectangle of the "playerHitbox" graphic that follows the "Player" graphic around. If it's a hit, deactivate the bullet, play the playerCrash sound,
set the playerCrash state to true, activate the explosion effects at the player, deactivate the bullet, send all the enemies back to their offscreen secret base for a victory party. Exit check for collisons so it doesn't keep running all these missile,bullet,enemy checks while the player's ship animates it's demise.

Code: Select all

   --//send CheckFactoryCollisions to stack (the mainStack of this stack) in 2 milliseconds
end CheckFactoryCollisions
If the player isn't crashed then keep checking collisions there's a war to be won!
This has last line before the end has been deprecated, all handlers important to gameplay and are sent repeatedly are controlled in a "Game Controller" type handler (ie PlayGame) instead of individual handlers sending messages willy nilly and running amok. Well all handlers but enemies, bullets, missiles and explosions..have yet to figure out how to maintain their various info and have a single controller take care of the relevant actions.
Last edited by xAction on Sat Oct 09, 2021 12:11 pm, edited 4 times in total.

xAction
Posts: 86
Joined: Sun Oct 03, 2021 4:14 am

Re: LiveCode Game Tutorial: Side Scrolling Shoot'em'up

Post by xAction » Tue Oct 05, 2021 9:15 am

Step 14b. The CheckFactoryCollisions handler in one code box for quick copy/paste.

Code: Select all

on CheckFactoryCollisions
   if the tool is not "browse tool" then exit CheckFactoryCollisions
   if playerCrash is true or playerCrash is "null" then exit CheckFactoryCollisions
   put the number of lines of activeEnemies  into nLines
   if nLines > 0 then 
      if lastEnemy+1 > nLines then put 0 into lastEnemy
      put 0 into lastEnemy
      repeat while lastEnemy < nLines
         add 1 to lastEnemy
         put line lastEnemy of activeEnemies into cEnemyName
         if cEnemyName is not empty and cEnemyName is not "empty" and cEnemyName is not "activeEnemies" then 
            put the loc of graphic cEnemyName into eXY
            
            --// enemy versus player missiles 
            put 0 into lastMissileCheck
            repeat while lastMissileCheck <20
               add 1 to lastMissileCheck
               put "Missile,"&lastMissileCheck into nMissile 
               put the loc of graphic nMissile into mXY 
               if mXY is within the rect of graphic cEnemyName then
                  send MissileHit to graphic cEnemyName 
                  EnemyExplosions (mXY)
                  replace "Enemy" with empty in cEnemyName
                  filter activeEnemies without "*"&cEnemyName  
                  set the loc of graphic nMissile to -1000,-1000
                  exit repeat
               end if
            end repeat
            --// 
            
            --// Enemy vs Player: check for player collision and reset
            if eXY  is within the rect of graphic "playerHitBox" then 
               send DeactivateEnemy to graphic cEnemyName in 0 milliseconds
               replace "Enemy" with empty in cEnemyName
               filter activeEnemies without "*"&cEnemyName  
               put true into playerCrash
               play soundDirectory() & "/PlayerCrash.wav"
               EnemyExplosions (eXY) 
            end if
         end if
      end repeat
   end if
   
   --//  enemy bullets vs. player
   repeat with B = 1 to 20
      put "Bullet"& B into tBullet
      put the loc of graphic tBullet into bXY
      if bXY  is within the rect of graphic "playerHitBox" then 
         put tBullet into cBulletName
         replace "Bullet" with empty in cBulletName
         filter activeBullets without "*"&cBulletName  
         put true into playerCrash
         play soundDirectory() & "/PlayerCrash.wav"
         EnemyExplosions (loc of graphic "Player") 
         send DeactivateBullet to graphic tBullet in 4 milliseconds
         send StageEnemies to stack (the mainStack of this stack) in 0 milliseconds
         --exit repeat
         exit CheckFactoryCollisions
      end if
   end repeat
   
   send CheckFactoryCollisions to stack (the mainStack of this stack) in 2 milliseconds
end CheckFactoryCollisions
Last edited by xAction on Fri Oct 08, 2021 6:40 am, edited 2 times in total.

xAction
Posts: 86
Joined: Sun Oct 03, 2021 4:14 am

Re: LiveCode Game Tutorial: Side Scrolling Shoot'em'up

Post by xAction » Tue Oct 05, 2021 9:38 am

Step 14c. Crash Test Dummy
EnemyFactoryv4.png
Enemy Factory version 4, with crash test dummy
In the Enemy Factory stack I added a "Player" object surrounded by a big "PlayerHitBox" labeled "Target Dummy" so that you can see the enemies wrecking havoc on the player.

I've created a repeating handler to run a simulation:
MouseAcitvateEnemies

Code: Select all

on MouseAcitvateEnemies
   if playerCrash is true or playerCrash is "null"then 
      TestDummyCrash
   else
      ActivateEnemies
      set the label of button "activeEnemies" to activeEnemies
      set the label of button "activeBullets" to activeBullets
      CheckFactoryCollisions
   end if
   put "0,0,"& stackDepth() into stackRect
   if the mouseLoc is within stackRect and the tool is "browse tool" then 
      send MouseAcitvateEnemies  to stack (the mainStack of this stack)in 2 milliseconds
   end if
end MouseAcitvateEnemies
If the player is not in a crashed state then ActivateEnemies, post activeEnemies and activeBullets lists in labels of the appropriate buttons (because posting to fields is slow), CheckFactoryCollisions, and if the mouse is still in the stack then repeat this handler.

This is activated via button "Activate Enemies"

If the player is in a crashed state then run:
TestDummyCrash

Code: Select all

on TestDummyCrash
   if crashTime < 10   then
      put "null" into playerCrash
      put the lineSize of graphic "PlayerHitBox" into LinSiz
      add 2 to LinSiz
      set the lineSize of graphic "PlayerHitBox" to LinSiz
      add 1 to crashTime
      --send TestDummyPlayer to stack (the mainStack of this stack) in 2 milliseconds
   else
      put 0 into crashTime
      put false into playerCrash
      set the lineSize of graphic "PlayerHitBox" to 2
   end if
end TestDummyCrash
While the player is in a crash state the handler will run 10 times before resetting the player back to it's un-crashed state. During the loop it will increase the line size of the "PlayerHitBox" object to give feedback that it's running.
In the game the player spins out of control and descends while fading out during the crash state, but this one is a dummy and stays put.

It's rather satisfying to watch.
See Step 11 for the complete Enemy Factory stack download
Last edited by xAction on Sat Oct 09, 2021 12:13 pm, edited 2 times in total.

jiml
Posts: 336
Joined: Sat Dec 09, 2006 1:27 am
Location: Los Angeles

Re: LiveCode Game Tutorial: Side Scrolling Shoot'em'up

Post by jiml » Tue Oct 05, 2021 5:53 pm

xAction, thanks for generously sharing your work!

Jim Lambert

xAction
Posts: 86
Joined: Sun Oct 03, 2021 4:14 am

Re: LiveCode Game Tutorial: Side Scrolling Shoot'em'up

Post by xAction » Wed Oct 06, 2021 12:36 pm

Part 15. SHMUP RESOURCE
I appreciate the appreciation folks.
SHMUPRESOURCEv2Preview.png
SHMUP_RESOURCEv2.zip
Enemy & Missile Factory all in one, new features. 99% game ready scripts.
(19.77 KiB) Downloaded 187 times
I noticed that in the collision detection routine I had to comment out the player missile section because the Enemy Factory had no missiles.
Obviously I'd have to add missiles to the Enemy Factory and if I was going to do that then all the Missile Factory stuff would have to come with it.
So I did that, and now the Missile/Enemy factories are SHMUP_RESOURCE.

Lots of changes:
The "Edit x Script" buttons have been removed, and the editing on the scripts stored in buttons is facilitated by a script on the group that holds them:

Code: Select all

on mouseDown
   put the short name of the mouseControl into tControl
   do "edit the script of button" && tControl
end mouseDown
MakeMissiles & MakeBullets is replaced by MakeProjectiles, just pass it a name "Missile", "Bullet" and it'll make those and pass the appropriate script to them. If I give up on my freaky explosion graphics I can probably do the same for them, tempting, since I found big slow down when running tests with the explosions, had to shorten their lives significantly which makes it overkill to run their shape shifting scripts at all. They stay for now.

GetExplosions, GetBullets, GetMissiles are replaced with GetGraphics again pass it the name: "Missile", "Bullet", "Explosion" and it will grab them and display them on screen, as well as update their script in case you've been editing. GetEnemies had to remain as the enemies are layed out in a neat little grid.

StageEnemies, StageExplosions, StageMissiles, StageBullets has been replaced with StageGraphics again pass it the appropriate name and it will scoot the objects off screen.

I've added the list variable activeMissiles and now only loop over the active missiles when checking collisions.
Along with that the missile scripts gained, a DeactivateMissile handler and the stack script gained an InactiveMissiles handler to clean up the activeMissiles list.

All objects gained an originXY custom property, an originLoc local variable and a RecordOrigin handler. The stack can set the originXY property on all the graphic objects in one shot with the RecordGraphicOrigins handler. The custom property and/or the local variable is used to snap the objects right back where they came from after testing. In game they will snap back to their staged off screen position.

The Activate Bullet button has a mouseUp handler that now sends MouseTestBullets to the stack which then sets the playerCrash to false, activates CheckCollisions, and sends an ActivateBullet message to a random enemy who then shoots the target dummy.

The Fire Missiles has mouseDown and mouseStillDown handlers that do a bunch:
First it sends PrepareShootingRange which runs all the Get*game object commands to put everything in place, it sets the timeLevel based on the thumb position of the "timeLevel" scrollbar, which is the max number of enemies that are active at a time. Despite whatever value that slider gives, the button sends FullActiveEnemies which simply puts every enemy into the activeEnemies list. Then it sends BobTargetDummy which moves the TargetDummy group containing graphics "Player" and "PlayerHitBox" up and down along the rows occupied by the enemy graphics. Then it activates CheckCollisions.

After all that the button runs the MouseStillDown handler if you're still holding the mouse down on it, Here it sends MouseFireMissiles which updates the flyDir variable based on the angle of the "Player" graphic , sets playerCrash to false and sends FireMissile command. If playerCrash is true CheckCollisions exits, important to remember if you're wondering why nothing is colliding, as I have done many times.
The explosions activate at the missile position when active enemies in the galley get shot. The only indications that they are hit is the activeEnemies list displayed on the right shrinking as they are blasted and the explosion graphics at their location on account of their DeactivateEnemy handler snapping them back to their originLoc instead of somewhere offsrceen.

The BulletStyle and MissileStyle buttons now send their name and their menu pick to UpdateGraphicStyle which updates the appropriate graphic style. This sets stack local variables missileStyle and bulletStyle which the MakeProjectiles script will use to retain your style choice if you remake the objects. The "Line" style now uses variables xMod & yMod derived from the DirToInt handler to draw the chevron shape facing left or right depending on the direction given, or the direction the player is facing if no direction is given.
DirToInt simply returns 1 or -1 based on string values "up,down,left,right"

Button "Run Sim" pops up an ask dialog requesting the seconds to run a simulation and then sends PrepareSimulation

PrepareSimulation sets the local variable maxSimTime with the value given. The local variable beginSimTime is set to the current seconds. the bSimRunning local bollean variable is set to true. playerCrash is set to false, Sends PrepareShootingRange, then sends UpdateScore a value of -1 which sets the score to 0. It sets the local variable stackRect because increasing the stack window just before the simulation gives more space to simulate stuff, so its a good idea to update that variable for scripts that will check it. The "Player" graphic gets flipped to face right, else it'll just shoot all the enemies in the gallery, flyDir variable is updated to "right", if the hilite of the CheckCollisions checkbox is set, then CheckCollisions will begin to loop. Finally, RunSimulation is called.

RunSimulation updates the timeLevel from the "timeLevel" scrollbar, which again limits how many activeEnemies there can be.
if the hilite of button "TestDummy" is true then BobTargetDummy is called and randomly FireMissile is called.
if the hilite of button "TestEnemies" is true then SimulateEnemies is called, which calls ActivateEnemies unless playerCrash is true or "null", in which case it calls TestDummyCrash
If the current seconds minus beginSimTime is less than maxSimTime RunSimulation is sent to the stack again in 2 milliseconds.
Otherwise the "Player" is flipped back to the left, flyDir is update as "left" PrepareShootingRange puts everyone back in the gallery.
bSimRunning is set to false
and throughoout it all UpDateListLabels keeps us updated about whats happening in activeEnemies, activeBullets,& activeMissiles

The simulation reveals interesting things, especially about the CheckCollision routine which exits if the mouse is out of the window, there is an immediate insane speed boost in the simulation once the collision Repeat loops aren't called. Apparently enemies aren't deactivating at some point. Not sure why...lets see. Okay I tink I fixed that, seems they were flying right pass their targetRect, so I made it bigger and I'm using DeltaToTarget to guide them to it rather than the bunch of if statements I had before.

There's a few other changes in this Shump_Resource over what I did previously, Maybe I'll redo the tutorial now that I have a fresh guide and fixed bugs.
Or we'll just carry on from here, eh? Good idea. Tomorrow I move the SHMUP_RESOURCE stuff into the game stack, hide all the "Developer" controls when in play mode and signal the simulation to run as a game and see what happens.

Oh and a reminder, if this stack lives in the same folder as the Data folder that countains the Sound folder with the sound files, you get sound effects, otherwise it's very quiet.

xAction
Posts: 86
Joined: Sun Oct 03, 2021 4:14 am

Re: LiveCode Game Tutorial: Side Scrolling Shoot'em'up

Post by xAction » Thu Oct 07, 2021 10:37 am

Step 16. SHMUP GAME TUTORIAL STACK 08
SHMUPTUTORIALSTACK08_prvw.png
NOTE: Old SHMUP Tutorial Stacks removed. Will attach latest and greatest stacks to my first and last posts of thread.
  • Changes to Tutorial Stack 8
    1. merged Enemy & Missile Factory objects , scripts and variables to game
    2. renamed playerCrash variable to playerCrashed
    3. made PlayerCrash & PlayerRespawn handlers
    4. expanded ability of LoadingScreen handler to handle "GameOver" state
    5. Added InitGame to handle game init sequence
    6. Added GameOver handle game ovver state
    7. Updated CheckCollissions to exit if no enemies are active..this may cause a bug with stray bullets?
    8. Added InitMusic, RandomSong,
    9. Added PlayGame handler as a Game Controller that controls the game play
    10. Replaced PlayTest and other handlers like GetEnemies for more efficient handlers like "GetGraphics aName"
    11 Replaced RecycleBullets with InactiveBullets
    12.Probably more but I forget now, was a long night
Okay, the merge went super smooth. Here we have a working game, shootin' scorin' flyin' and dyin'
There's a score, there's indicators for remaining lives, there's a game over screen (handled by LoadingScreen) and the game pops back to the Title after you're beaten.

It's crazy fast. except when the player shoots, something about that mouseDown check maybe?
Or the send in time? I'm not sure. Just a bit of a stutter.

I changed the playerCrash variable to playerCrashed so I could create a playerCrash handler to handle the crashing action.
Just 28 instances of the word to change in the scripts.

I brought over all the SHMUP_RESOURCE controls except the simulation stuff. It's all contained in group "Developer" which gets hidden unless you hold down the control key when InitGame is being called.

I left all the graphics on screen so you can see whats going in this stack before they all get staged off screen in the final gameplay. You'll note you can't shoot the inactive enemies despite them sitting on screen. Oh also I squeeze the stack down to about as small as it can be, stretch it out before playing, while you are on the Title screen. I'll have to figure out...a bunch of things for various window resizes.

There is an additional group "DevControlButtonGroup" with the Title button which let you jump back to the Title card,
Resume button will resume the game if you've made it stop by mouse exiting the window or maybe some other reasons I haven't foreseen, the Resume button will also set the tool to "browse tool" on mouseEnter so you don't have to make an extra trip to the tool palette to click the buttons over on that side of the screen.
Reset the game by sending InitGame and the most important button, the STOP button will switch the tool to 'pointer tool' which should effectively exit all the scripts that repeat, and then it also reveals the Developer group, in case you need to make some changes, like to bullet speed for instance. Eventualy that stuff will be hidden away and any horrible infinte loops will be left to a force quit to escape from, but I think most of that kind of issue has been taken care of for this game.

I've modified the Play card's script On MouseEnter handler to exit if the Developer group is visible, so that it doesn't try starting the game and fire missiles when you attempt to click buttons. If Developer buttons aren't getting clicked, try leaving the stack and re-entering.

What else? I think that's all for the front end, let's go behind the scenes. When the Play card is entered The card script runs like this:

Code: Select all

on MouseEnter
   if the visible of group "Developer" is true and the mouseloc is within the rect of group "Developer" then exit mouseEnter
   if inProgress is true then
      if the tool is "browse tool" then PlayGame
      end if
end MouseEnter
Ah right, enter the stack from the bottom where the group lives to prevent the game from running while you do developer stuff.
PlayGame sets the inProgress variable to true. We don't want to run PlayGame without having gone through InitGame first.

Code: Select all

on OpenCard
   if the currentCard of this stack is  "Play" then 
     if exists(graphic "Loading") then set the label of graphic "loading" to ""
      set the tool to "browse tool"
      set the loc of graphic "player" to CenterScreen()
      send InitGame to stack (the mainStack of this stack) in 1 milliseconds
   end if
end OpenCard
Here's where we send InitGame and get things rolling.
Not sure I need that message about graphic "Loading" anymore, it was stuck displaying the score between screens for 30 minutes because I had a typo.

Okay I'll break InitGame into pieces here:

Code: Select all

--// set up the scene to play
on InitGame
   put false into cheatMode
   put  the rect of stack (the mainStack of this stack) into lastRect
   put "0,0,"& stackDepth() into stackRect
   if the currentCard of this stack is not "play" then exit InitGame
   set itemDel to comma
   lock screen
   --hide group "GameOver"
   if the controlKey is down then
      show group "Developer"
   else
      hide group "Developer"
   end if
That's all pretty self explanatory ya? CheatMode is for collision free testing. the commented out "GameOver" group I've left off the stack, it's just a big black rectangle with a "play again" button, we just jump to the Title now for that. I thought i might miss it and put back in.

Code: Select all

put 0 into score 
   updateScore "reset"
   put 0 into bulletDelay
   put 0 into missileDelay
   put "" into activeEnemies 
   put "" into activeBullets
   put "" into activeMissiles
   put false into playerCrashed
   put "right" into flyDir
Setting some variables , updateScore handler now clears the score when it's passed "reset" I had been checking if the value was less than 0, but it turns out, now with the delta dx,dy being use for enemy 'speed' which in turn gives the score, there's negative score values. oops. The negative scores are treated with abs() function to get a positive result now.

Code: Select all

   PresentAllGraphics  --// temporary while developing
Currently in this stack we use this handler to display all the enemies, bullets, missiles, and explosions in a gallery on the left of the screen

Code: Select all

-//  set the game objects off screen for actual game
   put "missile,enemy,bullet,explosion" into gameObjs
    repeat for each item obj  in gameObjs
     Stage obj
   end repeat
When we are ready to stop playing developer this will hide every thing before the game starts. It's commented out in the script now.

Code: Select all

   --// reseet the background mountain scrolls...after we make them later
   put the width of graphic "background" into hW
   set the hScroll of group "backgroup" to hW/2
   set the vScroll of group "Backgroup" to 152
When we add the additional mountain graphics and Stars, they will live in the Backgroup group. This is also commented out right now.

Code: Select all

   --// generate the foreground mountain group
   InitForeground -- could randomize the front most mountain everytime if you like
   if exists (graphic "mountains") then put the width of graphic "mountains" into hW
   if exists(group "Mgroup") then set the hScroll of group "MGroup" to hW/2
Generate the mountain foreground, group it, set the group to the width of the window, park it at the bottom of the screen, add a pair of warp points set the hScroll of the group to the middle of the width of the mountain. This hasn't change since we started all those steps ago.

Code: Select all

   InitPlayer

Reset all the player stuff, we'll look at that in a minute.

Code: Select all

   --// memorize where everyone starts with originXY costume property
   RecordGraphicOrigins

   --// hide the loading screen
   LoadingScreen

   put the seconds into timeOfInit
   unlock screen
   --// play
   PlayGame 
end InitGame
All graphics get the originXY custom property set to their current location, the loadingScreen is hidden when not sent an additional argument, the timeOfInit is near the very end so that some other process doesn't give the enemies an unfair advtange if any stalls on the init, finally the game can play!

InitPlayer

Code: Select all

--// reset the player and ship count
on InitPLayer
   repeat with i = 1 to 3
      put "PlayerLives"& i into shipN
      set the foregroundcolor of graphic shipN to Green
      set the blendLevel of graphic shipN to 0
   end repeat
   set the blendLevel of graphic "Player" to 0
   put 3 into  playerLives 
   put CenterScreen() into pXY
   set the loc of graphic "player" to pXY
   set the loc of graphic "sMultBox" to pXY
   put false into playerCrashed
end InitPLayer
"PlayerLives" graphics are 3 little triangles at the top of the screen that indicate how many lives the player has.
The rest is pretty simple, yeah?

Ill move on to another post to explain more game handlers..
Last edited by xAction on Fri Oct 15, 2021 12:00 am, edited 6 times in total.

xAction
Posts: 86
Joined: Sun Oct 03, 2021 4:14 am

Re: LiveCode Game Tutorial: Side Scrolling Shoot'em'up

Post by xAction » Thu Oct 07, 2021 11:09 am

Part 16b. PlayGame Handler
Ill break it down.
PlayGame <<<< this evolves as we progress development!

Code: Select all

on PlayGame
   --// many ways to escape the infinite loop to avoid a force quit
   if  the currentCard of this stack is not "Play" then exit PlayGame
   if the mouseLoc is within the rect of button "stopGame" then set the tool to "pointer tool"
   if the tool is not "browse tool" then exit PlayGame
   if the mouseloc is not within the rect of this card then exit PlayGame
Since this handler gets sent every 2 milliseconds we have to make sure we get out every which way we can before we throw errors all over or have to force quit.

Code: Select all

 --// more enemies over time
   put the seconds - timeOfInit into timePassed
   put floor(timePassed/60)+2 into timeLevel -- every 3 minutes more enemies
   if timeLevel > 10 then put 10 into timeLevel
   view "Level:" && timeLevel
Handle the 'timeLevel' two enemies to start, then 3 then 4 etc as the minutes fly by.

Code: Select all

   --// exit for game over
   if playerLives  < 1 then  exit PlayGame
No playerLives then no play.

Code: Select all

   --// update stack info before giving enemies, missiles, bullets info about it
   if the rect of stack(the mainStack of this stack) is not lastRect then UpdateStackSize
UpdateStackSize probably needs some work to get the mountains sorted, if you change window size during the game.

Code: Select all

   --// intended for use during pause screen/options menu, 
   put true into inProgress 
Finally used this variable in the card script to detect if we've already run InitGame before resuming PlayGame from some stopped state.

Code: Select all

   --// leave play mode until player has exited crashed state
   if playerCrashed is true then  PlayerCrash
PlayerCrash will keep repeating it's activity until the playerCrashed variable is "Null" or false. If it's "Null" nothing happens in PlayGame as the player respawns.

Code: Select all

  --// actually play the game
   if playerCrashed is false then 
      --// ship moves
      PlayerMove 
     
      --// landsape scrolls
      ScrollLandscape  
     
      --// enemies attack!
      ActivateEnemies 
     
      --// player shoots
      if the mouse is down then FireMissile 
   
      --// collisions are checked
      CheckCollisions
   end if
The Meat! Player moves, the background scrolls, the enemies attack, player shoots, collisions are checked, that's a game!

Code: Select all

--// if the music has stopped at the end of it's track play a new song
   add 1 to delayCheck
   if delayCheck> 100 then 
      put 0 into delayCheck
      --UpdateMusic
   end if
I haven't added the music players to the Play card yet, so UpdateMusic is unused at the moment.
Basically if the music player sits at the same time for too long then it's obviously time to change tunes.

Code: Select all

if the currentCard of this stack is "Play" then
      --// if we have warped, fade the warp object
      if the visible of group "WarpPointSource" then send fadeWarpPoint to stack (the mainStack of this stack) in 2 milliseconds
      --// repeat this process
      if the tool is "browse tool" then send PlayGame to stack (the mainStack of this stack) in 2 milliseconds
   end if
end PlayGame
That check for card name at the bottom is probably uneeded now with the check at the top, but switching back to the title card from game over state kep triggering WarpPointSource fade with an error, was driving me nuts. If we aren't in developer mode PlayGame will keep repeating every 2 milliseconds. I'm wondering if I can give it more of a delay so maybe the keyboard will register some input? Right now PlayGame and its sibling processes own the whole computer.

We've seen PlayerMove , ScrollLandscape , and CheckCollisions in previous steps of the tutorial.
I think this one little change to CheckCollisions sped things up 30%

Code: Select all

  put the number of lines of activeEnemies  into nLines
   if nLines is 0 then exit CheckCollisions
There must be some precious milliseconds where there are no active enemies and so the whole check collisions routine is skipped and we gain extra clock cycles every time it happens. In theory.

Another post for another handler.
Last edited by xAction on Sat Oct 09, 2021 12:21 pm, edited 5 times in total.

xAction
Posts: 86
Joined: Sun Oct 03, 2021 4:14 am

Re: LiveCode Game Tutorial: Side Scrolling Shoot'em'up

Post by xAction » Thu Oct 07, 2021 11:41 am

Part 16c. PlayerCrash, PlayerRespawn, GameOver & LoadingScreen Handlers
I'll break it down.

PlayerCrash

Code: Select all

--// Player Destruction
on PlayerCrash
   if  playerCrashed is false or playerCrash is "null" then exit PlayerCrash
   
"null" state is our transition to respawning.
While playerCrashed is true we cycle this animation part.

Code: Select all

   --// spin
   put the Angle of graphic "Player" into tAng
   add 10 to tAng
   set the Angle of Graphic "Player" to tAng
   
Spinning out of control

Code: Select all

   --// fall
   put the loc of graphic "Player" into tLoc
   add 4 to item 2 of tLoc
   set the loc of graphic "Player" to tLoc
Falling.

Code: Select all

   if playerLives > 0 then 
      put "PlayerLives"&playerLives into tShipToFade
      set the foregroundcolor of graphic tShipToFade to Red
      put the blendLevel of graphic tShipToFade into shpBL
      add 1 to shpBL
      set the blendLevel of graphic tShipToFade to shpBL
      set the blendLevel of graphic "player" to shpBL
   end if
Fades the "PlayerLives" object at the top of the screen associated with the number of lives left before this crash event.
Fades the player too.

Code: Select all

  --// Game Over
   if the blendLevel of graphic "player" > 90 then
      subtract 1 from playerLives
      if playerLives < 1 then 
         play soundDirectory() & "/PlayerCrash.wav"
         GameOver
         exit PlayerCrash
      end if
When the "Player" graphic's blendLevel reaches 100, we subtract from the available lives, and if there are none left, we go to GameOver.

Code: Select all

      --// Respawn Player
      put "null" into playerCrashed
      send PlayerRespawn  to stack (the mainStack of this stack ) in 2 milliseconds
   end if
end PlayerCrash
-------END of PlayerCrash

PlayerRespawn

Code: Select all

on PlayerRespawn
   if playerCrashed is false and playerCrashed is not "playerCrashed" then exit PlayerRespawn
   if playerLives < 1 then exit playerRespawn
   
Don't run this script if we are in that crashing animation above. No lives = no game

Code: Select all

   put "" into activeBullets
   put "" into activeEnemies
   put activeBullets & activeEnemies into tPot[
   filter tPot without empty
   if tPot is not "" then
      send PlayerRespawn to stack (the mainStack of this stack) in 30 milliseconds
I'm fuzzy on why I did this. I don't think I need the if check,
Simply setting activeBullets and activeEnemies to empty turns off the collisions that were causing trouble with respawning.
I'll probably clean this up. Update-- this skin tag extra code has been removed in later stacks.

Code: Select all

   else
      --send RandomSong to stack (the mainStack of this stack) in 1 milliseconds
      set the blendLevel of graphic "Player" to 0
      set the width of graphic "Player" to 24
      set the height of graphic "Player" to 18
      set the outerGlow["spread"] of graphic "player" to 34
      set the outerGlow["size"] of graphic "player" to 34
      set the angle of graphic "player" to 0
      set the loc of graphic "Player" to CenterScreen() 
      put false into playerCrashed
      play soundDirectory() & "/Jump.wav"
      send PlayGame to stack (the mainStack of this stack) in 3 milliseconds
   end if
end PlayerRespawn
The rest is pretty self explanatory. RandomSong is disabled until I add the music players to the stack.
--- END of PlayerRespawn

GameOver

Code: Select all

on GameOver
   put false into inProgress
   loadingScreen "GameOver"
   wait 3 seconds
   set the label of graphic "loading" to ""
   go Card "Title"
end GameOver
Show the loadingScreen graphic , but now it says "Game Over" and displays the score, wait 3 seconds, clean the text of the loading screen label and go to card "Title"

LoadingScreen has evolved

Code: Select all

-- // a simple loading screen show with message "show" or hide by default
on LoadingScreen S
   if exists(graphic "loading") is false then
      send  createLoadingScreen to stack (the mainStack of this stack) in 0 milliseconds
      wait 1 second
   end if
   set the label of graphic "loading" to empty
      put "0,0,"   & stackDepth() into stackRect 
         set the rect of graphic "loading" to stackRect
   switch S
      case ""
         hide graphic "loading" 
         break
      case "show" 
         set the label of graphic "loading" to "Loading"
         show graphic "loading"
         break
      case "GameOver"
         set the label of graphic "loading" to "GAME OVER!" & cr & "Score:" & floor(score)
         show graphic "loading"
         break
   end switch
end LoadingScreen
Clears the text on any call to it, hides if not given an argument, shows "Loading" when told to "Show" and on "GameOver" shows text Game Over and a score. Oh and just in case it hasn't been created it will send a message to createLoadingScreen which makes one with various attributes.

I think that's the core of the game, all that's left is adding some media player objects to the stack for music. Next stack.
Last edited by xAction on Sat Oct 09, 2021 12:22 pm, edited 2 times in total.

xAction
Posts: 86
Joined: Sun Oct 03, 2021 4:14 am

Re: LiveCode Game Tutorial: Side Scrolling Shoot'em'up

Post by xAction » Fri Oct 08, 2021 5:08 am

Part 17. Tutorial Stack 9: PauseGame, ResumeGame, EscapeKey & CheckMouseInWindow
TutorialStackk09Preview.png
NOTE: Old SHMUP Tutorial Stacks removed. Will attach latest and greatest stacks to my first and last posts of thread.

Changes in Tutorial Stack 9:
  • 1. Added outsideStackTime, bPaused, bWithinInit, escapeKeyBuffer variables
    2. Added pBaused variable and related handlers to all game objects but the player
    3. Added CheckMouseInWindow handler to Fix mouse exit window causing game stop
    4. Expanded EscapeKey handler to handle game pause and to switch to pointer tool when mouse outside window
    5. Added PauseGame handler, and PauseActivity handlers to all objects except player
    6. Added ResumeActivity handlers to all objects and expanded ResumeGame to handle game resumption
    7. Added a little glow to the loading graphic text but it only shows when opaque is false
    8. Removed unneccsary if...then check for enemies& bullets in PlayerRespawn
There was a "bug" I kept having where the enemies and bullets went super high speed, killed the player and the game just stopped.
Turns out my mouse was briefly leaving the screen long enough to trigger a game exit...from handlers that check for the mouse in order to continue.
So I added CheckForMouseInWindow and signal it at the top of PlayGame before running any other processes.
Now the game won't pause unless the mouse has been outside the game screen for a consecutive 60 milliseconds.
It snapback to 0 count whenever the mouse re-enters the window.
CheckMouseInWindow

Code: Select all

on CheckMouseInWindow
   if the mouseloc is within the rect of this card then then 
      put 0 into outSideStackTime
   else
      add 1 to OutsideStackTime
      if outSideStackTime > 60 then put true into bPaused
   end if
end CheckMouseInWindow
Here's the relevant part of PlayGame

Code: Select all

--// check if the mouse is inside the stakck window
   CheckMouseInWindow
   if bPaused is true then 
      PauseGame 
      exit Playgame
   end if
   --if the mouseloc is not within the rect of this card then exit PlayGame
As you see bPaused boolean sends PauseGame and exits any further game activity.

That last commented line is what the new method replaces, exiting the scripts when exiting the window was all about desperately trying to control runaway scripts. I had lots of them while building this, a typo here or there and madness happens. The longest part of this whole pause/escapekey development was an extra lower case "s" buried in one line checking for outSideStackTime to be greater than >200, 2000, or even 200000.
It kept returning true and I was totally baffled.

I also had issues with getting the escapeKey to do what I wanted and of course it shouldn't just be a develoepr crutch to stop the scripts so I wanted to get it to pause the game like any other game. I had to put a check for how many times it's cycled while being pressed via escapeKeyBuffer variable. See you can be pressing the key and the game can be ignoring you until it is done doing other things then all of a sudden it registers a bunch of keypresses all at once and if you are usng it to flip from paused to play, or pointer to browse tool it'll do wacky stuff if not managed.
Now it pauses/resumes game and only switches to pointer tool if the mouse is outside the window so we can still try to stop a runaway script while developing.
EscapeKey

Code: Select all

on EscapeKey
   add 1 to escapeKeyBuffer
   switch escapeKeyBuffer
      case 1
      case 2
         if bPaused is true then
            if the mouseLoc is within the rect of this card then
               put false into bPaused
               put 0 into outsideStackTime
               set the tool to "browse tool"
               loadingscreen
               ResumeGame
            end if
         else
            put true into bPaused
         end if
         break
      case 3
         set the tool to "pointer tool"
         put 0 into escapeKeyBuffer
         break
   end switch
end EscapeKey
if escapekeybuffer is 1 or 2 and mouse is in the window then: if the game is paused then resume, else pause the game.
If the escapeyBuffer reaches 3 while mouse is oustside the window then choose the pointer tool, which should stop 99% of the scripts immediately

PauseGame

Code: Select all

on  PauseGame
   LoadingScreen "Pause"
   
   put activeEnemies & cr & activeBullets &cr  & activeMissiles into activeObjects 
   put cr & "explosion1" & cr & "explosion2"  & cr & "explosion3"  after activeObjects
   filter activeObjects without empty
   repeat  for each line AO in activeObjects
      send "PauseActivity" to graphic AO
   end repeat
   
end PauseGame
The loading/pause screen pops up.
All the active game objects get sent a PauseActivity message.
All the game objects now have their own bPaused variable and a check to exit their activated state handlers if its true.
Then when sent ResumeActivity they will get back to work where they left off. here's an example from the enemy scripts.

Partial Script of button "EnemyScript"

Code: Select all

on PauseActivity
   put true into bPaused
end PauseActivity

on ResumeActivity
   put false into bPaused
   if enemyActivated is true then EnemyTravel
end ResumeActivity

--// repeats while enemy travels on tracjectory derived from targetLoc and enteres rectangle of targetRect
on EnemyTravel
   if bPaused is true then exit EnemyTravel
-- etc etc 
The relevant change to LoadingScreen is as follows:

Code: Select all

  case "Pause"
         set the opaque of graphic "Loading" to false
         set the label of graphic "Loading" to "PAUSED"
         show graphic "Loading"
         break
Normally graphic "loading" is opaque to hide any left behind clutter that might be in the Play window when we switch cards.
Here we set it's opaque false to keep the game stuff visible.
I also added lock screen/unlock screen to the handler because "loading"'s showName value is set true and it was showing "loading" when the label was set to empty, a split second before being set to "Paused"

When we resume the game with ResumeGame via the button press at the top right of the screen, or escape key then:

Code: Select all

--// return to the game
on ResumeGame
   set the tool to "browse tool"
   put false into bPaused
   put 0 into escapeKeyBuffer
   LoadingScreen
   put activeEnemies & cr & activeBullets &cr  & activeMissiles into activeObjects 
   put cr & "explosion1" & cr & "explosion2"  & cr & "explosion3"  after activeObjects
   filter activeObjects without empty
   repeat  for each line AO in activeObjects
      send "ResumeActivity" to graphic AO
   end repeat
   send PlayGame to stack (the mainStack of this stack) in 10 milliseconds
end ResumeGame
All the relevant variables are set so the game will run, the loading/pause screen is hdden and all the objects that were active are reactivated and PlayGame gets back to cycling all the game stuff.
Last edited by xAction on Fri Oct 15, 2021 12:01 am, edited 1 time in total.

Post Reply

Return to “Games”