
Flappie Bird: - Let's go
- Tutorial

This is a story about how to write your game on Corona.
The level of entry is minimal (and the nerd 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 appropriate and, of course, we repeat after the first astronaut
-Go!
What will happen in 2 hours of programming?
Here'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 programmer as linear as algebra.
Learned? Yes, this is a game of add-ons from Ishkv from Vietnam.
Let me remind you that the application brought the author $ 50,000 per day. Not bad in my opinion.
If you look closely in my clip to the fragments of a bird’s collision with a pillar, you see the inscription HABR. I use this picture as a texture for the particle effect.
Corona Tool Installation
It takes 10 minutes and is beautifully painted in many places.
Creating a gagarinbird project
Launch Corona on your machine (I have a Mac) and select the Create a new empty project menu item .
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.luaText editor - at your discretion. The programming language is Lua.
First of all, we need pictures and sounds for the game.
How to get them without torturing the author?
- Download the file Flappy Bird [Dong Nguyen] (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 file Flappy Bird [Dong Nguyen] (v1.2 LP os60) -Orbicos-ICPDA.rc309.zip
- Right-click on the Flap.app file and select Show Package Content
- Copy pictures and sounds to our gagarinbird directory
For convenience, make a subdirectory gagarinbird / Assets and place all the pictures here.
Similarly, we make the gagarinbird / Sounds subdirectory and put all the sounds here.
The first 2 lines of code - checking sounds Mu
Insert the first two lines of code into the main.lua file
dieSound = audio.loadSound( "Sounds/sfx_die.caf" )
audio.play( dieSound )
Click in Corona (many call it Corona Simulator) 2 hot buttons / cmd / + R and our application will launch in a window similar to a phone and make a death sound! Ay-ah, everything works.
The background is virgin black, but it doesn’t matter - we just learned how to play any sounds. And they sound the same, both on Android, on the iPhone, and on (forgive me, Lord) Windows.
Download sounds and make everything beautiful
We 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()
Upload a background image and process tap-tap
Learning to draw a picture of the background on the whole 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 about such a beautiful picture of

Ur, now we can draw any picture, place it anywhere and transform as we want.
For instance,
ground.rotation = 90
will rotate the background image 90 degrees.
Add poking to the screen.
ground:addEventListener("tap", wing)
local function wing()
audio.play( wingSound )
end
We added a Listener that catches all clicks on ground and calls the wing () function.
If you look at the code, each poke in 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 poles) and setting parameters will require more code than just starting a timer (40 times per second) with checking for collisions and dynamics in the Earth’s gravitational field. Earth in the porthole. Sorry, distracted.
Let's make dynamics in the language of a time machine.
-- запускаем таймер, который вызывается каждые 25 миллисекунд
gameLoopTimer = timer.performWithDelay( 25, gameLoop, 0 )
local function gameLoop()
vBird = vBird + dt * g
yBird = yBird + dt * vBird
end
Our function gameLoop () is called 40 times per second. The
dynamics of bird movement is recorded in two lines of this function. Here g is the gravitational acceleration (for iPhone it is 800 pixels per second per second)
dt = 0.025 - time step
uBird - bird speed along the X axis
vBird - bird speed along the Y axis
xBird - bird coordinate along the X axis
yBird - bird coordinate along Y-axis The
dynamics of the movement of 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 connect everything in an elegant cattle program. So, in our game there are 4 states.
State 0 (gameStatus = 0) everything is frozen and ready to start the game. We look forward to clicking on the screen. When pressed, we go to state 1.
State 1 (gameStatus = 1), the bird flies, the columns move, gravity works. Tapping the screen adds the bird speed straight up (vBird = jumpSpeed).
State 2 (gameStatus = 2) the bird collided with a green pillar and falls strictly down, the pillars are standing, gravity is working. Tapping the screen does not affect anything.
State 3 (gameStatus = 3) the bird collided with the Earth, everything froze, scoring and showing the result of the flight. We are waiting for a click to go to state 0.
In principle, state 1 and 2 can be combined by assigning a horizontal speed of 0 in the latter, this is a matter of taste.
Wings and sprite animation
The slope of the bird is proportional to the velocity vector.
That is equal to the arc tangent of the angle.
bird.rotation = math.atan(vBird/uBird)
To animate the flapping of the wings of a bird when you click on the screen, the Corona sprite list is used. This is a little more complicated than just a PNG image.

We will form animation frames in a separate png file. Size 400 by 100 pixels. The size of each internal sprite is 100 per 100. That is, we have 4 frames.
Bird initialization code is as follows
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, when you click 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 basic.
First, checking for collisions with the Earth’s surface
if yBird>yLand then
yBird = yLand
crash()
end
Secondly, collision check with poles
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 at the point where the bird collides with the pillar.
The code looks cumbersome, but changing the 20 parameters of the effect, 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 yourself.
local function explosion()
emitter.x = bird.x
emitter.y = bird.y
emitter:start()
end
Function explosion () is called when the bird collides with the column. I could not pixelate the effect in the style of all the pictures in the game. Perhaps you have some advice on how to do this. If anything, then Scale did not work.
thanks
The entire project can be downloaded for free without SMS from the Corona Marketplace during the May holidays. The code is being moderated.
Among other things, I became an evangelist of Corona, received the first salary and all this thanks to the publication on Habré. Thanks to the resource and of course to you, readers. Hello to the algebraists.
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 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 )