Bubble Fish
Updated on June 26th, 2021 at 1:09 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 game can be described as a target shooter. You tap somewhere on the bottom of that screen to blow a bubble at that location. When a bubble is blown, it will float up to the top of the water. Meanwhile, fish will be swimming from either side of the screen at random speeds. The goal of the game is to blow bubbles at the right time and at the right place so that the bubbles will intercept the fish that are swimming by. If a fish and a bubble collide, the fish will be captured by the bubble and gently float up to the surface.
A basic counter will keep track of how many bubbles you have blown as well as how many fish you have captured. Your score is based on the average bubble to captured fish ratio. The better your average, the better your score.

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
----------------------------------------------------------------------------------------- -- Target Shooter -- Ryan Bains-Jordan -- -- Main ----------------------------------------------------------------------------------------- display.setStatusBar( display.HiddenStatusBar ) -- Get the screen metrics (use the entire device screen area) local WIDTH = display.actualContentWidth local HEIGHT = display.actualContentHeight local xMin = display.screenOriginX local yMin = display.screenOriginY local xMax = xMin + WIDTH local yMax = yMin + HEIGHT local xCenter = (xMin + xMax) / 2 local yCenter = (yMin + yMax) / 2 -------------------- --Objects -------------------- local blowBubble local popBubble local trapFish local bubbleDone local newFrame local spawnFish local hitTest local hasCollidedCircle local hits = {} local misses = {} local percent = {} local percentUpdate -------------------- --Display Groups -------------------- local background = display.newGroup( ) local bubbles = display.newGroup( ) local fishes = display.newGroup( ) local traps = display.newGroup( ) ----------------------------------------------------------------------------------------- -- Init Game ----------------------------------------------------------------------------------------- local function initGame() --Background local bg = display.newImage( background, "background.png", 380, 570 ) bg.x, bg.y = xCenter, yCenter local water = display.newImageRect( background, "waves.png", 760, 130 ) water.anchorX = 0 water.y = 50 local function waterAnimation() water.x = -380 transition.to( water, { x = 0, time = 5000, onComplete = waterAnimation, onCancel = waterAnimation } ) end background:toBack( ) waterAnimation() -- Text Color local color = { hightlight = { r=0.2, g=0.2, b=0.2 }, shadow = { r=0.5, g=0.5, b=0.5 } } -- Hit Score hits.number = 0 hits.image = display.newImage( "trapped_fish.png" ) hits.image:scale( .5, .5 ) hits.image.x = xMin + 30 hits.image.y = yMin + 30 hits.text = display.newEmbossedText( "'s = 0", xMin + 60, yMin + 30, native.systemFont, 25 ) hits.text:setFillColor( 0.3, 0.4, 0.5 ) hits.text:setEmbossColor( color ) hits.text.anchorX = 0 --Miss text misses.number = 0 misses.image = display.newImage( "bubble.png" ) misses.image:scale( .6, .6 ) misses.image.x = xMin + 30 misses.image.y = yMin + 80 misses.text = display.newEmbossedText( "'s = 0", xMin + 60, yMin + 80, native.systemFont, 25 ) misses.text:setFillColor( 0.3, 0.4, 0.5 ) misses.text:setEmbossColor( color ) misses.text.anchorX = 0 --Percentage text percent.number = 0 percent.text = display.newEmbossedText( string.format( "%3.f", percent.number ) .. "%", xMax-50, yMin+30, native.systemFontBold, 25 ) percent.text:setFillColor( 0.3, 0.4, 0.5 ) percent.text:setEmbossColor( color ) -------------------- --Listeners -------------------- bg:addEventListener( "touch", blowBubble ) Runtime:addEventListener( "enterFrame", newFrame ) end ----------------------------------------------------------------------------------------- -- Main Functions ----------------------------------------------------------------------------------------- -- Main Loop function newFrame() spawnFish() hitTest() percentUpdate() end -- Spawn the bubble when sand is touched function blowBubble( event ) if event.phase == "began" and event.y > 430 then local bubble = display.newImage( bubbles, "bubble.png", true ) bubble.x = event.x bubble.y = event.y transition.to( bubble, { time = 3000, y = yMin - 40, onComplete = popBubble, onCancel = bubbleDone } ) return true end end -- Pop the bubble when it reaches the surface function popBubble( object ) object:removeSelf( ) misses.number = misses.number + 1 misses["text"].text = "'s = " .. misses.number end -- Spawns the fish function spawnFish() if math.random( 1, 50 ) == 1 then fish = display.newImage(fishes, "fish.png", true) fish.x = xMin - 40 fish.y = math.random( yMin + 150, 360 ) transition.to( fish, { time = math.random( 2000, 5000 ), x = xMax + 40, onComplete = bubbleDone } ) end end -- Trap the fish if the bubble hits it function trapFish( object ) object:removeSelf( ) bubbleTrap = display.newImage( traps, "trapped_fish.png", true ) bubbleTrap.x = object.x bubbleTrap.y = object.y transition.to( bubbleTrap, { y = 20 , time = 2000, onComplete = blowUp } ) hits.number = hits.number + 1 hits["text"].text = "'s = " .. hits.number end -- Blow up the bubble with the fish when it reaches the surface function blowUp( object ) transition.to( object, { xScale = 2, yScale = 2, alpha = 0, time = 250, onComplete = bubbleDone } ) end -- Remove the bubble object when completed function bubbleDone( object ) object:removeSelf( ) end -- Check for hits every frame function hitTest() for b = 1, bubbles.numChildren do for f = 1, fishes.numChildren do if hasCollidedCircle( bubbles[b], fishes[f] ) then transition.cancel( bubbles[b] ) transition.cancel( fishes[f] ) trapFish(fishes[f]) end end end end -- Circle-based collision detection (From Corona Docs) function hasCollidedCircle( obj1, obj2 ) if ( obj1 == nil ) then -- Make sure the first object exists return false end if ( obj2 == nil ) then -- Make sure the other object exists return false end local dx = obj1.x - obj2.x local dy = obj1.y - obj2.y local distance = math.sqrt( dx*dx + dy*dy ) local objectSize = (obj2.contentWidth/2) + (obj1.contentWidth/2) if ( distance < objectSize ) then return true end return false end -- Update the percentage each frame function percentUpdate() if hits.number + misses.number == 0 then percent.number = 100 else percent.number = ( hits.number / ( hits.number + misses.number ) ) * 100 end percent["text"].text = string.format( "%3.f", percent.number ) .. "%" end ----------------------------------------------------------------------------------------- -- InitGame Call ----------------------------------------------------------------------------------------- initGame()