OK.. so for last couple of weeks Ive been playing with the idea of creating a simple framework to allow games to be written using no external media.
Ive come up with what Im calling Block games. The graphics are very low res (160x120) zoomed x4 for display purposes. 16 colour fixed pallette. A couple of methods allow simple sounds to be generated.
I knocked up a space invaders game to try it out and it's damm fun writing it. It still needs a bit of work but it gives a good idea of what its all about.
anyways.. in case anyone is interested.. or bored.. or curious.. or whatever here is the source ( like I said.. no external media :) ).
I would be interested if it works OK on the older machines out there. btw.. dont worry if you cant get more than 30FPS.. thats a fast as it's supposed to go !.
Oh yeah. Press F2 to smooth the graphics display. F1 to go back to square blocks. ESC will quit the app.
In the game. P to start a game. Cursor keys move the player. Space key fires a shot.
First the framework. Save this first file as TBlockGame.bmx.
and here is the Invaders game. Save as BlockInvaders.bmx. Compile and run. :)
Ive come up with what Im calling Block games. The graphics are very low res (160x120) zoomed x4 for display purposes. 16 colour fixed pallette. A couple of methods allow simple sounds to be generated.
I knocked up a space invaders game to try it out and it's damm fun writing it. It still needs a bit of work but it gives a good idea of what its all about.
anyways.. in case anyone is interested.. or bored.. or curious.. or whatever here is the source ( like I said.. no external media :) ).
I would be interested if it works OK on the older machines out there. btw.. dont worry if you cant get more than 30FPS.. thats a fast as it's supposed to go !.
Oh yeah. Press F2 to smooth the graphics display. F1 to go back to square blocks. ESC will quit the app.
In the game. P to start a game. Cursor keys move the player. Space key fires a shot.
First the framework. Save this first file as TBlockGame.bmx.
SuperStrict Import BRL.GLMax2D Import BRL.PNGLoader Import BRL.FreeAudioAudio Import BRL.Random ' modules which may be required: ' Import BRL.BMPLoader Const WAVE_SIN:Int = 0 Const WAVE_SQUARE:Int = 1 Const WAVE_TRUNCATE:Int = 0 Const WAVE_BOUNCE:Int = 4 Const WAVE_WRAP:Int = 8 Type TBaseSprite Field xPos:Float Field yPos:Float Field lifeSpan:Int Field dead:Int Field layer:Int Field xMove:Float Field yMove:Float Method Draw(canvas:TPixmap) Abstract Method Update() xPos :+ xMove yPos :+ yMove If lifeSpan >= 0 Then lifeSpan :- 1 If lifeSpan < 0 Then dead = True End If End If End Method Method SetMove ( newXMove:Float, newYMove:Float) xMove = newXMove yMove = newYMove End Method End Type Type TSprite Extends TBaseSprite ' Field image:TPixel[] Field image:Int[] Field width:Int Field height:Int Field frame:Int Field transparent:Int Method Draw(canvas:TPixmap) Local xPix:Int Local yPix:Int Local imagePos:Int Local colPos:Int Local xPosPix:Int Local yPosPix:Int imagePos = frame * ( width * height ) For yPix = 0 To height - 1 For xPix = 0 To width - 1 ' image[imagePos].xPos = xPos + xPix ' image[imagePos].yPos = yPos + yPix xPosPix = xPos + xPix yPosPix = yPos + yPix ' colPos = image[imagePos].colour * 3 colPos = image[imagePos] * 3 If colPos <> transparent Then ' image[imagePos].Draw(canvas) If xPosPix >=0 And xPosPix <= 159 And yPosPix >= 0 And yPosPix <= 119 Then WritePixel(canvas,Ceil(xPosPix), Ceil(yPosPix), $ff Shl 24 | TBlockGame.colours[colPos] Shl 16 | TBlockGame.colours[colPos+1] Shl 8 | TBlockGame.colours[colPos+2]) End If End If imagePos :+ 1 Next Next End Method End Type Type TPixel Extends TBaseSprite Field colour:Int Method Draw(canvas:TPixmap) Local colPos:Int = colour*3 If xPos >=0 And xPos <= 159 And yPos >= 0 And yPos <= 119 Then WritePixel(canvas,Ceil(xPos), Ceil(yPos), $ff Shl 24 | TBlockGame.colours[colPos] Shl 16 | TBlockGame.colours[colPos+1] Shl 8 | TBlockGame.colours[colPos+2]) End If End Method Function CreatePixel:TPixel(xPos:Int,yPos:Int, colour:Int, lifeSpan:Int=0, layer:Int=0 ) Local pixel:TPixel pixel = New TPixel pixel.xPos = xPos pixel.yPos = yPos pixel.colour = colour pixel.lifeSpan = lifeSpan pixel.layer = layer Return pixel End Function End Type Type TBlockGame Global colours:Int[] = [0,0,0, 128,0,0, 0,128,0, 0,0,128, 128,128,0, 0,128,128, 128,0,128, 128,128,128, 64,64,64, 255,0,0, 0,255,0, 0,0,255, 255,255,0, 0, 255,255, 255,0,255, 255,255,255] ' charset = chars 32-126 = !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ ' characters are displayed in a 5*5 grid Global text:String[] = [.. "00000 00000 00000 00000 00000", .. "00100 00100 00100 00000 00100", .. "00000 01010 01010 00000 00000", .. "01010 11111 01010 11111 01010", .. "01111 10100 01110 00101 11110", .. "10001 00010 00100 01000 10001", .. "01100 10010 01110 10011 01111", .. "00100 00100 00000 00000 00000", .. "00100 01000 01000 01000 00100", .. "00100 00010 00010 00010 00100", .. "10101 00100 11111 00100 10101", .. "00100 00100 11111 00100 00100", .. "00000 00000 00000 00010 00100", .. "00000 00000 11111 00000 00000", .. "00000 00000 00000 00000 00100", .. "00001 00010 00100 01000 10000", .. .. "01110 10001 10101 10001 01110", .. "00100 01100 00100 00100 00100", .. "11110 00001 01110 10000 11111", .. "11110 00001 00110 00001 11110", .. "10000 10000 10100 11111 00100", .. "11111 10000 11110 00001 11110", .. "01100 10000 11110 10001 01110", .. "11111 00001 00010 00010 00010", .. "01110 10001 01110 10001 01110", .. "01110 10001 01111 00001 00110", .. .. "00000 00100 00000 00100 00000", .. "00000 00100 00000 00100 01000", .. "00010 00100 01000 00100 00010", .. "00000 11111 00000 11111 00000", .. "01000 00100 00010 00100 01000", .. "01110 00001 00110 00100 00100", .. "01110 10001 10111 10001 01111", .. .. "01110 10001 11111 10001 10001", .. "11110 10001 11110 10001 11110", .. "01110 10000 10000 10000 01110", .. "11110 10001 10001 10001 11110", .. "11111 10000 11110 10000 11111", .. "11111 10000 11110 10000 10000", .. "01110 10000 10011 10001 01111", .. "10001 10001 11111 10001 10001", .. "01110 00100 00100 00100 01110", .. "11111 00001 00001 10001 01110", .. "10010 10100 11000 10100 10010", .. "10000 10000 10000 10000 11111", .. "10001 11011 11011 10101 10101", .. "10001 11001 10101 10011 10001", .. "01110 10001 10001 10001 01110", .. "11110 10001 11110 10000 10000", .. "01110 10001 10001 10011 01111", .. "11110 10001 11110 10010 10001", .. "01111 10000 01110 00001 11110", .. .. "11111 00100 00100 00100 00100", .. "10001 10001 10001 10001 01111", .. "10001 10001 01010 01010 00100", .. "10101 10101 11011 11011 10001", .. "10001 01010 00100 01010 10001", .. "10001 01010 00100 00100 00100", .. "11111 00010 00100 01000 11111", .. .. "01110 01000 01000 01000 01110", .. "10000 01000 00100 00010 00001", .. "01110 00010 00010 00010 01110", .. "00100 01010 10001 00000 00000", .. "00000 00000 00000 00000 11111", .. "00100 00010 00000 00000 00000", .. .. "01110 10001 11111 10001 10001", .. "11110 10001 11110 10001 11110", .. "01110 10000 10000 10000 01110", .. "11110 10001 10001 10001 11110", .. "11111 10000 11110 10000 11111", .. "11111 10000 11110 10000 10000", .. "01110 10000 10011 10001 01111", .. "10001 10001 11111 10001 10001", .. "01110 00100 00100 00100 01110", .. "11111 00001 00001 10001 01110", .. "10010 10100 11000 10100 10010", .. "10000 10000 10000 10000 11111", .. "10001 11011 11011 10101 10101", .. "10001 11001 10101 10011 10001", .. "01110 10001 10001 10001 01110", .. "11110 10001 11110 10000 10000", .. "01110 10001 10001 10011 01111", .. "11110 10001 11110 10010 10001", .. "01111 10000 01110 00001 11110", .. .. "11111 00100 00100 00100 00100", .. "10001 10001 10001 10001 01111", .. "10001 10001 01010 01010 00100", .. "10101 10101 11011 11011 10001", .. "10001 01010 00100 01010 10001", .. "10001 01010 00100 00100 00100", .. "11111 00010 00100 01000 11111", .. .. "00110 01000 11000 01000 00110", .. "00100 00100 00000 00100 00100", .. "01100 00010 00011 00010 01100", .. "00000 01000 10101 00010 00000", .. "11111 11111 11111 11111 11111" .. ] Const Black:Int = 0 Const Red:Int = 1 Const Green:Int = 2 Const Blue:Int = 3 Const Yellow:Int = 4 Const Cyan:Int = 5 Const Magenta:Int = 6 Const White:Int = 7 Const Bright:Int = 8 Field sprites:TList Field spriteLayers:TList[] Field layerCount:Int Field SoundEffect:TSoundEffect Field canvas:TPixmap Field scrImage:TImage Method New() sprites = CreateList() InitLayers(1) soundEffect = New TSoundEffect End Method Method InitLayers(newlayerCount:Int) layerCount = newlayerCount spritelayers = New TList[layerCount] Local layer:Int For layer = 0 To layerCount - 1 spriteLayers[layer] = CreateList() Next End Method Method GameRun() Graphics 640,480,0,30 SetScale 4,4 SetBlend(solidblend) scrImage = CreateImage(160,120,1,0) GameInit() While Not KeyHit ( Key_ESCAPE ) ' F1 will smooth the displayed image. F1 will display the image normally If KeyHit(key_F1) Then scrImage = CreateImage(160,120) End If If KeyHit(key_F2) Then scrImage = CreateImage(160,120,1,0) End If canvas = LockImage(scrImage) ClearPixels(canvas) Local sprite:TBaseSprite Local spriteLayer:TList For spriteLayer = EachIn spriteLayers For sprite = EachIn spriteLayer If Not sprite.dead Then sprite.Draw(canvas) sprite.Update() Else ListRemove(sprites, sprite) ListRemove(spriteLayer,sprite) End If Next Next Update() UnlockImage(scrImage) 'SetColor 255,255,255 DrawImage(scrImage,0,0) Flip If KeyHit(key_S) Then Local scrdump:TPixmap scrDump = GrabPixmap(0,0,640,480) SavePixmapPNG(scrDump,"ScrDump.png",5) End If Wend End Method Method WriteText(xPos:Int, yPos:Int, colour:Int, msg:String) Local msgPos:Int Local char:Int Local charX:Int Local charY:Int Local chardef:String For msgPos = 0 To Len(msg)-1 char = msg[msgpos] & $7f If char < 32 Then char = 127 End If chardef = text[char-32] For charY = 0 To 4 For charX = 0 To 4 If chardef[charY*6 + charX] = Asc("1") Then Local colPos:Int = colour*3 WritePixel(canvas,Ceil(xPos+msgPos*6+charX), Ceil(yPos+charY), $ff Shl 24 | colours[colPos] Shl 16 | colours[colPos+1] Shl 8 | colours[colPos+2]) End If Next Next Next End Method Method DrawPixel:TPixel(xPos:Int,yPos:Int, colour:Int, lifeSpan:Int=0, layer:Int=0 ) Local pixel:TPixel pixel = TPixel.CreatePixel ( xPos, yPos, colour, lifeSpan, layer) ListAddLast(sprites, pixel) ListAddLast(spriteLayers[layer],pixel) Return pixel End Method Method MakeSprite:TSprite(width:Int, height:Int, frames:Int=1, lifeSpan:Int=-1, layer:Int=0 ) Local sprite:TSprite sprite = New TSprite ' sprite.image = New TPixel[width*height*frames] sprite.image = New Int[width*height*frames] sprite.width = width sprite.height = height sprite.lifeSpan = lifeSpan sprite.layer = layer ListAddLast(sprites, sprite) ListAddLast(spriteLayers[layer],sprite) Return sprite End Method Method CloneSprite:TSprite(sourceSprite:TSprite) Local sprite:TSprite sprite = New TSprite sprite.image = sourceSprite.image sprite.width = sourceSprite.width sprite.height = sourceSprite.height sprite.lifeSpan = sourceSprite.lifeSpan sprite.xPos = sourceSprite.xPos sprite.yPos = sourceSprite.yPos sprite.frame = sourceSprite.frame sprite.layer = sourceSprite.layer sprite.transparent = sourceSprite.transparent ListAddLast(sprites, sprite) ListAddLast(spriteLayers[sprite.layer],sprite) Return sprite End Method Method SetSpriteImage( sprite:TSprite, pixels:Int[], frame:Int=0) Local xPix:Int Local yPix:Int Local colour:Int Local pixelPos:Int Local colourPos:Int pixelPos = sprite.width*sprite.height*frame colourPos = 0 For yPix = 0 To sprite.height - 1 For xPix = 0 To sprite.width - 1 colour = pixels[colourPos] ' sprite.image[pixelPos] = TPixel.CreatePixel(xPix,yPix,colour,-1) sprite.image[pixelPos] = colour pixelPos :+ 1 colourPos :+ 1 Next Next End Method Method SetSpritePixel ( sprite:TSprite, xpos:Int, ypos:Int, colour:Int, frame:Int=0) ' sprite.image[sprite.width*ypos+xpos] = TPixel.CreatePixel(xpos,ypos,colour,-1) sprite.image[sprite.width*ypos+xpos] = colour End Method Method GetSpritePixel:Int ( sprite:TSprite, xpos:Int, ypos:Int, frame:Int=0) ' Return sprite.image[sprite.width*ypos+xpos].colour Return sprite.image[sprite.width*ypos+xpos] End Method Method SetSpritePos ( sprite:TSprite, xPos:Int, yPos:Int ) sprite.xPos = xPos sprite.yPos = yPos End Method Method SetSpriteLayer ( sprite:TSprite, newLayer:Int ) ListRemove(sprites,sprite) ListRemove(spriteLayers[sprite.layer], sprite) sprite.layer = newLayer ListAddLast(sprites, sprite) ListAddLast(spriteLayers[sprite.layer],sprite) End Method Method SetSpriteTransparent ( sprite:TSprite, newTransparent:Int ) sprite.transparent = newTransparent End Method Method CheckSpriteCollision:Int(sprite1:TSprite, sprite2:TSprite ) Local xMin:Int Local xMax:Int Local yMin:Int Local yMax:Int Local collided:Int ' Find the largest minimum x pixel of the sprites If sprite1.xPos < sprite2.xPos Then xMin = sprite2.xPos Else xMin = sprite1.xPos End If ' find the smallest maximum x pixel of the sprites If sprite1.xPos + sprite1.width - 1 > sprite2.xPos + sprite2.width - 1 Then xMax = sprite2.xPos + sprite2.width - 1 Else xMax = sprite1.xPos + sprite1.width - 1 End If ' If the overlapping area in the x axis is inside the sprites If xMin <= xMax Then ' find the largest minimum y pixel of the sprites If sprite1.yPos < sprite2.yPos Then yMin = sprite2.yPos Else yMin = sprite1.yPos End If ' find tge smallest maximum y pixel of the sprites If sprite1.yPos + sprite1.height - 1 > sprite2.yPos + sprite2.height - 1 Then yMax = sprite2.yPos + sprite2.height - 1 Else yMax = sprite1.yPos + sprite1.height - 1 End If ' if the overlapping are in the y axis is inside the sprites If yMin <= yMax Then ' Now we have a potential collision. We also know the ' area that we need to check for pixel collisions Local xPos:Int Local yPos:Int For xPos = xMin To xMax For yPos = yMin To yMax ' If sprite1.image[(xPos-sprite1.xPos)+(yPos-sprite1.yPos)*sprite1.width].colour <> 0 Then ' If sprite2.image[(xPos-sprite2.xPos)+(yPos-sprite2.yPos)*sprite2.width].colour <> 0 Then If sprite1.image[(xPos-sprite1.xPos)+(yPos-sprite1.yPos)*sprite1.width] <> 0 Then If sprite2.image[(xPos-sprite2.xPos)+(yPos-sprite2.yPos)*sprite2.width] <> 0 Then collided = True End If End If Next Next End If End If Return collided End Method Method GameInit() Init() End Method Method init() Abstract Method Update() Abstract Method SetColour(colourNum:Int) Local colPos:Int = colourNum*3 SetColor ( colours[colPos], colours[colPos+1], colours[colPos+2] ) End Method Method SetClsColour(colourNum:Int) Local colPos:Int = colourNum*3 SetClsColor ( colours[colPos], colours[colPos+1], colours[colPos+2] ) End Method Method AddSoundEffect(duration:Float, startFreq:Float, endFreq:Float, startVol:Float, endVol:Float, noise:Float, waveType:Int) soundEffect.AddBlock(duration, startFreq, endFreq, startVol, endVol, noise, waveType) End Method Method MakeSoundEffect:TSound() Local sound:TSound sound = soundEffect.MakeSound() soundEffect = New TSoundEffect Return sound End Method End Type Type TSoundEffect Field blocks:TList Field totalSamples:Int Method New() blocks = CreateList() End Method Method AddBlock(duration:Float, startFreq:Float, endFreq:Float, startVol:Float, endVol:Float, noise:Float, waveType:Int) Local block:TSoundEffectBlock block = TSoundEffectBlock.Create(duration, startFreq, endFreq, startVol, endVol,noise, waveType ) totalSamples :+ duration*11025 ListAddLast(blocks, block) End Method Method MakeSound:TSound() Local sample:TAudioSample Local currentBlock:TSoundEffectBlock Local startSample:Int Local endSample:Int Local samplesInBlock:Int Local freqAngle:Float Local volume:Float Local multiplier:Float Local k:Float Local angle:Float Local currentFreq:Float Local noiseValue:Float Local waveType:Int Local writesample:Float startSample = 0 endSample = 0 Local lastrandomnoise:Float sample = CreateAudioSample( totalSamples,11025,SF_MONO8 ) For currentBlock = EachIn blocks noiseValue = currentBlock.noise/100 waveType = currentBlock.waveType startSample = endSample samplesInBlock = currentBlock.duration * 11025 endSample = startSample + samplesInBlock For k = startSample Until endSample multiplier = (k-startSample) / samplesInBlock volume = ( currentBlock.startvol - ( ( currentBlock.startvol-currentBlock.endvol ) * multiplier ) ) * 127.5 / 100 currentFreq = currentBlock.startfreq + ( ( currentBlock.endfreq-currentBlock.startfreq ) * multiplier ) freqAngle = currentFreq/11025*180 angle :+ freqAngle angle = angle Mod 360 writesample = Sin(angle)*volume If waveType & WAVE_SQUARE Then If writeSample > 0 Then writeSample = volume Else writeSample = -volume End If End If lastrandomnoise = Rnd(-noisevalue,noisevalue) writesample :+ lastrandomnoise*volume If waveType & WAVE_BOUNCE Then If writeSample > 127.5 Then writeSample :- ( writeSample - 127.5) Else If writeSample < -127.5 Then writeSample :- ( writeSample + 127.5 ) End If End If Else If waveType & WAVE_WRAP Then If writeSample > 127.5 Then writeSample :- 255 End If If writeSample < -127.5 Then writeSample :+ 255 End If Else ' Truncate the sound If writesample > 127.5 Then writesample = 127.5 End If If writesample < -127.5 Then writesample = -127.5 End If End If End If writesample :+ 127.5 sample.samples[k]=writesample Next Next Local sound:TSound=LoadSound( sample,False ) Return sound End Method End Type Type TSoundEffectBlock Field duration:Float Field startFreq:Float Field endFreq:Float Field startVol:Float Field endVol:Float Field noise:Float Field waveType:Int Function Create:TSoundEffectBlock(duration:Float, startFreq:Float, endFreq:Float, startVol:Float, endVol:Float, noise:Float, waveType:Int) Local this:TSoundEffectBlock this = New TSoundEffectBlock this.duration = duration this.startFreq = startFreq this.endFreq = endFreq this.startVol = startVol this.endVol = endVol this.noise = noise this.waveType = waveType Return this End Function End Type
and here is the Invaders game. Save as BlockInvaders.bmx. Compile and run. :)
SuperStrict Framework BRL.FreeAudioAudio Import BRL.StandardIO Import "TBlockGame.bmx" Local game:invGame game = New invGame game.GameRun() Type TBomb Field sprite:TSprite Field speed:Int Function Create:TBomb(setSprite:TSprite, setSpeed:Int ) Local bomb:TBomb bomb = New TBomb bomb.sprite = setSprite bomb.speed = setSpeed Return bomb End Function End Type Type invGame Extends TBlockGame Const TOTAL_INVADERS:Int = 5*11 Field gameStage:Int ' what stage the game is in ( title page, game play, end game etc ) Field Invaders:TSprite[TOTAL_INVADERS] ' list of all the invaders Field InvaderValues:Int [TOTAL_INVADERS] ' points value of each invader Field invadersLeft:Int ' how many invaders are alive Field SoundInvader:TSound[4] ' invader movement makes four noises Field soundInvNum:Int ' current noise invaders will make Field currentInv:Int Field SoundInvExplosion:TSound ' the sound an invader makes when it is hit Field wavesCleared:Int ' number of waves currently cleared Field bombs:TList Field bombDropPause:Int Field invStage:Int ' invader stages ( build wave, move right, down, left ) Field invEdgeHit:Int ' while a wave moves if an invaders reaches the side of the screen this is set Field invPause:Int ' a counter when pausing after a wave is destroyed and a new one is built Field UFO:TSprite Field UFOPause:Int Field UFOValue:Int Field UFOShowScore:Int Field shields:TSprite[4] ' each level contains this many shields Field SoundUFO:TSound Field SoundUFOExplosion:TSound Field SoundUFOTime:Int Field Player:TSprite ' the player Field playerLives:Int ' Number of lives the player has left Field soundPlayerDie:TSound Field playerExplosion:TSprite Field soundShot:TSound ' the sound a shot makes Field shot:TSprite ' the shot Field Ground:TSprite ' the line under the player Field score:Int Field highScore:Int Field stagePause:Int Const LAYER_SHIELD:Int = 0 ' each type of sprite exists on different layers Const LAYER_INV:Int = 1 Const LAYER_BOMBS:Int = 2 Const LAYER_SHOTS:Int = 3 Const LAYER_PLAYER:Int = 4 Field moveInvX:Int ' current invader that is moving Field moveInvY:Int Method Init() Local xInv:Int Local yInv:Int ' tell the graphics engine how many layers we require InitLayers(5) bombs = CreateList() ' Now build the sounds ' create the shot sound AddSoundEffect(.2,5000,1000,30,30,50,WAVE_SQUARE + WAVE_BOUNCE) AddSoundEffect(.2,1000,500,30,0,50,WAVE_SQUARE + WAVE_BOUNCE) soundShot = MakeSoundEffect() ' create the invader movement sounds ' create invader move sound 0 AddSoundEffect(.1,190,290,100,100,0,WAVE_SQUARE) AddSoundEffect(.1,290,190,100,100,0,WAVE_SQUARE) soundInvader[0] = MakeSoundEffect() ' create invader move sound 1 AddSoundEffect(.1,100,200,100,100,0,WAVE_SQUARE) AddSoundEffect(.1,200,100,100,100,0,WAVE_SQUARE) soundInvader[1] = MakeSoundEffect() ' create invader move sound 2 AddSoundEffect(.1,160,260,100,100,0,WAVE_SQUARE) AddSoundEffect(.1,260,160,100,100,0,WAVE_SQUARE) soundInvader[2] = MakeSoundEffect() ' create invader move sound 3 AddSoundEffect(.1,130,230,100,100,0,WAVE_SQUARE) AddSoundEffect(.1,230,130,100,100,0,WAVE_SQUARE) soundInvader[3] = MakeSoundEffect() Local l:Int For l = 0 To 200 AddSoundEffect(.01,Rand(100,500),Rand(100,500),(100-l/2),(100-l/2),20,1+WAVE_BOUNCE) Next soundPlayerDie = MakeSoundEffect() ' create the invader explode sound AddSoundEffect(.1,1000,3000,30,30,25,WAVE_SQUARE + WAVE_BOUNCE) soundInvExplosion = MakeSoundEffect() ' create the UFO move sound AddSoundEffect(.25,2000,3500,30,30,25,WAVE_SQUARE + WAVE_BOUNCE) AddSoundEffect(.25,3500,2000,30,30,25,WAVE_SQUARE + WAVE_BOUNCE) soundUFO = MakeSoundEffect() ' create UFO explosion Sound AddSoundEffect(.2,4000,4500,80,50,25,WAVE_BOUNCE) AddSoundEffect(.2,0,0,0,0,0,WAVE_BOUNCE) AddSoundEffect(.2,4000,4500,80,50,25,WAVE_BOUNCE) AddSoundEffect(.2,0,0,0,0,0,WAVE_BOUNCE) AddSoundEffect(.2,4000,4500,80,50,25,WAVE_BOUNCE) AddSoundEffect(.2,0,0,0,0,0,WAVE_BOUNCE) AddSoundEffect(.2,4000,4500,80,50,25,WAVE_BOUNCE) AddSoundEffect(.2,0,0,0,0,0,WAVE_BOUNCE) soundUFOExplosion = MakeSoundEffect() SetGameStage(0) ' The game assumes a UFO Srpite exists CreateUFO() UFO.dead = True End Method ' this is the main loop for the game Method Update() CountFPS() writeText 0,114,RED+BRIGHT,"FPS:"+Int(fps) ' Show score and Hi Score at the top WriteText(5,0,GREEN+BRIGHT,"SCORE") Local numStr:String numStr = "000000" + score numStr = numStr[Len(numStr)-6..] WriteText(40,0,GREEN+BRIGHT,numStr) WriteText(100,0,GREEN+BRIGHT,"HI") numStr = "000000" + highScore numStr = numStr[Len(numStr)-6..] WriteText(115,0,GREEN+BRIGHT,numStr) ' Show lives at the bottom right WriteText(100,115,GREEN+BRIGHT,"LIVES") numStr = "00" + playerLives numStr = numStr[Len(numStr)-2..] WriteText(140,115,GREEN+BRIGHT,numStr) Select gameStage Case 0 ' Intro Page Intro() Case 1 ' Play Game GamePlay() Case 2 PlayerDie() Case 3 ' End Game GameEnd() End Select End Method Method Intro() WriteText(37,30,WHITE+BRIGHT,"Block Invaders") WriteText(35,60,WHITE+BRIGHT,"Press P to Play") WriteText(15,85,GREEN+BRIGHT,"Written By Quietbloke") WriteText(50,95,GREEN+BRIGHT,"Sept 2007") WriteText(5,105,GREEN+BRIGHT,"using BlockGame Framework") If KeyHit(key_P) Then StartGame() End If End Method Method PlayerDie() UpdateUFO() stagePause :+ 1 ' draw the explosion pixele. Each has a limited lifespan If stagePause < 150 Then Local p:Int For p = 0 To 3 drawpixel(player.Xpos - 4 + Rand(0,13),player.yPos-4+Rand(0,9),Rand(1,15),Rand(5,15)) Next End If ' After a while if the player has more lives then flash the player on the screen a few times If stagePause >150 And playerLives > 0 Then player.frame = Ceil(stagePause / 10) Mod 2 End If If stagePause > 200 Then If playerLives = 0 Then SetGameStage(3) Else SetGameStage(1) End If End If End Method Method GameEnd() stagePause :+ 1 ' The player can press P now to start a new game If KeyHit(key_P) Then ClearGame() StartGame() End If If stagePause < 90 Or Ceil((stagePause-90)/10) Mod 2 Then If stagePause > 10 Then WriteText ( 60, 55, RED+BRIGHT,"G") End If If stagePause > 20 Then WriteText ( 66, 55, RED+BRIGHT,"A") End If If stagePause > 30 Then WriteText ( 72, 55, RED+BRIGHT,"M") End If If stagePause > 40 Then WriteText ( 78, 55, RED+BRIGHT,"E") End If If stagePause > 50 Then WriteText ( 90, 55, RED+BRIGHT,"O") End If If stagePause > 60 Then WriteText ( 96, 55, RED+BRIGHT,"V") End If If stagePause > 70 Then WriteText ( 102, 55, RED+BRIGHT,"E") End If If stagePause > 80 Then WriteText ( 108, 55, RED+BRIGHT,"R") End If End If If stagePause > 300 Then SetGameStage(0) ClearGame() End If End Method Method ClearGame() ' remove the player and ground player.dead = True shot.dead = True ground.dead = True player = Null shot = Null ground = Null ' delete all the invaders Local pos:Int For pos = 0 Until TOTAL_INVADERS If invaders[pos] <> Null invaders[pos].dead = True invaders[pos] = Null End If Next invadersLeft = 0 For pos = 0 To 3 If shields[pos] <> Null shields[pos].dead = True shields[pos] = Null End If Next Local bomb:TBomb For bomb = EachIn bombs bomb.sprite.dead = True ListRemove(bombs,bomb) Next End Method Method StartGame() score = 0 playerLives = 3 invStage = 0 wavesCleared = 0 ' Create the ground and player ' create the player sprite Player = MakeSprite(5,5,2,-1,LAYER_PLAYER) SetSpriteImage(Player,[$0,$0,$0,$0,$0, $0,$0,$f,$0,$0, $0,$f,$f,$f,$0, $f,$f,$f,$f,$f, $f,$f,$f,$f,$f]) SetSpritePos(Player,120,107) ' create a shot CreateShot() ' create the ground Local xInv:Int ground = MakeSprite(158,1,1,-1,LAYER_SHIELD) SetSpritePos(Ground,1,113) For xInv = 0 To 157 SetSpritePixel(ground,xInv,0,RED+BRIGHT) Next SetGameStage(1) End Method Method GamePlay() ' See if shot is dead then recreate it ' a shot can die by either hitting something or it expires when it reaches the top of the screen If shot.dead Then CreateShot() End If CheckShotCollisions() CheckBombCollisions() MoveInvaders() MoveBombs() If invStage > 1 Then UFOPause :+ 1 If UFOPause > 500 Then CreateUFO() UFOPause = 0 End If End If UpdateUFO() ' should we drop a bomb ? bombDropPause:- 1 If bombDropPause <= 0 Then If invStage > 1 Then If Rnd() > .80 Then DropBomb() End If End If End If ' move the player based on player input If KeyDown(Key_Left) Then Player.xPos :- 1 End If If KeyDown(Key_Right) Then Player.xPos :+ 1 End If ' Make sure player remains in the play area If Player.xPos < 10 Then Player.XPos = 10 End If If Player.XPos > 149-Player.width Then Player.XPos = 149-Player.width End If ' If shot is not moving then place it wherever the player is If shot.yMove = 0 Then shot.yPos = player.yPos shot.xPos = player.xPos + 2 End If ' Is shot is not moving we now see if the player wishes to fire it If KeyHit(Key_SPACE) And shot.yMove = 0 Then PlaySound(soundShot) ' set the lifespan so it will automatically expire as it hits the top of the screen shot.lifespan = 25 ' tell the shot sprite to start moving up the screen shot.SetMove(0,-4) End If If score > highScore Then highScore = score End If End Method Method CreateShot() Shot = MakeSprite(1,4,1,-1,LAYER_SHOTS) SetSpriteImage(shot,[$c,$c,$c,$c]) shot.yPos = player.yPos shot.xPos = player.xPos + 2 End Method Method MoveInvaders() Local inv:TSprite Local x:Int Local y:Int Local newInvStage:Int If invadersLeft = 0 And invStage > 1 Then InvStage = 0 End If newInvStage = invStage Select invStage Case 0 ' pause If invPause = 0 Then CreateShields() End If invPause :+ 1 If invPause > 100 Then invPause = 0 newInvStage :+ 1 currentInv = 0 End If Case 1 ' build the wave Inv=makeSprite(6,5,2,-1,LAYER_INV) SetSpriteImage(Inv,[$0,$f,$f,$f,$f,$0, $f,$0,$f,$f,$0,$f, $0,$f,$f,$f,$f,$0, $0,$f,$0,$0,$f,$0, $f,$0,$0,$0,$0,$f],0) SetSpriteImage(Inv,[$0,$f,$f,$f,$f,$0, $f,$0,$f,$f,$0,$f, $0,$f,$f,$f,$f,$0, $0,$f,$0,$0,$f,$0, $0,$f,$0,$0,$f,$0],1) y = (currentInv / 11) x = (currentInv - y*11) SetSpritePos(Inv,x*12 + 10,107 - 40 - y*10) ' + 22) InvaderValues[currentInv] = Floor(y /2)* 10 + 10 invaders[currentInv] = inv invadersLeft :+ 1 currentInv :+ 1 If currentInv >= TOTAL_INVADERS Then newInvStage = 2 currentInv = TOTAL_INVADERS End If invPause = 0 Default ' If there is more than 1 invader left then find the next one that is going to move If invadersLeft > 0 Then Repeat currentInv :+ 1 If currentInv >= TOTAL_INVADERS currentInv = 0 PlaySound(soundInvader[soundInvNum]) soundInvNum :+ 1 If soundInvNum > 3 Then soundInvNum = 0 End If If invStage = 3 Then newInvStage = 4 invEdgeHit = False Else If invStage = 5 Then newInvStage = 2 invEdgeHit = False Else If invEdgeHit Then newInvStage :+ 1 invEdgeHit = False End If End If End If End If Until invaders[currentInv] <> Null Select newInvStage Case 2 ' moving right x = 2 y = 0 Case 3 ' Moving right/down x = 0 y = 5 Case 4 ' Moving left x = -2 y = 0 Case 5 ' Moving left/Down x = 0 y = 5 End Select ' move the invader invaders[currentInv].xPos :+ x invaders[currentInv].yPos :+ y invaders[currentInv].frame :+ 1 If invaders[currentInv].frame > 1 Then invaders[currentInv].frame = 0 End If If invaders[currentInv].xPos > 147 Then invEdgeHit = True End If If invaders[currentInv].xPos < 6 Then invEdgeHit = True End If If invaders[currentInv].yPos >= 107 Then playerLives = 1 SetGameStage(2) End If End If Local shield:Int ' Now the fancy bit.. we need to remove pixels from the shield that lie ' under the invader Local xPos2:Int Local yPos2:Int For shield = 0 To 3 If checkSpriteCollision(shields[shield],invaders[currentInv]) Then For xPos2=0 To invaders[currentInv].width-1 For yPos2=0 To invaders[currentInv].height-1 Local xPos3:Int Local yPos3:Int xPos3 = invaders[currentInv].xPos-shields[shield].xPos+xPos2 yPos3 = invaders[currentInv].yPos-shields[shield].yPos+yPos2 If xPos3 >= 0 And xPos3 < shields[shield].width Then If yPos3 >= 0 And yPos3 < shields[shield].height Then SetSpritePixel(shields[shield],xPos3,yPos3,0) End If End If Next Next End If Next End Select invStage = newInvStage End Method Method CheckShotCollisions() Local shield:Int Local inv:Int ' check if shot has hit a shield For shield = 0 To 3 If shields[shield] <> Null Then If CheckSpriteCollision(shot,shields[shield]) ' the shot moves more that 1 pixel at a time so we need to ' roll it back to find the exact position where it collided Repeat shot.YPos :+ 1 Until CheckSpriteCollision(shot,shields[shield]) = False shot.yPos :- 1 ShieldExplosion(shields[shield],shot.xPos-2, shot.yPos-2) shot.dead = True End If End If Next ' check each invader to see if the shot has hit it For inv = 0 Until TOTAL_INVADERS ' Only do the check if the invader at this position still exists If invaders[inv] <> Null Then If CheckSpriteCollision(shot,invaders[inv]) Then ' delete the shot and the invader and reduce the invader count shot.dead = True invaders[inv].dead = True CreateInvaderExplosion(invaders[inv].xPos, invaders[inv].yPos) invaders[inv] = Null score :+ invaderValues[inv] invadersLeft :- 1 PlaySound(SoundInvExplosion) End If End If Next ' Check the UFO to see if it has been hit If CheckSpriteCollision(shot,UFO) Then shot.dead = True UFO.dead = True UFOValue = 150 + Rand(0,6) * 50 UFOShowScore = 75 PlaySound(soundUFOExplosion) End If End Method Method CheckBombCollisions() ' Check if a bomb has hit a shield Local bomb:TBomb Local shield:Int For bomb = EachIn bombs For shield = 0 To 3 If shields[shield] <> Null Then If CheckSpriteCollision(bomb.sprite,shields[shield]) Repeat bomb.sprite.YPos :- 1 Until CheckSpriteCollision(bomb.sprite,shields[shield]) = False bomb.sprite.YPos :+ 1 ShieldExplosion(shields[shield], bomb.sprite.xPos, bomb.sprite.yPos+2) bomb.sprite.dead = True ListRemove(bombs,bomb) End If End If Next Next ' Check if bomb has hit the player For bomb = EachIn bombs If CheckSpriteCollision(bomb.sprite,player) bomb.sprite.dead = True ListRemove(bombs,bomb) PlaySound(soundPlayerDie) SetGameStage(2) End If Next End Method Method MoveBombs() Local bomb:TBomb For bomb = EachIn bombs bomb.sprite.yPos :+ bomb.speed If bomb.sprite.yPos > 110 Then bomb.sprite.dead = True ListRemove(bombs,bomb) End If Next End Method Method InvSound() PlaySound(soundInvader[soundInvNum]) soundInvNum :+ 1 If soundInvNum > 3 Then soundInvNum = 0 End If End Method Method CreateShields() Local shield:Int For shield = 0 To 3 ' Remove the existing shield sprite If shields[shield] <> Null Then shields[shield].dead = True End If shields[shield]=makeSprite(15,15,1,-1,LAYER_SHIELD) SetSpriteImage(shields[shield],[.. $0,$0,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$0,$0, .. $0,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$0, .. $a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a, .. $a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a, .. $a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a, .. $a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a, .. $a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a, .. $a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a, .. $a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a, .. $a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a, .. $a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a, .. $a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a, .. $a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a,$a, .. $a,$a,$a,$0,$0,$0,$0,$0,$0,$0,$0,$0,$a,$a,$a, .. $a,$a,$0,$0,$0,$0,$0,$0,$0,$0,$0,$0,$0,$a,$a],0) SetSpritePos(shields[shield],15+shield*38 ,107-20) Next End Method Method CreateUFO() UFO = makeSprite(10,5,1,159-10,LAYER_INV) SetSpriteImage(UFO,[$0,$0,$f,$f,$f,$f,$f,$f,$0,$0, .. $0,$f,$f,$f,$f,$f,$f,$f,$f,$0, .. $f,$f,$0,$f,$0,$0,$f,$0,$f,$f, .. $0,$f,$f,$f,$f,$f,$f,$f,$f,$0, .. $0,$0,$f,$f,$f,$f,$f,$f,$0,$0],0) If Rnd() > .5 Then SetSpritePos(UFO,0,5) UFO.SetMove(1,0) Else SetSpritePos(UFO,159-UFO.width,5) UFO.SetMove(-1,0) End If End Method Method CreateInvaderExplosion(xPos:Int, yPos:Int) Local explosion:TSprite explosion=makeSprite(6,5,1,10,LAYER_SHIELD) SetSpriteImage(explosion,[$f,$0,$f,$0,$0,$f, $0,$f,$f,$0,$f,$0, $f,$f,$0,$0,$f,$f, $0,$f,$0,$f,$f,$0, $f,$0,$0,$f,$0,$f],0) SetSpritePos(explosion,xPos,yPos) End Method Method ShieldExplosion(shield:TSprite,xPos:Int,yPos:Int) Local explosion:TSprite explosion=makeSprite(5,5,1,10,LAYER_SHIELD) If Rnd() > .5 Then SetSpriteImage(explosion,[$0,$c,$0,$c,$c, $c,$c,$c,$c,$0, $0,$c,$c,$c,$0, $0,$c,$c,$c,$c, $c,$c,$0,$c,$0],0) Else SetSpriteImage(explosion,[$c,$c,$0,$c,$0, $0,$c,$c,$c,$c, $0,$c,$c,$c,$0, $c,$c,$c,$c,$0, $0,$c,$0,$c,$c],0) End If SetSpritePos(explosion,xPos,yPos) ' Now the fancy bit.. we need to remove pixels from the shield that lie ' under the explosion sprite Local xPos2:Int Local yPos2:Int For xPos2=0 To explosion.width-1 For yPos2=0 To explosion.height-1 If GetSpritePixel(explosion,xPos2,yPos2) <> Black Then Local xPos3:Int Local yPos3:Int xPos3 = explosion.xPos-shield.xPos+xPos2 yPos3 = explosion.yPos-shield.yPos+yPos2 If xPos3 >= 0 And xPos3 < shield.width Then If yPos3 >= 0 And yPos3 < shield.height Then SetSpritePixel(shield,xPos3,yPos3,0) End If End If End If Next Next End Method Method DropBomb() Local invader:Int If invadersLeft > 0 And invStage > 1 Then ' pick a random invader invader = Rand(0,TOTAL_INVADERS-1) If invaders[invader] = Null Repeat invader:+ 1 If invader > 5*11-1 Then invader = 0 End If Until invaders[invader] <> Null End If ' go down the invaders until there is no invader below or bottom reached Local invBelow:Int Local finished:Int Repeat invBelow = invader - 11 If invBelow > 0 Then If invaders[invBelow] <> Null Then invader = invBelow Else finished = True End If Else finished = True End If Until finished = True ' Create a bomb for the invader Local sprite:TSprite sprite=makeSprite(1,4,1,-1,LAYER_BOMBS) SetSpriteImage(sprite,[$f, $f, $f, $f],0) SetSpritePos(sprite,invaders[invader].xPos+2,invaders[invader].yPos+2) ListAddLast(bombs,TBomb.Create(sprite, Rand(1,2))) bombDropPause = 30 End If End Method Method SetGameStage(newGameStage:Int) gameStage = newGameStage Select gameStage Case 1 UFOPause = 0 Case 2 playerLives :- 1 PlaySound(soundPlayerDie) ' remove the shop and hide the player shot.dead = True player.frame = 1 Case 3 player.frame = 1 End Select stagePause = 0 FlushKeys() End Method Method UpdateUFO() If UFO.dead = False Then SoundUFOTime :-1 If SoundUFOTime < 0 Then PlaySound(SoundUFO) SoundUFOTime = 10 End If End If If UFOShowScore > 0 Then WriteText( UFO.xPos, UFO.yPos, RED+BRIGHT, UFOValue) UFOShowScore :- 1 End If End Method End Type Global fps:Float, fpst:Float,fpsc:Float Function CountFPS:Float() If fpst < MilliSecs() Then fpst=MilliSecs()+1000 fps = fpsc fpsc = 0 Else fpsc = fpsc + 1 End If Return fps End Function