📜 ⬆️ ⬇️

Flappy Bird: - Let's go


This is a story about how to write your game on Corona.
The level of entry is minimal (and the botanist from the department of algebra will understand).

I remind you that Corona is an engine for creating 2D games on all platforms and, touch-touch, today is Cosmonautics Day. The plot for the game is selected and, of course, we repeat after the first cosmonaut.
-Go!

What happens in 2 hours of programming?


That's what I got in 2 hours of programming.

Of course, all this tops will take you 3 times less time, since I am a linear programmer, like algebra.
')

Learned? Yes, this is an ad game of Ishq from Vietnam.

Let me remind you that the application brought the author $ 50,000 per day. In my opinion, not bad.

If you look in my clip to the fragments of the collision of a bird with a pillar, you can see the inscription HABR. I use this picture as a texture for particle effect.

Installing Corona Tool


Takes 10 minutes and is beautifully painted in many places.

Creating a gagarinbird project


Run Corona on your machine (I have a Mac) and select the menu item Create New Empty Project .

As a result, the gagarinbird directory is created in the specified location, in which there is a bunch of all kinds of useful garbage, among which in the future we will edit a single file called
main.lua
Text editor - at your discretion. Programming language - Lua.

First of all, we need pictures and sounds for the game.

How to get them without torturing the author?
  • Download the Flappy Bird [Dong Nguyen] file (v1.2 LP os60) -Orbicos-ICPDA.rc309.ipa
  • Rename the file Flappy Bird [Dong Nguyen] (v1.2 LP os60) -Orbicos-ICPDA.rc309.zip
  • Unpack the Flappy Bird [Dong Nguyen] file (v1.2 LP os60) -Orbicos-ICPDA.rc309.zip
  • Click the right mouse button on the Flap.app file and select the Show Package Content option.
  • Copy pictures and sounds to our gagarinbird directory


For convenience, make a subdirectory of gagarinbird / Assets and place all the pictures here.
Similarly, we do the gagarinbird / Sounds subdirectory and put all the sounds here.

The first 2 lines of code - checking sounds Mu


Paste the first two lines of code into the main.lua file.

dieSound = audio.loadSound( "Sounds/sfx_die.caf" ) audio.play( dieSound ) 

Press in Corona (many people call it Corona Simulator) 2 hot buttons / cmd / + R and our application will start in a window that looks like a phone and makes a sound of death! Ay-ah, everything works.
The background is pristine black, but it doesn't matter - we just learned to play any sounds. And they sound the same, on Android, on the iPhone, on Windows (forgive me, Lord).

We load sounds and we make everything beautiful


Make the loadSounds () function and call it

 local function loadSounds() dieSound = audio.loadSound( "Sounds/sfx_die.caf" ) hitSound = audio.loadSound( "Sounds/sfx_hit.caf" ) pointSound = audio.loadSound( "Sounds/sfx_point.aif" ) swooshingSound = audio.loadSound( "Sounds/sfx_swooshing.caf" ) wingSound = audio.loadSound( "Sounds/sfx_wing.caf" ) boomSound = audio.loadSound( "Sounds/sfx_boom.mp3" ) end -- Start application point loadSounds() 

Load the background image and process tap-tap


Learning to draw a background picture on the entire screen of the phone

  local function initBackGround() local ground = display.newImageRect( "Assets/ground.png", display.actualContentWidth, display.actualContentHeight ) ground.x = display.contentCenterX ground.y = display.contentCenterY end --    initBackGround() 

Run (ctrl + R), get some pretty nice picture



Hurray, now we can draw any picture, place it anywhere and transform as we want.

For example,

 ground.rotation = 90 

rotates the background image 90 degrees.

Add a poke into the screen.

  ground:addEventListener("tap", wing) local function wing() audio.play( wingSound ) end 

We added a Listener, which catches all the clicks on the ground and calls the wing () function.
If you look at the code, each poking into our ground should cause the sound of wing.caf.

We start - everything works.

Flight physics and timer


Corona has a physics library, with gravity and collisions. But for our game it is redundant. Adding objects (birds, Earth and pillars) and setting parameters will require more code than simply starting a timer (40 times per second) with checking collisions and driving dynamics in the Earth’s gravitational field. Earth in the porthole. Sorry, distracted.

Let's make the dynamics in the language of the time machine

 --  ,    25  gameLoopTimer = timer.performWithDelay( 25, gameLoop, 0 ) local function gameLoop() vBird = vBird + dt * g yBird = yBird + dt * vBird end 

