-
-
Save Westenburg/5743643 to your computer and use it in GitHub Desktop.
Basic version of Foggy Bummer - a side scrolling game written in Codea
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- Foggy Bummer | |
-- By West | |
--Game overview: A sideways scroller where the player controls a bumblebee. Touch screen to provide upward thrust to the bee. Collect as many rings as you can in 24 hours or until you complete the full course. Don't hit the top or bottom of the screen. | |
--Feel free to use this code as you wish to help you learn and understand codea. Most of the images are either hand drawn or sections extracted from photos - feel free to use, mangle and adapt the sprite sheet too. Just please don't take the code and images wholesale and upload them to the apple store! | |
--Often a good way to learn to program is to take existing code, tweak it and build on it. Hopefully this will give someone a foot up towards making their own sideways scroller. This is the most basic version, but I've also added the following if anyone is interested | |
--Added bonuses | |
--Added particle effects | |
--Added different weather | |
--Basic Challenges | |
--Make spiders move up and down the screen and kill the player if they hit them | |
--Swap the moon image for an image of a planet sized space station (That's no moon...) | |
--Add a bonus which reverses the direction of gravity for a short period of time | |
--Add an explosion of flower particles when bonuses are picked up | |
--Change the control of the bee to use a tilt input from the ipad instead of touches | |
--More advanced challenges | |
--Make the bee fire a globule of pollen if the screen is double tapped - this can be used to kill spiders | |
--Add a hi-score table | |
--Re-cast the game as an underwater adventure with the main character as a submarine picking up jellyfish and avoiding sharks. Different weather effects would be required, for example bubbles rising to the surface instead of raindrops | |
--Add "the flight of the bumblebee" as a soundtrack - the faster you go, the faster the soundtrack plays | |
--Add vertical scrolling and hills rather than a flat terrain. | |
--Have fun! Any questions, then drop me (@West) on the Codea forums | |
--Set up screen and fix orientation to landscape | |
--for development change to FULLSCREEN to make it easier to get back to the development environment. A triple tap while in FULLSCREEN_NO_BUTTONS will bring up the buttons to allow you to exit, but this can be a pain. | |
displayMode(FULLSCREEN_NO_BUTTONS) | |
supportedOrientations(LANDSCAPE_ANY) | |
backingMode(STANDARD) | |
function setup() | |
--Set up constants to hold the various game states | |
MENU=1 --the main start screen | |
PLAY=2 --the game is being played | |
GAMEOVER=3 --the game has been lost | |
WON=4 -- the game has been won | |
gamestate=MENU --set the current state of the game to menu | |
--an array to hold the touches | |
touches={} | |
--A variable to hold different game over messages | |
msg="" | |
--set initial sizes for the bee | |
beebodysizex=100 | |
beebodysizey=100 | |
beewingsizex=130 | |
beewingsizey=130 | |
beescalefactor=0.5 | |
--Sprite sheet information | |
--The spritesheet image contains all the graphical elements of the game | |
--The spritesheet is split into 100 by 100 pixel blocks (in the main) | |
--download the spritesheet from here:https://www.dropbox.com/s/ztwc51abhl3ks1k/foggyspritesheet%402x.png | |
--update the next line to point to the spritesheet file | |
spritesheetimg=readImage("Dropbox:foggyspritesheet") | |
cols=10 --number of columns thatmakeupthe spritesheef | |
rows=6 -- number of rows that make up the spritesheet | |
--background is made up of a number of repeating objects which scroll across the screen at different speeds | |
numlayers=8 | |
bgxsize={WIDTH,800,20,200,30,40,65,98} | |
bgysize={HEIGHT,450,120,60,90,114,195,289} | |
bgxpos={WIDTH/2,0,0,0,0,0,0,0} | |
bgypos={120,80,100,50,20,15,10,100} | |
--x,y,width and height arrays of each background object with reference to the appropriate spritesheet 100 by 100 block | |
--layers are: mountain, mountain, fence, wall, flowers x4 | |
bgx={6,6,4,0,5,5,5,5} | |
bgy={0,0,0,3,0,0,0,0} | |
bgw={4,4,0.5,3,1,1,1,1} | |
bgh={3,3,2,1,3,3,3,3} | |
scrollspeed={0,0.2,0.3,0.4,0.6,0.7,0.8,0.9} -- the speed at which the object moves across the screen - 0 means it doesn't move | |
overlap={1.5,1.9,1,1,1,1,1,1}--the amount an object overlaps the next object | |
layerdata={} | |
for i=1,numlayers do | |
layerdata[i]={} --an array for holding a list of the objects. If a 0 is stored the then object will be drawn. If it is a 0 then it won't | |
for j=1,1000 do | |
--for the nearest layer (large flower) don't draw 14 out of 15 (on average) | |
if i==8 then | |
layerdata[i][j]=math.random(15)-1 | |
if layerdata[i][j]>1 then | |
layerdata[i][j]=1 | |
end | |
--for the second nearest layer don't draw half of them (on average) | |
elseif i==7 then | |
layerdata[i][j]=math.random(2)-1 | |
else | |
layerdata[i][j]=0 | |
end | |
end | |
end | |
--a mesh to hold all the graphical elements. The order in which the objects are added to the mesh matters, so in draw the scene will be built up from the back forward. The sky elements will be drawn first, followed by the mountains then clouds, then fence and wall, etc | |
mainmesh=mesh() | |
mainmesh.texture=spritesheetimg | |
end | |
function initialise() | |
--initialise variables - called when the game is reset | |
sunAngle=0 | |
globalx=150 | |
anim=0 | |
speed=8 | |
minspeed=8 | |
upthrust=0 | |
beex=150 | |
grav=-12 | |
beevel=0 | |
t=0.1 | |
beey=HEIGHT/2 | |
topSpeed=30 | |
score=0 | |
numrings=250 | |
ringx={} | |
ringy={} | |
ringmarker={} | |
ringscale={} | |
ringcount=0 | |
ringy[1]=HEIGHT/2 | |
b=0 | |
--generate 500 rings at random. Each subsequent ring should be displaced vertically by a random distance between -50 and +50 | |
for i=1,numrings do | |
ringx[i]=500*i --each ring is placed 500 pixels apart | |
ringmarker[i]=0 | |
ringscale[i]=2+math.random(6)/2 | |
if i>1 then | |
ringy[i]=ringy[i-1]-100+math.random(200) | |
if ringy[i]>HEIGHT-100 then | |
ringy[i]=HEIGHT-100 | |
end | |
if ringy[i]<100 then | |
ringy[i]=100 | |
end | |
end | |
end | |
end | |
-- This function gets called once every frame | |
function draw() | |
if gamestate==MENU then | |
--main menu screen - not very exciting but does the job. Double tap to play the game | |
background(33, 46, 69, 255) | |
font("ArialRoundedMTBold") | |
fontSize(72) | |
text("Foggy Bummer",WIDTH/2,HEIGHT/2) | |
fontSize(36) | |
text("by West",WIDTH/2,HEIGHT/2-120) | |
text("Double tap screen to start",WIDTH/2,HEIGHT/2-240) | |
fontSize(12) | |
--check for a double tap | |
for k,touch in pairs(touches) do | |
if touch.tapCount==2 then | |
initialise() | |
gamestate=PLAY | |
end | |
end | |
elseif gamestate==GAMEOVER then | |
--Display a message to the user stating the game is over along with the score | |
background(63, 32, 32, 255) | |
fill(230, 165, 61, 255) | |
font("ArialRoundedMTBold") | |
fontSize(72) | |
text("Game Over",WIDTH/2,HEIGHT/2+120) | |
fontSize(36) | |
text(msg,WIDTH/2,HEIGHT/2) | |
text("Your score was: "..score,WIDTH/2,HEIGHT/2-120) | |
fontSize(12) | |
--check for as double tap to restart | |
for k,touch in pairs(touches) do | |
if touch.tapCount==2 then | |
initialise() | |
gamestate=PLAY | |
end | |
end | |
elseif gamestate==WON then | |
--Display a message to the user stating they have won | |
background(87, 246, 23, 255) | |
fill(57, 58, 220, 255) | |
font("ArialRoundedMTBold") | |
fontSize(72) | |
text("Congratulations you won!",WIDTH/2,HEIGHT/2) | |
fontSize(36) | |
text("Your score was: "..score,WIDTH/2,HEIGHT/2-120) | |
fontSize(12) | |
--check for a double tap to play again | |
for k,touch in pairs(touches) do | |
if touch.tapCount==2 then | |
initialise() | |
gamestate=PLAY | |
end | |
end | |
elseif gamestate==PLAY then | |
--Clear the mesh | |
mainmesh:clear() | |
background(60, 120, 240, 255) | |
sunAngle = sunAngle - 0.001 | |
time=360-math.deg(sunAngle)+270 | |
if time>360 then | |
time = time- 360 | |
end | |
--Calculate an equivalent time | |
hours=math.floor(24*time/360) | |
mins=math.floor(60*((24*time/360)-hours)) | |
if mins<10 then | |
timestr=hours..":0"..mins | |
else | |
timestr=hours..":"..mins | |
end | |
if sunAngle<0 then | |
sunAngle=math.rad(360) | |
end | |
--Deal with the user interaction | |
--If the screen is currently being touched | |
if CurrentTouch.state==MOVING or CurrentTouch.state==BEGAN then | |
upthrust = upthrust + 10 --cumulatively add some upward force - alter the 10 to get different levels of force | |
--limit the force | |
if upthrust>30 then | |
upthrust=30 | |
end | |
end | |
--decrease the upward force when the finger is removed, but not immediately | |
if CurrentTouch.state==ENDED then | |
upthrust=upthrust-5 | |
if upthrust<0 then | |
upthrust=0 | |
end | |
accx=0 | |
end | |
acc=upthrust+grav -- change the vertical displacemnt according to the amount of force | |
--equation of motion to calculate the displacement in the y direction s=ut+0.5at*t | |
beey=beey+beevel*t+0.5*acc*t*t | |
--equation of motion for the new velocity v=u+at | |
beevel=beevel+acc*t | |
speed=speed-0.01 | |
if speed>topSpeed then | |
speed=topSpeed | |
end | |
if speed<minspeed then | |
speed=minspeed | |
end | |
globalx = globalx + speed | |
--Check to see if the bee hits the top of the screen - if so then game over | |
if beey>HEIGHT-20 then | |
beey=HEIGHT-20 | |
upthrust=0 | |
gamestate=GAMEOVER | |
msg="Like Icarus, you flew too high" | |
end | |
--check to see if the been hits the ground - if so then game over | |
if beey<20 then | |
beey=20 | |
speed=speed-0.5 | |
gamestate=GAMEOVER | |
msg="Bees cannot run" | |
end | |
--create the meshes for the scrolling background layer by layer | |
for i=1,numlayers do | |
for j=1,overlap[i]*(WIDTH/bgxsize[i])+3 do | |
if layerdata[i][j]==0 then | |
local gid = mainmesh:addRect(((bgxpos[i])+bgxsize[i]*j-bgxsize[i])/overlap[i],bgypos[i],bgxsize[i],bgysize[i]) | |
mainmesh:setRectTex(gid, bgx[i]/cols, bgy[i]/rows, bgw[i]/cols, bgh[i]/rows) | |
end | |
end | |
end | |
--calculate and shift the layers depending on speed | |
for i=1,numlayers do | |
bgxpos[i]=bgxpos[i]-(scrollspeed[i]*speed) | |
if bgxpos[i]>bgxsize[i] then | |
bgxpos[i]=0 | |
temp=layerdata[i][#layerdata[i]] | |
for c=1,#layerdata[i] do | |
layerdata[i][#layerdata[i]-c+1]=layerdata[i][#layerdata[i]-c] | |
end | |
layerdata[i][1]=temp | |
end | |
if bgxpos[i]<-1*bgxsize[i] then | |
bgxpos[i]=0 | |
temp=layerdata[i][1] | |
for c=1,#layerdata[i]-1 do | |
layerdata[i][c]=layerdata[i][c+1] | |
end | |
layerdata[i][#layerdata[i]]=temp | |
end | |
end | |
--draw the rings. The rings are split into 2 to give the impression that the bee is flying through the rings rather than in front or behind them | |
for i=1,numrings do | |
if ringx[i]>globalx-(WIDTH) and ringx[i]<globalx+(WIDTH) then | |
local idx = mainmesh:addRect(WIDTH/2+ringx[i]-globalx,ringy[i],(107/10)*ringscale[i],(224/5)*ringscale[i]) | |
mainmesh:setRectTex(idx, 3/cols, 0/rows, 0.5/cols, 2/rows) | |
mainmesh:setRectColor(idx,255,255,255,90) | |
--check to see if the bee has hit a ring | |
if globalx>ringx[i]-20+WIDTH/2-150 and globalx<ringx[i]+20+WIDTH/2-150 and beey>ringy[i]-25*ringscale[i] and beey<ringy[i]+25*ringscale[i] and ringmarker[i]==0 then | |
--increase the score | |
score = score + (6-ringscale[i])*10 | |
--increase the speed | |
speed = speed*2 | |
ringmarker[i]=1 | |
ringcount = ringcount + 1 | |
sound(SOUND_PICKUP, 49740) | |
end | |
end | |
end | |
--always be slowing the bee | |
speed = speed*0.99 | |
--add the bee body to the mesh | |
local idx = mainmesh:addRect(beex,beey,beebodysizex*beescalefactor,beebodysizey*beescalefactor) | |
mainmesh:setRectTex(idx, 0/cols, 1/rows, 1/cols, 1/rows) | |
--add the bee wing to the mesh. When adding rotate by anim degrees (use math.rad to convert to the equivalent im radians) | |
local idw = mainmesh:addRect(beex,beey+28*beescalefactor,beewingsizex*beescalefactor,beewingsizey*beescalefactor,math.rad(anim)) | |
mainmesh:setRectTex(idw, 1/cols, 1/rows, 1/cols, 1/rows) | |
mainmesh:setRectColor(idw,255,255,255,180) | |
--cycle the bee wing animation marker | |
anim = anim + 6 | |
if anim>27 then | |
anim=-6 | |
end | |
--draw the second half of the rings which should appear in front of the bee | |
for i=1,numrings do | |
if ringx[i]>globalx-(WIDTH) and ringx[i]<globalx+(WIDTH) then | |
local idx = mainmesh:addRect(WIDTH/2+ringx[i]-globalx+(105/10)*ringscale[i]+1,ringy[i],(107/10)*ringscale[i],(224/5)*ringscale[i]) | |
mainmesh:setRectTex(idx, 3.5/cols, 0/rows, 0.5/cols, 2/rows) | |
mainmesh:setRectColor(idx,255,255,255,90) | |
end | |
end | |
--if the full distance is covered then the player wins | |
if globalx>ringx[numrings]+500 then | |
gamestate=WON | |
end | |
--if time runs out then the player loses | |
if hours==5 and mins==59 then | |
gamestate=GAMEOVER | |
msg="You ran out of time" | |
end | |
--draw the mesh on the screen | |
mainmesh:draw() | |
font("ArialRoundedMTBold") | |
fontSize(36) | |
fill(213, 198, 89, 255) | |
--add the score and time to the screen | |
text("Score: "..score,100,730) | |
text(timestr,900,730) | |
end | |
end | |
function touched(touch) | |
--handle the touches | |
if touch.state == ENDED then | |
touches[touch.id] = nil | |
else | |
touches[touch.id] = touch | |
end | |
end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment