Cloud Buster

Updated on June 26th, 2021 at 1:13 am

What is it?

This was a game that I created in my mobile development class at my Junior College. The app was made with the Corona SDK and programmed in Lua. The concept of the game is based on a popular game called “Flappy Bird”. You play as an airplane that you can control by touching anywhere on the screen. The longer you touch the screen, the longer the plane flies up. As you release your finger from the screen, the plane falls down.

The goal of the game is to run into as many white clouds as you can. There is no end game screen, instead, the goal is to raise your score as high as possible. Your plane only has three lives and flying into gray clouds drops lives. After losing all three lives, the game is over and your score drops to 0. However, you are able to gain lives back by flying into the rare and mysterious rainbow clouds.

screenshot of the fly game
A screenshot of Cloud Buster

What does it do?

This app was designed to be a 2k mobile game. It was created with the Corona SDK and as such, can be built for iOS and Android devices. It may be possible to build the game for macOS and Windows as well.

How does it work?

Games made with the Corona SDK are primarily made of Lua Scripts. Each script would generally equate to a single scene or level within the game. Since this game only has a single scene, there is only one script. The rest of the files in the project are made up of images, icons, backgrounds, and sprites.

main.lua

-----------------------------------------------------------------------------------------
-- Fly Game 2
-- Ryan Bains-Jordan
--
-- Main
-----------------------------------------------------------------------------------------

-- Status Bar
display.setStatusBar( display.HiddenStatusBar )

-- Variable Declarations
local w = display.contentWidth
local h = display.contentHeight
local bg = {}
local plane = {}
local cloud = {}
local cloudSheetWInst
local cloudSheetDInst
local cloudSheetRInst
local touched = false -- Has the screen been touched (Used in screenTouched())
local hits = 0 -- Counts the clouds intercepted by plane (Used in updateScore())
local hitsText
local lives = {}
local splash = {}
local rButton = nil
local rButtonText = ""


-----------------------------------------------------------------------------------------
-- Game Initiation Function
-----------------------------------------------------------------------------------------

function initGame()
	-- Background
	bg = display.newImageRect( "background.png", w * 4, h )
	bg.starting = 660
	bg.ending = -250
	bg.x = bg.starting
	bg.y = h / 2 

	-- Plane
	plane = display.newImageRect( "plane.png", 77, 43 )
	plane.x = 75
	plane.y = h-100
	plane.velocity = 0
	plane.acceleration = 0.5 / 2
	plane.bounce = 0.5
	plane.sound = audio.loadSound( "plane.wav" )
	plane.skid = audio.loadSound("skid.wav")
	plane.lives = 3

	-- Lives
	lives.life1 = display.newImage( "plane.png", 50, 20 )
	lives.life2 = display.newImage( "plane.png", 100, 20 )
	lives.life3 = display.newImage( "plane.png", 150, 20 )
	lives.life1:scale( .2, .2)
	lives.life2:scale( .2, .2)
	lives.life3:scale( .2, .2)
	lives.score = {lives.life1, lives.life2, lives.life3}

	-- Text Color
	local color = 
	{
		hightlight = { r=0.6, g=0.4, b=0.2 },
		shadow = { r=0.3, g=0.3, b=0.3 }
	}

	-- Hit Score
	hitsText = display.newEmbossedText( "", w/2, 100, native.systemFont, 25 )
	hitsText:setFillColor( 1, .804, 0 )
	hitsText:setEmbossColor( color )

	--  White Clouds
	for i = 1, 3 do
		cloud[i] = display.newImageRect("cloud.png", 75, 50)
		cloud[i].x = -100 + (i * 200)
		cloud[i].y = 100
	end

	-- Dark Clouds
	cloud[4] = display.newImageRect("dark_cloud.png", 75, 50)
	cloud[4].x = -100

	-- Rainbow Clouds
	cloud[5] = display.newImageRect( "rain_cloud.png", 75, 50 )
	cloud[5].x = -100

	-- Cloud Initiations
	cloud.white = { animation=false }
	cloud.dark = { visable=false, aniamation=false }
	cloud.rain = { visable=false, animation=false }
	cloud.sound = audio.loadSound( "poof.wav" )

	-- Cloud Animation
	cloudSheet = graphics.newImageSheet( "cloud_sheet.png", { width=128, height=128, numFrames=48 } )

	cloudSheetWInst = display.newSprite( cloudSheet, { start=1, count=16, time=500, loopCount=1 } )
	cloudSheetDInst = display.newSprite( cloudSheet, { start=17, count=16, time=500, loopCount=1 } )
	cloudSheetRInst = display.newSprite( cloudSheet, { start=33, count=16, time=500, loopCount=1 } )

	cloudSheetWInst.x = -100
	cloudSheetWInst.y = -100
	cloudSheetDInst.x = -100
	cloudSheetDInst.y = -100
	cloudSheetRInst.x = -100
	cloudSheetRInst.y = -100

	-- Game Over Screen
	splash.screen = display.newRoundedRect( w/2, h/2, w-100, h-100, 25 )
	splash.screen:setFillColor( 0.5, 0.5, 0.5, 0.9 )
	splash.screen.isVisible = false
	splash.header = display.newEmbossedText( "Game Over", w/2, h/2-100, native.systemFont, 30 )
	splash.header:setFillColor( 1, 0.8, 0 )
	splash.header.isVisible = false
	splash.score = display.newEmbossedText( "Your score was", w/2, h/2-50, native.systemFont, 20 )
	splash.score:setFillColor( 1, 0.8, 0 )
	splash.score.isVisible = false
	splash.hits = display.newEmbossedText( "", w/2, h/2, native.systemFont, 25 )
	splash.hits:setFillColor( 1, 0.8, 0 )
	splash.hits.isVisible = false

	-- Reset Button
	rButton = display.newRoundedRect( w/2, h/2+100, 100, 30, 10 )
	rButton.strokeWidth = 3
	rButton:setFillColor( 1, 0.8, 0 )
	rButton:setStrokeColor( 0.6, 0.4, 0.2 )
	rButton.isVisible = false

	rButtonText = display.newEmbossedText( "Reset", rButton.x, rButton.y, native.systemFont, 15 )
	rButtonText:setFillColor( 0.6, 0.4, 0.2 )
	rButtonText.isVisible = false
	
	-- Reset the Score Function
	function rButton:tap( event )
		plane.y = h-100
		plane.lives = 3
		hits = 0
		updateScore()
		hitsText.isVisible = true
		displayLives()

		splash.screen.isVisible = false
		splash.header.isVisible = false
		splash.score.isVisible = false
		splash.hits.isVisible = false
		rButton.isVisible = false
		rButtonText.isVisible = false

		Runtime:addEventListener( "enterFrame", newFrame )
		Runtime:addEventListener( "touch", screenTouched )
	end
 
	-- Install the animation event listeners
	Runtime:addEventListener( "enterFrame", newFrame )
	Runtime:addEventListener( "touch", screenTouched )
	rButton:addEventListener( "tap", rButton )

end

-----------------------------------------------------------------------------------------
-- Main Functions
-----------------------------------------------------------------------------------------

-- Animate Background
function bgAnimate()
	bg.x = bg.x - 5
	if bg.x <= bg.ending then
		bg.x = bg.starting
	end
end

-- Randomise height of clouds
function randomHeight( i )
	local rnd = math.random( 50, 250 )
	cloud[i].y = rnd
end

-- Calculate Velocity and Acceleration of Fall
function falling()

	-- Move the plane according to its current velocity
	plane.y = plane.y + plane.velocity

	-- If the plane passed the bottom during this frame, make it bounce
	if (plane.y > h - 100 ) then
		if ( plane.velocity > 3 ) then
			audio.play( plane.skid, { channel=6, loops=0 } )
		end
		plane.y = plane.y - plane.velocity	-- Undo the downward movement that went past the bottom
		plane.velocity = - plane.velocity * plane.bounce   -- Reverse the downward speed and apply bounce factor
	end

	-- Change the current velocity over time by applying the acceleration (gravity)
	plane.velocity = plane.velocity + plane.acceleration
end

-- Calculate Velocity of Climb
function flying()
	plane.y = plane.y - 5
	plane.velocity = 0
	plane.acceleration = 0.5 / 2
end

-- Plane moving event
function screenTouched( event )
	if event.phase == "began" or event.phase == "moved" then
		touched = true
	else 
		touched = false
	end

	if event.phase == "began" then
		audio.play( plane.sound , { channel=5, loops=-1 })
	end
	if event.phase == "ended" then
		audio.stop( 5 )
	end
end

-- Check Collision
function checkCollision()
	local collision = false

	plane.ftx = plane.x + 38 -- Plane Front Top X Position
	plane.fty = plane.y - 21 -- Plane Front Top Y Position
	plane.fbx = plane.x + 38 -- Plane Front Bottom X Position
	plane.fby = plane.y + 21 -- Plane Front Bottom Y Position
 
	for i = 1, table.getn(cloud) do
		cloud.ftx = cloud[i].x - 37 -- Cloud Front Top X Position
		cloud.fty = cloud[i].y - 25 -- Cloud Front Top Y Position
		cloud.fbx = cloud[i].x - 37 -- Cloud Front Bottom X Position
		cloud.fby = cloud[i].y + 25 -- Cloud Front Bottom Y Position

		if plane.ftx > cloud.ftx - 5 and plane.ftx < cloud.fbx + 5 then
			if plane.fby > cloud.fty and plane.fby < cloud.fby then -- If bottom front of plane hits cloud
				collision = true

			elseif plane.fty > cloud.fty and plane.fty < cloud.fby then -- If top front of plane hits cloud
				collision = true
			else
				collision = false
			end

			if collision == true then
				if i ~= 4 then
					hits = hits + 1
					if i == 5 and plane.lives < 3 then
						plane.lives = plane.lives + 1
						displayLives()

					end
				else
					if plane.lives > 0 then
						plane.lives = plane.lives - 1
						if plane.lives == 0 then
							endGame()
						end
						displayLives()
					end

				end
				animateCloud(i)
				cloud[i].x = math.random( 400, 800 )
				randomHeight(i)
				audio.play( cloud.sound )
				updateScore()
				collision = false
			end
		end
	end