40 times per second our gameLoop () function is called

The movement of the bird is recorded in two lines of this function. Here g is the acceleration due to gravity (for iPhone it’s 800 pixels per second per second)

dt = 0.025 - time step

uBird - the speed of the bird along the X axis
vBird - the speed of the bird along the Y axis

xBird - the X coordinate of the bird
yBird - the Y coordinate of the bird

The dynamics of the movement of the pillars and the surface is even simpler - the movement occurs only along the X axis

  for i=1,3 do pipes[i].x = pipes[i].x + dt * uBird end 

Game states


All the bricks are ready, it remains to put everything together in an elegant cattle-program. So, in our game there are 4 states.

State 0 (gameStatus = 0) is all frozen and ready to start the game. We are waiting for clicking on the screen. When pressed, go to state 1.
State 1 (gameStatus = 1) the bird flies, the pillars move, gravity works. Tapping the screen adds speed to the bird strictly up (vBird = jumpSpeed).
State 2 (gameStatus = 2) the bird collided with a green pillar and falls strictly down, the pillars are standing, gravity works. Tapping the screen does not affect anything.
State 3 (gameStatus = 3) the bird collided with the Earth, everything froze, scoring and displaying the result of the flight. We are waiting for clicking to go to state 0.

In principle, state 1 and 2 can be combined by setting the horizontal velocity to 0 in the latter, this is a matter of taste.

Waving wings and sprite animation


The slope of the bird is proportional to the velocity vector.
That is equal to the arctangent of the angle.

  bird.rotation = math.atan(vBird/uBird) 

To animate the flapping of the bird's wings when you tap the screen, the Corona sprite list is used. It is a bit more complicated than just a png picture.



Let's create animation frames in a separate png file. 400 by 100 pixels. The size of each internal sprite is 100 by 100. That is, we have 4 frames.

Initialization code birdies next

 local function setupBird() local options = { width = 100, height = 100, numFrames = 4, sheetContentWidth = 400, -- width of original 1x size of entire sheet sheetContentHeight = 100 -- height of original 1x size of entire sheet } local imageSheet = graphics.newImageSheet( "Assets/bird.png", options ) local sequenceData = { name="walking", start=1, count=3, time=300, loopCount = 2, -- Optional ; default is 0 (loop indefinitely) loopDirection = "forward" -- Optional ; values include "forward" or "bounce" } bird = display.newSprite( imageSheet, sequenceData ) bird.x = xBird bird.y = yBird end 

Now, at the moment of clicking on the screen, we call the line of code

 bird:play() 

And the bird flaps its wings 2 times in a row.

Collision and particle effect


Collision checking is elementary.

First, checking for collisions with the Earth’s surface

  if yBird>yLand then yBird = yLand crash() end 

Second, the collision check with the pillars

  local function checkCollision(i) local dx = 40 --      local dy = 50 --      local boom = 0 local x = pipes[i].x local y = pipes[i].y if xBird > (x-dx) and xBird < (x+dx) then if yBird > (y+dy) or yBird < (y-dy) then boom = 1 end end return boom end 

Add a particle effect in the place where the bird collides with the pole.

The code looks cumbersome, but by changing the effect's 20 parameters, you can get amazing explosions, flashes and fireballs.

 local function setupExplosion() local dx = 31 local p = "Assets/habra.png" local emitterParams = { startParticleSizeVariance = dx/2, startColorAlpha = 0.61, startColorGreen = 0.3031555, startColorRed = 0.08373094, yCoordFlipped = 0, blendFuncSource = 770, blendFuncDestination = 1, rotatePerSecondVariance = 153.95, particleLifespan = 0.7237, tangentialAcceleration = -144.74, startParticleSize = dx, textureFileName = p, startColorVarianceAlpha = 1, maxParticles = 128, finishParticleSize = dx/3, duration = 0.75, finishColorRed = 0.078, finishColorAlpha = 0.75, finishColorBlue = 0.3699196, finishColorGreen = 0.5443883, maxRadiusVariance = 172.63, finishParticleSizeVariance = dx/2, gravityy = 220.0, speedVariance = 258.79, tangentialAccelVariance = -92.11, angleVariance = -300.0, angle = -900.11 } emitter = display.newEmitter(emitterParams ) emitter:stop() end 

I leave this piece of code without comment - just indulge in the parameters themselves.

 local function explosion() emitter.x = bird.x emitter.y = bird.y emitter:start() end 

Function explosion () is called at the moment of collision of a bird with a pillar. I could not pixelate the effect in the style of all the pictures in the game. Perhaps you have advice on how to do this. If anything, Scale did not work.

thank


The entire project for free without SMS can be downloaded from the Corona Marketplace during the May holidays. The code is moderated.

Among other things, I became a Corona evangelist, received the first salary and all this thanks to the publication on Habré. Thanks to the resource and of course you, readers. Algebraists also hello.

Project code


Project code in one main.lua file
 ----------------------------------------------------------------------------------------- -- -- main.lua -- ----------------------------------------------------------------------------------------- local gameStatus = 0 local yLand = display.actualContentHeight - 160 local hLand = 60 local xLand = display.contentCenterX local yBird = display.contentCenterY-50 local xBird = display.contentCenterX-50 local wPipe = display.contentCenterX+10 local yReady = display.contentCenterY-140 local uBird = -200 local vBird = 0 local wBird = -320 local g = 800 local dt = 0.025 local score = 0 local bestScore = 0 local scoreStep = 5 local bird local land local title local getReady local gameOver local emitter local board local scoreTitle local bestTitle local silver local gold local pipes = {} local function loadSounds() dieSound = audio.loadSound( "Sounds/sfx_die.caf" ) hitSound = audio.loadSound( "Sounds/sfx_hit.caf" ) pointSound = audio.loadSound( "Sounds/sfx_point.aif" ) swooshingSound = audio.loadSound( "Sounds/sfx_swooshing.caf" ) wingSound = audio.loadSound( "Sounds/sfx_wing.caf" ) boomSound = audio.loadSound( "Sounds/sfx_boom.mp3" ) end local function calcRandomHole() return 60 + 20*math.random(10) end local function loadBestScore() local path = system.pathForFile( "bestscore.txt", system.DocumentsDirectory ) -- Open the file handle local file, errorString = io.open( path, "r" ) if not file then -- Error occurred; output the cause print( "File error: " .. errorString ) else -- Read data from file local contents = file:read( "*a" ) -- Output the file contents bestScore = tonumber( contents ) -- Close the file handle io.close( file ) end file = nil end local function saveBestScore() -- Path for the file to write local path = system.pathForFile( "bestscore.txt", system.DocumentsDirectory ) local file, errorString = io.open( path, "w" ) if not file then -- Error occurred; output the cause print( "File error: " .. errorString ) else file:write( bestScore ) io.close( file ) end file = nil end local function setupBird() local options = { width = 70, height = 50, numFrames = 4, sheetContentWidth = 280, -- width of original 1x size of entire sheet sheetContentHeight = 50 -- height of original 1x size of entire sheet } local imageSheet = graphics.newImageSheet( "Assets/bird.png", options ) local sequenceData = { name="walking", start=1, count=3, time=300, loopCount = 2, -- Optional ; default is 0 (loop indefinitely) loopDirection = "forward" -- Optional ; values include "forward" or "bounce" } bird = display.newSprite( imageSheet, sequenceData ) bird.x = xBird bird.y = yBird end local function prompt(tempo) bird:play() end local function initGame() score = 0 scoreStep = 5 title.text = score for i=1,3 do pipes[i].x = 400 + display.contentCenterX * (i-1) pipes[i].y = calcRandomHole() end yBird = display.contentCenterY-50 xBird = display.contentCenterX-50 getReady.y = 0 getReady.alpha = 1 gameOver.y = 0 gameOver.alpha = 0 board.y = 0 board.alpha = 0 audio.play( swooshingSound ) transition.to( bird, { time=300, x=xBird, y=yBird, rotation = 0 } ) transition.to( getReady, { time=600, y=yReady, transition=easing.outBounce, onComplete=prompt } ) end local function wing() if gameStatus==0 then gameStatus=1 getReady.alpha = 0 end if gameStatus==1 then vBird = wBird bird:play() audio.play( wingSound ) end if gameStatus==3 then gameStatus=0 initGame() end end local function setupExplosion() local dx = 31 local p = "Assets/habra.png" local emitterParams = { startParticleSizeVariance = dx/2, startColorAlpha = 0.61, startColorGreen = 0.3031555, startColorRed = 0.08373094, yCoordFlipped = 0, blendFuncSource = 770, blendFuncDestination = 1, rotatePerSecondVariance = 153.95, particleLifespan = 0.7237, tangentialAcceleration = -144.74, startParticleSize = dx, textureFileName = p, startColorVarianceAlpha = 1, maxParticles = 128, finishParticleSize = dx/3, duration = 0.75, finishColorRed = 0.078, finishColorAlpha = 0.75, finishColorBlue = 0.3699196, finishColorGreen = 0.5443883, maxRadiusVariance = 172.63, finishParticleSizeVariance = dx/2, gravityy = 220.0, speedVariance = 258.79, tangentialAccelVariance = -92.11, angleVariance = -300.0, angle = -900.11 } emitter = display.newEmitter(emitterParams ) emitter:stop() end local function explosion() emitter.x = bird.x emitter.y = bird.y emitter:start() end local function crash() gameStatus = 3 audio.play( hitSound ) gameOver.y = 0 gameOver.alpha = 1 transition.to( gameOver, { time=600, y=yReady, transition=easing.outBounce } ) board.y = 0 board.alpha = 1 if score>bestScore then bestScore = score saveBestScore() end bestTitle.text = bestScore scoreTitle.text = score if score<10 then silver.alpha = 0 gold.alpha = 0 elseif score<50 then silver.alpha = 1 gold.alpha = 0 else silver.alpha = 0 gold.alpha = 1 end transition.to( board, { time=600, y=yReady+100, transition=easing.outBounce } ) end local function collision(i) local dx = 40 -- horizontal space of hole local dy = 50 -- vertical space of hole local boom = 0 local x = pipes[i].x local y = pipes[i].y if xBird > (x-dx) and xBird < (x+dx) then if yBird > (y+dy) or yBird < (y-dy) then boom = 1 end end return boom end local function gameLoop() local eps = 10 local leftEdge = -60 if gameStatus==1 then xLand = xLand + dt * uBird if xLand<0 then xLand = display.contentCenterX*2+xLand end land.x = xLand for i=1,3 do local xb = xBird-eps local xOld = pipes[i].x local x = xOld + dt * uBird if x<leftEdge then x = wPipe*3+x pipes[i].y = calcRandomHole() end if xOld > xb and x <= xb then score = score + 1 title.text = score if score==scoreStep then scoreStep = scoreStep + 5 audio.play( pointSound ) end end pipes[i].x = x if collision(i)==1 then explosion() audio.play( dieSound ) gameStatus = 2 end end end if gameStatus==1 or gameStatus==2 then vBird = vBird + dt * g yBird = yBird + dt * vBird if yBird>yLand-eps then yBird = yLand-eps crash() end bird.x = xBird bird.y = yBird if gameStatus==1 then bird.rotation = -30*math.atan(vBird/uBird) else bird.rotation = vBird/8 end end end local function setupLand() land = display.newImageRect( "Assets/land.png", display.actualContentWidth*2, hLand*2 ) land.x = xLand land.y = yLand+hLand end local function setupImages() local ground = display.newImageRect( "Assets/ground.png", display.actualContentWidth, display.actualContentHeight ) ground.x = display.contentCenterX ground.y = display.contentCenterY ground:addEventListener("tap", wing) for i=1,3 do pipes[i] = display.newImageRect( "Assets/pipe.png", 80, 1000 ) pipes[i].x = 440 + wPipe * (i-1) pipes[i].y = calcRandomHole() end getReady = display.newImageRect( "Assets/getready.png", 200, 60 ) getReady.x = display.contentCenterX getReady.y = yReady getReady.alpha = 0 gameOver = display.newImageRect( "Assets/gameover.png", 200, 60 ) gameOver.x = display.contentCenterX gameOver.y = 0 gameOver.alpha = 0 board = display.newGroup() local img = display.newImageRect(board, "Assets/board.png", 240, 140 ) scoreTitle = display.newText(board, score, 80, -18, "Assets/troika.otf", 21) scoreTitle:setFillColor( 0.75, 0, 0 ) bestTitle = display.newText(board, bestScore, 80, 24, "Assets/troika.otf", 21) bestTitle:setFillColor( 0.75, 0, 0 ) silver = display.newImageRect(board, "Assets/silver.png", 44, 44 ) silver.x = -64 silver.y = 4 gold = display.newImageRect(board, "Assets/gold.png", 44, 44 ) gold.x = -64 gold.y = 4 board.x = display.contentCenterX board.y = 0 board.alpha = 0 local txt = { x=display.contentCenterX, y=10, text="", font="Assets/troika.otf", fontSize=35 } title = display.newText(txt) title:setFillColor( 1, 1, 1 ) end -- Start application point loadSounds() setupImages() setupBird() setupExplosion() setupLand() initGame() loadBestScore() gameLoopTimer = timer.performWithDelay( 25, gameLoop, 0 ) 

Source: https://habr.com/ru/post/353402/


All Articles