end

--Animate the Cloud
function animateCloud(i)
	-- Dark Cloud
	if i == 4 then 
		cloud.dark.animation = true
		cloudSheetDInst.x = cloud[i].x
		cloudSheetDInst.y = cloud[i].y
		cloudSheetDInst:play()
	-- Rain Cloud
	elseif i == 5 then
		cloud.rain.animation = true
		cloudSheetRInst.x = cloud[i].x
		cloudSheetRInst.y = cloud[i].y
		cloudSheetRInst:play()
	--White Cloud
	else
		cloud.white.animation = true
		cloudSheetWInst.x = cloud[i].x
		cloudSheetWInst.y = cloud[i].y
		cloudSheetWInst:play()
	end
end

-- Update the Score
function updateScore()
	hitsText:setText(hits)
	splash.hits:setText(hits)
end

-- Dispaly the Planes Lives
function displayLives()

	for i = 1, 3 do
		if i <= plane.lives then
			lives.score[i].isVisible = true
		else
			lives.score[i].isVisible = false
		end
	end

end

-- Game Over Screen
function endGame()
	splash.screen.isVisible = true
	splash.header.isVisible = true
	splash.score.isVisible = true
	splash.hits.isVisible = true
	hitsText.isVisible = false

	timer.performWithDelay( 1000, buttonVisability ) --Delay Buttons Visability

	Runtime:removeEventListener( "enterFrame", newFrame )
	Runtime:removeEventListener( "touch", screenTouched )
	audio.stop( 5 )
	touched = false
end

-- Button Visability
function buttonVisability()
	rButton.isVisible = true
	rButtonText.isVisible = true
end

-----------------------------------------------------------------------------------------
-- Animation
-----------------------------------------------------------------------------------------
function newFrame()
	
	bgAnimate()

	if touched == false then
		falling()
	end

	if touched == true then
		flying()
	end

	--  Draw White Clouds
	for i = 1, 3 do
		cloud[i].x = cloud[i].x - 3

		if cloud[i].x <= w - 380 then
			cloud[i].x = w + 380
			randomHeight(i)
		end
	end

	-- Draw Dark Clouds
	if cloud.dark.visable == false then
		local dc = math.random( 200 ) -- Larger = More Rare
		if dc == 50 then -- Any number
			cloud.dark.visable = true
			randomHeight(4)
		end
	end

	-- 	Move the Dark Cloud if it Exists
	if cloud.dark.visable == true then
		cloud[4].x = cloud[4].x - 2
		if cloud[4].x <= w - 380 then
			cloud[4].x = w + 380
			cloud.dark.visable = false
		end
	end 

	-- Draw Rainbow Clouds
	if cloud.rain.visable == false then 
		local rc = math.random( 800 ) -- Larger = More Rare
		if rc == 50 then -- Any number
			cloud.rain.visable = true
			randomHeight(5)
		end
	end

	-- 	Move the Rainbow Cloud if it Exists
	if cloud.rain.visable == true then
		cloud[5].x = cloud[5].x - 5
		if cloud[5].x <= w - 380 then
			cloud[5].x = w + 380
			cloud.rain.visable = false
		end
	end 

	-- Move White Cloud Animation
	if cloud.white.animation == true then
		cloudSheetWInst.x = cloudSheetWInst.x - 3

		if cloudSheetWInst.x <= -380 then
			cloud.white.animation = false
		end
	end

	-- Move Dark Cloud Animation
	if cloud.dark.animation == true then
		cloudSheetDInst.x = cloudSheetDInst.x - 2

		if cloudSheetDInst.x <= -380 then
			cloud.dark.animation = false
		end
	end

	-- Move Rainbow Cloud Animation
	if cloud.rain.animation == true then
		cloudSheetRInst.x = cloudSheetRInst.x - 3

		if cloudSheetRInst.x <= -380 then
			cloud.rain.animation = false
		end
	end

	checkCollision()

end

-----------------------------------------------------------------------------------------
-- Game Initiation
-----------------------------------------------------------------------------------------
initGame()